@metaobjectsdev/codegen-ts-tanstack 0.21.3 → 0.21.4

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/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # @metaobjectsdev/codegen-ts-tanstack
2
2
 
3
- TanStack codegen for MetaObjects. Provides `tanstackQuery()` (per-entity `<Entity>.hooks.ts` — 5 React Query hooks, plus a `use<Source><Relation>(sourceId, opts?)` collection hook per many-to-many relationship), `tanstackGrid()` (`<Entity>.columns.tsx` for `@tanstack/react-table`), and `tanstackGridHook()`.
3
+ TanStack codegen for MetaObjects. Provides `tanstackQuery()` (per-entity `<Entity>.hooks.ts` — 5 React Query hooks, plus a `use<Source><Relation>(sourceId, opts?)` collection hook per many-to-many relationship), `tanstackGrid()` (`<Entity>.columns.tsx` for `@tanstack/react-table`), and `tanstackGridHook()` (`<Entity>.grid.ts` — the controlled grid state + query).
4
+
5
+ ### Grids are opt-in per entity
6
+
7
+ `tanstackQuery()` emits hooks for **every** entity. `tanstackGrid()` and `tanstackGridHook()` emit **only for an entity that declares a `layout.dataGrid` child** — declaring one is how you say "this entity is displayed in a grid". If you wire the grid generators and get no `.columns.tsx`/`.grid.ts`, that is the reason, and `meta gen` says so in its warnings. Opt an entity in with:
8
+
9
+ ```jsonc
10
+ { "layout.dataGrid": { "name": "default", "@columns": ["name", "email"] } }
11
+ ```
12
+
13
+ `@columns` is an ordered list; omit it to get every field. Each `layout.dataGrid` yields a `<entity><Grid>Columns` + `<entity><Grid>Grid` pair from `tanstackGrid()` and a `use<Entity><Grid>Grid()` from `tanstackGridHook()`.
4
14
 
5
15
  ### M:N collection hooks (FR-018)
6
16
 
@@ -18,13 +28,28 @@ In your `metaobjects.config.ts`:
18
28
 
19
29
  ```ts
20
30
  import { defineConfig } from "@metaobjectsdev/cli";
21
- import { tanstackQuery, tanstackGrid } from "@metaobjectsdev/codegen-ts-tanstack";
31
+ import { tanstackQuery, tanstackGrid, tanstackGridHook } from "@metaobjectsdev/codegen-ts-tanstack";
22
32
 
23
33
  export default defineConfig({
24
- generators: [tanstackQuery(), tanstackGrid()],
34
+ generators: [tanstackQuery(), tanstackGrid(), tanstackGridHook()],
25
35
  });
26
36
  ```
27
37
 
38
+ Then render. `<EntityGrid>` is fully controlled — beyond the columns it needs
39
+ `rowCount`, a `state` object and three `onChange` callbacks — so pair it with the
40
+ generated grid hook, which returns exactly that prop shape:
41
+
42
+ ```tsx
43
+ import { EntityGrid } from "@metaobjectsdev/tanstack";
44
+ import { authorDefaultColumns, authorDefaultGrid } from "./generated/Author.columns";
45
+ import { useAuthorDefaultGrid } from "./generated/Author.grid";
46
+
47
+ export function AuthorList() {
48
+ const grid = useAuthorDefaultGrid(); // owns sorting/pagination/filters + the query
49
+ return <EntityGrid {...grid} columns={authorDefaultColumns} grid={authorDefaultGrid} />;
50
+ }
51
+ ```
52
+
28
53
  `tanstackQuery`/`tanstackGrid`/`tanstackGridHook` each accept `{ target }` to route
29
54
  their output (hooks/columns/grids) to a named target such as the browser app — see
30
55
  `@metaobjectsdev/cli` README, "Multiple output targets". The generated files import
