metaobjects 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. metaobjects/__init__.py +75 -0
  2. metaobjects/agent_context/__init__.py +55 -0
  3. metaobjects/agent_context/_content/README.md +14 -0
  4. metaobjects/agent_context/_content/servers/csharp.meta.json +5 -0
  5. metaobjects/agent_context/_content/servers/java.meta.json +5 -0
  6. metaobjects/agent_context/_content/servers/kotlin.meta.json +5 -0
  7. metaobjects/agent_context/_content/servers/python.meta.json +5 -0
  8. metaobjects/agent_context/_content/servers/typescript.meta.json +5 -0
  9. metaobjects/agent_context/_content/skills/metaobjects-authoring/SKILL.md +301 -0
  10. metaobjects/agent_context/_content/skills/metaobjects-codegen/SKILL.md +99 -0
  11. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/csharp.md +87 -0
  12. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/java.md +94 -0
  13. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/kotlin.md +110 -0
  14. metaobjects/agent_context/_content/skills/metaobjects-codegen/references/typescript.md +135 -0
  15. metaobjects/agent_context/_content/skills/metaobjects-prompts/SKILL.md +148 -0
  16. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/csharp.md +110 -0
  17. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/java.md +108 -0
  18. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/kotlin.md +130 -0
  19. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/python.md +116 -0
  20. metaobjects/agent_context/_content/skills/metaobjects-prompts/references/typescript.md +150 -0
  21. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/SKILL.md +130 -0
  22. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/java.md +96 -0
  23. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/kotlin.md +99 -0
  24. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/react.md +86 -0
  25. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/tanstack.md +119 -0
  26. metaobjects/agent_context/_content/skills/metaobjects-runtime-ui/references/typescript.md +92 -0
  27. metaobjects/agent_context/_content/skills/metaobjects-verify/SKILL.md +107 -0
  28. metaobjects/agent_context/_content/skills/metaobjects-verify/references/migration.md +72 -0
  29. metaobjects/agent_context/_content/templates/always-on.md.mustache +27 -0
  30. metaobjects/agent_context/assemble.py +133 -0
  31. metaobjects/agent_context/content_root.py +54 -0
  32. metaobjects/agent_context/scaffold.py +191 -0
  33. metaobjects/agent_context/types.py +44 -0
  34. metaobjects/attr_class_map.py +23 -0
  35. metaobjects/cli.py +696 -0
  36. metaobjects/codegen/__init__.py +0 -0
  37. metaobjects/codegen/config.py +11 -0
  38. metaobjects/codegen/constants.py +13 -0
  39. metaobjects/codegen/extract_delegate_emitter.py +384 -0
  40. metaobjects/codegen/extract_schema_emitter.py +139 -0
  41. metaobjects/codegen/format.py +31 -0
  42. metaobjects/codegen/fr010_field_mapping.py +220 -0
  43. metaobjects/codegen/generator.py +62 -0
  44. metaobjects/codegen/generator_registry.py +163 -0
  45. metaobjects/codegen/generators/__init__.py +0 -0
  46. metaobjects/codegen/generators/entity_model.py +263 -0
  47. metaobjects/codegen/generators/extractor_generator.py +317 -0
  48. metaobjects/codegen/generators/filter_allowlist_generator.py +309 -0
  49. metaobjects/codegen/generators/m2m_codegen.py +192 -0
  50. metaobjects/codegen/generators/output_parser_generator.py +272 -0
  51. metaobjects/codegen/generators/output_prompt_generator.py +192 -0
  52. metaobjects/codegen/generators/payload_vo_generator.py +672 -0
  53. metaobjects/codegen/generators/render_helper_generator.py +451 -0
  54. metaobjects/codegen/generators/router_generator.py +635 -0
  55. metaobjects/codegen/generators/template_generator.py +70 -0
  56. metaobjects/codegen/generators/tph_plan.py +120 -0
  57. metaobjects/codegen/generators/trace_helper_generator.py +336 -0
  58. metaobjects/codegen/instance_artifacts.py +15 -0
  59. metaobjects/codegen/output_format_spec_emitter.py +79 -0
  60. metaobjects/codegen/overwrite_policy.py +27 -0
  61. metaobjects/codegen/runner.py +110 -0
  62. metaobjects/codegen/runtime/__init__.py +6 -0
  63. metaobjects/codegen/runtime/filter_parser.py +193 -0
  64. metaobjects/codegen/type_map.py +84 -0
  65. metaobjects/core_types.py +809 -0
  66. metaobjects/datatype.py +19 -0
  67. metaobjects/documentation/__init__.py +28 -0
  68. metaobjects/documentation/doc_constants.py +20 -0
  69. metaobjects/documentation/doc_provider.py +20 -0
  70. metaobjects/documentation/doc_schema.py +24 -0
  71. metaobjects/errors.py +124 -0
  72. metaobjects/loader/__init__.py +0 -0
  73. metaobjects/loader/merge.py +287 -0
  74. metaobjects/loader/meta_data_loader.py +245 -0
  75. metaobjects/loader/sources/__init__.py +24 -0
  76. metaobjects/loader/sources/directory_source.py +50 -0
  77. metaobjects/loader/sources/file_source.py +41 -0
  78. metaobjects/loader/sources/meta_data_source.py +67 -0
  79. metaobjects/loader/sources/uri_source.py +56 -0
  80. metaobjects/loader/validate_discriminator.py +181 -0
  81. metaobjects/loader/validate_field_readonly.py +146 -0
  82. metaobjects/loader/validate_source_parameter_ref.py +159 -0
  83. metaobjects/loader/validate_source_physical_names.py +140 -0
  84. metaobjects/loader/validation_passes.py +1513 -0
  85. metaobjects/meta/__init__.py +1 -0
  86. metaobjects/meta/core/__init__.py +0 -0
  87. metaobjects/meta/core/attr/__init__.py +0 -0
  88. metaobjects/meta/core/attr/attr_constants.py +31 -0
  89. metaobjects/meta/core/attr/meta_attr.py +136 -0
  90. metaobjects/meta/core/field/__init__.py +0 -0
  91. metaobjects/meta/core/field/field_constants.py +105 -0
  92. metaobjects/meta/core/field/meta_field.py +76 -0
  93. metaobjects/meta/core/identity/__init__.py +0 -0
  94. metaobjects/meta/core/identity/identity_constants.py +19 -0
  95. metaobjects/meta/core/identity/meta_identity.py +8 -0
  96. metaobjects/meta/core/object/__init__.py +0 -0
  97. metaobjects/meta/core/object/meta_object.py +65 -0
  98. metaobjects/meta/core/object/meta_object_aware.py +43 -0
  99. metaobjects/meta/core/object/object_class_registry.py +56 -0
  100. metaobjects/meta/core/object/object_constants.py +13 -0
  101. metaobjects/meta/core/object/object_extract.py +400 -0
  102. metaobjects/meta/core/object/value_object.py +70 -0
  103. metaobjects/meta/core/relationship/__init__.py +0 -0
  104. metaobjects/meta/core/relationship/derive_m2m_fields.py +180 -0
  105. metaobjects/meta/core/relationship/meta_relationship.py +54 -0
  106. metaobjects/meta/core/relationship/relationship_constants.py +51 -0
  107. metaobjects/meta/core/validator/__init__.py +0 -0
  108. metaobjects/meta/core/validator/validator_constants.py +18 -0
  109. metaobjects/meta/meta_data.py +206 -0
  110. metaobjects/meta/meta_root.py +8 -0
  111. metaobjects/meta/persistence/__init__.py +0 -0
  112. metaobjects/meta/persistence/db/__init__.py +1 -0
  113. metaobjects/meta/persistence/db/db_constants.py +41 -0
  114. metaobjects/meta/persistence/db/db_provider.py +60 -0
  115. metaobjects/meta/persistence/origin/__init__.py +0 -0
  116. metaobjects/meta/persistence/origin/meta_origin.py +8 -0
  117. metaobjects/meta/persistence/origin/origin_constants.py +20 -0
  118. metaobjects/meta/persistence/source/__init__.py +0 -0
  119. metaobjects/meta/persistence/source/meta_source.py +137 -0
  120. metaobjects/meta/persistence/source/source_constants.py +115 -0
  121. metaobjects/meta/presentation/__init__.py +0 -0
  122. metaobjects/meta/presentation/layout/__init__.py +0 -0
  123. metaobjects/meta/presentation/layout/layout_constants.py +13 -0
  124. metaobjects/meta/presentation/layout/meta_layout.py +8 -0
  125. metaobjects/meta/presentation/view/__init__.py +0 -0
  126. metaobjects/meta/presentation/view/meta_view.py +8 -0
  127. metaobjects/meta/presentation/view/view_constants.py +22 -0
  128. metaobjects/meta/template/__init__.py +0 -0
  129. metaobjects/meta/template/meta_template.py +46 -0
  130. metaobjects/meta/template/template_constants.py +112 -0
  131. metaobjects/meta/template/template_provider.py +43 -0
  132. metaobjects/parser.py +380 -0
  133. metaobjects/parser_yaml.py +82 -0
  134. metaobjects/provider.py +111 -0
  135. metaobjects/py.typed +0 -0
  136. metaobjects/registry.py +210 -0
  137. metaobjects/registry_manifest.py +223 -0
  138. metaobjects/render/__init__.py +74 -0
  139. metaobjects/render/email_document.py +14 -0
  140. metaobjects/render/escapers.py +109 -0
  141. metaobjects/render/extract/__init__.py +59 -0
  142. metaobjects/render/extract/coerce.py +279 -0
  143. metaobjects/render/extract/extract.py +211 -0
  144. metaobjects/render/extract/extract_map.py +61 -0
  145. metaobjects/render/extract/json_forgiving_reader.py +203 -0
  146. metaobjects/render/extract/locate.py +65 -0
  147. metaobjects/render/extract/normalize.py +96 -0
  148. metaobjects/render/extract/strip.py +20 -0
  149. metaobjects/render/extract/types.py +332 -0
  150. metaobjects/render/extract/xml_forgiving_reader.py +162 -0
  151. metaobjects/render/filesystem_provider.py +51 -0
  152. metaobjects/render/prompt/__init__.py +32 -0
  153. metaobjects/render/prompt/output_format_renderer.py +340 -0
  154. metaobjects/render/prompt/output_format_spec.py +28 -0
  155. metaobjects/render/prompt/prompt_field.py +29 -0
  156. metaobjects/render/prompt/prompt_overrides.py +29 -0
  157. metaobjects/render/prompt/prompt_style.py +38 -0
  158. metaobjects/render/renderer.py +358 -0
  159. metaobjects/render/verify.py +266 -0
  160. metaobjects/runtime/__init__.py +39 -0
  161. metaobjects/runtime/llm_recorder.py +210 -0
  162. metaobjects/runtime/n2m_resolver.py +155 -0
  163. metaobjects/runtime/object_manager.py +715 -0
  164. metaobjects/runtime/tph.py +50 -0
  165. metaobjects/serializer_json.py +172 -0
  166. metaobjects/shared/__init__.py +0 -0
  167. metaobjects/shared/base_types.py +16 -0
  168. metaobjects/shared/separators.py +4 -0
  169. metaobjects/shared/structural.py +9 -0
  170. metaobjects/source/__init__.py +79 -0
  171. metaobjects/source/error_source.py +266 -0
  172. metaobjects/source/json_path.py +106 -0
  173. metaobjects/source/semantic_diff.py +98 -0
  174. metaobjects/source/yaml_positions.py +174 -0
  175. metaobjects/super_resolve.py +128 -0
  176. metaobjects/yaml_desugar.py +481 -0
  177. metaobjects-0.9.0.dist-info/METADATA +97 -0
  178. metaobjects-0.9.0.dist-info/RECORD +181 -0
  179. metaobjects-0.9.0.dist-info/WHEEL +4 -0
  180. metaobjects-0.9.0.dist-info/entry_points.txt +2 -0
  181. metaobjects-0.9.0.dist-info/licenses/LICENSE +189 -0
