@metaobjectsdev/codegen-ts 0.21.4 → 0.21.6
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/dist/api-surface.d.ts +18 -0
- package/dist/api-surface.d.ts.map +1 -0
- package/dist/api-surface.js +42 -0
- package/dist/api-surface.js.map +1 -0
- package/dist/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +35 -9
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/instance-artifacts.d.ts +15 -9
- package/dist/instance-artifacts.d.ts.map +1 -1
- package/dist/instance-artifacts.js +37 -11
- package/dist/instance-artifacts.js.map +1 -1
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +4 -1
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/entity-meta-file.d.ts +7 -0
- package/dist/templates/entity-meta-file.d.ts.map +1 -0
- package/dist/templates/entity-meta-file.js +47 -0
- package/dist/templates/entity-meta-file.js.map +1 -0
- package/dist/templates/projection-decl.d.ts.map +1 -1
- package/dist/templates/projection-decl.js +6 -2
- package/dist/templates/projection-decl.js.map +1 -1
- package/dist/templates/routes-file-hono.d.ts.map +1 -1
- package/dist/templates/routes-file-hono.js +12 -3
- package/dist/templates/routes-file-hono.js.map +1 -1
- package/dist/templates/view-decl.d.ts +9 -0
- package/dist/templates/view-decl.d.ts.map +1 -1
- package/dist/templates/view-decl.js +14 -6
- package/dist/templates/view-decl.js.map +1 -1
- package/dist/templates/zod-validators.d.ts +4 -0
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +3 -2
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +8 -8
- package/src/api-surface.ts +45 -0
- package/src/generators/routes-file-hono.ts +34 -4
- package/src/index.ts +18 -0
- package/src/instance-artifacts.ts +40 -14
- package/src/reference/entity.ts +6 -1
- package/src/reference/queries.ts +6 -1
- package/src/templates/entity-file.ts +4 -1
- package/src/templates/entity-meta-file.ts +51 -0
- package/src/templates/projection-decl.ts +6 -2
- package/src/templates/routes-file-hono.ts +13 -3
- package/src/templates/view-decl.ts +23 -7
- package/src/templates/zod-validators.ts +3 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// `<Entity>.meta.ts` — the entity descriptor, with no database in the module graph.
|
|
2
|
+
//
|
|
3
|
+
// The generated UI files (hooks, grid hooks, forms) value-import exactly ONE thing
|
|
4
|
+
// from the entity module: the `<Entity>` descriptor const. Everything else they take
|
|
5
|
+
// is `import type`, which the compiler erases. But `<Entity>.ts` also constructs the
|
|
6
|
+
// Drizzle table at module scope, so that single value import dragged the entire ORM
|
|
7
|
+
// into every browser bundle — measured at 716 KB for one generated hook, containing
|
|
8
|
+
// `SQLiteTable` and the whole driver.
|
|
9
|
+
//
|
|
10
|
+
// Nothing in the UI path READS the table. The descriptor is plain data — `$table` is
|
|
11
|
+
// a string, not a table object — so this was never a semantic dependency on the
|
|
12
|
+
// database, only a physical one created by sharing a module. A `/* @__PURE__ */`
|
|
13
|
+
// annotation on the table construction does NOT help (measured: still 716 KB), so
|
|
14
|
+
// separating the module is the fix.
|
|
15
|
+
//
|
|
16
|
+
// Deliberately ADDITIVE. `<Entity>.ts` is untouched and still exports the descriptor,
|
|
17
|
+
// so every existing import keeps working and this stays a PATCH. The UI generators
|
|
18
|
+
// simply take the descriptor from here instead, and each emits this file itself —
|
|
19
|
+
// byte-identical between them, which #266 collapses — rather than depending on the
|
|
20
|
+
// consumer having wired an extra generator, since the entity generator is
|
|
21
|
+
// scaffold-and-own (ADR-0034) and cannot be changed from the package.
|
|
22
|
+
|
|
23
|
+
import { code } from "ts-poet";
|
|
24
|
+
import type { MetaObject } from "@metaobjectsdev/metadata";
|
|
25
|
+
import { GENERATED_HEADER } from "../constants.js";
|
|
26
|
+
import { renderEntityConstants } from "./entity-constants.js";
|
|
27
|
+
|
|
28
|
+
/** File name (no directory) of an entity's DB-free descriptor module. */
|
|
29
|
+
export function entityMetaFileName(entityName: string): string {
|
|
30
|
+
return `${entityName}.meta.ts`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Module specifier the UI files use to reach it — a same-directory sibling. */
|
|
34
|
+
export function entityMetaSpecifier(entityName: string, extStyle: string | undefined): string {
|
|
35
|
+
return `./${entityName}.meta${extStyle === "js" ? ".js" : ""}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function renderEntityMetaFile(entity: MetaObject, apiPrefix = ""): string {
|
|
39
|
+
const body = code`
|
|
40
|
+
// ${GENERATED_HEADER} — DO NOT EDIT.
|
|
41
|
+
// Source metadata: ${entity.name}
|
|
42
|
+
//
|
|
43
|
+
// Browser-safe: this module contains ONLY the entity descriptor — plain data, no
|
|
44
|
+
// Drizzle table and no database import of any kind. The generated UI files import it
|
|
45
|
+
// from here so a client bundle never pulls the ORM in. \`${entity.name}.ts\` also
|
|
46
|
+
// exports this descriptor, for server-side and pre-existing consumers.
|
|
47
|
+
|
|
48
|
+
${renderEntityConstants(entity, apiPrefix)}
|
|
49
|
+
`;
|
|
50
|
+
return body.toString();
|
|
51
|
+
}
|
|
@@ -22,6 +22,7 @@ import { renderFilterAllowlist, renderSortAllowlist } from "./filter-allowlist.j
|
|
|
22
22
|
import { renderFilterType } from "./filter-type.js";
|
|
23
23
|
import { inferViewKind, currencyMetaFor, labelFor } from "./field-meta.js";
|
|
24
24
|
import { renderExistingViewDecl, renderViewReadZodObject } from "./view-decl.js";
|
|
25
|
+
import { primaryIdentityFieldNames } from "./zod-validators.js";
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
// Public interface
|
|
@@ -152,15 +153,18 @@ export function renderProjectionDecl(
|
|
|
152
153
|
const camelName = projName.charAt(0).toLowerCase() + projName.slice(1);
|
|
153
154
|
const path = pathFromProjectionName(projName);
|
|
154
155
|
|
|
156
|
+
// The projection's primary-identity fields type non-null in the view decl +
|
|
157
|
+
// read schema even without @required (a PK is never NULL; see ViewDeclOpts).
|
|
158
|
+
const pkFieldNames: ReadonlySet<string> = new Set(primaryIdentityFieldNames(projection));
|
|
155
159
|
const sections: Code[] = [
|
|
156
160
|
...(includeViewDecl
|
|
157
161
|
? [renderExistingViewDecl(allFields, viewName, `${camelName}View`, {
|
|
158
|
-
dialect, columnNamingStrategy, timestampMode, voRef,
|
|
162
|
+
dialect, columnNamingStrategy, timestampMode, voRef, pkFieldNames,
|
|
159
163
|
})]
|
|
160
164
|
: []),
|
|
161
165
|
code`
|
|
162
166
|
export const ${projName}Schema = ${renderViewReadZodObject(allFields, {
|
|
163
|
-
dialect, columnNamingStrategy, timestampMode, voRef,
|
|
167
|
+
dialect, columnNamingStrategy, timestampMode, voRef, pkFieldNames,
|
|
164
168
|
})};
|
|
165
169
|
`,
|
|
166
170
|
code`
|
|
@@ -29,7 +29,7 @@ import type { MetaObject } from "@metaobjectsdev/metadata";
|
|
|
29
29
|
import { type RenderContext } from "../render-context.js";
|
|
30
30
|
import { entityModuleSpecifier } from "../import-path.js";
|
|
31
31
|
import { GENERATED_HEADER } from "../constants.js";
|
|
32
|
-
import { isProjection } from "../projection/projection-detector.js";
|
|
32
|
+
import { isProjection, isWriteThrough } from "../projection/projection-detector.js";
|
|
33
33
|
|
|
34
34
|
export function renderRoutesFileHono(entity: MetaObject, ctx: RenderContext): string {
|
|
35
35
|
const entityName = entity.name;
|
|
@@ -98,6 +98,16 @@ export function ${handlerName}(app: ${HonoSym}<any, any, any>, deps: { db: unkno
|
|
|
98
98
|
// --- Vanilla / write-through entity path: full CRUD routes ---
|
|
99
99
|
const tableVar = ctx.collectionName(entityName);
|
|
100
100
|
|
|
101
|
+
// #214: a write-through entity reads through its replica view so derived origin.*
|
|
102
|
+
// columns are present. Without this the Hono adapter returned rows missing every
|
|
103
|
+
// derived field the generated type and Zod schema promise, and a filter or sort on
|
|
104
|
+
// one — both ALLOWED by the generated allowlists — hit a column the table does not
|
|
105
|
+
// have, producing a 500. Fastify has done this since #214; Hono was simply never
|
|
106
|
+
// wired, exactly as it was never wired for #286's `.all()`/`.get()` fix.
|
|
107
|
+
const writeThrough = isWriteThrough(entity);
|
|
108
|
+
const camelName = entityName.charAt(0).toLowerCase() + entityName.slice(1);
|
|
109
|
+
const readViewLine = writeThrough ? `\n readView: ${camelName}View,` : "";
|
|
110
|
+
|
|
101
111
|
const HonoSym = imp("t:Hono@hono");
|
|
102
112
|
const mountCrudRoutesSym = imp("mountCrudRoutes@@metaobjectsdev/runtime-ts/hono");
|
|
103
113
|
|
|
@@ -108,7 +118,7 @@ import {
|
|
|
108
118
|
${entityName}InsertSchema,
|
|
109
119
|
${entityName}UpdateSchema,
|
|
110
120
|
${entityName}FilterAllowlist,
|
|
111
|
-
${entityName}SortAllowlist
|
|
121
|
+
${entityName}SortAllowlist,${writeThrough ? `\n ${camelName}View,` : ""}
|
|
112
122
|
} from ${JSON.stringify(entityFileSpec)};
|
|
113
123
|
`;
|
|
114
124
|
|
|
@@ -127,7 +137,7 @@ export function ${handlerName}(app: ${HonoSym}<any, any, any>, deps: { db: unkno
|
|
|
127
137
|
app,
|
|
128
138
|
path: ${pathExpr},
|
|
129
139
|
db: deps.db,
|
|
130
|
-
table: ${tableVar}
|
|
140
|
+
table: ${tableVar},${readViewLine}
|
|
131
141
|
insertSchema: ${entityName}InsertSchema,
|
|
132
142
|
updateSchema: ${entityName}UpdateSchema,
|
|
133
143
|
filterAllowlist: ${entityName}FilterAllowlist,
|
|
@@ -29,6 +29,15 @@ export interface ViewDeclOpts {
|
|
|
29
29
|
* `RenderContext.resolveValueObjectName` + `valueObjectModuleSpecifier`.
|
|
30
30
|
*/
|
|
31
31
|
readonly voRef: (field: MetaField) => { name: string; module: string };
|
|
32
|
+
/**
|
|
33
|
+
* Field names of the owning object's PRIMARY identity (see
|
|
34
|
+
* `primaryIdentityFieldNames` in zod-validators). A PK column sits on the FROM
|
|
35
|
+
* side of every synthesized view, so it is never NULL even when the field
|
|
36
|
+
* carries no `@required` — the view column gets `.notNull()` and the read
|
|
37
|
+
* schema stays non-nullable, matching the generated api-docs, queries and
|
|
38
|
+
* UpdateSchema, all of which already treat the PK as non-null.
|
|
39
|
+
*/
|
|
40
|
+
readonly pkFieldNames: ReadonlySet<string>;
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
/**
|
|
@@ -49,9 +58,13 @@ function viewColumnLine(f: MetaField, opts: ViewDeclOpts): Code {
|
|
|
49
58
|
// typing) and `.notNull()` (shapes the SELECT type) carry to an existing view;
|
|
50
59
|
// `.primaryKey()`/`.default()`/`.references()`/`.unique()` are table-DDL concerns
|
|
51
60
|
// and invalid on a `.existing()` view declaration. Preserve the canonical order.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
// A primary-identity column additionally gains `.notNull()`: on the table side
|
|
62
|
+
// its non-null comes from `.primaryKey()` (which the view cannot carry), so
|
|
63
|
+
// without this the view SELECT type — and the Zod read schema derived from it —
|
|
64
|
+
// typed the PK `T | null` for a column that can never be NULL.
|
|
65
|
+
const kept = spec.modifiers.filter((m) => m === ".array()" || m === ".notNull()");
|
|
66
|
+
if (opts.pkFieldNames.has(f.name) && !kept.includes(".notNull()")) kept.push(".notNull()");
|
|
67
|
+
const viewModifiers = kept.join("");
|
|
55
68
|
// Narrow the column to its resolved element/value type — `.$type<…>()` — mirroring
|
|
56
69
|
// the entity column so the read row is typed (not `unknown`) and an array column
|
|
57
70
|
// reads as `T[]`, not `T`.
|
|
@@ -113,10 +126,13 @@ export function renderViewReadZodObject(fields: readonly MetaField[], opts: View
|
|
|
113
126
|
const { dialect, columnNamingStrategy, timestampMode } = opts;
|
|
114
127
|
const z = imp("z@zod");
|
|
115
128
|
const lines: Code[] = fields.map((f) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
// Non-null when the column is `.notNull()` OR it is a primary-identity
|
|
130
|
+
// field — a PK is never NULL, but its table-side non-null is expressed via
|
|
131
|
+
// `.primaryKey()`, not a `.notNull()` modifier (see viewColumnLine above).
|
|
132
|
+
const nonNull =
|
|
133
|
+
mapColumnType(f, dialect, columnNamingStrategy, timestampMode).modifiers.includes(".notNull()") ||
|
|
134
|
+
opts.pkFieldNames.has(f.name);
|
|
135
|
+
const nullable = nonNull ? "" : ".nullable()";
|
|
120
136
|
const hasObjectRef =
|
|
121
137
|
f.subType === FIELD_SUBTYPE_OBJECT &&
|
|
122
138
|
typeof f.attr(FIELD_ATTR_OBJECT_REF) === "string" &&
|
|
@@ -129,8 +129,9 @@ ${joinCode(fieldLines, { on: ",\n" })}
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
/** Field names participating in the object's PRIMARY identity, normalized to a
|
|
132
|
-
* list. Empty when the object has no primary identity.
|
|
133
|
-
|
|
132
|
+
* list. Empty when the object has no primary identity. Exported for the
|
|
133
|
+
* view-decl read-schema path, which must type PK columns non-null too. */
|
|
134
|
+
export function primaryIdentityFieldNames(obj: MetaObject): string[] {
|
|
134
135
|
const primary = obj.primaryIdentity();
|
|
135
136
|
if (!primary) return [];
|
|
136
137
|
const fields = primary.attr(IDENTITY_ATTR_FIELDS);
|