@@ -0,0 +1,32 @@
1
+ import type { MetaObject } from "@metaobjectsdev/metadata";
2
+ import type { GenContext } from "@metaobjectsdev/codegen-ts";
3
+ /** True when the entity declares (or inherits) at least one `layout.dataGrid`. */
4
+ export declare function hasDataGridLayout(entity: MetaObject): boolean;
5
+ /**
6
+ * Grid artifacts are opt-IN per entity: an entity produces one only when it declares
7
+ * a `layout.dataGrid` child. That is intended — but nothing used to say so. An
8
+ * adopter who wired `tanstackQuery() + tanstackGrid()` got `.hooks.ts` for every
9
+ * entity and `.columns.tsx` for none, and reasonably concluded codegen was broken
10
+ * (#287). The package description promising "hooks and column definitions"
11
+ * unconditionally made it worse.
12
+ *
13
+ * So the run itself now says it, following the repo's tell-at-`meta gen`-time
14
+ * precedent (#226 / #258) rather than a doc line that gets missed the same way this
15
+ * one was. Warning only: the exit code is untouched, and `--dry-run` reports it too,
16
+ * since generators run before the write phase branches.
17
+ *
18
+ * It fires ONLY when the generator emitted **nothing at all** — which is the reported
19
+ * condition ("no columns file for ANY entity") and makes the note self-extinguishing:
20
+ * declare one `layout.dataGrid` anywhere in the model and it goes quiet forever. A
21
+ * 50-entity model with 3 grids therefore stays silent, because that author plainly
22
+ * knows how grids work and a permanent 47-name nag is exactly the cry-wolf failure
23
+ * that got the old `timestampMode` warning deleted from `runner.ts` rather than left
24
+ * to fire forever.
25
+ *
26
+ * `passesOtherGates` is the generator's own filter MINUS the layout check, so the
27
+ * named set is exactly "objects the layout gate — and nothing else — held back".
28
+ * `object.value`s are excluded from it: a value object is a payload/DTO shape, never
29
+ * a grid candidate, so naming one would be advice worth ignoring.
30
+ */
31
+ export declare function warnMissingDataGridLayout(ctx: GenContext, passesOtherGates: (entity: MetaObject) => boolean, artifact: string): void;
32
+ //# sourceMappingURL=data-grid-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-grid-gate.d.ts","sourceRoot":"","sources":["../src/data-grid-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAG7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,UAAU,EACf,gBAAgB,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,EACjD,QAAQ,EAAE,MAAM,GACf,IAAI,CAaN"}
@@ -0,0 +1,45 @@
1
+ import { LAYOUT_SUBTYPE_DATA_GRID, OBJECT_SUBTYPE_VALUE } from "@metaobjectsdev/metadata";
2
+ /** True when the entity declares (or inherits) at least one `layout.dataGrid`. */
3
+ export function hasDataGridLayout(entity) {
4
+ // layouts() is effective — own + inherited layouts (from extends:/super:).
5
+ return entity.layouts().some((l) => l.subType === LAYOUT_SUBTYPE_DATA_GRID);
6
+ }
7
+ /**
8
+ * Grid artifacts are opt-IN per entity: an entity produces one only when it declares
9
+ * a `layout.dataGrid` child. That is intended — but nothing used to say so. An
10
+ * adopter who wired `tanstackQuery() + tanstackGrid()` got `.hooks.ts` for every
11
+ * entity and `.columns.tsx` for none, and reasonably concluded codegen was broken
12
+ * (#287). The package description promising "hooks and column definitions"
13
+ * unconditionally made it worse.
14
+ *
15
+ * So the run itself now says it, following the repo's tell-at-`meta gen`-time
16
+ * precedent (#226 / #258) rather than a doc line that gets missed the same way this
17
+ * one was. Warning only: the exit code is untouched, and `--dry-run` reports it too,
18
+ * since generators run before the write phase branches.
19
+ *
20
+ * It fires ONLY when the generator emitted **nothing at all** — which is the reported
21
+ * condition ("no columns file for ANY entity") and makes the note self-extinguishing:
22
+ * declare one `layout.dataGrid` anywhere in the model and it goes quiet forever. A
23
+ * 50-entity model with 3 grids therefore stays silent, because that author plainly
24
+ * knows how grids work and a permanent 47-name nag is exactly the cry-wolf failure
25
+ * that got the old `timestampMode` warning deleted from `runner.ts` rather than left
26
+ * to fire forever.
27
+ *
28
+ * `passesOtherGates` is the generator's own filter MINUS the layout check, so the
29
+ * named set is exactly "objects the layout gate — and nothing else — held back".
30
+ * `object.value`s are excluded from it: a value object is a payload/DTO shape, never
31
+ * a grid candidate, so naming one would be advice worth ignoring.
32
+ */
33
+ export function warnMissingDataGridLayout(ctx, passesOtherGates, artifact) {
34
+ const candidates = ctx.entities.filter((e) => e.subType !== OBJECT_SUBTYPE_VALUE && passesOtherGates(e));
35
+ if (candidates.length === 0)
36
+ return;
37
+ if (candidates.some(hasDataGridLayout))
38
+ return; // the model uses grids — say nothing
39
+ ctx.warn(`no ${artifact} emitted for ${candidates.map((e) => e.name).join(", ")} — grid ` +
40
+ `artifacts are opt-in per entity via a \`layout.dataGrid\` child, and no object in ` +
41
+ `this model declares one. Add e.g. ` +
42
+ `{"layout.dataGrid": {"name": "default", "@columns": ["id", "name"]}} to an entity ` +
43
+ `to emit its grid.`);
44
+ }
45
+ //# sourceMappingURL=data-grid-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-grid-gate.js","sourceRoot":"","sources":["../src/data-grid-gate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAG1F,kFAAkF;AAClF,MAAM,UAAU,iBAAiB,CAAC,MAAkB;IAClD,2EAA2E;IAC3E,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,wBAAwB,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,yBAAyB,CACvC,GAAe,EACf,gBAAiD,EACjD,QAAgB;IAEhB,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CACpC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,oBAAoB,IAAI,gBAAgB,CAAC,CAAC,CAAC,CACjE,CAAC;IACF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACpC,IAAI,UAAU,CAAC,IAAI,CAAC,iBAAiB,CAAC;QAAE,OAAO,CAAG,qCAAqC;IACvF,GAAG,CAAC,IAAI,CACN,MAAM,QAAQ,gBAAgB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU;QAC9E,oFAAoF;QACpF,oCAAoC;QACpC,oFAAoF;QACpF,mBAAmB,CACtB,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"tanstack-grid-hook.d.ts","sourceRoot":"","sources":["../src/tanstack-grid-hook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,EAA6B,KAAK,gBAAgB,EAAkF,MAAM,4BAA4B,CAAC;AAG9K,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EA8BxB,gBAAgB,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"tanstack-grid-hook.d.ts","sourceRoot":"","sources":["../src/tanstack-grid-hook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAA6B,KAAK,gBAAgB,EAAwH,MAAM,4BAA4B,CAAC;AAIpN,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EA8CxB,gBAAgB,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC"}
@@ -1,10 +1,6 @@
1
- import { LAYOUT_SUBTYPE_DATA_GRID } from "@metaobjectsdev/metadata";
2
- import { perEntity, formatTs, entityOutputPath, emitsInstanceArtifacts, CODEGEN_ATTR_EMIT_TANSTACK } from "@metaobjectsdev/codegen-ts";
1
+ import { perEntity, formatTs, entityOutputPath, emitsInstanceArtifacts, isTphSubtype, CODEGEN_ATTR_EMIT_TANSTACK, CODEGEN_ATTR_EMIT_GRID } from "@metaobjectsdev/codegen-ts";
2
+ import { hasDataGridLayout, warnMissingDataGridLayout } from "./data-grid-gate.js";
3
3
  import { renderGridHookFile } from "./templates/grid-hook-file.js";