@@ -0,0 +1,96 @@
1
+ # Java server runtime
2
+
3
+ The Java runtime tier is **OMDB** (`metaobjects-omdb`) —
4
+ `com.metaobjects.manager.db.ObjectManagerDB`, a metadata-driven persistence engine
5
+ on modernized JDBC + Spring-tx. It reads the same metadata at runtime and drives
6
+ CRUD with no per-entity ORM boilerplate. OMDB is pure data-access (CRUD / query /
7
+ codec / transactions); schema is owned by the Node `meta` migration tool, not OMDB.
8
+
9
+ ## Construct an `ObjectManagerDB`
10
+
11
+ `ObjectManagerDB` has a no-arg constructor. Set its `DataSource` (and a
12
+ `DatabaseDriver` for the dialect), then call `init()`:
13
+
14
+ ```java
15
+ import com.metaobjects.manager.db.ObjectManagerDB;
16
+ import com.metaobjects.manager.db.ObjectConnection;
17
+ import com.metaobjects.manager.db.driver.PostgresDriver;
18
+ import com.metaobjects.object.MetaObject;
19
+ import com.metaobjects.object.ValueObject;
20
+
21
+ import javax.sql.DataSource;
22
+ import java.util.Collection;
23
+
24
+ ObjectManagerDB om = new ObjectManagerDB();
25
+ om.setDatabaseDriver(new PostgresDriver());
26
+ om.setDataSource(/* javax.sql.DataSource */);
27
+ om.init();
28
+ ```
29
+
30
+ ## CRUD + query
31
+
32
+ Every CRUD/query call takes an `ObjectConnection` obtained from
33
+ `om.getConnection()`; release it with `om.releaseConnection(oc)` when done. Objects
34
+ are `ValueObject` instances created from a `MetaObject` (look the `MetaObject` up by
35
+ its fully-qualified name on the loader's registry).
36
+
37
+ ```java
38
+ ObjectConnection oc = om.getConnection();
39
+ try {
40
+ MetaObject mo = registry.findMetaObjectByName("acme::blog::Author");
41
+
42
+ // Create
43
+ ValueObject author = (ValueObject) mo.newInstance();
44
+ author.setString("name", "Ada");
45
+ om.createObject(oc, author);
46
+
47
+ // Load (refresh an object's state by its identity)
48
+ om.loadObject(oc, author);
49
+
50
+ // Update
51
+ author.setString("name", "Ada Lovelace");
52
+ om.updateObject(oc, author);
53
+
54
+ // Query — all rows of the MetaObject
55
+ Collection<?> all = om.getObjects(oc, mo);
56
+
57
+ // Delete
58
+ om.deleteObject(oc, author);
59
+ } finally {
60
+ om.releaseConnection(oc);
61
+ }
62
+ ```
63
+
64
+ `getObjects` also has a filtered overload `om.getObjects(oc, mo, queryOptions)`
65
+ taking a `QueryOptions` (built from an `Expression`). `ValueObject` is the
66
+ map-backed runtime carrier.
67
+
68
+ ## Spring wiring
69
+
70
+ `metaobjects-core-spring` (or the Spring Boot starter) declares an
71
+ `ObjectManagerDB` bean from the Spring `DataSource` and enrolls it in Spring-managed
72
+ transactions — annotate your service methods `@Transactional` and OMDB participates.
73
+ Inject the bean into your services rather than constructing it by hand.
74
+
75
+ ## Return-type contract
76
+
77
+ OMDB returns **native in-process Java types**, never wire strings:
78
+
79
+ - `field.decimal` → `java.math.BigDecimal` — exact, **lossless end-to-end**, no
80
+ float round-tripping.
81
+ - temporal fields → native date/instant types.
82
+ - `field.object` (jsonb) → a native `Map`.
83
+
84
+ Wire canonicalization (currency → integer minor units as `Long`, temporals →
85
+ ISO-8601, UUID → canonical hex) happens only when a row leaves over HTTP — at the
86
+ serialization boundary in your Spring controllers — never inside the OMDB query
87
+ path. Compute with `BigDecimal` in-process; let the HTTP layer encode.
88
+
89
+ ## Serving the REST contract
90
+
91
+ `codegen-spring`'s `SpringControllerGenerator` emits `<Entity>Controller.java` per
92
+ writable entity, on the cross-port REST contract (five CRUD endpoints, `?sort`,
93
+ `?limit`/`?offset`, `?withCount=1` envelope, 404/400 envelopes). The generated
94
+ `<Entity>Repository.java` is a stubbed interface you implement against OMDB (or any
95
+ persistence layer) — wire the controller to call it. The same universal TS/Angular
96
+ web client consumes those controllers unchanged.
@@ -0,0 +1,99 @@
1
+ # Kotlin server runtime
2
+
3
+ The Kotlin port runtime is **generated Exposed `Table` objects plus your own
4
+ JetBrains Exposed transactions** — there is no Kotlin-specific persistence engine.
5
+ `KotlinExposedTableGenerator` emits one `<Entity>Table.kt` per entity with a
6
+ `source.rdb`; you hand-write the (trivial) transaction bodies, since the table
7
+ column definitions and the `@Serializable` entity data class are both generated
8
+ from the same metadata.
9
+
10
+ (If you want a fully metadata-driven engine instead of hand-written Exposed, the
11
+ Java **OMDB** runtime — `metaobjects-omdb`, `ObjectManagerDB` — is on the JVM and
12
+ callable from Kotlin; see the Java runtime reference. OMDB is pure data-access:
13
+ CRUD / query / codec / transactions. Schema is owned by the Node `meta` migration
14
+ tool, not the runtime.)
15
+
16
+ ## The generated table
17
+
18
+ For an `Author` entity, `KotlinEntityGenerator` + `KotlinExposedTableGenerator`
19
+ emit a data class and an Exposed `Table`:
20
+
21
+ ```kotlin
22
+ // generated/acme/blog/Author.kt
23
+ @Serializable
24
+ data class Author(
25
+ val id: Long,
26
+ val name: String,
27
+ val bio: String? = null,
28
+ )
29
+
30
+ // generated/acme/blog/AuthorTable.kt
31
+ object AuthorTable : Table("authors") {
32
+ val id = long("id").autoIncrement()
33
+ val name = varchar("name", 200)
34
+ val bio = varchar("bio", 2000).nullable()
35
+ override val primaryKey = PrimaryKey(id)
36
+ }
37
+ ```
38
+
39
+ ## Query + persist with Exposed
40
+
41
+ Obtain a `Database` (the generated `MetadataExposedConfig` `@Configuration` calls
42
+ `Database.connect(...)` for you, or do it yourself), then wrap reads/writes in
43
+ `transaction(db) { ... }`:
44
+
45
+ ```kotlin
46
+ import org.jetbrains.exposed.sql.Database
47
+ import org.jetbrains.exposed.sql.selectAll
48
+ import org.jetbrains.exposed.sql.insert
49
+ import org.jetbrains.exposed.sql.transactions.transaction
50
+
51
+ @Service
52
+ class AuthorService(private val db: Database) {
53
+ fun list(): List<Author> = transaction(db) {
54
+ AuthorTable.selectAll().map {
55
+ Author(
56
+ id = it[AuthorTable.id],
57
+ name = it[AuthorTable.name],
58
+ bio = it[AuthorTable.bio],
59
+ )
60
+ }
61
+ }
62
+
63
+ fun create(name: String, bio: String? = null): Long = transaction(db) {
64
+ AuthorTable.insert {
65
+ it[AuthorTable.name] = name
66
+ it[AuthorTable.bio] = bio
67
+ } get AuthorTable.id
68
+ }
69
+ }
70
+ ```
71
+
72
+ Filtered reads use Exposed's `selectAll()` plus a `where { ... }` op tree (e.g.
73
+ `AuthorTable.selectAll().where { AuthorTable.name eq someName }`), exactly as the
74
+ `integration-tests-kotlin` query-conformance runner does against Testcontainers
75
+ Postgres.
76
+
77
+ ## Return-type contract
78
+
79
+ An Exposed read yields **native in-process Kotlin/JVM types** at the column, never
80
+ wire strings — this is verified by the port's runtime-return-type test:
81
+
82
+ - `field.decimal` (NUMERIC) → `java.math.BigDecimal` — exact, lossless, no float
83
+ round-tripping.
84
+ - `field.long` → `Long`; other scalars to their native Kotlin types.
85
+ - a `timestamp`-with-tz field → `java.time.Instant` (the metaobjects
86
+ `instantWithTimeZone` `Column<Instant>` path — native temporal, not a String).
87
+
88
+ Wire canonicalization (currency → integer minor units as `Long`, temporals →
89
+ ISO-8601, UUID → canonical hex) happens only when a row leaves over HTTP — at the
90
+ serialization boundary in your Spring controller — never inside the query path.
91
+ Compute with `BigDecimal`/`Instant` in-process; let the HTTP layer encode.
92
+
93
+ ## Serving the REST contract
94
+
95
+ `KotlinSpringControllerGenerator` emits `<Entity>Controller.kt` per writable entity
96
+ (`source.rdb` `@kind="table"`) as a Spring `@RestController` on the cross-port REST
97
+ contract (five CRUD endpoints, `?sort`, `?limit`/`?offset`, `?withCount=1`
98
+ envelope). The same universal TS/Angular web client consumes those controllers
99
+ unchanged — the wire format matches the C# and Java backends byte-for-byte.
@@ -0,0 +1,86 @@
1
+ # React web client
2
+
3
+ `@metaobjectsdev/react` is the browser-side React runtime. It is **universal** — it
4
+ consumes any backend (TS / Java / Kotlin / C# / Python) that speaks the cross-port
5
+ REST contract, not just a TS server. It pairs with the `codegen-ts-react`
6
+ `formFile()` generator, which emits `<Entity>.form.tsx` files that import from this
7
+ package.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @metaobjectsdev/react @metaobjectsdev/runtime-web
13
+ npm install --save-dev @metaobjectsdev/codegen-ts-react
14
+ ```
15
+
16
+ `@metaobjectsdev/react` peer-deps on `react`, `react-hook-form`,
17
+ `@hookform/resolvers`, and `zod`.
18
+
19
+ ## Key exports
20
+
21
+ | Export | Purpose |
22
+ |---|---|
23
+ | `useEntityForm(Entity, InsertSchema)` | React Hook Form bound to a generated Zod insert schema; returns the full `UseFormReturn<T>` plus a `.input.<field>` accessor |
24
+ | `<CurrencyInput>` | controlled bidirectional money input — strips symbol/grouping on focus, re-formats on blur, emits integer minor units (cents) to `onChange` |
25
+
26
+ ## Generated forms (`formFile()`)
27
+
28
+ Wire `formFile()` in `metaobjects.config.ts` and `meta gen` emits a
29
+ `<Entity>.form.tsx` per entity. The form spreads `.input.<field>` onto each
30
+ control; every metadata-derived attribute (placeholder, type, aria-label, RHF
31
+ validation rule) rides along automatically:
32
+
33
+ ```ts
34
+ // metaobjects.config.ts
35
+ import { defineConfig } from "@metaobjectsdev/cli";
36
+ import { entityFile, queriesFile, barrel } from "@metaobjectsdev/codegen-ts/generators";
37
+ import { formFile } from "@metaobjectsdev/codegen-ts-react";
38
+
39
+ export default defineConfig({
40
+ outDir: "src/generated",
41
+ apiPrefix: "/api",
42
+ generators: [entityFile(), queriesFile(), barrel(), formFile()],
43
+ });
44
+ ```
45
+
46
+ ```tsx
47
+ // generated/Author.form.tsx (consumer's view)
48
+ import { useEntityForm } from "@metaobjectsdev/react";
49
+ import { Author, AuthorInsertSchema } from "./Author";
50
+
51
+ export function AuthorForm({ onSubmit }: { onSubmit: (v: AuthorInsert) => void }) {
52
+ const form = useEntityForm(Author, AuthorInsertSchema);
53
+ return (
54
+ <form onSubmit={form.handleSubmit(onSubmit)}>
55
+ <input {...form.input.name} />
56
+ <textarea {...form.input.bio} />
57
+ <button type="submit">Save</button>
58
+ </form>
59
+ );
60
+ }
61
+ ```
62
+
63
+ Validation runs through `zodResolver` against the generated `AuthorInsertSchema`.
64
+ `handleSubmit`, `formState`, `setValue`, etc. are all available since the hook
65
+ returns the full RHF surface.
66
+
67
+ ## Currency input
68
+
69
+ `field.currency` stores + transmits integer minor units; the browser formats.
70
+ `<CurrencyInput>` keeps editing native (cents in, cents out); `formatCurrency`
71
+ (from `@metaobjectsdev/runtime-web`) is the display side.
72
+
73
+ ```tsx
74
+ import { formatCurrency } from "@metaobjectsdev/runtime-web";
75
+ import { CurrencyInput } from "@metaobjectsdev/react";
76
+
77
+ formatCurrency(1599, "USD", "en-US"); // "$15.99"
78
+ <CurrencyInput value={1599} onChange={setCents} currency="USD" locale="en-US" />
79
+ ```
80
+
81
+ ## Talking to the backend
82
+
83
+ React forms submit through whatever data layer you wire. For list/query + mutation
84
+ hooks against the REST contract, add the TanStack client
85
+ (`@metaobjectsdev/tanstack` + `codegen-ts-tanstack`) — see `tanstack.md`. Both sit
86
+ on the single `EntityFetcher` seam your app supplies at its root.
@@ -0,0 +1,119 @@
1
+ # TanStack web client
2
+
3
+ `@metaobjectsdev/tanstack` is the browser-side TanStack runtime — TanStack Query
4
+ hooks + a TanStack Table grid component. Like the React client it is **universal**:
5
+ it consumes any backend (TS / Java / Kotlin / C# / Python) that speaks the
6
+ cross-port REST contract. It pairs with `codegen-ts-tanstack`, which emits
7
+ `<Entity>.hooks.ts` and `<Entity>.columns.tsx` that import from this package.
8
+
9
+ ## Contents
10
+ - Install
11
+ - Key exports
12
+ - The `EntityFetcher` contract
13
+ - Generated hooks (`tanstackQuery()`)
14
+ - Generated grid (`tanstackGrid()`)
15
+ - Cell renderer overrides
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @metaobjectsdev/tanstack @metaobjectsdev/runtime-web
21
+ npm install --save-dev @metaobjectsdev/codegen-ts-tanstack
22
+ ```
23
+
24
+ Peer-deps: `@tanstack/react-query`, `@tanstack/react-table`.
25
+
26
+ ## Key exports
27
+
28
+ | Export | Purpose |
29
+ |---|---|
30
+ | `<EntityFetcherProvider value={fetcher}>` | supplies the single `EntityFetcher` every generated hook reads |
31
+ | `useEntityFetcher()` | reads the fetcher from context (generated hooks call this) |
32
+ | `<EntityGrid>` | opinionated TanStack Table component |
33
+ | `<CellRendererProvider>` + `defaultCellRenderers` | renderer overrides keyed by the column's `meta.view` |
34
+
35
+ ## The `EntityFetcher` contract
36
+
37
+ The client never calls `fetch` directly. Every generated hook delegates to one
38
+ fetcher you supply once at the app root:
39
+
40
+ ```ts
41
+ // from @metaobjectsdev/runtime-web
42
+ export type EntityFetcher = <T>(path: string, init?: RequestInit) => Promise<T>;
43
+ ```
44
+
45
+ ```tsx
46
+ import { EntityFetcherProvider } from "@metaobjectsdev/tanstack";
47
+
48
+ const fetcher = async <T,>(path: string, init?: RequestInit): Promise<T> => {
49
+ const res = await fetch(path, {
50
+ ...init,
51
+ credentials: "include",
52
+ headers: { "Content-Type": "application/json", ...(init?.headers ?? {}) },
53
+ });
54
+ if (!res.ok) throw new Error(`HTTP ${res.status} on ${path}`); // hooks rely on the throw
55
+ return res.status === 204 ? (undefined as T) : ((await res.json()) as T);
56
+ };
57
+
58
+ export function App() {
59
+ return (
60
+ <EntityFetcherProvider value={fetcher}>
61
+ <AuthorList />
62
+ </EntityFetcherProvider>
63
+ );
64
+ }
65
+ ```
66
+
67
+ The fetcher resolves `path` (always starting with `apiPrefix`) to a full URL,
68
+ attaches auth per your policy, parses JSON, and **throws on non-2xx** — the hooks
69
+ depend on the throw for error state.
70
+
71
+ ## Generated hooks (`tanstackQuery()`)
72
+
73
+ Emits `<Entity>.hooks.ts` — 5 hooks for a writable entity (2 for read-only
74
+ projections):
75
+
76
+ | Hook | Verb / Path |
77
+ |---|---|
78
+ | `useAuthor(id)` | `GET /api/author/:id` |
79
+ | `useAuthors(filter?)` | `GET /api/author?filter[..]=..&sort=..&limit=N&offset=N` |
80
+ | `useCreateAuthor()` | `POST /api/author` |
81
+ | `useUpdateAuthor()` | `PATCH /api/author/:id` |
82
+ | `useDeleteAuthor()` | `DELETE /api/author/:id` |
83
+
84
+ Query hooks return `UseQueryResult`; mutation hooks return `UseMutationResult` and
85
+ invalidate the entity's query keys so lists re-fetch after writes.
86
+
87
+ ## Generated grid (`tanstackGrid()`)
88
+
89
+ Emits `<Entity>.columns.tsx` from the entity's `layout.dataGrid` child — TanStack
90
+ `ColumnDef<T>[]`, each carrying `meta.view` for the renderer registry. Render with
91
+ `<EntityGrid>`:
92
+
93
+ ```tsx
94
+ import { useAuthors } from "./generated/Author.hooks";
95
+ import { authorColumns } from "./generated/Author.columns";
96
+ import { EntityGrid } from "@metaobjectsdev/tanstack";
97
+
98
+ const { data } = useAuthors({ sort: "name:asc", limit: 25, offset: 0, withCount: 1 });
99
+ <EntityGrid columns={authorColumns} data={data?.rows ?? []} rowCount={data?.total ?? 0} />
100
+ ```
101
+
102
+ `tanstackGridHook()` (optional) wraps the sorting/pagination/filter state plumbing
103
+ into a `useAuthorGrid()` so the consumer renders `<EntityGrid {...useAuthorGrid()} />`.
104
+
105
+ ## Cell renderer overrides
106
+
107
+ `<EntityGrid>` routes rendering through `CellRendererProvider`, keyed by `meta.view`
108
+ (`text` / `number` / `date` / `boolean` / `currency` / `dropdown` / …). Override a
109
+ key without touching generated code; per-column `cell` always wins, the provider
110
+ fills in otherwise.
111
+
112
+ ```tsx
113
+ import { CellRendererProvider } from "@metaobjectsdev/tanstack";
114
+ import { formatCurrency } from "@metaobjectsdev/runtime-web";
115
+
116
+ <CellRendererProvider value={{ currency: (ctx) => formatCurrency(ctx.getValue() as number, "EUR", "fr-FR") }}>
117
+ <EntityGrid {...gridProps} />
118
+ </CellRendererProvider>
119
+ ```
@@ -0,0 +1,92 @@
1
+ # TypeScript server runtime
2
+
3
+ The Node-side runtime tier is `@metaobjectsdev/runtime-ts`. It supplies both the
4
+ helpers the generated routes lean on (`parseFilterParams`) and a metadata-driven
5
+ `ObjectManager` for full-runtime CRUD / validation / relationship traversal.
6
+
7
+ ## Two ways to persist
8
+
9
+ **1. Generated query helpers (the common path).** `queriesFile()` emits a
10
+ `<Entity>.queries.ts` per entity with typed CRUD. Per ADR-0008 every generated
11
+ helper takes the Drizzle/Kysely `db` as its **first parameter** — no module-level
12
+ `db` singleton:
13
+
14
+ ```ts
15
+ import { findAuthorById, createAuthor, listAuthors } from "./generated/Author.queries";
16
+
17
+ const author = await findAuthorById(db, 42); // db passed, not imported
18
+ const created = await createAuthor(db, { name: "Ada" });
19
+ const page = await listAuthors(db, { limit: 25 });
20
+ ```
21
+
22
+ You own the connection lifecycle and thread `db` through every call — that keeps
23
+ the code testable and lets one process talk to multiple databases.
24
+
25
+ **2. The `ObjectManager` runtime (dynamic CRUD / admin UIs / MCP tools).** Drives
26
+ behavior directly off loaded metadata, no per-entity generated file needed:
27
+
28
+ ```ts
29
+ import { MetaDataLoader } from "@metaobjectsdev/metadata";
30
+ import { FileSource } from "@metaobjectsdev/metadata/core";
31
+ import { ObjectManager } from "@metaobjectsdev/runtime-ts";
32
+ import { kyselyDriver } from "@metaobjectsdev/runtime-ts/drivers";
33
+
34
+ const { root } = await new MetaDataLoader().load([
35
+ new FileSource("metaobjects/meta.blog.json"),
36
+ ]);
37
+
38
+ const om = new ObjectManager({
39
+ metadata: root,
40
+ driver: kyselyDriver({ db: kyselyInstance, dialect: "postgres" }),
41
+ });
42
+
43
+ const post = await om.create("Post", { title: "Hello", authorId: 1 });
44
+ const found = await om.findById("Post", post.id, { include: ["author"] });
45
+ const list = await om.findMany("Post", { authorId: 1 }, { limit: 10 });
46
+ await om.update("Post", post.id, { title: "Updated" });
47
+ await om.delete("Post", post.id);
48
+
49
+ const result = om.validate("Post", { title: "x" }); // pure, no DB hit
50
+ if (!result.ok) console.log(result.errors);
51
+
52
+ await om.transaction(async (tx) => { /* ... */ });
53
+ ```
54
+
55
+ ### Drivers
56
+
57
+ - `kyselyDriver({ db, dialect })` — real DBs (SQLite/libsql/Turso, Postgres via
58
+ `pg` / Neon). You provide the Kysely instance.
59
+ - `inMemoryDriver({ seed?, pkFields? })` — Map-backed; unit tests, prototyping, MCP
60
+ sandboxing.
61
+
62
+ `findMany` filters take a Mongo-style object: `{ field: value }` (eq),
63
+ `{ field: null }` (IS NULL), `{ field: [a, b] }` (IN), or explicit operators
64
+ `{ field: { $gte, $like, $in, ... } }`.
65
+
66
+ > Driver note: generated CRUD uses Kysely's `.returning()`. Works on libsql/Turso,
67
+ > `node-postgres`, `@neondatabase/serverless`; NOT on `better-sqlite3` / `bun:sqlite`
68
+ > (no native RETURNING) — use a custom driver or `inMemoryDriver` there.
69
+
70
+ ## Return-type contract
71
+
72
+ The runtime returns **native in-process types**, never wire strings — temporal
73
+ fields as native dates, jsonb as native objects. The one documented TS outlier:
74
+ `field.decimal` comes back as a **`string`** (JS has no native exact decimal),
75
+ preserving precision. Wire canonicalization (currency → integer minor units,
76
+ temporals → ISO-8601, UUID → canonical hex) is applied only at the HTTP
77
+ serialization boundary, never inside the query path.
78
+
79
+ ## Serving the REST contract
80
+
81
+ `routesFile()` (Fastify) or `routesFileHono()` (Hono/Workers/edge) emits CRUD
82
+ routes on the cross-port contract. Mount them with the `db` injected:
83
+
84
+ ```ts
85
+ import { registerAuthorRoutes } from "./generated/Author.routes";
86
+ registerAuthorRoutes(app, { db }); // GET/POST/PATCH/PUT/DELETE under apiPrefix
87
+ ```
88
+
89
+ The routes call `parseFilterParams` (from `@metaobjectsdev/runtime-ts/drizzle-fastify`)
90
+ to validate `?filter[..][..]=..&sort=..&limit=&offset=` against the generated
91
+ `<Entity>FilterAllowlist` / `<Entity>SortAllowlist`, returning HTTP 400 on an
92
+ unknown field or disallowed operator.
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: metaobjects-verify
3
+ description: Use when verifying MetaObjects: drift checks (verify --db/--codegen/--templates), schema migrations, and interpreting conformance/test failures.
4
+ ---
5
+
6
+ # MetaObjects verify + migrations
7
+
8
+ The third pillar: **drift detection.** MetaObjects treats metadata as the source of
9
+ truth and generated code + DB schema + prompts as derived. `verify` is the
10
+ cross-cutting discipline that catches divergence; schema migration is the build-time
11
+ pipeline that brings the database into line with metadata. This skill is the
12
+ procedure for running both and reading the failures.
13
+
14
+ ## The drift sources
15
+
16
+ Drift is any place where a derived artifact has fallen out of sync with the
17
+ metadata that should define it. The ones a developer must actively guard:
18
+
19
+ - **DB-vs-metadata** — the live database schema has diverged from the metadata
20
+ (a column the metadata no longer declares, a missing index, a type mismatch).
21
+ - **Generated-vs-metadata (codegen)** — committed generated code no longer matches
22
+ what the current metadata would emit (someone edited a `@generated` file, or
23
+ forgot to regenerate after changing metadata).
24
+ - **Prompt-vs-payload (templates)** — a template references a `{{field}}` that
25
+ isn't on its `@payloadRef` payload VO (a renamed source field silently degrading
26
+ a prompt).
27
+
28
+ Two more are caught structurally rather than by a command: **generated-edited**
29
+ (the `@generated` header + three-way merge surface hand-edits at code review) and
30
+ **migration-vs-metadata** (migrations are emitted *from* metadata diffs, so they
31
+ can't drift by construction).
32
+
33
+ ## The `verify` subverbs
34
+
35
+ `verify` has three drift checks. Run them in CI.
36
+
37
+ - **`--db`** — schema drift. Introspects the live database and fails if it has
38
+ diverged from metadata. This is a **schema concern, so it is the Node toolchain's
39
+ job regardless of your server language** (see migrations below). On the JVM ports
40
+ a runtime startup validator catches generated-table drift at app boot as a
41
+ complementary check, but the authoritative DB-vs-metadata gate is the Node
42
+ `verify --db`.
43
+
44
+ - **`--codegen`** — regeneration drift. Re-runs generation and diffs the result
45
+ against the committed generated files; a non-empty diff means someone edited
46
+ generated code or skipped a regen. Wire it into CI so a stale `@generated` file
47
+ fails the build.
48
+
49
+ - **`--templates`** — prompt/payload drift. For every `template.prompt` /
50
+ `template.output`, resolves the text, parses each `{{...}}` reference, and fails
51
+ if any reference isn't on the payload VO. This is the build-time gate for the
52
+ prompt-construction pillar.
53
+
54
+ A clean run is silent; a failure names the entity/template, the drifted artifact,
55
+ and (for templates) the missing reference. **Bias toward trusting the tool** — a
56
+ verify failure almost always means the metadata changed and a derived artifact
57
+ didn't follow.
58
+
59
+ ## Schema migrations are the shared TypeScript engine — for every port
60
+
61
+ This is the load-bearing architectural fact (ADR-0015): **schema migrations are
62
+ owned by one shared TypeScript engine, regardless of your server language.** The
63
+ Node `meta migrate` and `meta verify --db` are the migration + live-DB-drift
64
+ toolchain for TS, Java, Kotlin, C#, and Python alike.
65
+
66
+ What this means in practice:
67
+
68
+ - The Node `meta` CLI emits the migration SQL (diffing metadata → DDL) and applies
69
+ it. You point it at the same database your server connects to:
70
+
71
+ ```
72
+ meta migrate --db postgresql://... --slug initial # emit migration SQL
73
+ meta migrate --db postgresql://... --apply # apply pending migrations
74
+ meta migrate --dry-run # preview without writing
75
+ ```
76
+
77
+ - Dialects: `postgres` (default), `sqlite`, and `d1` (Cloudflare D1, TS-only).
78
+ - The JVM and Python ports have **no** migration command of their own — their
79
+ former migrate goals/modules were removed. A JVM service may auto-create
80
+ dev/test tables at startup for convenience, but production schema is always the
81
+ Node migrate engine's output.
82
+
83
+ So even in a Java or Python or C# project, schema migration and `verify --db` run
84
+ through the Node `meta` tool. The per-port `gen`/codegen tooling stays native to
85
+ the language; only schema crosses to Node.
86
+
87
+ ## Interpreting conformance / test failures
88
+
89
+ MetaObjects' behavior is pinned by cross-port **conformance corpora** (metamodel,
90
+ render, persistence, API-contract, verify). When a test or conformance fixture
91
+ fails:
92
+
93
+ - A **loader** failure cites an `ERR_*` code (e.g. `ERR_RESERVED_ATTR`,
94
+ `ERR_UNKNOWN_EXTENDS`, `ERR_MISSING_REQUIRED_ATTR`, `ERR_BAD_ATTR_VALUE`,
95
+ `ERR_YAML_COERCION`) — fix the metadata, not the loader.
96
+ - A **render/verify** failure means the rendered bytes or the template-drift
97
+ result diverged from the pinned expectation — usually a payload/text mismatch.
98
+ - A **persistence / API-contract** failure means a query result row or an HTTP
99
+ response shape diverged from the cross-port expectation — treat a deviation as a
100
+ bug in the code under test, not in the corpus.
101
+
102
+ The corpus is the contract: when output disagrees with a fixture, the output is
103
+ what's wrong.
104
+
105
+ ---
106
+
107
+ For the migration tooling read `references/migration.md`.
@@ -0,0 +1,72 @@
1
+ # Schema migrations — the shared TypeScript engine (every port)
2
+
3
+ Schema migration is owned by **one shared TypeScript engine** regardless of your
4
+ server language (ADR-0015). The Node `meta` CLI (`@metaobjectsdev/cli`, on top of
5
+ `@metaobjectsdev/migrate-ts`) is the migration + live-DB-drift toolchain for **TS,
6
+ Java, Kotlin, C#, and Python alike**. The non-TS ports have **no** migration command
7
+ of their own — their former migrate goals/modules were removed. A JVM service may
8
+ auto-create dev/test tables at startup for convenience, but production schema is
9
+ always the Node migrate engine's output.
10
+
11
+ So even in a Java / Python / C# / Kotlin project you run `meta migrate` and
12
+ `meta verify --db` through Node. Only schema crosses to Node; per-port `gen`/codegen
13
+ stays native to the language.
14
+
15
+ ## Install (Node, dev-only)
16
+
17
+ ```bash
18
+ npm install --save-dev @metaobjectsdev/cli @metaobjectsdev/migrate-ts
19
+ ```
20
+
21
+ You point the tool at the **same database your server connects to** — its
22
+ connection is independent of your runtime tier.
23
+
24
+ ## The workflow
25
+
26
+ 1. **Generate a migration** by diffing metadata vs the prior state (the live DB or a
27
+ committed snapshot). The engine emits paired `up.sql` + `down.sql`:
28
+
29
+ ```bash
30
+ meta migrate --db postgresql://... # emit up.sql + down.sql
31
+ meta migrate --db postgresql://... --slug initial # name the migration
32
+ meta migrate --dry-run # preview without writing
33
+ ```
34
+
35
+ 2. **Review the SQL.** Read the emitted `up.sql` (forward) and `down.sql`
36
+ (rollback) before applying. Destructive changes (drop column / drop table) are
37
+ opt-in — the engine blocks them unless explicitly allowed, and routes ambiguous
38
+ rename-vs-drop+add decisions through a prompt rather than guessing.
39
+
40
+ 3. **Apply** the pending migrations against the DB; migration history is tracked in
41
+ a ledger table:
42
+
43
+ ```bash
44
+ meta migrate --db postgresql://... --apply # run pending up.sql
45
+ meta migrate --db postgresql://... --rollback # run down.sql for the last migration
46
+ ```
47
+
48
+ ## Dialects
49
+
50
+ - `postgres` (default) — native `ALTER`s.
51
+ - `sqlite` (libsql / Turso) — native `ALTER`s where supported (≥ 3.35), bundling
52
+ recreate-and-copy per table when a change needs it.
53
+ - `d1` (Cloudflare D1) — **TS-only**; targets D1 via the wrangler CLI, writes
54
+ Wrangler's native `migrations/<seq>_<slug>.sql` layout. Pass `--dialect d1`.
55
+
56
+ ## Live-DB drift: `meta verify --db`
57
+
58
+ `meta verify --db` introspects the live database and fails if its schema has
59
+ diverged from the metadata (a column the metadata no longer declares, a missing
60
+ index, a type mismatch). This is the **authoritative** DB-vs-metadata gate for every
61
+ port — wire it into CI. On the JVM ports a runtime startup validator can catch
62
+ generated-table drift at app boot as a complementary check, but the gate that owns
63
+ DB drift is the Node `meta verify --db`.
64
+
65
+ A clean run is silent; a failure names the drifted table/column. Bias toward
66
+ trusting the tool — a drift failure almost always means the metadata changed and the
67
+ DB didn't follow.
68
+
69
+ ## Not yet shipped
70
+
71
+ Triggers, generated columns, partial/exclusion/check constraints, MySQL, and data
72
+ migrations (column-type changes needing data transformation error out with a hint).