@metaobjectsdev/sdk 0.5.0-rc.1
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.
- package/LICENSE +189 -0
- package/README.md +32 -0
- package/dist/agent-docs/body.d.ts +2 -0
- package/dist/agent-docs/body.d.ts.map +1 -0
- package/dist/agent-docs/body.js +563 -0
- package/dist/agent-docs/body.js.map +1 -0
- package/dist/agent-docs/content-hash.d.ts +8 -0
- package/dist/agent-docs/content-hash.d.ts.map +1 -0
- package/dist/agent-docs/content-hash.js +23 -0
- package/dist/agent-docs/content-hash.js.map +1 -0
- package/dist/agent-docs/index.d.ts +3 -0
- package/dist/agent-docs/index.d.ts.map +1 -0
- package/dist/agent-docs/index.js +4 -0
- package/dist/agent-docs/index.js.map +1 -0
- package/dist/config.d.ts +113 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +53 -0
- package/dist/config.js.map +1 -0
- package/dist/forge-types.d.ts +47 -0
- package/dist/forge-types.d.ts.map +1 -0
- package/dist/forge-types.js +133 -0
- package/dist/forge-types.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.d.ts +30 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +105 -0
- package/dist/memory.js.map +1 -0
- package/dist/package.d.ts +65 -0
- package/dist/package.d.ts.map +1 -0
- package/dist/package.js +105 -0
- package/dist/package.js.map +1 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +26 -0
- package/dist/paths.js.map +1 -0
- package/dist/records/any.d.ts +467 -0
- package/dist/records/any.d.ts.map +1 -0
- package/dist/records/any.js +14 -0
- package/dist/records/any.js.map +1 -0
- package/dist/records/convention.d.ts +90 -0
- package/dist/records/convention.d.ts.map +1 -0
- package/dist/records/convention.js +9 -0
- package/dist/records/convention.js.map +1 -0
- package/dist/records/core.d.ts +84 -0
- package/dist/records/core.d.ts.map +1 -0
- package/dist/records/core.js +47 -0
- package/dist/records/core.js.map +1 -0
- package/dist/records/decision.d.ts +90 -0
- package/dist/records/decision.d.ts.map +1 -0
- package/dist/records/decision.js +9 -0
- package/dist/records/decision.js.map +1 -0
- package/dist/records/failure.d.ts +93 -0
- package/dist/records/failure.d.ts.map +1 -0
- package/dist/records/failure.js +10 -0
- package/dist/records/failure.js.map +1 -0
- package/dist/records/glossary.d.ts +111 -0
- package/dist/records/glossary.d.ts.map +1 -0
- package/dist/records/glossary.js +14 -0
- package/dist/records/glossary.js.map +1 -0
- package/dist/records/principle.d.ts +99 -0
- package/dist/records/principle.d.ts.map +1 -0
- package/dist/records/principle.js +12 -0
- package/dist/records/principle.js.map +1 -0
- package/dist/storage/errors.d.ts +14 -0
- package/dist/storage/errors.d.ts.map +1 -0
- package/dist/storage/errors.js +27 -0
- package/dist/storage/errors.js.map +1 -0
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +6 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/lifecycle.d.ts +5 -0
- package/dist/storage/lifecycle.d.ts.map +1 -0
- package/dist/storage/lifecycle.js +27 -0
- package/dist/storage/lifecycle.js.map +1 -0
- package/dist/storage/list.d.ts +8 -0
- package/dist/storage/list.d.ts.map +1 -0
- package/dist/storage/list.js +42 -0
- package/dist/storage/list.js.map +1 -0
- package/dist/storage/read.d.ts +9 -0
- package/dist/storage/read.d.ts.map +1 -0
- package/dist/storage/read.js +43 -0
- package/dist/storage/read.js.map +1 -0
- package/dist/storage/write.d.ts +8 -0
- package/dist/storage/write.d.ts.map +1 -0
- package/dist/storage/write.js +20 -0
- package/dist/storage/write.js.map +1 -0
- package/dist/workspace.d.ts +49 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +280 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +48 -0
- package/src/agent-docs/body.ts +562 -0
- package/src/agent-docs/content-hash.ts +25 -0
- package/src/agent-docs/index.ts +8 -0
- package/src/config.ts +69 -0
- package/src/forge-types.ts +167 -0
- package/src/index.ts +98 -0
- package/src/memory.ts +116 -0
- package/src/package.ts +120 -0
- package/src/paths.ts +30 -0
- package/src/records/any.ts +15 -0
- package/src/records/convention.ts +10 -0
- package/src/records/core.ts +55 -0
- package/src/records/decision.ts +10 -0
- package/src/records/failure.ts +11 -0
- package/src/records/glossary.ts +15 -0
- package/src/records/principle.ts +13 -0
- package/src/storage/errors.ts +23 -0
- package/src/storage/index.ts +10 -0
- package/src/storage/lifecycle.ts +38 -0
- package/src/storage/list.ts +53 -0
- package/src/storage/read.ts +54 -0
- package/src/storage/write.ts +32 -0
- package/src/workspace.ts +342 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship made available under
|
|
36
|
+
the License, as indicated by a copyright notice that is included in
|
|
37
|
+
or attached to the work (an example is provided in the Appendix below).
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
40
|
+
form, that is based on (or derived from) the Work and for which the
|
|
41
|
+
editorial revisions, annotations, elaborations, or other transformations
|
|
42
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
43
|
+
of this License, Derivative Works shall not include works that remain
|
|
44
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
45
|
+
the Work and Derivative Works thereof.
|
|
46
|
+
|
|
47
|
+
"Contribution" shall mean, as submitted to the Licensor for inclusion
|
|
48
|
+
in the Work by the copyright owner or by an individual or Legal Entity
|
|
49
|
+
authorized to submit on behalf of the copyright owner. For the purposes
|
|
50
|
+
of this definition, "submitted" means any form of electronic, verbal,
|
|
51
|
+
or written communication sent to the Licensor or its representatives,
|
|
52
|
+
including but not limited to communication on electronic mailing lists,
|
|
53
|
+
source code control systems, and issue tracking systems that are managed
|
|
54
|
+
by, or on behalf of, the Licensor for the purpose of recording and
|
|
55
|
+
discussing modifications to the Work, but excluding communication that
|
|
56
|
+
is conspicuously marked or designated in writing by the copyright owner
|
|
57
|
+
as "Not a Contribution."
|
|
58
|
+
|
|
59
|
+
"Contributor" shall mean Licensor and any Legal Entity on behalf of
|
|
60
|
+
whom a Contribution has been received by the Licensor and included
|
|
61
|
+
within the Work.
|
|
62
|
+
|
|
63
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
64
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
65
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
66
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
67
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
68
|
+
Work and such Derivative Works in Source or Object form.
|
|
69
|
+
|
|
70
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
71
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
72
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
73
|
+
(except as stated in this section) patent license to make, have made,
|
|
74
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
75
|
+
where such license applies only to those patent claims licensable
|
|
76
|
+
by such Contributor that are necessarily infringed by their
|
|
77
|
+
Contribution(s) alone or by the combination of their Contribution(s)
|
|
78
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
79
|
+
institute patent litigation against any entity (including a cross-claim
|
|
80
|
+
or counterclaim in a lawsuit) alleging that the Work or any
|
|
81
|
+
Contribution embodied within the Work constitutes direct or contributory
|
|
82
|
+
patent infringement, then any patent licenses granted to You under
|
|
83
|
+
this License for that Work shall terminate as of the date such
|
|
84
|
+
litigation is filed.
|
|
85
|
+
|
|
86
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
87
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
88
|
+
modifications, and in Source or Object form, provided that You
|
|
89
|
+
meet the following conditions:
|
|
90
|
+
|
|
91
|
+
(a) You must give any other recipients of the Work or Derivative
|
|
92
|
+
Works a copy of this License; and
|
|
93
|
+
|
|
94
|
+
(b) You must cause any modified files to carry prominent notices
|
|
95
|
+
stating that You changed the files; and
|
|
96
|
+
|
|
97
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
98
|
+
that You distribute, all copyright, patent, trademark, and
|
|
99
|
+
attribution notices from the Source form of the Work,
|
|
100
|
+
excluding those notices that do not pertain to any part of
|
|
101
|
+
the Derivative Works; and
|
|
102
|
+
|
|
103
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
104
|
+
distribution, You must include a readable copy of the
|
|
105
|
+
attribution notices contained within such NOTICE file, in
|
|
106
|
+
at least one of the following places: within a NOTICE text
|
|
107
|
+
file distributed as part of the Derivative Works; within
|
|
108
|
+
the Source form or documentation, if provided along with the
|
|
109
|
+
Derivative Works; or, within a display generated by the
|
|
110
|
+
Derivative Works, if and wherever such third-party notices
|
|
111
|
+
normally appear. The contents of the NOTICE file are for
|
|
112
|
+
informational purposes only and do not modify the License.
|
|
113
|
+
You may add Your own attribution notices within Derivative
|
|
114
|
+
Works that You distribute, alongside or in addition to the
|
|
115
|
+
NOTICE text from the Work, provided that such additional
|
|
116
|
+
attribution notices cannot be construed as modifying the License.
|
|
117
|
+
|
|
118
|
+
You may add Your own license statement for Your modifications and
|
|
119
|
+
may provide additional grant of rights to use, reproduce, modify,
|
|
120
|
+
prepare derivative works of, distribute, and sublicense such modifications,
|
|
121
|
+
as an additional requirement of this License.
|
|
122
|
+
|
|
123
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
124
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
125
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
126
|
+
this License, without any additional terms or conditions.
|
|
127
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
128
|
+
the terms of any separate license agreement you may have executed
|
|
129
|
+
with Licensor regarding such Contributions.
|
|
130
|
+
|
|
131
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
132
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
133
|
+
except as required for reasonable and customary use in describing the
|
|
134
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
135
|
+
|
|
136
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
137
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
138
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
139
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
140
|
+
implied, including, without limitation, any conditions of TITLE,
|
|
141
|
+
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR
|
|
142
|
+
PURPOSE. You are solely responsible for determining the
|
|
143
|
+
appropriateness of using or reproducing the Work and assume any
|
|
144
|
+
risks associated with Your exercise of permissions under this License.
|
|
145
|
+
|
|
146
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
147
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
148
|
+
unless required by applicable law (such as deliberate and grossly
|
|
149
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
150
|
+
liable to You for damages, including any direct, indirect, special,
|
|
151
|
+
incidental, or exemplary damages of any character arising as a
|
|
152
|
+
result of this License or out of the use or inability to use the
|
|
153
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
154
|
+
work stoppage, computer failure or malfunction, or all other
|
|
155
|
+
commercial damages or losses), even if such Contributor has been
|
|
156
|
+
advised of the possibility of such damages.
|
|
157
|
+
|
|
158
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
159
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
160
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
161
|
+
or other liability obligations and/or rights consistent with this
|
|
162
|
+
License. However, in accepting such obligations, You may offer
|
|
163
|
+
only obligations consistent to this License provided that the
|
|
164
|
+
obligations are consistent with this License.
|
|
165
|
+
|
|
166
|
+
END OF TERMS AND CONDITIONS
|
|
167
|
+
|
|
168
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
169
|
+
|
|
170
|
+
To apply the Apache License to your work, attach the following
|
|
171
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
172
|
+
replaced with your own identifying information. (Don't include
|
|
173
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
174
|
+
comment syntax for the file format in question. Also add information
|
|
175
|
+
on how to contact you electronically and/or by mail.
|
|
176
|
+
|
|
177
|
+
Copyright 2026 MetaObjects Contributors
|
|
178
|
+
|
|
179
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
180
|
+
you may not use this file except in compliance with the License.
|
|
181
|
+
You may obtain a copy of the License at
|
|
182
|
+
|
|
183
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
184
|
+
|
|
185
|
+
Unless required by applicable law or agreed to in writing, software
|
|
186
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
187
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
188
|
+
See the License for the specific language governing permissions and
|
|
189
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @metaobjectsdev/sdk
|
|
2
|
+
|
|
3
|
+
Programmatic SDK for MetaObjects: workspace memory records, path resolution, project config loading, and the agent-docs reference content. Consumed by the `meta` CLI and by AI-collaboration tooling (MCP exposers, codegen prompts).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @metaobjectsdev/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { resolveMetaRoot, loadConfig } from "@metaobjectsdev/sdk";
|
|
15
|
+
|
|
16
|
+
const metaRoot = await resolveMetaRoot(process.cwd());
|
|
17
|
+
const config = await loadConfig(metaRoot);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The canonical agent reference docs (scaffolded by `meta init`) are available via a sub-path:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { AGENT_DOCS_BODY, withContentHash } from "@metaobjectsdev/sdk/agent-docs";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Links
|
|
27
|
+
|
|
28
|
+
- [Spec](https://github.com/metaobjectsdev/metaobjects/tree/main/spec)
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
Apache 2.0 — see [LICENSE](../../../../LICENSE) at the repo root.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const AGENT_DOCS_BODY = "# Meta Forge \u2014 agent reference\n\nThis file is scaffolded by `meta init` and lives alongside your `metaobjects/` records. It teaches AI coding assistants (Claude Code, Codex, etc.) how to read and modify MetaObjects metadata correctly. Refresh after CLI updates with `meta init --refresh-docs`.\n\n## Five working principles (read first)\n\nThese shape every interaction with a metaobjects-driven project. Follow them when you author metadata, write hand-coded business logic, or review someone else's work.\n\n### 1. If it's pattern-derivable from metadata, generate it. Never hand-write boilerplate.\n\nThe metaobjects raison d'\u00EAtre is that anything the metadata fully describes \u2014 schemas, FK references, basic CRUD, query helpers, Zod validators, route handlers, RHF rules, form fields \u2014 should be produced by codegen, not hand-typed. If you find yourself hand-writing something the metadata already knows about, stop and use the generated artifact.\n\nThe first version of the trainer website's database layer had hand-written Drizzle schemas, Zod schemas, and CRUD endpoints. Every one of those is now generated. The hand-written code that remains is real business logic (Stripe webhooks, Loops integration, custom auth flows) \u2014 things the generator genuinely cannot derive.\n\nWhen you're about to add a new field or entity: edit `metaobjects/*.json` and re-run `meta gen`. Don't reach for the generated file directly.\n\n### 2. Use the generated constants. Never use magic strings that touch metadata.\n\nAfter `meta gen`, each entity file exports a rich metadata-constants block. Each non-dollar-prefixed key is a per-field object that carries everything a consumer might need (name, label, view, html input type, placeholder, RHF validation rules):\n\n```ts\nexport const Subscriber = {\n $entity: \"Subscriber\", // entity name string\n $table: \"subscribers\", // SQL table name\n $path: \"/subscribers\", // REST resource path\n\n email: {\n name: \"email\", // field name string (use for filters, register())\n label: \"Email Address\", // humanized fallback or @label override\n view: \"text\", // MetaView subtype\n htmlType: \"email\", // optional; maps view \u2192 HTML <input type=>\n placeholder: \"you@example.com\", // optional; only when @placeholder is set on the view\n helpText: \"We never share this.\", // optional; only when @helpText is set\n rules: { // optional; derived from validator children\n required: \"Email is required\",\n maxLength: { value: 255, message: \"Too long\" },\n pattern: { value: /.../, message: \"Invalid email\" },\n },\n },\n firstName: { name: \"firstName\", label: \"First Name\", view: \"text\", htmlType: \"text\", rules: { required: \"First Name is required\" } },\n // ...\n} as const;\n```\n\n**Use them everywhere \u2014 in both generated AND hand-written code:**\n\n```tsx\n// \u2717 Don't:\n<input name=\"email\" type=\"email\" placeholder=\"Email\" />\n\n// \u2713 Do:\n<input\n type={Subscriber.email.htmlType}\n name={Subscriber.email.name}\n placeholder={Subscriber.email.placeholder}\n aria-label={Subscriber.email.label}\n/>\n```\n\nRename a field in `metaobjects/` and re-gen \u2014 TypeScript catches every stale reference.\n\n**Special case \u2014 Drizzle column access:** when you're already inside Drizzle's typed builder, just use the column properties directly. Drizzle's table-const types are themselves derived from metadata, so `weeks.programId` is already TS-safe:\n\n```ts\n// \u2713 Use Drizzle's typed accessor directly \u2014 no constants needed here:\ndb.select().from(weeks).where(eq(weeks.programId, X))\n\n// \u2717 Don't do this \u2014 it's redundant indirection:\ndb.select().from(weeks).where(eq(weeks[Week.programId.name], X))\n```\n\nUse the constants when you need a STRING (filter object keys, registration arguments, REST paths, labels). Use Drizzle properties directly when the type system already does the work.\n\n### 3. Forms: spread `form.input.<field>` from useEntityForm. One line per input.\n\nFor React forms, use `useEntityForm` from `@metaobjectsdev/react`. It returns the standard React Hook Form surface plus a pre-bound `.input` accessor \u2014 one entry per field, ready to spread onto an `<input>`:\n\n```tsx\nimport { useEntityForm } from '@metaobjectsdev/react';\nimport { Subscriber, SubscriberInsertSchema } from './generated/Subscriber';\n\nconst form = useEntityForm(Subscriber, SubscriberInsertSchema);\n\n<label>{Subscriber.email.label}</label>\n<input {...form.input.email} /> // \u2190 type, placeholder, name, rules, aria-label all spread automatically\n```\n\nFor non-`<input>` controls (textarea, select), the `type` attr is omitted from `form.input.X` \u2014 pick the right element yourself.\n\nThe same Zod schema (`SubscriberInsertSchema`) validates on the server (in Fastify routes) and on the client (via the resolver). One schema, two surfaces, zero drift.\n\n### 4. Routes: use the generated `<Entity>.routes.ts` for stock CRUD. Hand-write only what's custom.\n\n`meta gen` emits a per-entity routes file that mounts the 5 standard verbs via `mountCrudRoutes` from `@metaobjectsdev/runtime-ts/drizzle-fastify`. The runtime is plain Drizzle + Zod \u2014 no extra ORM.\n\nFor custom flows (Stripe webhooks, side effects, auth-gated actions), hand-write the route \u2014 but import the entity constants + generated Zod schema. The boilerplate (CRUD, validation, 404 mapping, pagination) lives in the helper; your hand-written code is just the business logic.\n\n**Auth pattern:** install a plugin-level Fastify `preHandler` hook at the top of your route plugin. The hook applies to every route registered after it \u2014 both hand-written handlers AND metaobjects-generated routes via the `routeOptions` field. Beats sprinkling `if (!auth(...)) return;` at the top of every handler.\n\n### 5. Hand-coded code is always available, but coexists with generated code.\n\nGenerated code does the boilerplate. Hand-coded code does the business logic. They live in the same project, the same package, sometimes the same file. The hand-coded code consumes the generated constants and generated Zod schemas \u2014 it never duplicates schema, never hard-codes paths, never declares its own validators that metadata could declare.\n\nConcrete pattern from the trainer website:\n- Generated `Subscriber.routes.ts` registers GET / GET-by-id / POST / PATCH / DELETE on `/api/subscribers`.\n- Hand-written `apps/api/src/routes/subscribers.ts` keeps `POST /subscribe` \u2014 the custom endpoint with the Loops side-effect.\n- Both registered with `fastify.register()`. Both validate via `SubscriberInsertSchema`. Both use `Subscriber.email.name` / etc. Neither knows the other exists.\n\n## Metaobjects metamodel \u2014 quick rules\n\nThe format used by `metaobjects/*.json` is **metaobjects metadata**, a cross-language standard. Eight base types:\n\n| Type | Purpose |\n|---|---|\n| `metadata` | Root document wrapper |\n| `object` | An entity (table/record) |\n| `field` | A property on an object |\n| `attr` | Named scalar/array decoration on any parent |\n| `validator` | A validation rule |\n| `view` | A UI control kind |\n| `identity` | A primary/secondary key |\n| `relationship` | An association between objects |\n\n### Two most-violated rules\n\n1. **Attribute uniqueness.** Within a single parent metadata node, all attribute names must be unique. You cannot have two `attr` children both named `alternative`. For multi-value, use a single `stringarray` attr: `\"@alternatives\": [\"a\", \"b\", \"c\"]`.\n\n2. **Inline `@<name>` and `attr` child are the same thing.** `\"@maxLength\": 50` is shorthand for `{\"attr\": {\"name\": \"maxLength\", \"subType\": \"int\", \"value\": \"50\"}}`. The parser converts inline form into attr children. Don't use both forms for the same attribute name on the same parent.\n\n### Object subtypes (v0.3)\n\n- `base` \u2014 abstract template (no runtime semantics)\n- `entity` \u2014 persistent record; should have a primary identity\n- `value` \u2014 value-object; equality by content; must NOT have a primary identity\n\nJava-runtime strategies (pojo / map / proxy) belong on `@javaRuntime`, not in `subType`.\n\n### Reserved structural keys (NOT attributes)\n\n`name`, `subType`, `package`, `extends`, `isAbstract`, `children`, `merge`, `value`.\n\nThe v0.2 keys (`super`, `overlay`, `override`, `isInterface`, `implements`) are **gone**. The current parser will reject them. Use:\n- `extends:` instead of `super:` for the supertype reference\n- `merge: true` instead of `overlay: true` / `override: true` for in-place modification\n- `@isAbstract: true` instead of `isInterface: true` (multiple inheritance is not supported)\n\n### Package paths and inheritance\n\n- Package segments separated by `::` \u2014 `acme::common::id`\n- Relative references in `extends:` \u2014 `..::common::id` means \"go up to parent package, descend into `common::id`\"\n- Cross-file resolution works as long as all files are passed to Loader (or live in the same `metaobjects/` directory)\n\n### Two special intercepted attrs (parser-routed)\n\n- `@isArray` \u2192 marks a field as a collection\n- `@isAbstract` \u2192 marks a node as abstract (inheritable but not instantiable)\n\n## Validators \u2014 two layers\n\nValidators can attach in two places, and they compose:\n\n**Field-level validators** describe what makes the *stored value* valid. They survive across UI, API, batch import, manual SQL \u2014 anywhere data enters the system. The generated Zod `<Entity>InsertSchema` encodes these.\n\n```json\n{\"field\": {\"name\": \"email\", \"subType\": \"string\",\n \"children\": [\n {\"validator\": {\"subType\": \"required\"}},\n {\"validator\": {\"subType\": \"regex\", \"@pattern\": \"^[^@]+@[^@]+\\\\.[^@]+$\"}}\n ]\n}}\n```\n\n**View-level validators** describe what makes user *input* valid in a specific UI surface \u2014 possibly stricter, possibly with different messages, possibly format-specific. They run client-side in generated forms. They do NOT necessarily reject the stored value if it's already in the DB.\n\n```json\n{\"field\": {\"name\": \"phone\", \"subType\": \"string\",\n \"children\": [\n {\"validator\": {\"subType\": \"regex\", \"@pattern\": \"^\\\\+?[0-9]+$\"}},\n {\"view\": {\"subType\": \"text-input\", \"@label\": \"Phone\",\n \"children\": [\n {\"validator\": {\"subType\": \"length\", \"@min\": 7, \"@max\": 20,\n \"@message\": \"Phone must be 7-20 digits\"}}\n ]\n }}\n ]\n}}\n```\n\nRule of thumb: rules that protect data integrity \u2192 field. Rules that improve input UX \u2192 view.\n\n## metaobjects.config.ts \u2014 generator wiring (project root)\n\n`meta gen` reads `metaobjects.config.ts` at the project root. This is where you declare which generators run and their options. It is TypeScript, type-checked, and imported via `jiti` at run time.\n\n```ts\nimport { defineConfig } from \"@metaobjectsdev/cli\";\nimport {\n entityFile, queriesFile, routesFile, /* formFile, */ barrel,\n} from \"@metaobjectsdev/codegen-ts/generators\";\n\nexport default defineConfig({\n outDir: \"packages/database/src/generated\",\n extStyle: \"none\",\n dbImport: \"../index\",\n dialect: \"sqlite\",\n generators: [\n entityFile(),\n queriesFile(),\n routesFile(),\n // formFile(), // opt-in: emits stock React forms per entity\n barrel(),\n ],\n});\n```\n\n3rd-party generator example: `import { tanstackQuery } from \"@metaobjectsdev/codegen-ts-tanstack\"; // then add tanstackQuery({ ... }) to the generators array`\n\nFilters live on the generator entry: `routesFile({ filter: e => e.name !== \"AuditLog\" })`\n\n`.metaobjects/config.json` is unchanged \u2014 it still holds static project state (schema_version, pending_in_git, confidence_thresholds). Generator wiring belongs in `metaobjects.config.ts` so TypeScript can type-check the imports.\n\n## Generated hooks + grids (TanStack)\n\nWhen `tanstackQuery()` is in your `metaobjects.config.ts`, every entity gets `<Entity>.hooks.ts` with a query-key factory + `useEntity`, `useEntities`, `useCreate`, `useUpdate`, `useDelete` hooks. When `tanstackGrid()` is in the config, entities with a `layout[dataGrid]` child also get `<Entity>.columns.tsx`.\n\n```tsx\nimport { usePrograms, useCreateProgram } from \"@your-pkg/database/generated/Program.hooks\";\nimport { programDefaultColumns, programDefaultGrid } from \"@your-pkg/database/generated/Program.columns\";\nimport { EntityGrid } from \"@metaobjectsdev/tanstack\";\n\nconst { data, isLoading } = usePrograms();\nconst create = useCreateProgram({ onSuccess: () => navigate(\"/programs\") });\n\n<EntityGrid\n columns={programDefaultColumns}\n grid={programDefaultGrid}\n data={data ?? []}\n isLoading={isLoading}\n onRowClick={(row) => navigate(`/admin/programs/${row.id}`)}\n/>\n```\n\n**Provider setup.** Wrap your app with `<EntityFetcherProvider value={fetcher}>` (supplies the HTTP fetcher to all generated hooks). For an admin subtree with different auth, wrap a second time inside: `<EntityFetcherProvider value={adminFetch}>...</EntityFetcherProvider>` overrides the outer one.\n\n**Metadata layer \u2014 grid definition:**\n\n```jsonc\n{ \"layout\": {\n \"subType\": \"dataGrid\",\n \"name\": \"default\",\n \"@pageSize\": 25,\n \"@defaultSortField\": \"createdAt\",\n \"@defaultSortOrder\": \"desc\",\n \"@filterable\": true,\n \"@columns\": [\"email\", \"firstName\", \"subscribed\", \"createdAt\"]\n}}\n```\n\nThe `@columns` attr is a flat string array listing fields to display. Per-column rendering comes from each field's own `view` subtype (the same one that drives forms); sortability comes from the field's `@sortable` attr; width belongs in app CSS. There are no nested per-column children \u2014 just `@columns`.\n\n**Cell renderers.** Field rendering inside grids comes from each field's own `view` subtype (the same one that drives forms). Override defaults app-wide with `<CellRendererProvider value={{ currency: ({ getValue }) => <Money value={getValue()} /> }}>`.\n\n**Per-entity opt-out.** `@emitTanstack: false` on an entity skips both hooks and columns.\n\n## Filtering generated lists\n\nMark filterable fields in metadata with `@filterable: true`:\n\n```jsonc\n{ \"field\": { \"name\": \"email\", \"subType\": \"string\", \"@filterable\": true } }\n```\n\nThe generated `useSubscribers(filter)` hook accepts a typed filter:\n\n```tsx\nconst { data } = useSubscribers({\n email: { like: \"%@example.com\" },\n subscribed: true,\n sort: \"createdAt:desc\",\n limit: 25,\n});\n```\n\nURL sent: `/subscribers?filter[email][like]=%25@example.com&filter[subscribed]=true&sort=createdAt:desc&limit=25`\n\n**Operators by field subtype:**\n- String: `eq, ne, in, like, isNull`\n- Number/date: `eq, ne, gt, gte, lt, lte, in, isNull`\n- Boolean: `eq, isNull`\n\nIllegal combinations like `useSubscribers({ subscribed: { gte: true } })` fail to compile (booleans don't support `gte`).\n\n**Per-grid preset filter** via layout `@filter`:\n\n```jsonc\n{ \"layout\": { \"subType\": \"dataGrid\", \"name\": \"active\",\n \"@filter\": { \"subscribed\": true },\n \"@columns\": [\"email\", \"firstName\", \"subscribed\"] }}\n```\n\nGenerates `subscriberActiveFilter` const consumable in pages. Compose with ad-hoc filters via object spread.\n\n## Projections (read models with joined/aggregated columns)\n\nWhen a list needs computed columns (counts, sums, joined fields), create a **projection** \u2014 an entity that extends a base entity but reads from a SQL view:\n\n```json\n// metaobjects/meta.commerce.json (inline with Program)\n{\n \"object\": {\n \"name\": \"ProgramSummary\",\n \"subType\": \"entity\",\n \"extends\": \"Program\",\n \"children\": [\n { \"source\": { \"subType\": \"dbView\", \"@name\": \"v_program_summary\" } },\n { \"field\": { \"name\": \"weekCount\", \"subType\": \"int\", \"children\": [\n { \"origin\": { \"subType\": \"aggregate\",\n \"@agg\": \"count\", \"@of\": \"Week.id\", \"@via\": \"Program.weeks\" }}\n ]}},\n { \"identity\": { \"subType\": \"primary\", \"@fields\": \"id\" } }\n ]\n }\n}\n```\n\n`meta gen` produces a read-only `useProgramSummaries(filter)` hook, a SQL view DDL in the migration, and a read-only GET-only route.\n\n**Aggregate vocabulary**: `count`, `sum`, `avg`, `min`, `max`.\n\n**Multi-level via paths** are supported: `@via: \"Program.weeks.workouts\"` builds a 2-level JOIN tree.\n\n**For pages that need a full nested tree** (e.g., Program \u2192 Weeks \u2192 Workouts \u2192 Exercises), use 4 entity hooks with Project D's filter syntax for batched lookups (no projection needed \u2014 flat hooks + client-side stitching is enough):\n\n```tsx\nconst { data: weeks } = useWeeks({ programId, sort: \"weekNumber:asc\" });\nconst weekIds = weeks?.map((w) => w.id) ?? [];\nconst { data: workouts } = useWorkouts(\n weekIds.length ? { weekId: { in: weekIds } } : undefined,\n);\n```\n\n## Currency fields\n\nDeclare a money field with `subType: \"currency\"`:\n\n```json\n{ \"field\": { \"name\": \"priceCents\", \"subType\": \"currency\", \"@currency\": \"USD\" } }\n```\n\nStorage stays as integer minor units (cents for USD). The generated `<Entity>` constants block carries `view`, `currency`, `locale` so admin grids auto-format prices.\n\n**Imports \u2014 use sub-paths in browser code:**\n\n```tsx\nimport { formatCurrency } from \"@metaobjectsdev/runtime-web\";\nimport { CurrencyInput } from \"@metaobjectsdev/react\";\n```\n\n**Display:**\n\n```tsx\n<span>{formatCurrency(program.priceCents)}</span> // $15.00\n<span>{formatCurrency(p.amountCents, \"EUR\", \"de-DE\")}</span> // 15,00 \u20AC\n```\n\n**Form input:**\n\n```tsx\n<CurrencyInput value={priceCents} onChange={setPriceCents} currency=\"USD\" />\n```\n\nUser types `15.99` \u2192 component emits `1599` to `onChange` on blur. Wire format is always integer cents.\n\n**Locale override** via a `view[currency]` child:\n\n```json\n{ \"field\": { \"name\": \"priceCents\", \"subType\": \"currency\", \"@currency\": \"EUR\",\n \"children\": [{ \"view\": { \"subType\": \"currency\", \"@locale\": \"de-DE\" } }]\n}}\n```\n\nCurrency code lives on the field; locale lives on the view.\n\n## Generated artifacts \u2014 what `meta gen` produces\n\nAfter `meta gen`, you get one barrel + per-entity files in your configured `outDir` (default `packages/database/src/generated/`):\n\n| File | What's in it | When to touch by hand |\n|---|---|---|\n| `<Entity>.ts` | Drizzle table, relations(), inferred types, Zod insert/update schemas, and the rich `<Entity>` constants block (per-field objects with name, label, view, htmlType, rules, etc.) | Never. Regenerate. |\n| `<Entity>.queries.ts` | Typed query helpers (`findUserById`, `listUsers`, `createUser`, ...) using prepared statements | Never. Regenerate. |\n| `<Entity>.routes.ts` | Fastify CRUD plugin delegating to `mountCrudRoutes` from `@metaobjectsdev/runtime-ts/drizzle-fastify` (5 verbs, Zod validation, 404/204 mapping, Drizzle-direct under the hood) | Never. Regenerate. |\n| `<Entity>.form.tsx` | React form using `useEntityForm` + the entity constants. **OPT-IN at project level:** add `formFile()` to `generators` in `metaobjects.config.ts`. Opt out per-entity via `@emitForm: false`. | Never. Regenerate. |\n| `index.ts` | Barrel re-exporting every entity file | Never. Regenerate. |\n\nFor business logic the generator doesn't cover, create a SIBLING file: `<Entity>.extra.ts` for query/route helpers, or any file you like in your apps directory. Import the constants from the generated `<Entity>.ts`.\n\n### Stock route mounting\n\n```ts\nimport { subscriberRoutes } from \"@your-pkg/database/generated/Subscriber.routes\";\nfastify.register(subscriberRoutes, { prefix: \"/api\" });\n```\n\nThat mounts: `GET /api/subscribers`, `GET /api/subscribers/:id`, `POST /api/subscribers`, `PATCH /api/subscribers/:id`, `DELETE /api/subscribers/:id`. Pagination via `?limit=` & `?offset=`. Validation via the generated Zod schemas. Drizzle calls under the hood.\n\n### Mixing custom routes alongside generated\n\n```ts\nimport { db, subscribers } from \"@your-pkg/database\";\nimport { Subscriber, SubscriberInsertSchema } from \"@your-pkg/database/generated/Subscriber\";\nimport { eq } from \"drizzle-orm\";\n\nfastify.post(\"/subscribe\", async (req, reply) => {\n // Generated Zod schema validates the body \u2014 same schema the API route uses.\n const parsed = SubscriberInsertSchema.safeParse(req.body);\n if (!parsed.success) return reply.code(400).send({ issues: parsed.error.issues });\n\n // Drizzle's typed accessors are already TS-safe; no need for indirection.\n const existing = await db.select().from(subscribers).where(eq(subscribers.email, parsed.data.email)).get();\n if (existing) return reply.code(409).send({ error: \"Already subscribed\" });\n\n const [row] = await db.insert(subscribers).values(parsed.data).returning();\n // ... your business logic (analytics, Loops/Mailchimp, navigation, etc.)\n return reply.code(201).send(row);\n});\n```\n\nThe fact that every metadata-derived value flows from `Subscriber` / `SubscriberInsertSchema` / the typed `subscribers` table is what makes rename-the-field-in-metadata-and-regen safe.\n\n### Hand-written form using `useEntityForm`\n\n```tsx\nimport { useEntityForm } from '@metaobjectsdev/react';\nimport { Subscriber, SubscriberInsertSchema, type Subscriber as Row } from './generated/Subscriber';\n\nexport function SubscribeForm() {\n const form = useEntityForm(Subscriber, SubscriberInsertSchema);\n const { handleSubmit, formState: { errors } } = form;\n\n return (\n <form onSubmit={handleSubmit(/* your onSubmit */)} className=\"your-design-system\">\n <label>{Subscriber.email.label}</label>\n <input {...form.input.email} />\n {errors.email && <span>{errors.email.message}</span>}\n\n <label>{Subscriber.firstName.label}</label>\n <input {...form.input.firstName} />\n {errors.firstName && <span>{errors.firstName.message}</span>}\n\n <button type=\"submit\">Subscribe</button>\n </form>\n );\n}\n```\n\nSpread `form.input.<field>` \u2014 it carries name, type, placeholder, rules, aria-label automatically. No magic strings.\n\n## Meta Forge additions\n\n### `@forge*` attribute namespace\n\nProvenance and confidence concerns expressed as inline attributes on any metadata child. Names use camelCase (no separator).\n\nMost common:\n- `@forgeConfidence` (double 0..1) \u2014 confidence the record is correct\n- `@forgeSource` (string) \u2014 `human` | `claude` | `ts-ast` | `drizzle` | ...\n- `@forgePrimaryLocation` (string) \u2014 file path for an entity\n- `@forgeRationale` (string, decision only) \u2014 why this decision\n- `@forgeAlternatives` (stringarray, decision only) \u2014 alternatives considered\n\nFull inventory in `packages/sdk/FORGE-METADATA.md`.\n\n### New top-level types\n\nRegistered by `@metaobjectsdev/sdk` into the TypeRegistry:\n\n| Type | Purpose |\n|---|---|\n| `decision` | Architectural or design decision |\n| `principle` | Design principle (advisory/enforced) |\n| `convention` | Coding/structural convention |\n| `glossary` | Domain-term definition |\n| `failure` | Recorded failure mode |\n\nThese coexist with `object` children in the same package files. `meta gen` and `meta migrate` only consume `object`; the descriptive types are context for AI tooling and don't drive codegen.\n\n## File layout\n\n```\nmetaobjects/\n\u251C\u2500\u2500 meta.common.json shared base fields/validators (optional)\n\u251C\u2500\u2500 meta.<domain>.json your entity packages(s)\n\u2514\u2500\u2500 _pending/<pkg>.json proposed packages awaiting review\n\n.metaobjects/\n\u251C\u2500\u2500 config.json static project state\n\u251C\u2500\u2500 migrations/ written by meta migrate\n\u2514\u2500\u2500 .gen-state/ codegen merge base (gitignored)\n\nmetaobjects.config.ts generator wiring (committed)\n```\n\n## Worked example\n\n```json\n{\n \"metadata\": {\n \"package\": \"myapp\",\n \"children\": [\n {\n \"object\": {\n \"name\": \"User\",\n \"subType\": \"entity\",\n \"@forgeConfidence\": 0.95,\n \"@forgeSource\": \"human\",\n \"@forgePrimaryLocation\": \"src/db/users.schema.ts\",\n \"children\": [\n {\"field\": {\"name\": \"id\", \"extends\": \"..::common::id\"}},\n {\"field\": {\"name\": \"email\", \"subType\": \"string\",\n \"@dbColumn\": \"email_address\",\n \"children\": [{\"validator\": {\"subType\": \"required\"}}]\n }},\n {\"identity\": {\"name\": \"pk\", \"subType\": \"primary\", \"@fields\": [\"id\"], \"@generation\": \"increment\"}}\n ]\n }\n },\n {\n \"decision\": {\n \"name\": \"useTanstackQuery\",\n \"subType\": \"global\",\n \"@forgeConfidence\": 0.9,\n \"@forgeSource\": \"human\",\n \"@forgeRationale\": \"Real-time invalidation matters for live game state.\",\n \"@forgeAlternatives\": [\"swr\", \"redux-toolkit-query\"]\n }\n }\n ]\n }\n}\n```\n\n## Authoring guidance\n\n| Situation | Action |\n|---|---|\n| Adding a field to an existing entity | Edit the `object`'s `children`; append a `field` node, then `meta gen` |\n| New entity in an existing domain | Append an `object` to the appropriate package file, then `meta gen` |\n| Renaming an entity or field | Edit the metadata, regenerate; TS will surface every stale consumer of the constants |\n| New REST resource | Already done \u2014 `meta gen` produced `<Entity>.routes.ts`. Just `fastify.register(...)` it |\n| Custom business logic (Stripe webhook, side-effects, auth flows) | Hand-write a route/handler that imports the generated constants + `om()` |\n| Architectural choice affecting how entities are built | Add a `decision` with `@forgeRationale` + `@forgeAlternatives` |\n| Coding convention | Add a `convention` with `@forgePatternDescription` + `@forgeAppliesTo` |\n| Domain term | Add a `glossary` entry with `@forgeTerm` + `@forgeDefinition` |\n\n## Deeper references\n\n- `packages/metadata/METAMODEL.md` \u2014 full metamodel reference\n- `packages/sdk/FORGE-METADATA.md` \u2014 full `@forge*` inventory + MetaObjects layout details\n- `docs/strategy/2026-05-12-v0.3-ai-first-metadata-loading.md` \u2014 current strategy (v0.3 vocab, packages, AI-first loading)\n";
|
|
2
|
+
//# sourceMappingURL=body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../../src/agent-docs/body.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,eAAe,qr0BAgjB3B,CAAC"}
|