4
- function hasDataGridLayout(entity) {
5
- // layouts() is effective — own + inherited layouts (from extends:/super:).
6
- return entity.layouts().some((l) => l.subType === LAYOUT_SUBTYPE_DATA_GRID);
7
- }
8
4
  /**
9
5
  * Per-entity generator that emits <Entity>.grid.ts — one
10
6
  * use<Entity><Grid>Grid() hook per layout[dataGrid] declared on the entity.
@@ -14,24 +10,40 @@ function hasDataGridLayout(entity) {
14
10
  */
15
11
  export const tanstackGridHook = function tanstackGridHook(opts) {
16
12
  const userFilter = opts?.filter ?? (() => true);
13
+ // Every gate EXCEPT the dataGrid-layout opt-in: the framework instance-artifact
14
+ // guard (skips abstract types), the metadata opt-out, and the user filter. Split
15
+ // out so the discoverability note can name exactly the entities the LAYOUT gate
16
+ // alone held back (#287).
17
+ //
18
+ // The TPH clause must MATCH tanstackGrid's exactly. A TPH subtype inherits its base's
19
+ // dataGrid layout via extends, so `hasDataGridLayout` is true for it — but tanstackGrid
20
+ // deliberately emits no per-subtype columns without an own `@emitGrid: true` (the base's
21
+ // polymorphic grid is the single source of truth). Without the same clause here, a TPH
22
+ // subtype got a `<Sub>.grid.ts` whose sibling `<Sub>.columns.tsx` is never emitted:
23
+ // a dangling `use<Sub>DefaultGrid()` with nothing to pair it with, and an outright
24
+ // TS2307 when the inherited layout carries an `@filter` preset (the hook then imports
25
+ // `<sub>DefaultFilter` from the missing columns module).
26
+ const passesOtherGates = (e) => emitsInstanceArtifacts(e)
27
+ // ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
28
+ && e.attr(CODEGEN_ATTR_EMIT_TANSTACK) !== false
29
+ && userFilter(e)
30
+ && (!isTphSubtype(e) || e.attr(CODEGEN_ATTR_EMIT_GRID) === true);
31
+ const emit = perEntity(async (entity, ctx) => {
32
+ if (!ctx.renderContext) {
33
+ throw new Error("tanstack-grid-hook: renderContext is required (provided by runGen)");
34
+ }
35
+ return {
36
+ path: entityOutputPath(ctx.renderContext.outputLayout, entity.package, `${entity.name}.grid.ts`),
37
+ content: await formatTs(renderGridHookFile(entity, ctx.renderContext)),
38
+ };
39
+ });
17
40
  const generator = {
18
41
  name: "tanstack-grid-hook",
19
- // AND-composes the framework instance-artifact guard (skips abstract types),
20
- // opt-out, user filter, and dataGrid layout presence.
21
- filter: (e) => emitsInstanceArtifacts(e)
22
- // ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
23
- && e.attr(CODEGEN_ATTR_EMIT_TANSTACK) !== false
24
- && userFilter(e)
25
- && hasDataGridLayout(e),
26
- generate: perEntity(async (entity, ctx) => {
27
- if (!ctx.renderContext) {
28
- throw new Error("tanstack-grid-hook: renderContext is required (provided by runGen)");
29
- }
30
- return {
31
- path: entityOutputPath(ctx.renderContext.outputLayout, entity.package, `${entity.name}.grid.ts`),
32
- content: await formatTs(renderGridHookFile(entity, ctx.renderContext)),
33
- };
34
- }),
42
+ filter: (e) => passesOtherGates(e) && hasDataGridLayout(e),
43
+ generate: async (ctx) => {
44
+ warnMissingDataGridLayout(ctx, passesOtherGates, "<Entity>.grid.ts");
45
+ return emit(ctx);
46
+ },
35
47
  };
36
48
  if (opts?.target) {
37
49
  generator.target = opts.target;
@@ -1 +1 @@
1
- {"version":3,"file":"tanstack-grid-hook.js","sourceRoot":"","sources":["../src/tanstack-grid-hook.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,SAAS,EAAyC,QAAQ,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAC9K,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAOnE,SAAS,iBAAiB,CAAC,MAAkB;IAC3C,2EAA2E;IAC3E,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,wBAAwB,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,gBAAgB,CAAC,IAA2B;IACnF,MAAM,UAAU,GAAG,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,SAAS,GAAc;QAC3B,IAAI,EAAE,oBAAoB;QAC1B,6EAA6E;QAC7E,sDAAsD;QACtD,MAAM,EAAE,CAAC,CAAa,EAAE,EAAE,CACxB,sBAAsB,CAAC,CAAC,CAAC;YACzB,2FAA2F;eACxF,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,KAAK,KAAK;eAC5C,UAAU,CAAC,CAAC,CAAC;eACb,iBAAiB,CAAC,CAAC,CAAC;QACzB,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;YACxC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;YACxF,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,gBAAgB,CACpB,GAAG,CAAC,aAAa,CAAC,YAAY,EAC9B,MAAM,CAAC,OAAO,EACd,GAAG,MAAM,CAAC,IAAI,UAAU,CACzB;gBACD,OAAO,EAAE,MAAM,QAAQ,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;aACvE,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;IACF,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;QACjB,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACjC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAkD,CAAC"}
1
+ {"version":3,"file":"tanstack-grid-hook.js","sourceRoot":"","sources":["../src/tanstack-grid-hook.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAyC,QAAQ,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,YAAY,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpN,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAOnE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,gBAAgB,CAAC,IAA2B;IACnF,MAAM,UAAU,GAAG,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAChD,gFAAgF;IAChF,iFAAiF;IACjF,gFAAgF;IAChF,0BAA0B;IAC1B,EAAE;IACF,sFAAsF;IACtF,wFAAwF;IACxF,yFAAyF;IACzF,uFAAuF;IACvF,oFAAoF;IACpF,mFAAmF;IACnF,sFAAsF;IACtF,yDAAyD;IACzD,MAAM,gBAAgB,GAAG,CAAC,CAAa,EAAW,EAAE,CAClD,sBAAsB,CAAC,CAAC,CAAC;QACzB,2FAA2F;WACxF,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,KAAK,KAAK;WAC5C,UAAU,CAAC,CAAC,CAAC;WACb,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,KAAK,IAAI,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,EAAE,MAAkB,EAAE,GAAG,EAAE,EAAE;QACvD,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACxF,CAAC;QACD,OAAO;YACL,IAAI,EAAE,gBAAgB,CACpB,GAAG,CAAC,aAAa,CAAC,YAAY,EAC9B,MAAM,CAAC,OAAO,EACd,GAAG,MAAM,CAAC,IAAI,UAAU,CACzB;YACD,OAAO,EAAE,MAAM,QAAQ,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;SACvE,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,SAAS,GAAc;QAC3B,IAAI,EAAE,oBAAoB;QAC1B,MAAM,EAAE,CAAC,CAAa,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC;QACtE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACtB,yBAAyB,CAAC,GAAG,EAAE,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;YACrE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;KACF,CAAC;IACF,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;QACjB,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACjC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAkD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"tanstack-grid.d.ts","sourceRoot":"","sources":["../src/tanstack-grid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,EAA6B,KAAK,gBAAgB,EAAwH,MAAM,4BAA4B,CAAC;AAGpN,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD;;;;GAIG;AACH,eAAO,MAAM,YAAY,EA+BpB,gBAAgB,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"tanstack-grid.d.ts","sourceRoot":"","sources":["../src/tanstack-grid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAA6B,KAAK,gBAAgB,EAAwH,MAAM,4BAA4B,CAAC;AAIpN,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAqCpB,gBAAgB,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC"}
@@ -1,10 +1,6 @@
1
- import { LAYOUT_SUBTYPE_DATA_GRID } from "@metaobjectsdev/metadata";
2
1
  import { perEntity, formatTs, entityOutputPath, emitsInstanceArtifacts, isTphSubtype, CODEGEN_ATTR_EMIT_TANSTACK, CODEGEN_ATTR_EMIT_GRID } from "@metaobjectsdev/codegen-ts";
2
+ import { hasDataGridLayout, warnMissingDataGridLayout } from "./data-grid-gate.js";
3
3
  import { renderColumnsFile } from "./templates/columns-file.js";
4
- function hasDataGridLayout(entity) {
5
- // layouts() is effective — own + inherited layouts (from extends:/super:).
6
- return entity.layouts().some((l) => l.subType === LAYOUT_SUBTYPE_DATA_GRID);
7
- }
8
4
  /**
9
5
  * Per-entity opt-out via `@emitTanstack: false`. Per-entity opt-IN: presence of
10
6
  * at least one `dataGrid` layout on the object. If both pass and the user-supplied
@@ -12,29 +8,35 @@ function hasDataGridLayout(entity) {
12
8
  */
13
9
  export const tanstackGrid = function tanstackGrid(opts) {
14
10
  const userFilter = opts?.filter ?? (() => true);
11
+ // Every gate EXCEPT the dataGrid-layout opt-in: the framework instance-artifact
12
+ // guard (skips abstract types), the metadata opt-out, and the user filter.
13
+ // FR-017 Tier 3: a TPH discriminator base emits ONE polymorphic grid. Its
14
+ // subtypes inherit the base's dataGrid layout via extends, but per-subtype grids
15
+ // are opt-IN only (own `@emitGrid: true`) — otherwise the polymorphic grid is the
16
+ // single source of truth.
17
+ // Split out so the discoverability note can name exactly the entities the LAYOUT
18
+ // gate alone held back (#287) — an opted-out or abstract type is not a surprise.
19
+ const passesOtherGates = (e) => emitsInstanceArtifacts(e)
20
+ // ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
21
+ && e.attr(CODEGEN_ATTR_EMIT_TANSTACK) !== false
22
+ && userFilter(e)
23
+ && (!isTphSubtype(e) || e.attr(CODEGEN_ATTR_EMIT_GRID) === true);
24
+ const emit = perEntity(async (entity, ctx) => {
25
+ if (!ctx.renderContext) {
26
+ throw new Error("tanstack-grid: renderContext is required (provided by runGen)");
27
+ }
28
+ return {
29
+ path: entityOutputPath(ctx.renderContext.outputLayout, entity.package, `${entity.name}.columns.tsx`),
30
+ content: await formatTs(renderColumnsFile(entity, ctx.renderContext)),
31
+ };
32
+ });
15
33
  const generator = {
16
34
  name: "tanstack-grid",
17
- // Always set: AND-composes the framework instance-artifact guard (skips
18
- // abstract types), opt-out, user filter, and dataGrid layout presence.
19
- // FR-017 Tier 3: a TPH discriminator base emits ONE polymorphic grid. Its
20
- // subtypes inherit the base's dataGrid layout via extends, but per-subtype
21
- // grids are opt-IN only (own `@emitGrid: true`) — otherwise the polymorphic
22
- // grid is the single source of truth.
23
- filter: (e) => emitsInstanceArtifacts(e)
24
- // ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
25
- && e.attr(CODEGEN_ATTR_EMIT_TANSTACK) !== false
26
- && userFilter(e)
27
- && hasDataGridLayout(e)
28
- && (!isTphSubtype(e) || e.attr(CODEGEN_ATTR_EMIT_GRID) === true),
29
- generate: perEntity(async (entity, ctx) => {
30
- if (!ctx.renderContext) {
31
- throw new Error("tanstack-grid: renderContext is required (provided by runGen)");
32
- }
33
- return {
34
- path: entityOutputPath(ctx.renderContext.outputLayout, entity.package, `${entity.name}.columns.tsx`),
35
- content: await formatTs(renderColumnsFile(entity, ctx.renderContext)),
36
- };
37
- }),
35
+ filter: (e) => passesOtherGates(e) && hasDataGridLayout(e),
36
+ generate: async (ctx) => {
37
+ warnMissingDataGridLayout(ctx, passesOtherGates, "<Entity>.columns.tsx");
38
+ return emit(ctx);
39
+ },
38
40
  };
39
41
  if (opts?.target) {
40
42
  generator.target = opts.target;
@@ -1 +1 @@
1
- {"version":3,"file":"tanstack-grid.js","sourceRoot":"","sources":["../src/tanstack-grid.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,SAAS,EAAyC,QAAQ,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,YAAY,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpN,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAOhE,SAAS,iBAAiB,CAAC,MAAkB;IAC3C,2EAA2E;IAC3E,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,wBAAwB,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,YAAY,CAAC,IAAuB;IACvE,MAAM,UAAU,GAAG,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,SAAS,GAAc;QAC3B,IAAI,EAAE,eAAe;QACrB,wEAAwE;QACxE,uEAAuE;QACvE,0EAA0E;QAC1E,2EAA2E;QAC3E,4EAA4E;QAC5E,sCAAsC;QACtC,MAAM,EAAE,CAAC,CAAa,EAAE,EAAE,CACxB,sBAAsB,CAAC,CAAC,CAAC;YACzB,2FAA2F;eACxF,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,KAAK,KAAK;eAC5C,UAAU,CAAC,CAAC,CAAC;eACb,iBAAiB,CAAC,CAAC,CAAC;eACpB,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,KAAK,IAAI,CAAC;QAClE,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;YACxC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;YACnF,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,IAAI,cAAc,CAAC;gBACpG,OAAO,EAAE,MAAM,QAAQ,CAAC,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;aACtE,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;IACF,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;QACjB,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACjC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAA8C,CAAC"}
1
+ {"version":3,"file":"tanstack-grid.js","sourceRoot":"","sources":["../src/tanstack-grid.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAyC,QAAQ,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,YAAY,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpN,OAAO,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAOhE;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,YAAY,CAAC,IAAuB;IACvE,MAAM,UAAU,GAAG,IAAI,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAChD,gFAAgF;IAChF,2EAA2E;IAC3E,0EAA0E;IAC1E,iFAAiF;IACjF,kFAAkF;IAClF,0BAA0B;IAC1B,iFAAiF;IACjF,iFAAiF;IACjF,MAAM,gBAAgB,GAAG,CAAC,CAAa,EAAW,EAAE,CAClD,sBAAsB,CAAC,CAAC,CAAC;QACzB,2FAA2F;WACxF,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,KAAK,KAAK;WAC5C,UAAU,CAAC,CAAC,CAAC;WACb,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,sBAAsB,CAAC,KAAK,IAAI,CAAC,CAAC;IACnE,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,EAAE,MAAkB,EAAE,GAAG,EAAE,EAAE;QACvD,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACnF,CAAC;QACD,OAAO;YACL,IAAI,EAAE,gBAAgB,CAAC,GAAG,CAAC,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,IAAI,cAAc,CAAC;YACpG,OAAO,EAAE,MAAM,QAAQ,CAAC,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC;SACtE,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,MAAM,SAAS,GAAc;QAC3B,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,CAAC,CAAa,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC;QACtE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACtB,yBAAyB,CAAC,GAAG,EAAE,gBAAgB,EAAE,sBAAsB,CAAC,CAAC;YACzE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;KACF,CAAC;IACF,IAAI,IAAI,EAAE,MAAM,EAAE,CAAC;QACjB,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACjC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAA8C,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/codegen-ts-tanstack",
3
- "version": "0.21.3",
4
- "description": "TanStack codegen for metaobjects — emits hooks and column definitions for TanStack Query and Table.",
3
+ "version": "0.21.4",
4
+ "description": "TanStack codegen for metaobjects — TanStack Query hooks for every entity, plus TanStack Table column definitions and grid-state hooks for entities declaring a layout.dataGrid.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -44,8 +44,8 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@metaobjectsdev/metadata": "0.21.3",
48
- "@metaobjectsdev/codegen-ts": "0.21.3",
47
+ "@metaobjectsdev/metadata": "0.21.4",
48
+ "@metaobjectsdev/codegen-ts": "0.21.4",
49
49
  "ts-poet": "^6.10.0"
50
50
  },
51
51
  "peerDependencies": {
@@ -0,0 +1,54 @@
1
+ import type { MetaObject } from "@metaobjectsdev/metadata";
2
+ import { LAYOUT_SUBTYPE_DATA_GRID, OBJECT_SUBTYPE_VALUE } from "@metaobjectsdev/metadata";
3
+ import type { GenContext } from "@metaobjectsdev/codegen-ts";
4
+
5
+ /** True when the entity declares (or inherits) at least one `layout.dataGrid`. */
6
+ export function hasDataGridLayout(entity: MetaObject): boolean {
7
+ // layouts() is effective — own + inherited layouts (from extends:/super:).
8
+ return entity.layouts().some((l) => l.subType === LAYOUT_SUBTYPE_DATA_GRID);
9
+ }
10
+
11
+ /**
12
+ * Grid artifacts are opt-IN per entity: an entity produces one only when it declares
13
+ * a `layout.dataGrid` child. That is intended — but nothing used to say so. An
14
+ * adopter who wired `tanstackQuery() + tanstackGrid()` got `.hooks.ts` for every
15
+ * entity and `.columns.tsx` for none, and reasonably concluded codegen was broken
16
+ * (#287). The package description promising "hooks and column definitions"
17
+ * unconditionally made it worse.
18
+ *
19
+ * So the run itself now says it, following the repo's tell-at-`meta gen`-time
20
+ * precedent (#226 / #258) rather than a doc line that gets missed the same way this
21
+ * one was. Warning only: the exit code is untouched, and `--dry-run` reports it too,
22
+ * since generators run before the write phase branches.
23
+ *
24
+ * It fires ONLY when the generator emitted **nothing at all** — which is the reported
25
+ * condition ("no columns file for ANY entity") and makes the note self-extinguishing:
26
+ * declare one `layout.dataGrid` anywhere in the model and it goes quiet forever. A
27
+ * 50-entity model with 3 grids therefore stays silent, because that author plainly
28
+ * knows how grids work and a permanent 47-name nag is exactly the cry-wolf failure
29
+ * that got the old `timestampMode` warning deleted from `runner.ts` rather than left
30
+ * to fire forever.
31
+ *
32
+ * `passesOtherGates` is the generator's own filter MINUS the layout check, so the
33
+ * named set is exactly "objects the layout gate — and nothing else — held back".
34
+ * `object.value`s are excluded from it: a value object is a payload/DTO shape, never
35
+ * a grid candidate, so naming one would be advice worth ignoring.
36
+ */
37
+ export function warnMissingDataGridLayout(
38
+ ctx: GenContext,
39
+ passesOtherGates: (entity: MetaObject) => boolean,
40
+ artifact: string,
41
+ ): void {
42
+ const candidates = ctx.entities.filter(
43
+ (e) => e.subType !== OBJECT_SUBTYPE_VALUE && passesOtherGates(e),
44
+ );
45
+ if (candidates.length === 0) return;
46
+ if (candidates.some(hasDataGridLayout)) return; // the model uses grids — say nothing
47
+ ctx.warn(
48
+ `no ${artifact} emitted for ${candidates.map((e) => e.name).join(", ")} — grid ` +
49
+ `artifacts are opt-in per entity via a \`layout.dataGrid\` child, and no object in ` +
50
+ `this model declares one. Add e.g. ` +
51
+ `{"layout.dataGrid": {"name": "default", "@columns": ["id", "name"]}} to an entity ` +
52
+ `to emit its grid.`,
53
+ );
54
+ }
@@ -1,6 +1,6 @@
1
1
  import type { MetaObject } from "@metaobjectsdev/metadata";
2
- import { LAYOUT_SUBTYPE_DATA_GRID } from "@metaobjectsdev/metadata";
3
- import { perEntity, type Generator, type GeneratorFactory, formatTs, entityOutputPath, emitsInstanceArtifacts, CODEGEN_ATTR_EMIT_TANSTACK } from "@metaobjectsdev/codegen-ts";
2
+ import { perEntity, type Generator, type GeneratorFactory, formatTs, entityOutputPath, emitsInstanceArtifacts, isTphSubtype, CODEGEN_ATTR_EMIT_TANSTACK, CODEGEN_ATTR_EMIT_GRID } from "@metaobjectsdev/codegen-ts";
3
+ import { hasDataGridLayout, warnMissingDataGridLayout } from "./data-grid-gate.js";
4
4
  import { renderGridHookFile } from "./templates/grid-hook-file.js";
5
5
 
6
6
  export interface TanstackGridHookOpts {
@@ -8,11 +8,6 @@ export interface TanstackGridHookOpts {
8
8
  target?: string;
9
9
  }
10
10
 
11
- function hasDataGridLayout(entity: MetaObject): boolean {
12
- // layouts() is effective — own + inherited layouts (from extends:/super:).
13
- return entity.layouts().some((l) => l.subType === LAYOUT_SUBTYPE_DATA_GRID);
14
- }
15
-
16
11
  /**
17
12
  * Per-entity generator that emits <Entity>.grid.ts — one
18
13
  * use<Entity><Grid>Grid() hook per layout[dataGrid] declared on the entity.
@@ -22,29 +17,45 @@ function hasDataGridLayout(entity: MetaObject): boolean {
22
17
  */
23
18
  export const tanstackGridHook = function tanstackGridHook(opts?: TanstackGridHookOpts): Generator {
24
19
  const userFilter = opts?.filter ?? (() => true);
20
+ // Every gate EXCEPT the dataGrid-layout opt-in: the framework instance-artifact
21
+ // guard (skips abstract types), the metadata opt-out, and the user filter. Split
22
+ // out so the discoverability note can name exactly the entities the LAYOUT gate
23
+ // alone held back (#287).
24
+ //
25
+ // The TPH clause must MATCH tanstackGrid's exactly. A TPH subtype inherits its base's
26
+ // dataGrid layout via extends, so `hasDataGridLayout` is true for it — but tanstackGrid
27
+ // deliberately emits no per-subtype columns without an own `@emitGrid: true` (the base's
28
+ // polymorphic grid is the single source of truth). Without the same clause here, a TPH
29
+ // subtype got a `<Sub>.grid.ts` whose sibling `<Sub>.columns.tsx` is never emitted:
30
+ // a dangling `use<Sub>DefaultGrid()` with nothing to pair it with, and an outright
31
+ // TS2307 when the inherited layout carries an `@filter` preset (the hook then imports
32
+ // `<sub>DefaultFilter` from the missing columns module).
33
+ const passesOtherGates = (e: MetaObject): boolean =>
34
+ emitsInstanceArtifacts(e)
35
+ // ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
36
+ && e.attr(CODEGEN_ATTR_EMIT_TANSTACK) !== false
37
+ && userFilter(e)
38
+ && (!isTphSubtype(e) || e.attr(CODEGEN_ATTR_EMIT_GRID) === true);
39
+ const emit = perEntity(async (entity: MetaObject, ctx) => {
40
+ if (!ctx.renderContext) {
41
+ throw new Error("tanstack-grid-hook: renderContext is required (provided by runGen)");
42
+ }
43
+ return {
44
+ path: entityOutputPath(
45
+ ctx.renderContext.outputLayout,
46
+ entity.package,
47
+ `${entity.name}.grid.ts`,
48
+ ),
49
+ content: await formatTs(renderGridHookFile(entity, ctx.renderContext)),
50
+ };
51
+ });
25
52
  const generator: Generator = {
26
53
  name: "tanstack-grid-hook",
27
- // AND-composes the framework instance-artifact guard (skips abstract types),
28
- // opt-out, user filter, and dataGrid layout presence.
29
- filter: (e: MetaObject) =>
30
- emitsInstanceArtifacts(e)
31
- // ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
32
- && e.attr(CODEGEN_ATTR_EMIT_TANSTACK) !== false
33
- && userFilter(e)
34
- && hasDataGridLayout(e),
35
- generate: perEntity(async (entity, ctx) => {
36
- if (!ctx.renderContext) {
37
- throw new Error("tanstack-grid-hook: renderContext is required (provided by runGen)");
38
- }
39
- return {
40
- path: entityOutputPath(
41
- ctx.renderContext.outputLayout,
42
- entity.package,
43
- `${entity.name}.grid.ts`,
44
- ),
45
- content: await formatTs(renderGridHookFile(entity, ctx.renderContext)),
46
- };
47
- }),
54
+ filter: (e: MetaObject) => passesOtherGates(e) && hasDataGridLayout(e),
55
+ generate: async (ctx) => {
56
+ warnMissingDataGridLayout(ctx, passesOtherGates, "<Entity>.grid.ts");
57
+ return emit(ctx);
58
+ },
48
59
  };
49
60
  if (opts?.target) {
50
61
  generator.target = opts.target;
@@ -1,6 +1,6 @@
1
1
  import type { MetaObject } from "@metaobjectsdev/metadata";
2
- import { LAYOUT_SUBTYPE_DATA_GRID } from "@metaobjectsdev/metadata";
3
2
  import { perEntity, type Generator, type GeneratorFactory, formatTs, entityOutputPath, emitsInstanceArtifacts, isTphSubtype, CODEGEN_ATTR_EMIT_TANSTACK, CODEGEN_ATTR_EMIT_GRID } from "@metaobjectsdev/codegen-ts";
3
+ import { hasDataGridLayout, warnMissingDataGridLayout } from "./data-grid-gate.js";
4
4
  import { renderColumnsFile } from "./templates/columns-file.js";
5
5
 
6
6
  export interface TanstackGridOpts {
@@ -8,11 +8,6 @@ export interface TanstackGridOpts {
8
8
  target?: string;
9
9
  }
10
10
 
11
- function hasDataGridLayout(entity: MetaObject): boolean {
12
- // layouts() is effective — own + inherited layouts (from extends:/super:).
13
- return entity.layouts().some((l) => l.subType === LAYOUT_SUBTYPE_DATA_GRID);
14
- }
15
-
16
11
  /**
17
12
  * Per-entity opt-out via `@emitTanstack: false`. Per-entity opt-IN: presence of
18
13
  * at least one `dataGrid` layout on the object. If both pass and the user-supplied
@@ -20,30 +15,36 @@ function hasDataGridLayout(entity: MetaObject): boolean {
20
15
  */
21
16
  export const tanstackGrid = function tanstackGrid(opts?: TanstackGridOpts): Generator {
22
17
  const userFilter = opts?.filter ?? (() => true);
18
+ // Every gate EXCEPT the dataGrid-layout opt-in: the framework instance-artifact
19
+ // guard (skips abstract types), the metadata opt-out, and the user filter.
20
+ // FR-017 Tier 3: a TPH discriminator base emits ONE polymorphic grid. Its
21
+ // subtypes inherit the base's dataGrid layout via extends, but per-subtype grids
22
+ // are opt-IN only (own `@emitGrid: true`) — otherwise the polymorphic grid is the
23
+ // single source of truth.
24
+ // Split out so the discoverability note can name exactly the entities the LAYOUT
25
+ // gate alone held back (#287) — an opted-out or abstract type is not a surprise.
26
+ const passesOtherGates = (e: MetaObject): boolean =>
27
+ emitsInstanceArtifacts(e)
28
+ // ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
29
+ && e.attr(CODEGEN_ATTR_EMIT_TANSTACK) !== false
30
+ && userFilter(e)
31
+ && (!isTphSubtype(e) || e.attr(CODEGEN_ATTR_EMIT_GRID) === true);
32
+ const emit = perEntity(async (entity: MetaObject, ctx) => {
33
+ if (!ctx.renderContext) {
34
+ throw new Error("tanstack-grid: renderContext is required (provided by runGen)");
35
+ }
36
+ return {
37
+ path: entityOutputPath(ctx.renderContext.outputLayout, entity.package, `${entity.name}.columns.tsx`),
38
+ content: await formatTs(renderColumnsFile(entity, ctx.renderContext)),
39
+ };
40
+ });
23
41
  const generator: Generator = {
24
42
  name: "tanstack-grid",
25
- // Always set: AND-composes the framework instance-artifact guard (skips
26
- // abstract types), opt-out, user filter, and dataGrid layout presence.
27
- // FR-017 Tier 3: a TPH discriminator base emits ONE polymorphic grid. Its
28
- // subtypes inherit the base's dataGrid layout via extends, but per-subtype
29
- // grids are opt-IN only (own `@emitGrid: true`) — otherwise the polymorphic
30
- // grid is the single source of truth.
31
- filter: (e: MetaObject) =>
32
- emitsInstanceArtifacts(e)
33
- // ADR-0039: resolving — a concrete entity may inherit its @emit* opt-out flag via extends.
34
- && e.attr(CODEGEN_ATTR_EMIT_TANSTACK) !== false
35
- && userFilter(e)
36
- && hasDataGridLayout(e)
37
- && (!isTphSubtype(e) || e.attr(CODEGEN_ATTR_EMIT_GRID) === true),
38
- generate: perEntity(async (entity, ctx) => {
39
- if (!ctx.renderContext) {
40
- throw new Error("tanstack-grid: renderContext is required (provided by runGen)");
41
- }
42
- return {
43
- path: entityOutputPath(ctx.renderContext.outputLayout, entity.package, `${entity.name}.columns.tsx`),
44
- content: await formatTs(renderColumnsFile(entity, ctx.renderContext)),
45
- };
46
- }),
43
+ filter: (e: MetaObject) => passesOtherGates(e) && hasDataGridLayout(e),
44
+ generate: async (ctx) => {
45
+ warnMissingDataGridLayout(ctx, passesOtherGates, "<Entity>.columns.tsx");
46
+ return emit(ctx);
47
+ },
47
48
  };
48
49
  if (opts?.target) {
49
50
  generator.target = opts.target;