Guides / Bring your own objects
Bring your own objects
Draft objects from a schema your team already has, review the drafts, and accept the ones you want.
Generated from @oods/foundry 0.10.1
The guide below is the one the package ships, IMPORTING-OBJECTS.md in @oods/foundry@0.10.1, shown as
written.
Bring your own objects
object_import reads a schema your team already has and drafts OODS Foundry objects. Review the drafts and accept the objects and trait proposals you want. object_registry remains the tool for hand-written object or trait YAML.
Files in
Supported inputs are OpenAPI/Swagger 2.0, OpenAPI 3.0–3.2, JSON Schema draft-07 or 2020-12, Postgres DDL and migrations, Prisma schemas, dbt manifests/projects, OData CSDL XML/JSON, GraphQL SDL/introspection, or the OODS hub. Pass one local file, a schema folder, or inline content up to 1 MiB. source.format can select openapi, json-schema, sql, prisma, dbt, odata or graphql; otherwise the document and extension select the reader. Files have a combined limit of 128 MiB, checked before reading. Folder entries are processed in deterministic order.
OODS Foundry does not fetch a URL, connect to an API/database, import a module, or run your code. Local JSON Pointer $refs and relative file references are supported within the source folder. References that escape it, including through symlinks, are refused with the source file and pointer. Unreachable network and absolute references, missing local files and named anchors are reported at their file/pointer and skipped; nothing is fetched. Download remote specs yourself before importing them.
{"action":"draft","source":{"path":"/path/to/openapi.json"}}
The response gives an importId, counts, object names in dependency order, shipped-name clashes, proposal grades, and a diff-file path. Large listings are explicitly truncated in the response; order.json has the complete order and cycles. Nothing is registered at this step.
The complete bundle is in ~/.oods-foundry/imports/<importId>/, beside your objects and traits, outside the package install. A portable installation may set OODS_FOUNDRY_HOME before starting the server. objects/ holds the draft YAML, hub.json the normalized schemas, report.json every outcome, and diff.json the comparison with currently registered definitions. import.json is content-addressed and binds the hashes of its separately streamed hub and report; editing any of them invalidates the import. To hand-edit a draft, use object_registry to validate and register that YAML.
Review and accept
{"action":"show","importId":"<from draft>","object":"YourObject"}
show returns one draft's YAML, proposals with evidence and grades, unmapped elements, and the re-import comparison. A re-import reports added, removed and changed fields, trait and relationship changes, and potentially affected list/detail/form/timeline screens. It compares the unaccepted base draft with your effective registered definition, with previously accepted trait contributions listed separately as traitFields, rather than false removals; review those differences before overwriting.
A name alone is weak evidence and never raises a proposal's grade. Types, enums, formats and read-only declarations provide structural evidence. Trait parameter schemas validate every proposal; an invalid proposal is shown with errors and cannot be accepted. Proposals add a trait's canonical fields and views, so check that its fields express your model. Nothing is silently applied.
{"action":"apply","importId":"<from draft>","objects":[{"name":"YourObject","proposals":["<accepted proposal id>"]}]}
Omit proposals to accept an object with no traits. Accept dependencies in the same call unless they are already registered. The batch validates all definitions, writes them in dependency order, and composes each supported context transiently, including timeline where declared or contributed by an accepted lifecycle/history trait. Read-only sources omit form. Cyclic relationships are retained: all files exist before composition. A write or composition failure restores previous files byte for byte and removes newly created files. A filesystem lock excludes concurrent registration/import writes; this is rollback on operation failure, not a crash-recovery database transaction.
overwrite: true permits replacing your existing objects. A shipped-name replacement additionally requires that exact name in confirmShipped, for example "confirmShipped":["User"]. Reviewing one shipped replacement does not authorize another.
What is preserved
Primitive fields, supported formats, nullability, examples, defaults and constraints become object fields. Compatible allOf declarations merge; conflicting intersections are reported. oneOf/anyOf alternatives remain distinct, including discriminators; a null-plus-one-type union can become a nullable field. Named object references become relationship identifier fields. Source names and every normalized value retain file/pointer provenance.
Read-only fields are omitted from editable forms. Write-only fields and their samples are omitted from read screens. Maps, nested documents, unconstrained values, unsupported unions and unsupported metadata remain in the hub with explicit unmapped outcomes. API operations do not become executable actions. Samples without supplied examples are illustrative, not customer records. The report's mapped, proposed and unmapped counts sum to its total.
The hub vocabulary
The hub is JSON Schema 2020-12 plus x-oods annotations, with its own Ajv vocabulary instance. Its schema ships beside this guide. $defs contains named schemas. x-oods.version is 1; sources, sourceNames and provenance record the input identity. A provenance key is a hub JSON Pointer, and its value is { "file": "source.json", "pointer": "/components/schemas/Widget" }.
Schema annotations can carry relationships (target, via, cardinality, label), enumLabels, localeLabels, currency (field, optional positive minorUnits divisor), unit (field or symbol), lifecycle (field, states, initialState), history (field, timestampField), and traitProposals (name, parameters, grade, evidence). These annotations are preserved even when the current object projection cannot use them. Explicit currency annotations become money semantics; field names never imply money. Each proposal's evidence has file, pointer, reason, and kind (name, structure, declaration). Name-only evidence stays weak even if a producer labels it strong.
Export from your own tools
Run the export in your own trusted project, save the resulting JSON, then give only that file to OODS Foundry. These are export expressions/commands, not programs OODS Foundry runs:
| Source | One-line export |
|---|---|
| Zod 4 | console.log(JSON.stringify(z.toJSONSchema(YourSchema))) |
| Valibot | console.log(JSON.stringify(toJsonSchema(YourSchema))) using toJsonSchema from @valibot/to-json-schema |
| TypeBox | console.log(JSON.stringify(YourTypeBoxSchema)) |
| Effect 3 | console.log(JSON.stringify(JSONSchema.make(YourSchema))) using JSONSchema from effect |
| Pydantic | print(json.dumps(YourModel.model_json_schema())) with Python's json module |
| TypeScript types | npx ts-json-schema-generator --path 'src/model.ts' --type 'YourType' > schema.json |
Converters cannot represent every transformation, refinement or runtime behavior as JSON Schema. Review their export diagnostics and the import report. OODS Foundry's accepted YAML stays the canonical object definition; another discovery tool can produce the documented hub without access to OODS Foundry internals.
Broken local JSON Pointers are preserved as x-oods-unresolved-ref with an unmapped link and source location; they never become invented relationships. Network and named-anchor references are reported and skipped. Outside-folder and escaping-symlink references refuse the whole import before an outside read. Traversal is bounded to 160 nesting levels and ten million values, including YAML alias expansion.
Imported records use declared titles or natural keys, then identifiers. Samples are deterministic and checked against enum, pattern, format and bounds; unsatisfiable sample constraints are reported. Field help comes only from source descriptions. Date-only values display without a time. Declared relationships use related sample records in form pickers; applications supply their live choices.
Postgres DDL and migrations
Pass a .sql file or migration folder, or source.format: "sql" with inline SQL. The reader parses schema declarations and replays them in memory; it never connects to a database or executes SQL. It reads columns/types, NOT NULL, literal defaults, enum types, primary/unique keys, scalar foreign keys, comments, literal CHECK comparisons/IN lists, and direct-column view projections. now() supplies timestamp evidence without evaluation. Views are read-only. Composite foreign keys, complex view expressions, procedural SQL and unsupported syntax are individually reported.
Prisma migration timestamp folders, Flyway numeric versions, golang-migrate .up.sql files and Rails structure.sql work as files. Drizzle follows meta/_journal.json; unlisted SQL and down/undo migrations are reported and skipped. ALTER, DROP and RENAME update the replayed schema. Postgres is the supported dialect; MySQL and SQL Server dialect support remains later work, with unsupported statements reported rather than guessed.
Prisma and dbt
Pass format: "prisma" with a .prisma file or a schema folder. Models, enums, native types, keys, literal defaults and documentation are projected. Relation navigation uses its declared foreign key when available. Generated defaults and @updatedAt are read-only; source table/column mappings remain in the hub. Generators and datasource declarations are never executed.
Pass format: "dbt" with a manifest or a project properties folder. A compiled manifest is authoritative when supplied. YAML models, sources, snapshots, column tests, constraints and semantic declarations provide fields and relationships; both standalone semantic models and embedded column entities/dimensions with simple metrics are supported. All dbt objects are read-only. Snapshots include history and offer Supersedable. Jinja, SQL expressions, custom tests and dynamic descriptions are reported without execution. Missing types stay unmapped; a familiar column name does not supply a type.
OData and GraphQL
OData accepts CSDL JSON or XML/EDMX (format: "odata"). Entity/complex types, enums, keys, inheritance and navigation references become hub schemas. Draft names use local type names with deterministic collision suffixes; full service names stay in provenance. Scalar navigation uses its declared foreign key in one editor, including v2 association constraints. HeaderInfo names records, LineItem supplies metadata.listColumns, FieldGroup/Facets group details, Common labels/text/value lists shape fields and pickers, ISO currency pairs supply money semantics, literal units label values, and capability restrictions remove forms when inserts and updates are prohibited. Nullable display text is supported. Dynamic units, unresolved translation tokens and unsupported vocabulary expressions stay preserved and reported. XML refuses DTD/entity declarations; vocabulary URLs are never fetched. XML and JSON retain their respective nullability defaults.
GraphQL accepts SDL (.graphql/.gql) or introspection JSON (format: "graphql"). Objects and input objects draft separately. Enums, nullability, lists, descriptions and deprecation metadata are retained; interfaces and unions remain alternatives, not merged records. Object fields become relationships. Operation roots and resolvers are not run. Custom scalar names do not supply types: only explicitly supported @specifiedBy URLs provide format evidence. Duplicate field declarations are reported and the first stays in effect.
Hand-written objects can use metadata.listColumns: [{field: name, label: Name}, {field: total}] to select and order fields in list rows. Every column must name a composed field. Source annotation support uses the same object contract.
Accepted trait field bindings
A proposal carries one configuration per trait and all of its evidence. Accepting a Timestampable or Stateful proposal records fieldBindings on the trait reference, so screens and generated React and Vue apps use the original source fields. An alternative configuration stays in the report with its reason; review the selected binding before accepting.
Hand-written object definitions can use the same declaration:
traits:
- name: Stateful
parameters:
states: [open, closed]
initialState: open
fieldBindings:
status: state
- name: Timestampable
fieldBindings:
created_at: placedAt
updated_at: updatedAt
last_event: null
last_event_at: null
Binding targets must be fields declared in the object's schema with a matching type. null explicitly omits a canonical field the source does not provide, including its view bindings. Without fieldBindings, a trait keeps its existing fields and behavior. Imported proposals explicitly omit every canonical record field the source does not supply. A proposal with no supported field bindings says that it contributes no record views. A single supplied creation time also serves the list timestamp. Timestamped objects can show that fact on their timeline without invented lifecycle events.
Detail screens resolve related records from the same sample lookup used by form pickers. OData Common.Text supports a local label field or one navigation hop through a declared foreign-key constraint, such as Agency/Name. Generated lookup labels contain imported samples only: connect your application's relationship data for real records. Declared list-column labels take precedence over field semantics labels, then the human-readable field name.