@metaobjectsdev/codegen-ts 1.0.0-rc.3 → 1.0.0-rc.5
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/column-mapper.d.ts +12 -0
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +16 -0
- package/dist/column-mapper.js.map +1 -1
- package/dist/generator-registry.d.ts.map +1 -1
- package/dist/generator-registry.js +1 -0
- package/dist/generator-registry.js.map +1 -1
- package/dist/generators/agent-ui-page.d.ts.map +1 -1
- package/dist/generators/agent-ui-page.js +11 -9
- package/dist/generators/agent-ui-page.js.map +1 -1
- package/dist/generators/api-model.d.ts +2 -1
- package/dist/generators/api-model.d.ts.map +1 -1
- package/dist/generators/api-model.js +12 -6
- package/dist/generators/api-model.js.map +1 -1
- package/dist/generators/barrel.d.ts.map +1 -1
- package/dist/generators/barrel.js +2 -1
- package/dist/generators/barrel.js.map +1 -1
- package/dist/generators/callable-file.d.ts.map +1 -1
- package/dist/generators/callable-file.js +2 -1
- package/dist/generators/callable-file.js.map +1 -1
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +6 -5
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/entity-file.d.ts.map +1 -1
- package/dist/generators/entity-file.js +2 -1
- package/dist/generators/entity-file.js.map +1 -1
- package/dist/generators/names-file.d.ts +22 -1
- package/dist/generators/names-file.d.ts.map +1 -1
- package/dist/generators/names-file.js +12 -18
- package/dist/generators/names-file.js.map +1 -1
- package/dist/generators/queries-file.d.ts.map +1 -1
- package/dist/generators/queries-file.js +2 -1
- package/dist/generators/queries-file.js.map +1 -1
- package/dist/generators/render-helper-file.js +1 -1
- package/dist/generators/render-helper-file.js.map +1 -1
- package/dist/generators/requirements-markdown.d.ts.map +1 -1
- package/dist/generators/requirements-markdown.js +26 -1
- package/dist/generators/requirements-markdown.js.map +1 -1
- package/dist/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +2 -1
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/generators/routes-file.d.ts.map +1 -1
- package/dist/generators/routes-file.js +2 -1
- package/dist/generators/routes-file.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/names.d.ts.map +1 -1
- package/dist/names.js +5 -4
- package/dist/names.js.map +1 -1
- package/dist/overwrite-policy.d.ts +9 -7
- package/dist/overwrite-policy.d.ts.map +1 -1
- package/dist/overwrite-policy.js +22 -0
- package/dist/overwrite-policy.js.map +1 -1
- package/dist/runner.d.ts +24 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +96 -8
- package/dist/runner.js.map +1 -1
- package/dist/templates/callable-file.d.ts.map +1 -1
- package/dist/templates/callable-file.js +2 -1
- package/dist/templates/callable-file.js.map +1 -1
- package/dist/templates/drizzle-schema.d.ts.map +1 -1
- package/dist/templates/drizzle-schema.js +39 -14
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +4 -2
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/filter-allowlist.d.ts.map +1 -1
- package/dist/templates/filter-allowlist.js +10 -6
- package/dist/templates/filter-allowlist.js.map +1 -1
- package/dist/templates/filter-shared.d.ts +42 -1
- package/dist/templates/filter-shared.d.ts.map +1 -1
- package/dist/templates/filter-shared.js +68 -5
- package/dist/templates/filter-shared.js.map +1 -1
- package/dist/templates/inferred-types.d.ts.map +1 -1
- package/dist/templates/inferred-types.js +9 -6
- package/dist/templates/inferred-types.js.map +1 -1
- package/dist/templates/projection-decl.d.ts.map +1 -1
- package/dist/templates/projection-decl.js +4 -2
- package/dist/templates/projection-decl.js.map +1 -1
- package/dist/templates/queries-file.d.ts.map +1 -1
- package/dist/templates/queries-file.js +6 -5
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/relations-block.d.ts.map +1 -1
- package/dist/templates/relations-block.js +2 -1
- package/dist/templates/relations-block.js.map +1 -1
- package/dist/templates/render-helper.d.ts +12 -1
- package/dist/templates/render-helper.d.ts.map +1 -1
- package/dist/templates/render-helper.js +30 -7
- package/dist/templates/render-helper.js.map +1 -1
- package/dist/templates/routes-file-hono.d.ts.map +1 -1
- package/dist/templates/routes-file-hono.js +2 -1
- package/dist/templates/routes-file-hono.js.map +1 -1
- package/dist/templates/routes-file.d.ts.map +1 -1
- package/dist/templates/routes-file.js +18 -10
- package/dist/templates/routes-file.js.map +1 -1
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +7 -4
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +6 -6
- package/src/column-mapper.ts +17 -0
- package/src/generator-registry.ts +1 -0
- package/src/generators/agent-ui-page.ts +10 -10
- package/src/generators/api-model.ts +14 -7
- package/src/generators/barrel.ts +2 -1
- package/src/generators/callable-file.ts +2 -1
- package/src/generators/docs-data-builder.ts +6 -5
- package/src/generators/entity-file.ts +2 -1
- package/src/generators/names-file.ts +33 -4
- package/src/generators/queries-file.ts +2 -1
- package/src/generators/render-helper-file.ts +1 -1
- package/src/generators/requirements-markdown.ts +28 -1
- package/src/generators/routes-file-hono.ts +2 -1
- package/src/generators/routes-file.ts +2 -1
- package/src/index.ts +17 -5
- package/src/names.ts +5 -4
- package/src/overwrite-policy.ts +19 -9
- package/src/reference/barrel.ts +2 -1
- package/src/reference/entity.ts +2 -2
- package/src/reference/names.ts +26 -4
- package/src/reference/queries.ts +3 -3
- package/src/reference/routes-hono.ts +2 -1
- package/src/reference/routes.ts +2 -1
- package/src/runner.ts +112 -7
- package/src/templates/callable-file.ts +2 -1
- package/src/templates/drizzle-schema.ts +43 -13
- package/src/templates/entity-file.ts +4 -2
- package/src/templates/filter-allowlist.ts +9 -6
- package/src/templates/filter-shared.ts +91 -6
- package/src/templates/inferred-types.ts +9 -6
- package/src/templates/projection-decl.ts +4 -2
- package/src/templates/queries-file.ts +6 -5
- package/src/templates/relations-block.ts +2 -1
- package/src/templates/render-helper.ts +31 -6
- package/src/templates/routes-file-hono.ts +2 -1
- package/src/templates/routes-file.ts +18 -10
- package/src/templates/zod-validators.ts +7 -4
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
// Shared
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Shared answers to "how does this entity sort?" — used by every tier that has to
|
|
2
|
+
// answer it, so none of them can answer it differently.
|
|
3
|
+
//
|
|
4
|
+
// Two questions live here:
|
|
5
|
+
// WHICH fields sort — isSortableField / sortableFields, read by the generated
|
|
6
|
+
// <Entity>SortAllowlist (filter-allowlist.ts), the client
|
|
7
|
+
// <Entity>Filter sort union (filter-type.ts) and the agent
|
|
8
|
+
// UI page.
|
|
9
|
+
// WHICH WAY one sorts — declaredSortDefaultOrder / sortDefaultOrder /
|
|
10
|
+
// resolveGridDefaultSort, read by the same allowlist, the
|
|
11
|
+
// generated grid const + grid hook, and the agent UI page.
|
|
12
|
+
//
|
|
13
|
+
// Keeping both here is what prevents a client/server mismatch: a header the grid
|
|
14
|
+
// renders clickable for a field the endpoint rejects, or a grid whose initial sort
|
|
15
|
+
// runs the opposite way from the endpoint it queries.
|
|
5
16
|
|
|
6
|
-
import { MetaField, MetaObject } from "@metaobjectsdev/metadata";
|
|
7
|
-
import {
|
|
17
|
+
import { MetaField, MetaObject, type MetaData } from "@metaobjectsdev/metadata";
|
|
18
|
+
import {
|
|
19
|
+
FIELD_ATTR_FILTERABLE,
|
|
20
|
+
FIELD_ATTR_SORTABLE,
|
|
21
|
+
FIELD_ATTR_SORTABLE_DEFAULT_ORDER,
|
|
22
|
+
LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD,
|
|
23
|
+
LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER,
|
|
24
|
+
} from "@metaobjectsdev/metadata";
|
|
25
|
+
|
|
26
|
+
/** A resolved sort direction. Never widened to `string`: every door below either
|
|
27
|
+
* returns one of these two or says it has no answer. */
|
|
28
|
+
export type SortOrder = "asc" | "desc";
|
|
8
29
|
|
|
9
30
|
/**
|
|
10
31
|
* Returns true if the given field should be included in sort operations.
|
|
@@ -28,3 +49,67 @@ export function sortableFields(entity: MetaObject): MetaField[] {
|
|
|
28
49
|
// fields() returns effective fields, so inherited fields (from extends:/super:) are included in sort ops.
|
|
29
50
|
return entity.fields().filter(isSortableField);
|
|
30
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The direction `@sortableDefaultOrder` DECLARES on this field, or undefined when
|
|
55
|
+
* the field declares none.
|
|
56
|
+
*
|
|
57
|
+
* The declared value is what the generated `<Entity>SortAllowlist` carries, so an
|
|
58
|
+
* undeclared field stays `{}` there and the runtime's own `?? "asc"` remains the
|
|
59
|
+
* single place the fallback is spelled. Callers that need a concrete direction
|
|
60
|
+
* rather than a declaration use `sortDefaultOrder`.
|
|
61
|
+
*
|
|
62
|
+
* ADR-0039: resolving — `@sortableDefaultOrder` may be inherited via `extends`.
|
|
63
|
+
*/
|
|
64
|
+
export function declaredSortDefaultOrder(field: MetaField): SortOrder | undefined {
|
|
65
|
+
const v = field.attr(FIELD_ATTR_SORTABLE_DEFAULT_ORDER);
|
|
66
|
+
return v === "asc" || v === "desc" ? v : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The direction a sort runs when it names this field without giving one. */
|
|
70
|
+
export function sortDefaultOrder(field: MetaField): SortOrder {
|
|
71
|
+
return declaredSortDefaultOrder(field) ?? "asc";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A grid's initial sort: the field it runs on and the direction it runs. */
|
|
75
|
+
export interface GridDefaultSort {
|
|
76
|
+
field: string;
|
|
77
|
+
order: SortOrder;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A `layout.dataGrid`'s initial sort, resolved once for every tier that renders it.
|
|
82
|
+
*
|
|
83
|
+
* `@defaultSortOrder` wins when the layout declares one. When it does not, the
|
|
84
|
+
* NAMED FIELD's `@sortableDefaultOrder` applies — that is what the attribute has
|
|
85
|
+
* always been registered to mean ("the direction a sort takes when it names this
|
|
86
|
+
* field but omits the order"), and it is the same rule the runtime applies to
|
|
87
|
+
* `?sort=<field>` with no `:order`. Without this the two ends of one declaration
|
|
88
|
+
* disagree: the endpoint returns the field's declared order while the grid that
|
|
89
|
+
* queries it renders the opposite.
|
|
90
|
+
*
|
|
91
|
+
* Returns undefined when the layout names no field — a grid with no initial sort.
|
|
92
|
+
*
|
|
93
|
+
* The field lookup cannot miss on a LOADED model: `validateDataGridSortFields`
|
|
94
|
+
* refuses `@defaultSortField` naming a field the entity does not have
|
|
95
|
+
* (`ERR_BAD_DEFAULT_SORT_FIELD`). "asc" is the answer for a hand-built node that
|
|
96
|
+
* never went through the loader, so codegen degrades rather than throwing.
|
|
97
|
+
*/
|
|
98
|
+
// `layout` is typed as the base MetaData rather than MetaLayout because the three
|
|
99
|
+
// call sites hold the node at different static types (`entity.layouts()` gives
|
|
100
|
+
// MetaLayout, the agent page's own `dataGrids()` gives MetaData) and this reads
|
|
101
|
+
// nothing but `attr()`, which is declared there. Widening beats a cast per caller.
|
|
102
|
+
export function resolveGridDefaultSort(
|
|
103
|
+
entity: MetaObject,
|
|
104
|
+
layout: MetaData,
|
|
105
|
+
): GridDefaultSort | undefined {
|
|
106
|
+
// ADR-0039: resolving — a layout may inherit its grid attrs via extends.
|
|
107
|
+
const fieldName = layout.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_FIELD);
|
|
108
|
+
if (typeof fieldName !== "string" || fieldName === "") return undefined;
|
|
109
|
+
|
|
110
|
+
const declared = layout.attr(LAYOUT_DATA_GRID_ATTR_DEFAULT_SORT_ORDER);
|
|
111
|
+
if (declared === "asc" || declared === "desc") return { field: fieldName, order: declared };
|
|
112
|
+
|
|
113
|
+
const field = entity.fields().find((f) => f.name === fieldName);
|
|
114
|
+
return { field: fieldName, order: field === undefined ? "asc" : sortDefaultOrder(field) };
|
|
115
|
+
}
|
|
@@ -42,6 +42,7 @@ import { renderDocsFor } from "./jsdoc.js";
|
|
|
42
42
|
import { sharedEnumForField } from "../enum-shared.js";
|
|
43
43
|
import { sharedEnumImportSpecifier, providedEnumImportSpecifier } from "../enum-import.js";
|
|
44
44
|
import { fieldDeclaringPackage, type RenderContext } from "../render-context.js";
|
|
45
|
+
import { effectivePackage } from "../docs-paths.js";
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* Emit Drizzle's InferSelectModel / InferInsertModel aliases for an entity.
|
|
@@ -179,7 +180,7 @@ export function renderEnumTypeAliases(entity: MetaObject, ctx?: RenderContext):
|
|
|
179
180
|
// configured external module; never redeclare the union here.
|
|
180
181
|
const spec = shared.provided
|
|
181
182
|
? providedEnumImportSpecifier(ctx!, shared.name)
|
|
182
|
-
: sharedEnumImportSpecifier(ctx!, entity
|
|
183
|
+
: sharedEnumImportSpecifier(ctx!, effectivePackage(entity));
|
|
183
184
|
// Re-export the runtime constant beside the type for a MATERIALIZED shared enum, so a
|
|
184
185
|
// consumer reaches its members the same way it reaches an inline enum's, without having
|
|
185
186
|
// to know which kind it is or which module the shared one landed in.
|
|
@@ -305,9 +306,10 @@ function valueObjectFieldType(entity: MetaObject, field: MetaField, ctx?: Render
|
|
|
305
306
|
// layout/package/extStyle-aware helper (the SAME one the Zod schema +
|
|
306
307
|
// Drizzle .$type<> use) so all three agree. Without a ctx (bare unit-test
|
|
307
308
|
// calls) fall back to the bare name + flat same-dir specifier.
|
|
308
|
-
const
|
|
309
|
+
const entityPkg = effectivePackage(entity);
|
|
310
|
+
const refName = ctx ? ctx.resolveValueObjectName(ref, fieldDeclaringPackage(field, entityPkg)) : stripPackage(ref);
|
|
309
311
|
const moduleSpec = ctx
|
|
310
|
-
? valueObjectModuleSpecifier(refName, ctx.packageOf,
|
|
312
|
+
? valueObjectModuleSpecifier(refName, ctx.packageOf, entityPkg, ctx.outputLayout, ctx.extStyle)
|
|
311
313
|
: `./${refName}.js`;
|
|
312
314
|
const refImp = imp(`${refName}@${moduleSpec}`);
|
|
313
315
|
return field.resolvedIsArray() ? code`${refImp}[]` : code`${refImp}`;
|
|
@@ -319,9 +321,10 @@ function valueObjectFieldType(entity: MetaObject, field: MetaField, ctx?: Render
|
|
|
319
321
|
if (field.subType === FIELD_SUBTYPE_MAP) {
|
|
320
322
|
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
|
|
321
323
|
if (typeof ref === "string" && ref.length > 0) {
|
|
322
|
-
const
|
|
324
|
+
const entityPkg = effectivePackage(entity);
|
|
325
|
+
const refName = ctx ? ctx.resolveValueObjectName(ref, fieldDeclaringPackage(field, entityPkg)) : stripPackage(ref);
|
|
323
326
|
const moduleSpec = ctx
|
|
324
|
-
? valueObjectModuleSpecifier(refName, ctx.packageOf,
|
|
327
|
+
? valueObjectModuleSpecifier(refName, ctx.packageOf, entityPkg, ctx.outputLayout, ctx.extStyle)
|
|
325
328
|
: `./${refName}.js`;
|
|
326
329
|
const refImp = imp(`${refName}@${moduleSpec}`);
|
|
327
330
|
return code`Record<string, ${refImp}>`;
|
|
@@ -349,7 +352,7 @@ function valueObjectFieldType(entity: MetaObject, field: MetaField, ctx?: Render
|
|
|
349
352
|
if (shared !== undefined) {
|
|
350
353
|
const spec = shared.provided
|
|
351
354
|
? providedEnumImportSpecifier(ctx, shared.name)
|
|
352
|
-
: sharedEnumImportSpecifier(ctx, entity
|
|
355
|
+
: sharedEnumImportSpecifier(ctx, effectivePackage(entity));
|
|
353
356
|
const sym = imp(`t:${shared.name}@${spec}`);
|
|
354
357
|
return field.resolvedIsArray() ? code`${sym}[]` : code`${sym}`;
|
|
355
358
|
}
|
|
@@ -23,6 +23,7 @@ import { renderFilterType } from "./filter-type.js";
|
|
|
23
23
|
import { buildUiFieldDescriptor, resourcePath } from "./entity-ui-descriptor.js";
|
|
24
24
|
import { renderExistingViewDecl, renderViewReadZodObject } from "./view-decl.js";
|
|
25
25
|
import { primaryIdentityFieldNames } from "./zod-validators.js";
|
|
26
|
+
import { effectivePackage } from "../docs-paths.js";
|
|
26
27
|
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
28
29
|
// Public interface
|
|
@@ -103,14 +104,15 @@ export function renderProjectionDecl(
|
|
|
103
104
|
// package-qualified on a cross-package short-name collision, so the projection's
|
|
104
105
|
// VO import matches the entity's. Layout/package/extStyle-aware when a render
|
|
105
106
|
// context is present, else a flat same-dir import (zodFieldExpr's fallback).
|
|
107
|
+
const projectionPkg = effectivePackage(projection);
|
|
106
108
|
const voRef = (field: MetaField): { name: string; module: string } => {
|
|
107
109
|
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
|
|
108
110
|
const rawRef = typeof ref === "string" ? ref : "";
|
|
109
111
|
const name = ctx
|
|
110
|
-
? ctx.resolveValueObjectName(rawRef, fieldDeclaringPackage(field,
|
|
112
|
+
? ctx.resolveValueObjectName(rawRef, fieldDeclaringPackage(field, projectionPkg))
|
|
111
113
|
: stripPackage(rawRef);
|
|
112
114
|
const module = ctx
|
|
113
|
-
? valueObjectModuleSpecifier(name, ctx.packageOf,
|
|
115
|
+
? valueObjectModuleSpecifier(name, ctx.packageOf, projectionPkg, ctx.outputLayout, ctx.extStyle)
|
|
114
116
|
: `./${name}.js`;
|
|
115
117
|
return { name, module };
|
|
116
118
|
};
|
|
@@ -26,6 +26,7 @@ import { GENERATED_HEADER } from "../constants.js";
|
|
|
26
26
|
import { isTphDiscriminatorBase, tphConcreteSubtypes } from "./tph-discriminator.js";
|
|
27
27
|
import { isProjection, isWriteThrough } from "../projection/projection-detector.js";
|
|
28
28
|
import { hasAutoSetFields } from "./zod-validators.js";
|
|
29
|
+
import { effectivePackage } from "../docs-paths.js";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* The dialect-correct Drizzle `Db` type alias + its import — parameter-passed into every
|
|
@@ -94,7 +95,7 @@ export function renderQueriesFile(obj: MetaObject, ctx: RenderContext): string {
|
|
|
94
95
|
const entityFileName = entityModuleSpecifier(
|
|
95
96
|
ctx.selfTarget,
|
|
96
97
|
ctx.entityModuleTarget,
|
|
97
|
-
obj
|
|
98
|
+
effectivePackage(obj),
|
|
98
99
|
entityName,
|
|
99
100
|
ctx.extStyle,
|
|
100
101
|
);
|
|
@@ -163,7 +164,7 @@ function renderProjectionQueriesFile(obj: MetaObject, ctx: RenderContext): strin
|
|
|
163
164
|
const camelName = entityName.charAt(0).toLowerCase() + entityName.slice(1);
|
|
164
165
|
const viewVar = `${camelName}View`;
|
|
165
166
|
const entityFileName = entityModuleSpecifier(
|
|
166
|
-
ctx.selfTarget, ctx.entityModuleTarget, obj
|
|
167
|
+
ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(obj), entityName, ctx.extStyle,
|
|
167
168
|
);
|
|
168
169
|
const { fieldName: pkField, tsType: pkType } = getPkInfo(obj, ctx);
|
|
169
170
|
const eqSym = imp("eq@drizzle-orm");
|
|
@@ -215,7 +216,7 @@ function renderWriteThroughQueriesFile(obj: MetaObject, ctx: RenderContext): str
|
|
|
215
216
|
const tableVar = ctx.collectionName(entityName);
|
|
216
217
|
const singularVar = camelName;
|
|
217
218
|
const entityFileName = entityModuleSpecifier(
|
|
218
|
-
ctx.selfTarget, ctx.entityModuleTarget, obj
|
|
219
|
+
ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(obj), entityName, ctx.extStyle,
|
|
219
220
|
);
|
|
220
221
|
const { fieldName: pkField, tsType: pkType } = getPkInfo(obj, ctx);
|
|
221
222
|
const pkFields = getPkFields(obj);
|
|
@@ -309,7 +310,7 @@ function renderTphQueriesFile(base: MetaObject, ctx: RenderContext): string {
|
|
|
309
310
|
const { fieldName: pkField, tsType: pkType } = getPkInfo(base, ctx);
|
|
310
311
|
|
|
311
312
|
const baseFileSpec = entityModuleSpecifier(
|
|
312
|
-
ctx.selfTarget, ctx.entityModuleTarget, base
|
|
313
|
+
ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(base), baseName, ctx.extStyle,
|
|
313
314
|
);
|
|
314
315
|
const tableSym = imp(`${tableVar}@${baseFileSpec}`);
|
|
315
316
|
const baseTypeSym = imp(`t:${baseName}@${baseFileSpec}`);
|
|
@@ -342,7 +343,7 @@ export async function list${pluralize(baseName)}(db: Db, opts?: { limit?: number
|
|
|
342
343
|
const value = sub.ownAttr(OBJECT_ATTR_DISCRIMINATOR_VALUE) as string;
|
|
343
344
|
const valueLit = JSON.stringify(value);
|
|
344
345
|
const subFileSpec = entityModuleSpecifier(
|
|
345
|
-
ctx.selfTarget, ctx.entityModuleTarget, sub
|
|
346
|
+
ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(sub), sub.name, ctx.extStyle,
|
|
346
347
|
);
|
|
347
348
|
const subTypeSym = imp(`t:${sub.name}@${subFileSpec}`);
|
|
348
349
|
const subSchemaSym = imp(`${sub.name}Schema@${subFileSpec}`);
|
|
@@ -7,6 +7,7 @@ import { CARDINALITY_ONE, CARDINALITY_MANY } from "@metaobjectsdev/metadata";
|
|
|
7
7
|
import { type RenderContext } from "../render-context.js";
|
|
8
8
|
import { crossEntitySpecifier } from "../import-path.js";
|
|
9
9
|
import type { RelationEntry } from "../relation-resolver.js";
|
|
10
|
+
import { effectivePackage } from "../docs-paths.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Render the relations() block for one entity.
|
|
@@ -28,7 +29,7 @@ export function renderRelationsBlock(entity: MetaObject, ctx: RenderContext): Co
|
|
|
28
29
|
if (hasMany) paramParts.push("many");
|
|
29
30
|
const params = `{ ${paramParts.join(", ")} }`;
|
|
30
31
|
|
|
31
|
-
const thisEntityPackage = entity
|
|
32
|
+
const thisEntityPackage = effectivePackage(entity);
|
|
32
33
|
const lines: Code[] = entries.map((entry) =>
|
|
33
34
|
renderRelationEntry(entry, ctx, varName, thisEntityPackage),
|
|
34
35
|
);
|
|
@@ -53,6 +53,8 @@ import {
|
|
|
53
53
|
type VerifyError,
|
|
54
54
|
} from "@metaobjectsdev/render";
|
|
55
55
|
import { templateSymbolBase } from "../naming.js";
|
|
56
|
+
import { GENERATED_HEADER } from "../constants.js";
|
|
57
|
+
import type { ExtStyle } from "../metaobjects-config.js";
|
|
56
58
|
|
|
57
59
|
// ADR-0039: resolving — root has no super (children()==ownChildren()); a top-level object/template may itself extend, so resolve rather than work-by-accident.
|
|
58
60
|
// ADR-0042: package-local — resolveObjectRef binds a bare @objectRef in `referrerPkg` first (else root-level), an FQN exactly.
|
|
@@ -65,6 +67,20 @@ function findTemplate(root: MetaData, name: string): MetaData | undefined {
|
|
|
65
67
|
return root.children().find((c) => c.type === TYPE_TEMPLATE && c.name === name);
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The three lines every emitted render-helper module opens with: the generated-file
|
|
72
|
+
* marker, the `render()` import, and the typed payload import. Identical between the
|
|
73
|
+
* email and document kinds below — only the `@metaobjectsdev/render` type import
|
|
74
|
+
* differs (email additionally needs `EmailDocument`), so that piece is a parameter
|
|
75
|
+
* rather than a second near-copy of this block.
|
|
76
|
+
*/
|
|
77
|
+
function renderHelperHeader(typeImports: string, payloadTypeName: string, ext: string): string {
|
|
78
|
+
return `// ${GENERATED_HEADER} — DO NOT EDIT.
|
|
79
|
+
import { render } from "@metaobjectsdev/render";
|
|
80
|
+
import type { ${typeImports} } from "@metaobjectsdev/render";
|
|
81
|
+
import type { ${payloadTypeName} } from "./${payloadTypeName}${ext}";`;
|
|
82
|
+
}
|
|
83
|
+
|
|
68
84
|
/**
|
|
69
85
|
* Walk an `object.value` view-object into a render `PayloadField[]`. Object-ref
|
|
70
86
|
* fields recurse into their referenced view-object; a `seen` set guards a
|
|
@@ -150,6 +166,16 @@ export function renderRenderHelper(
|
|
|
150
166
|
root: MetaData,
|
|
151
167
|
templateName: string,
|
|
152
168
|
provider: Provider,
|
|
169
|
+
/**
|
|
170
|
+
* The project's import-extension style, as every other emitter honours it.
|
|
171
|
+
* Defaults to "js" — the engine default — so an existing caller is unchanged.
|
|
172
|
+
*
|
|
173
|
+
* This file used to hardcode `.js` regardless. Both spellings happen to resolve
|
|
174
|
+
* under nodenext AND bundler, so nothing broke; it was simply the one emitter
|
|
175
|
+
* that did not read the setting, which is how a project with `extStyle: "none"`
|
|
176
|
+
* ended up with one file spelling its imports differently from every other.
|
|
177
|
+
*/
|
|
178
|
+
extStyle: ExtStyle = "js",
|
|
153
179
|
): string {
|
|
154
180
|
const tmpl = findTemplate(root, templateName);
|
|
155
181
|
if (!tmpl) {
|
|
@@ -186,6 +212,9 @@ export function renderRenderHelper(
|
|
|
186
212
|
// resolution above (findObject/derivePayloadFieldTree) intentionally keeps
|
|
187
213
|
// using the raw `payloadRef`.
|
|
188
214
|
const payloadTypeName = stripPackage(payloadRef);
|
|
215
|
+
// The emitted relative import's extension, from the project's setting rather than
|
|
216
|
+
// hardcoded — see the `extStyle` parameter doc.
|
|
217
|
+
const ext = extStyle === "none" ? "" : ".js";
|
|
189
218
|
|
|
190
219
|
// ADR-0039: resolving — a template may inherit its @* refs/format/kind via extends.
|
|
191
220
|
const kind = ((tmpl.attr(TEMPLATE_ATTR_KIND) as string | undefined) ?? TEMPLATE_KIND_DEFAULT)
|
|
@@ -215,9 +244,7 @@ export function renderRenderHelper(
|
|
|
215
244
|
? `\n textBody: render({ ref: ${JSON.stringify(textBodyRef)}, payload, format: "text", provider, verify: ${ft} }),`
|
|
216
245
|
: "";
|
|
217
246
|
|
|
218
|
-
return
|
|
219
|
-
import type { Provider, EmailDocument } from "@metaobjectsdev/render";
|
|
220
|
-
import type { ${payloadTypeName} } from "./${payloadTypeName}.js";
|
|
247
|
+
return `${renderHelperHeader("Provider, EmailDocument", payloadTypeName, ext)}
|
|
221
248
|
|
|
222
249
|
/**
|
|
223
250
|
* Render the ${templateName} email (subject + html body${typeof textBodyRef === "string" ? " + text body" : ""}) from a
|
|
@@ -255,9 +282,7 @@ export function ${fnName}(payload: ${payloadTypeName}, provider: Provider): Emai
|
|
|
255
282
|
const maxCharsArg =
|
|
256
283
|
maxChars !== undefined && Number.isFinite(maxChars) ? `, maxChars: ${maxChars}` : "";
|
|
257
284
|
|
|
258
|
-
return
|
|
259
|
-
import type { Provider } from "@metaobjectsdev/render";
|
|
260
|
-
import type { ${payloadTypeName} } from "./${payloadTypeName}.js";
|
|
285
|
+
return `${renderHelperHeader("Provider", payloadTypeName, ext)}
|
|
261
286
|
|
|
262
287
|
/**
|
|
263
288
|
* Render the ${templateName} document from a typed ${payloadTypeName} payload. Wraps the
|
|
@@ -31,6 +31,7 @@ import { entityModuleSpecifier } from "../import-path.js";
|
|
|
31
31
|
import { GENERATED_HEADER } from "../constants.js";
|
|
32
32
|
import { isProjection, isWriteThrough } from "../projection/projection-detector.js";
|
|
33
33
|
import { type CrudVerb, exposeLine } from "../routes-expose.js";
|
|
34
|
+
import { effectivePackage } from "../docs-paths.js";
|
|
34
35
|
|
|
35
36
|
export function renderRoutesFileHono(
|
|
36
37
|
entity: MetaObject,
|
|
@@ -49,7 +50,7 @@ export function renderRoutesFileHono(
|
|
|
49
50
|
const entityFileSpec = entityModuleSpecifier(
|
|
50
51
|
ctx.selfTarget,
|
|
51
52
|
ctx.entityModuleTarget,
|
|
52
|
-
entity
|
|
53
|
+
effectivePackage(entity),
|
|
53
54
|
entityName,
|
|
54
55
|
ctx.extStyle,
|
|
55
56
|
);
|
|
@@ -30,6 +30,7 @@ import { isProjection, isWriteThrough } from "../projection/projection-detector.
|
|
|
30
30
|
import type { RelationEntry } from "../relation-resolver.js";
|
|
31
31
|
import { isTphDiscriminatorBase, tphPlan } from "./tph-discriminator.js";
|
|
32
32
|
import { type CrudVerb, exposeLine, intersectExpose, TPH_POLYMORPHIC_VERBS } from "../routes-expose.js";
|
|
33
|
+
import { effectivePackage } from "../docs-paths.js";
|
|
33
34
|
|
|
34
35
|
export function renderRoutesFile(
|
|
35
36
|
entity: MetaObject,
|
|
@@ -49,16 +50,17 @@ export function renderRoutesFile(
|
|
|
49
50
|
|
|
50
51
|
const entityName = entity.name;
|
|
51
52
|
const handlerName = routesHandlerName(entityName);
|
|
53
|
+
const entityPkg = effectivePackage(entity);
|
|
52
54
|
// Import the entity's own file. Same target → relative "./Entity"; cross
|
|
53
55
|
// target → importBase-qualified package path.
|
|
54
56
|
const entityFileSpec = entityModuleSpecifier(
|
|
55
57
|
ctx.selfTarget,
|
|
56
58
|
ctx.entityModuleTarget,
|
|
57
|
-
|
|
59
|
+
entityPkg,
|
|
58
60
|
entityName,
|
|
59
61
|
ctx.extStyle,
|
|
60
62
|
);
|
|
61
|
-
const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout,
|
|
63
|
+
const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout, entityPkg, ctx.dbImport, ctx.extStyle);
|
|
62
64
|
|
|
63
65
|
const header =
|
|
64
66
|
`// ${GENERATED_HEADER} — DO NOT EDIT.\n` +
|
|
@@ -260,10 +262,15 @@ function renderM2mMount(
|
|
|
260
262
|
ctx: RenderContext,
|
|
261
263
|
fastifyVar: string,
|
|
262
264
|
): Code {
|
|
265
|
+
// `source` never changes across this function, so its effective package is computed
|
|
266
|
+
// once and reused below (both crossEntitySpecifier calls, and the three
|
|
267
|
+
// resolveJunctionColumn calls' `fromPackage` — see that comment for why it must be
|
|
268
|
+
// SOURCE's package rather than the junction/target entity's own).
|
|
269
|
+
const sourcePkg = effectivePackage(source);
|
|
263
270
|
const junctionVarSym = imp(
|
|
264
271
|
`${ctx.collectionName(entry.junctionEntity)}@${crossEntitySpecifier(
|
|
265
272
|
ctx.outputLayout,
|
|
266
|
-
|
|
273
|
+
sourcePkg,
|
|
267
274
|
ctx.packageOf.get(entry.junctionEntity),
|
|
268
275
|
entry.junctionEntity,
|
|
269
276
|
ctx.extStyle,
|
|
@@ -272,7 +279,7 @@ function renderM2mMount(
|
|
|
272
279
|
const targetVarSym = imp(
|
|
273
280
|
`${ctx.collectionName(entry.targetEntity)}@${crossEntitySpecifier(
|
|
274
281
|
ctx.outputLayout,
|
|
275
|
-
|
|
282
|
+
sourcePkg,
|
|
276
283
|
ctx.packageOf.get(entry.targetEntity),
|
|
277
284
|
entry.targetEntity,
|
|
278
285
|
ctx.extStyle,
|
|
@@ -284,13 +291,13 @@ function renderM2mMount(
|
|
|
284
291
|
// fromPackage = source.package: this routes file is SOURCE's own module, never the
|
|
285
292
|
// junction's or the target's — see resolveJunctionColumn's doc comment (B1).
|
|
286
293
|
const sourceColumn: Code = junction
|
|
287
|
-
? resolveJunctionColumn(junction, entry.sourceJoinField!, ctx,
|
|
294
|
+
? resolveJunctionColumn(junction, entry.sourceJoinField!, ctx, sourcePkg)
|
|
288
295
|
: code`${JSON.stringify(entry.sourceJoinField!)}`;
|
|
289
296
|
const targetColumn: Code = junction
|
|
290
|
-
? resolveJunctionColumn(junction, entry.targetJoinField!, ctx,
|
|
297
|
+
? resolveJunctionColumn(junction, entry.targetJoinField!, ctx, sourcePkg)
|
|
291
298
|
: code`${JSON.stringify(entry.targetJoinField!)}`;
|
|
292
299
|
const targetPkColumn: Code = target
|
|
293
|
-
? resolveJunctionColumn(target, ctx.pkMap.get(entry.targetEntity)?.fieldName ?? "id", ctx,
|
|
300
|
+
? resolveJunctionColumn(target, ctx.pkMap.get(entry.targetEntity)?.fieldName ?? "id", ctx, sourcePkg)
|
|
294
301
|
: code`${JSON.stringify("id")}`;
|
|
295
302
|
|
|
296
303
|
return code` ${mountM2mRouteSym}({
|
|
@@ -368,10 +375,11 @@ function renderTphRoutesFile(
|
|
|
368
375
|
const discField = plan.discriminatorField;
|
|
369
376
|
const tableVar = ctx.collectionName(baseName);
|
|
370
377
|
|
|
378
|
+
const basePkg = effectivePackage(base);
|
|
371
379
|
const baseFileSpec = entityModuleSpecifier(
|
|
372
|
-
ctx.selfTarget, ctx.entityModuleTarget,
|
|
380
|
+
ctx.selfTarget, ctx.entityModuleTarget, basePkg, baseName, ctx.extStyle,
|
|
373
381
|
);
|
|
374
|
-
const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout,
|
|
382
|
+
const dbImportSpec = relativeModuleSpecifier(ctx.outputLayout, basePkg, ctx.dbImport, ctx.extStyle);
|
|
375
383
|
|
|
376
384
|
const FastifyInstanceSym = imp("t:FastifyInstance@fastify");
|
|
377
385
|
const mountCrudRoutesSym = imp("mountCrudRoutes@@metaobjectsdev/runtime-ts/drizzle-fastify");
|
|
@@ -405,7 +413,7 @@ function renderTphRoutesFile(
|
|
|
405
413
|
|
|
406
414
|
const subtypeMounts: Code[] = plan.subtypes.map(({ entity: sub, value, routeSegment: segment }) => {
|
|
407
415
|
const subFileSpec = entityModuleSpecifier(
|
|
408
|
-
ctx.selfTarget, ctx.entityModuleTarget, sub
|
|
416
|
+
ctx.selfTarget, ctx.entityModuleTarget, effectivePackage(sub), sub.name, ctx.extStyle,
|
|
409
417
|
);
|
|
410
418
|
const subInsertSym = imp(`${sub.name}InsertSchema@${subFileSpec}`);
|
|
411
419
|
// FR-036 Program B: the per-subtype UPDATE must carry the FR-035 present-key
|
|
@@ -42,6 +42,7 @@ import { valueObjectModuleSpecifier } from "../import-path.js";
|
|
|
42
42
|
// FR-035: the SAME required-predicate that drives the Drizzle column's .notNull()
|
|
43
43
|
// drives the UpdateSchema's .nullable() exclusion — shared so they cannot drift.
|
|
44
44
|
import { isRequired } from "../column-mapper.js";
|
|
45
|
+
import { effectivePackage } from "../docs-paths.js";
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* FR-017 Tier 1 — when this object is a TPH subtype (@discriminatorValue set
|
|
@@ -593,9 +594,10 @@ function zodFieldExpr(
|
|
|
593
594
|
// layout/package/extStyle-aware helper (the SAME one the field's TS type +
|
|
594
595
|
// Drizzle .$type<> use) so all three agree. Without owner/ctx (bare
|
|
595
596
|
// unit-test calls) fall back to the bare name + flat same-dir.
|
|
596
|
-
const
|
|
597
|
+
const ownerPkg = (ctx && owner) ? effectivePackage(owner) : undefined;
|
|
598
|
+
const refName = (ctx && owner) ? ctx.resolveValueObjectName(ref, fieldDeclaringPackage(field, ownerPkg)) : stripPackage(ref);
|
|
597
599
|
const moduleSpec = (ctx && owner)
|
|
598
|
-
? valueObjectModuleSpecifier(refName, ctx.packageOf,
|
|
600
|
+
? valueObjectModuleSpecifier(refName, ctx.packageOf, ownerPkg, ctx.outputLayout, ctx.extStyle)
|
|
599
601
|
: `./${refName}.js`;
|
|
600
602
|
const refImp = imp(`${refName}InsertSchema@${moduleSpec}`);
|
|
601
603
|
let base: Code = code`${refImp}`;
|
|
@@ -614,9 +616,10 @@ function zodFieldExpr(
|
|
|
614
616
|
if (field.subType === FIELD_SUBTYPE_MAP) {
|
|
615
617
|
const ref = field.attr(FIELD_ATTR_OBJECT_REF);
|
|
616
618
|
if (typeof ref === "string" && ref.length > 0) {
|
|
617
|
-
const
|
|
619
|
+
const ownerPkg = (ctx && owner) ? effectivePackage(owner) : undefined;
|
|
620
|
+
const refName = (ctx && owner) ? ctx.resolveValueObjectName(ref, fieldDeclaringPackage(field, ownerPkg)) : stripPackage(ref);
|
|
618
621
|
const moduleSpec = (ctx && owner)
|
|
619
|
-
? valueObjectModuleSpecifier(refName, ctx.packageOf,
|
|
622
|
+
? valueObjectModuleSpecifier(refName, ctx.packageOf, ownerPkg, ctx.outputLayout, ctx.extStyle)
|
|
620
623
|
: `./${refName}.js`;
|
|
621
624
|
const refImp = imp(`${refName}InsertSchema@${moduleSpec}`);
|
|
622
625
|
return appendValidatorChain(code`z.record(z.string(), ${refImp})`, field, forceRequired);
|