@pattern-stack/codegen 0.26.1 → 0.27.0
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/{chunk-CKLM57IE.js → chunk-AC6T2JUX.js} +14 -14
- package/dist/{chunk-ENAR3F5S.js → chunk-HEOISQ6W.js} +6 -6
- package/dist/{chunk-HLURWFIT.js → chunk-IASPGFFK.js} +5 -5
- package/dist/{chunk-CDLWYZVQ.js → chunk-IN3EWFB4.js} +4 -4
- package/dist/{chunk-XNRKZCVH.js → chunk-IXAE6BN6.js} +2 -2
- package/dist/{chunk-7XDB4OMR.js → chunk-VCXOPBYY.js} +9 -9
- package/dist/{chunk-6M6LZEP6.js → chunk-VDVEGTSW.js} +4 -4
- package/dist/{chunk-QXYKV4CE.js → chunk-W4JYZSQK.js} +8 -8
- package/dist/{chunk-QO35B6BN.js → chunk-YIVQ7KLS.js} +4 -4
- package/dist/runtime/http/pagination.d.ts +151 -0
- package/dist/runtime/http/pagination.js +98 -0
- package/dist/runtime/http/pagination.js.map +1 -0
- package/dist/runtime/subsystems/auth/auth.module.js +1 -1
- package/dist/runtime/subsystems/auth/index.js +6 -6
- package/dist/runtime/subsystems/bridge/bridge.module.js +9 -9
- package/dist/runtime/subsystems/bridge/index.js +9 -9
- package/dist/runtime/subsystems/events/events.module.js +2 -2
- package/dist/runtime/subsystems/events/index.js +4 -4
- package/dist/runtime/subsystems/index.js +45 -45
- package/dist/runtime/subsystems/integration/execute-integration.use-case.js +2 -2
- package/dist/runtime/subsystems/integration/index.js +31 -31
- package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js +3 -3
- package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js +3 -3
- package/dist/runtime/subsystems/integration/integration.module.js +6 -6
- package/dist/runtime/subsystems/jobs/index.js +24 -24
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +4 -4
- package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +2 -2
- package/dist/runtime/subsystems/jobs/job-worker.module.js +8 -8
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +7 -7
- package/dist/runtime/subsystems/observability/index.js +3 -3
- package/dist/src/cli/index.js +195 -20
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.js +8 -8
- package/package.json +1 -1
- package/runtime/http/pagination.ts +233 -0
- package/templates/entity/new/clean-lite-ps/controller.ejs.t +27 -6
- package/templates/entity/new/clean-lite-ps/dto/list-query.ejs.t +22 -0
- package/templates/entity/new/clean-lite-ps/index.ejs.t +1 -0
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +14 -0
- package/templates/entity/new/clean-lite-ps/use-cases/list.ejs.t +56 -3
- package/templates/entity/new/prompt.js +17 -0
- /package/dist/{chunk-CKLM57IE.js.map → chunk-AC6T2JUX.js.map} +0 -0
- /package/dist/{chunk-ENAR3F5S.js.map → chunk-HEOISQ6W.js.map} +0 -0
- /package/dist/{chunk-HLURWFIT.js.map → chunk-IASPGFFK.js.map} +0 -0
- /package/dist/{chunk-CDLWYZVQ.js.map → chunk-IN3EWFB4.js.map} +0 -0
- /package/dist/{chunk-XNRKZCVH.js.map → chunk-IXAE6BN6.js.map} +0 -0
- /package/dist/{chunk-7XDB4OMR.js.map → chunk-VCXOPBYY.js.map} +0 -0
- /package/dist/{chunk-6M6LZEP6.js.map → chunk-VDVEGTSW.js.map} +0 -0
- /package/dist/{chunk-QXYKV4CE.js.map → chunk-W4JYZSQK.js.map} +0 -0
- /package/dist/{chunk-QO35B6BN.js.map → chunk-YIVQ7KLS.js.map} +0 -0
package/dist/src/cli/index.js
CHANGED
|
@@ -46,14 +46,18 @@ import {
|
|
|
46
46
|
writeManifest
|
|
47
47
|
} from "../../chunk-K4BQQ2NN.js";
|
|
48
48
|
import "../../chunk-KVOWSC5S.js";
|
|
49
|
+
import "../../chunk-VCXOPBYY.js";
|
|
49
50
|
import "../../chunk-PRWIX6UW.js";
|
|
50
|
-
import "../../chunk-
|
|
51
|
-
import "../../chunk-QXYKV4CE.js";
|
|
51
|
+
import "../../chunk-W4JYZSQK.js";
|
|
52
52
|
import "../../chunk-EO2QPOKH.js";
|
|
53
|
-
import "../../chunk-
|
|
53
|
+
import "../../chunk-SQDOBLBP.js";
|
|
54
|
+
import "../../chunk-YIVQ7KLS.js";
|
|
55
|
+
import "../../chunk-LG57S2SC.js";
|
|
56
|
+
import "../../chunk-IASPGFFK.js";
|
|
57
|
+
import "../../chunk-S5G3HO7N.js";
|
|
58
|
+
import "../../chunk-MZ6GV4YF.js";
|
|
54
59
|
import "../../chunk-HNWZFNKP.js";
|
|
55
60
|
import "../../chunk-AHV4GDYM.js";
|
|
56
|
-
import "../../chunk-SQDOBLBP.js";
|
|
57
61
|
import "../../chunk-43SBT72G.js";
|
|
58
62
|
import "../../chunk-4MF3HKJA.js";
|
|
59
63
|
import "../../chunk-TIZXQU26.js";
|
|
@@ -63,10 +67,6 @@ import {
|
|
|
63
67
|
} from "../../chunk-5TK7MEN4.js";
|
|
64
68
|
import "../../chunk-4KNXX6TI.js";
|
|
65
69
|
import "../../chunk-3CJFPU6Q.js";
|
|
66
|
-
import "../../chunk-QO35B6BN.js";
|
|
67
|
-
import "../../chunk-MZ6GV4YF.js";
|
|
68
|
-
import "../../chunk-LG57S2SC.js";
|
|
69
|
-
import "../../chunk-S5G3HO7N.js";
|
|
70
70
|
import "../../chunk-U64T4YZE.js";
|
|
71
71
|
import "../../chunk-2E224ZSN.js";
|
|
72
72
|
|
|
@@ -5343,6 +5343,56 @@ function buildClientFile(ctx) {
|
|
|
5343
5343
|
` : "";
|
|
5344
5344
|
const body = `${imports}${baseUrlConst}
|
|
5345
5345
|
|
|
5346
|
+
/** Hard upper bound on a single list fetch \u2014 mirrors the backend pageSize clamp. */
|
|
5347
|
+
export const MAX_PAGE_SIZE = 200;
|
|
5348
|
+
|
|
5349
|
+
/**
|
|
5350
|
+
* Pagination envelope returned by every \`GET /<entities>\` (pagination-by-default).
|
|
5351
|
+
* Mirrors the backend \`Page<T>\` runtime contract
|
|
5352
|
+
* (\`@pattern-stack/codegen/runtime/http/pagination\`); duplicated here so the
|
|
5353
|
+
* generated frontend data layer carries no backend import. \`nextCursor\` is
|
|
5354
|
+
* contract-stable (the backend emits it from day one) \u2014 the offset engine
|
|
5355
|
+
* ignores it on request for now.
|
|
5356
|
+
*/
|
|
5357
|
+
export interface Page<T> {
|
|
5358
|
+
items: T[];
|
|
5359
|
+
page: number;
|
|
5360
|
+
pageCount: number;
|
|
5361
|
+
total: number;
|
|
5362
|
+
pageSize: number;
|
|
5363
|
+
nextCursor: string | null;
|
|
5364
|
+
}
|
|
5365
|
+
|
|
5366
|
+
/**
|
|
5367
|
+
* Request query for a list endpoint: page-based pagination + default sort, plus
|
|
5368
|
+
* arbitrary where-filters (passed through to the backend querystring). All keys
|
|
5369
|
+
* optional \u2014 the unfiltered first page is the default.
|
|
5370
|
+
*/
|
|
5371
|
+
export interface ListQuery {
|
|
5372
|
+
page?: number;
|
|
5373
|
+
cursor?: string;
|
|
5374
|
+
pageSize?: number;
|
|
5375
|
+
sort_by?: string;
|
|
5376
|
+
sort_order?: 'asc' | 'desc';
|
|
5377
|
+
[key: string]: string | number | boolean | null | undefined;
|
|
5378
|
+
}
|
|
5379
|
+
|
|
5380
|
+
/**
|
|
5381
|
+
* Serialize a {@link ListQuery} into a querystring (leading \`?\`), skipping
|
|
5382
|
+
* undefined/null values. Returns \`''\` for an empty/absent query so an
|
|
5383
|
+
* unfiltered \`list()\` hits the bare route.
|
|
5384
|
+
*/
|
|
5385
|
+
export function toListQueryString(query?: ListQuery): string {
|
|
5386
|
+
if (!query) return '';
|
|
5387
|
+
const params = new URLSearchParams();
|
|
5388
|
+
for (const [key, value] of Object.entries(query)) {
|
|
5389
|
+
if (value === undefined || value === null) continue;
|
|
5390
|
+
params.set(key, String(value));
|
|
5391
|
+
}
|
|
5392
|
+
const qs = params.toString();
|
|
5393
|
+
return qs ? \`?\${qs}\` : '';
|
|
5394
|
+
}
|
|
5395
|
+
|
|
5346
5396
|
/**
|
|
5347
5397
|
* Base REST transport for \`api\` sync-mode collections and entity api clients.
|
|
5348
5398
|
* Throws on non-2xx; returns parsed JSON, or \`undefined\` for 204 No Content.
|
|
@@ -5377,11 +5427,41 @@ function buildEntityApiFile(entity, ctx) {
|
|
|
5377
5427
|
const { config } = ctx;
|
|
5378
5428
|
const { camelName, plural, className, name } = entity;
|
|
5379
5429
|
const verb = updateVerb(config.architecture);
|
|
5380
|
-
const body = `import { request } from './client';
|
|
5430
|
+
const body = `import { MAX_PAGE_SIZE, type ListQuery, type Page, request, toListQueryString } from './client';
|
|
5381
5431
|
import type { ${className} } from '${config.dbEntitiesImport}/${name}';
|
|
5382
5432
|
|
|
5383
5433
|
export const ${camelName}Api = {
|
|
5384
|
-
|
|
5434
|
+
/**
|
|
5435
|
+
* Fetch one page of ${plural} (pagination-by-default). Threads page/cursor/
|
|
5436
|
+
* pageSize/sort + arbitrary where-filters into the querystring; returns the
|
|
5437
|
+
* \`Page<${className}>\` envelope. Call with no args for the unfiltered first page.
|
|
5438
|
+
*/
|
|
5439
|
+
list: (query?: ListQuery): Promise<Page<${className}>> =>
|
|
5440
|
+
request<Page<${className}>>('GET', \`/${plural}\${toListQueryString(query)}\`),
|
|
5441
|
+
|
|
5442
|
+
/**
|
|
5443
|
+
* Full-fetch escape hatch (LANDMINE 1): every ${className} across all pages as a
|
|
5444
|
+
* flat array. Pages through the envelope until exhausted so off-page FK
|
|
5445
|
+
* resolution (resolvers/lookups) stays correct under pagination-by-default \u2014
|
|
5446
|
+
* the backing collection only holds the current page, so FK targets that live
|
|
5447
|
+
* on another page would otherwise resolve to undefined. Used to hydrate the
|
|
5448
|
+
* store's resolvers/lookups; NOT for rendering a paged table.
|
|
5449
|
+
*/
|
|
5450
|
+
listAll: async (query?: Omit<ListQuery, 'page' | 'pageSize' | 'cursor'>): Promise<${className}[]> => {
|
|
5451
|
+
const all: ${className}[] = [];
|
|
5452
|
+
let page = 1;
|
|
5453
|
+
let pageCount = 1;
|
|
5454
|
+
do {
|
|
5455
|
+
const result = await request<Page<${className}>>(
|
|
5456
|
+
'GET',
|
|
5457
|
+
\`/${plural}\${toListQueryString({ ...query, page, pageSize: MAX_PAGE_SIZE })}\`,
|
|
5458
|
+
);
|
|
5459
|
+
all.push(...result.items);
|
|
5460
|
+
pageCount = result.pageCount;
|
|
5461
|
+
page += 1;
|
|
5462
|
+
} while (page <= pageCount);
|
|
5463
|
+
return all;
|
|
5464
|
+
},
|
|
5385
5465
|
|
|
5386
5466
|
get: (id: string): Promise<${className}> =>
|
|
5387
5467
|
request<${className}>('GET', \`/${plural}/\${id}\`),
|
|
@@ -5513,7 +5593,16 @@ export const ${camelName}Collection = createCollection(
|
|
|
5513
5593
|
id: '${plural}',
|
|
5514
5594
|
queryKey: ['${plural}'],
|
|
5515
5595
|
queryClient,
|
|
5516
|
-
|
|
5596
|
+
// pagination-by-default: the list endpoint returns a Page<T> envelope, so
|
|
5597
|
+
// unwrap \`.items\` to seed the collection with rows (the first page). The
|
|
5598
|
+
// paged table drives later fetches via the sync-layer useList; off-page FK
|
|
5599
|
+
// resolution hydrates from the full-fetch escape hatch (store/resolvers.ts).
|
|
5600
|
+
// async/await (not \`.then\`) so queryCollectionOptions' overload inference
|
|
5601
|
+
// keeps \`getKey\`'s \`item\` typed \u2014 the .then() form collapses it to unknown.
|
|
5602
|
+
queryFn: async () => {
|
|
5603
|
+
const page = await ${camelName}Api.list();
|
|
5604
|
+
return page.items;
|
|
5605
|
+
},
|
|
5517
5606
|
getKey: (item) => item.id,
|
|
5518
5607
|
schema: ${camelName}Schema,
|
|
5519
5608
|
}),
|
|
@@ -5644,21 +5733,33 @@ function buildStoreIndexFile(ctx) {
|
|
|
5644
5733
|
const entities = sortEntities(ctx.entities);
|
|
5645
5734
|
const hookImports = entities.map((e) => `import { ${e.camelName}Hooks } from '../entities/${e.name}';`).join("\n");
|
|
5646
5735
|
const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
|
|
5736
|
+
const fieldsImports = entities.map((e) => `import { ${e.camelName}Fields } from '../fields/${e.name}';`).join("\n");
|
|
5647
5737
|
const entityEntries = entities.map((e) => ` ${e.plural}: ${e.camelName}Hooks,`).join("\n");
|
|
5648
5738
|
const collectionEntries = entities.map((e) => ` ${e.plural}: ${e.camelName}Collection,`).join("\n");
|
|
5739
|
+
const fieldsEntries = entities.map((e) => ` ${e.plural}: ${e.camelName}Fields,`).join("\n");
|
|
5649
5740
|
const body = `import { createStore } from '@pattern-stack/frontend-patterns';
|
|
5650
5741
|
|
|
5651
5742
|
${hookImports}
|
|
5652
5743
|
|
|
5653
5744
|
${collectionImports}
|
|
5654
5745
|
|
|
5746
|
+
${fieldsImports}
|
|
5747
|
+
|
|
5748
|
+
import { createLookups } from './lookups';
|
|
5749
|
+
|
|
5655
5750
|
/**
|
|
5656
5751
|
* The application store \u2014 unified access to every entity.
|
|
5657
5752
|
*
|
|
5658
|
-
* Entities and
|
|
5753
|
+
* Entities, collections, and field metadata are keyed by their plural name:
|
|
5754
|
+
* store.${entities[0]?.plural ?? "things"}.useData() // useList + fields[plural] meta + hydrated lookups
|
|
5659
5755
|
* store.${entities[0]?.plural ?? "things"}.useList()
|
|
5660
5756
|
* store.resolve.<entity>(id)
|
|
5661
|
-
* store.lookups.
|
|
5757
|
+
* store.lookups.current
|
|
5758
|
+
*
|
|
5759
|
+
* \`fields\` carries the generated FieldMeta (\`fields/<entity>\` \u2192 \`<camel>Fields\`);
|
|
5760
|
+
* \`lookups\` is the generated lookups engine (\`{ hydrate(): Promise<EntityLookups>;
|
|
5761
|
+
* current }\`) \u2014 hydrated once so off-page FK resolution stays correct under
|
|
5762
|
+
* pagination-by-default. Both bind \`store.<entity>.useData()\`.
|
|
5662
5763
|
*/
|
|
5663
5764
|
export const store = createStore({
|
|
5664
5765
|
entities: {
|
|
@@ -5667,6 +5768,10 @@ ${entityEntries}
|
|
|
5667
5768
|
collections: {
|
|
5668
5769
|
${collectionEntries}
|
|
5669
5770
|
},
|
|
5771
|
+
fields: {
|
|
5772
|
+
${fieldsEntries}
|
|
5773
|
+
},
|
|
5774
|
+
lookups: createLookups(),
|
|
5670
5775
|
});
|
|
5671
5776
|
|
|
5672
5777
|
/** Store type for the \`useStore\` hook. */
|
|
@@ -5677,6 +5782,7 @@ export type AppStore = typeof store;
|
|
|
5677
5782
|
function buildResolversFile(ctx) {
|
|
5678
5783
|
const entities = sortEntities(ctx.entities);
|
|
5679
5784
|
const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
|
|
5785
|
+
const apiImports = entities.map((e) => `import { ${e.camelName}Api } from '../api/${e.name}';`).join("\n");
|
|
5680
5786
|
const typeImports = entities.map((e) => `import type { ${e.className} } from '${ctx.config.dbEntitiesImport}/${e.name}';`).join("\n");
|
|
5681
5787
|
const resolverIface = entities.map(
|
|
5682
5788
|
(e) => ` ${e.camelName}: (id: string | null | undefined) => ${e.className} | undefined;`
|
|
@@ -5684,9 +5790,16 @@ function buildResolversFile(ctx) {
|
|
|
5684
5790
|
const resolverImpls = entities.map(
|
|
5685
5791
|
(e) => ` ${e.camelName}: (id) => {
|
|
5686
5792
|
if (!id) return undefined;
|
|
5687
|
-
return ${e.camelName}Collection.state.get(id)
|
|
5793
|
+
return (${e.camelName}Collection.state.get(id) ??
|
|
5794
|
+
hydrationCache.${e.camelName}.get(id)) as ${e.className} | undefined;
|
|
5688
5795
|
},`
|
|
5689
5796
|
).join("\n");
|
|
5797
|
+
const hydrationCacheFields = entities.map((e) => ` ${e.camelName}: new Map<string, ${e.className}>(),`).join("\n");
|
|
5798
|
+
const hydrationCalls = entities.map(
|
|
5799
|
+
(e) => ` ${e.camelName}Api.listAll().then((rows) => {
|
|
5800
|
+
hydrationCache.${e.camelName} = new Map(rows.map((r) => [r.id as string, r]));
|
|
5801
|
+
}),`
|
|
5802
|
+
).join("\n");
|
|
5690
5803
|
const refBlocks = [];
|
|
5691
5804
|
for (const e of entities) {
|
|
5692
5805
|
const rels = resolvableRels(e, ctx);
|
|
@@ -5721,11 +5834,37 @@ ${hydrateFields}
|
|
|
5721
5834
|
${refBlocks.join("\n\n")}
|
|
5722
5835
|
` : "";
|
|
5723
5836
|
const body = `${collectionImports}
|
|
5837
|
+
${apiImports}
|
|
5724
5838
|
${typeImports}
|
|
5725
5839
|
|
|
5840
|
+
/**
|
|
5841
|
+
* Full-fetch hydration cache (LANDMINE 1 escape hatch for pagination-by-default).
|
|
5842
|
+
*
|
|
5843
|
+
* The backing collections hold only the CURRENT PAGE once lists paginate, so an
|
|
5844
|
+
* FK pointing at a row on another page resolves to undefined against collection
|
|
5845
|
+
* state alone. \`hydrateResolverCache()\` fetches the COMPLETE set per entity (via
|
|
5846
|
+
* \`api.listAll()\`, which pages through the envelope) into these id\u2192entity maps;
|
|
5847
|
+
* resolvers fall back to them on a collection-state miss. Call it once on mount.
|
|
5848
|
+
*/
|
|
5849
|
+
const hydrationCache = {
|
|
5850
|
+
${hydrationCacheFields}
|
|
5851
|
+
};
|
|
5852
|
+
|
|
5853
|
+
/**
|
|
5854
|
+
* Populate the {@link hydrationCache} from the full set of every entity. Await
|
|
5855
|
+
* (or fire-and-forget) once at app start so off-page FK resolution is correct.
|
|
5856
|
+
* Idempotent \u2014 re-running refreshes every cache map.
|
|
5857
|
+
*/
|
|
5858
|
+
export async function hydrateResolverCache(): Promise<void> {
|
|
5859
|
+
await Promise.all([
|
|
5860
|
+
${hydrationCalls}
|
|
5861
|
+
]);
|
|
5862
|
+
}
|
|
5863
|
+
|
|
5726
5864
|
/**
|
|
5727
5865
|
* FK resolvers \u2014 resolve a foreign-key id to the full entity object via the
|
|
5728
|
-
* backing collection's local state (\`O(1)\` \`Map.get\`)
|
|
5866
|
+
* backing collection's local state (\`O(1)\` \`Map.get\`), falling back to the
|
|
5867
|
+
* full-fetch hydration cache for ids not on the current page.
|
|
5729
5868
|
*
|
|
5730
5869
|
* Usage:
|
|
5731
5870
|
* const ${entities[0]?.camelName ?? "thing"} = resolvers.${entities[0]?.camelName ?? "thing"}(other.${entities[0]?.camelName ?? "thing"}Id);
|
|
@@ -5734,7 +5873,7 @@ export interface Resolvers {
|
|
|
5734
5873
|
${resolverIface}
|
|
5735
5874
|
}
|
|
5736
5875
|
|
|
5737
|
-
/** Build the resolver table over the generated collections. */
|
|
5876
|
+
/** Build the resolver table over the generated collections + hydration cache. */
|
|
5738
5877
|
export function createResolvers(): Resolvers {
|
|
5739
5878
|
return {
|
|
5740
5879
|
${resolverImpls}
|
|
@@ -5746,6 +5885,7 @@ ${refsSection}`;
|
|
|
5746
5885
|
function buildLookupsFile(ctx) {
|
|
5747
5886
|
const entities = sortEntities(ctx.entities);
|
|
5748
5887
|
const collectionImports = entities.map((e) => `import { ${e.camelName}Collection } from '../collections/${e.name}';`).join("\n");
|
|
5888
|
+
const apiImports = entities.map((e) => `import { ${e.camelName}Api } from '../api/${e.name}';`).join("\n");
|
|
5749
5889
|
const typeImports = entities.map((e) => `import type { ${e.className} } from '${ctx.config.dbEntitiesImport}/${e.name}';`).join("\n");
|
|
5750
5890
|
const lookupIface = entities.map((e) => ` ${e.plural}: Map<string, ${e.className}>;`).join("\n");
|
|
5751
5891
|
const lookupBuild = entities.map(
|
|
@@ -5756,7 +5896,12 @@ function buildLookupsFile(ctx) {
|
|
|
5756
5896
|
]),
|
|
5757
5897
|
),`
|
|
5758
5898
|
).join("\n");
|
|
5899
|
+
const lookupBuildAsyncDecls = entities.map((e) => ` ${e.camelName}Api.listAll(),`).join("\n");
|
|
5900
|
+
const lookupBuildAsyncFields = entities.map(
|
|
5901
|
+
(e, i) => ` ${e.plural}: new Map(rows[${i}].map((r) => [r.id as string, r as ${e.className}])),`
|
|
5902
|
+
).join("\n");
|
|
5759
5903
|
const body = `${collectionImports}
|
|
5904
|
+
${apiImports}
|
|
5760
5905
|
${typeImports}
|
|
5761
5906
|
|
|
5762
5907
|
/** All entity lookup maps, keyed by plural entity name (id \u2192 entity). */
|
|
@@ -5764,14 +5909,33 @@ export interface EntityLookups {
|
|
|
5764
5909
|
${lookupIface}
|
|
5765
5910
|
}
|
|
5766
5911
|
|
|
5767
|
-
/** Build fresh lookup maps from current collection state. */
|
|
5912
|
+
/** Build fresh lookup maps from current collection state (current page only). */
|
|
5768
5913
|
export function buildLookups(): EntityLookups {
|
|
5769
5914
|
return {
|
|
5770
5915
|
${lookupBuild}
|
|
5771
5916
|
};
|
|
5772
5917
|
}
|
|
5773
5918
|
|
|
5774
|
-
/**
|
|
5919
|
+
/**
|
|
5920
|
+
* Build lookup maps over the COMPLETE set (LANDMINE 1 escape hatch). Pages
|
|
5921
|
+
* through every entity's list via \`api.listAll()\` so off-page rows are present.
|
|
5922
|
+
* Prefer this over {@link buildLookups} whenever a lookup must resolve ids that
|
|
5923
|
+
* may not be on the current page.
|
|
5924
|
+
*/
|
|
5925
|
+
export async function buildLookupsAsync(): Promise<EntityLookups> {
|
|
5926
|
+
const rows = await Promise.all([
|
|
5927
|
+
${lookupBuildAsyncDecls}
|
|
5928
|
+
]);
|
|
5929
|
+
return {
|
|
5930
|
+
${lookupBuildAsyncFields}
|
|
5931
|
+
};
|
|
5932
|
+
}
|
|
5933
|
+
|
|
5934
|
+
/**
|
|
5935
|
+
* Caching lookup factory: \`build()\` (re)computes from collection state,
|
|
5936
|
+
* \`hydrate()\` (re)computes from the full-fetch escape hatch, \`current\` reads,
|
|
5937
|
+
* \`clear()\` resets.
|
|
5938
|
+
*/
|
|
5775
5939
|
export function createLookups() {
|
|
5776
5940
|
let cache: EntityLookups | null = null;
|
|
5777
5941
|
return {
|
|
@@ -5779,6 +5943,10 @@ export function createLookups() {
|
|
|
5779
5943
|
cache = buildLookups();
|
|
5780
5944
|
return cache;
|
|
5781
5945
|
},
|
|
5946
|
+
hydrate: async (): Promise<EntityLookups> => {
|
|
5947
|
+
cache = await buildLookupsAsync();
|
|
5948
|
+
return cache;
|
|
5949
|
+
},
|
|
5782
5950
|
get current(): EntityLookups | null {
|
|
5783
5951
|
return cache;
|
|
5784
5952
|
},
|
|
@@ -5794,8 +5962,8 @@ function buildStoreModuleIndexFile(ctx) {
|
|
|
5794
5962
|
const entities = sortEntities(ctx.entities);
|
|
5795
5963
|
const lines = [
|
|
5796
5964
|
"export { store, type AppStore } from './index';",
|
|
5797
|
-
"export { createResolvers, type Resolvers } from './resolvers';",
|
|
5798
|
-
"export { buildLookups, createLookups, type EntityLookups } from './lookups';"
|
|
5965
|
+
"export { createResolvers, hydrateResolverCache, type Resolvers } from './resolvers';",
|
|
5966
|
+
"export { buildLookups, buildLookupsAsync, createLookups, type EntityLookups } from './lookups';"
|
|
5799
5967
|
];
|
|
5800
5968
|
const refExports = entities.filter((e) => resolvableRels(e, ctx).length > 0).map(
|
|
5801
5969
|
(e) => `export { resolve${e.className}Refs, type ${e.className}Refs } from './resolvers';`
|
|
@@ -9402,6 +9570,13 @@ var VENDORED_RUNTIME_FILES = [
|
|
|
9402
9570
|
// Pipes — ZodValidationPipe is wired on every generated controller
|
|
9403
9571
|
// @Body() to give runtime Zod validation at the controller boundary.
|
|
9404
9572
|
{ runtime: "pipes/zod-validation.pipe.ts", target: "src/shared/pipes/zod-validation.pipe.ts" },
|
|
9573
|
+
// Pagination-by-default (Page<T> envelope, ListQuerySchema, resolveListQuery,
|
|
9574
|
+
// buildPage, opaque cursor codec) — imported by EVERY generated list
|
|
9575
|
+
// controller/dto/use-case. Vendored to `src/shared/http/page.ts` (alias
|
|
9576
|
+
// `@shared/http/page`), DISTINCT from the consumer's optional `@shared/http/
|
|
9577
|
+
// pagination` search contract so the two never collide. Package mode resolves
|
|
9578
|
+
// the same source via `@pattern-stack/codegen/runtime/http/pagination`.
|
|
9579
|
+
{ runtime: "http/pagination.ts", target: "src/shared/http/page.ts" },
|
|
9405
9580
|
// EAV helpers — referenced by generated services on `eav_value_table` entities
|
|
9406
9581
|
{ runtime: "eav-helpers.ts", target: "src/shared/eav-helpers.ts" },
|
|
9407
9582
|
// OpenAPI registry (OPENAPI-1/2) — generated modules register Zod DTOs
|