@entifix/next-shell 0.4.1 → 0.5.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/lib/command-palette/use-use-cases-source.d.ts +17 -4
- package/dist/lib/command-palette/use-use-cases-source.d.ts.map +1 -1
- package/dist/lib/command-palette/use-use-cases-source.js +5 -1
- package/dist/lib/command-palette/use-use-cases-source.js.map +1 -1
- package/dist/lib/config/config-route.d.ts +6 -19
- package/dist/lib/config/config-route.d.ts.map +1 -1
- package/dist/lib/config/config-route.js +6 -23
- package/dist/lib/config/config-route.js.map +1 -1
- package/dist/lib/config/configuration-source.d.ts +18 -0
- package/dist/lib/config/configuration-source.d.ts.map +1 -0
- package/dist/lib/config/configuration-source.js +15 -0
- package/dist/lib/config/configuration-source.js.map +1 -0
- package/dist/lib/crud/make-entity-crud.d.ts +7 -9
- package/dist/lib/crud/make-entity-crud.d.ts.map +1 -1
- package/dist/lib/crud/make-entity-crud.js +18 -12
- package/dist/lib/crud/make-entity-crud.js.map +1 -1
- package/dist/lib/crud/make-entity-crud.types.d.ts +12 -2
- package/dist/lib/crud/make-entity-crud.types.d.ts.map +1 -1
- package/dist/lib/crud/make-entity-crud.types.js.map +1 -1
- package/dist/lib/health/health-routes.d.ts +3 -4
- package/dist/lib/health/health-routes.d.ts.map +1 -1
- package/dist/lib/health/health-routes.js +7 -9
- package/dist/lib/health/health-routes.js.map +1 -1
- package/dist/lib/i18n/catalog/en.d.ts.map +1 -1
- package/dist/lib/i18n/catalog/en.js +0 -145
- package/dist/lib/i18n/catalog/en.js.map +1 -1
- package/dist/lib/i18n/catalog/es.d.ts +0 -159
- package/dist/lib/i18n/catalog/es.d.ts.map +1 -1
- package/dist/lib/i18n/catalog/es.js +0 -160
- package/dist/lib/i18n/catalog/es.js.map +1 -1
- package/dist/lib/i18n/catalog/index.d.ts +0 -282
- package/dist/lib/i18n/catalog/index.d.ts.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/package.json +9 -9
- package/dist/lib/config/service-token.d.ts +0 -5
- package/dist/lib/config/service-token.d.ts.map +0 -1
- package/dist/lib/config/service-token.js +0 -18
- package/dist/lib/config/service-token.js.map +0 -1
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import { type CommandSource, type Entity, type EntityConstructor, type EntityMetadataSource } from '@entifix/core';
|
|
2
2
|
/** How a declared verb is actually carried out, keyed by the verb. */
|
|
3
3
|
export type UseCaseHandlers = Readonly<Record<string, () => Promise<void>>>;
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* One entity whose served affordances contribute palette commands.
|
|
6
|
+
*
|
|
7
|
+
* The source comes one of two ways, and exactly one: built and handed over, or
|
|
8
|
+
* read from the host's composition by a hook — typically
|
|
9
|
+
* `() => useHostAdapters().userMetadata` — so the module declaring the entries
|
|
10
|
+
* need not import the transport that fetches.
|
|
11
|
+
*/
|
|
12
|
+
export type UseCaseCommandEntity<TEntity extends Entity = Entity> = {
|
|
6
13
|
readonly entityConstructor: EntityConstructor<TEntity>;
|
|
14
|
+
readonly handlers: UseCaseHandlers;
|
|
15
|
+
} & ({
|
|
7
16
|
/** Where `GET /api/<entity>/$metadata` is reached. */
|
|
8
17
|
readonly metadataSource: EntityMetadataSource;
|
|
9
|
-
readonly
|
|
10
|
-
}
|
|
18
|
+
readonly useMetadataSource?: never;
|
|
19
|
+
} | {
|
|
20
|
+
/** Resolves the source from composition. Called as a hook, once per render. */
|
|
21
|
+
readonly useMetadataSource: () => EntityMetadataSource;
|
|
22
|
+
readonly metadataSource?: never;
|
|
23
|
+
});
|
|
11
24
|
/**
|
|
12
25
|
* Every declared entity's palette verbs, as one source per entity.
|
|
13
26
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-use-cases-source.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/use-use-cases-source.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAI1B,MAAM,eAAe,CAAC;AASvB,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAE5E
|
|
1
|
+
{"version":3,"file":"use-use-cases-source.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/use-use-cases-source.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAI1B,MAAM,eAAe,CAAC;AASvB,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAE5E;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI;IAClE,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACvD,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC,GAAG,CACA;IACE,sDAAsD;IACtD,QAAQ,CAAC,cAAc,EAAE,oBAAoB,CAAC;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,KAAK,CAAC;CACpC,GACD;IACE,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;IACvD,QAAQ,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC;CACjC,CACJ,CAAC;AAwFF;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS,oBAAoB,EAAE,EACzC,IAAI,EAAE,MAAM,GACX,aAAa,EAAE,CAOjB"}
|
|
@@ -23,9 +23,13 @@ import { useMemo } from 'react';
|
|
|
23
23
|
* missing wire, and it is the same posture `assertSearchable` and `surfaceFor`
|
|
24
24
|
* already take.
|
|
25
25
|
*/ function useUseCasesSource(entity, term) {
|
|
26
|
+
var _entity_useMetadataSource;
|
|
26
27
|
const t = useT('shell');
|
|
27
28
|
const translateKey = useTranslateKey();
|
|
28
|
-
|
|
29
|
+
// The entries are module-scope, so which branch an entity takes never changes
|
|
30
|
+
// between renders and the hook count stays fixed.
|
|
31
|
+
const useSource = (_entity_useMetadataSource = entity.useMetadataSource) != null ? _entity_useMetadataSource : ()=>entity.metadataSource;
|
|
32
|
+
const { metadata, isLoading } = useEntityUseCases(entity.entityConstructor, useSource());
|
|
29
33
|
// Outside the memo: `useCasesForSurface` is a plain filter, but the name reads
|
|
30
34
|
// as a hook to `react-hooks/rules-of-hooks`, which then refuses it inside a
|
|
31
35
|
// callback. Hoisting is free — it is pure and cheap.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/command-palette/use-use-cases-source.ts"],"sourcesContent":["'use client';\n\nimport {\n type CommandOption,\n type CommandSource,\n EntifixLogicError,\n type Entity,\n type EntityConstructor,\n type EntityMetadataSource,\n envelopeEntityName,\n matchesCommand,\n parseKeywords,\n} from '@entifix/core';\nimport {\n useCasesForSurface,\n useT,\n useTranslateKey,\n} from '@entifix/react-controls';\nimport { useEntityUseCases } from '@entifix/react-integration';\nimport { useMemo } from 'react';\n\n/** How a declared verb is actually carried out, keyed by the verb. */\nexport type UseCaseHandlers = Readonly<Record<string, () => Promise<void>>>;\n\n
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/command-palette/use-use-cases-source.ts"],"sourcesContent":["'use client';\n\nimport {\n type CommandOption,\n type CommandSource,\n EntifixLogicError,\n type Entity,\n type EntityConstructor,\n type EntityMetadataSource,\n envelopeEntityName,\n matchesCommand,\n parseKeywords,\n} from '@entifix/core';\nimport {\n useCasesForSurface,\n useT,\n useTranslateKey,\n} from '@entifix/react-controls';\nimport { useEntityUseCases } from '@entifix/react-integration';\nimport { useMemo } from 'react';\n\n/** How a declared verb is actually carried out, keyed by the verb. */\nexport type UseCaseHandlers = Readonly<Record<string, () => Promise<void>>>;\n\n/**\n * One entity whose served affordances contribute palette commands.\n *\n * The source comes one of two ways, and exactly one: built and handed over, or\n * read from the host's composition by a hook — typically\n * `() => useHostAdapters().userMetadata` — so the module declaring the entries\n * need not import the transport that fetches.\n */\nexport type UseCaseCommandEntity<TEntity extends Entity = Entity> = {\n readonly entityConstructor: EntityConstructor<TEntity>;\n readonly handlers: UseCaseHandlers;\n} & (\n | {\n /** Where `GET /api/<entity>/$metadata` is reached. */\n readonly metadataSource: EntityMetadataSource;\n readonly useMetadataSource?: never;\n }\n | {\n /** Resolves the source from composition. Called as a hook, once per render. */\n readonly useMetadataSource: () => EntityMetadataSource;\n readonly metadataSource?: never;\n }\n);\n\n/**\n * The `unbound` verbs this caller may invoke, as one group.\n *\n * This is the surface ADR 0035 mapped and nothing rendered: all three\n * `unbound:*` cells resolve to `'command-palette'`, and until now the palette\n * did not exist, so a verb declared there was granted, exported, passed every\n * `@r10c/slices` invariant and simply never appeared.\n *\n * The descriptors are **served, not imported**: `$metadata` filters them through\n * `PolicyDecisionTag` against the verified principal, which is a stronger check\n * than anything the browser could do — and it keeps every `@useCase()` class's\n * `Effect` body, repository tags and import closure out of the client bundle.\n *\n * ⚠️ A descriptor with **no handler throws**, at the first render of the\n * palette rather than on the click. A verb that appears and does nothing is\n * worse than one that is absent: it reads as a broken feature rather than a\n * missing wire, and it is the same posture `assertSearchable` and `surfaceFor`\n * already take.\n */\nfunction useUseCasesSource(\n entity: UseCaseCommandEntity,\n term: string,\n): CommandSource {\n const t = useT('shell');\n const translateKey = useTranslateKey();\n // The entries are module-scope, so which branch an entity takes never changes\n // between renders and the hook count stays fixed.\n const useSource = entity.useMetadataSource ?? (() => entity.metadataSource);\n const { metadata, isLoading } = useEntityUseCases(\n entity.entityConstructor,\n useSource(),\n );\n\n // Outside the memo: `useCasesForSurface` is a plain filter, but the name reads\n // as a hook to `react-hooks/rules-of-hooks`, which then refuses it inside a\n // callback. Hoisting is free — it is pure and cheap.\n const descriptors = useCasesForSurface('command-palette', metadata?.useCases);\n\n return useMemo(() => {\n const name = envelopeEntityName(entity.entityConstructor);\n\n const options: CommandOption[] = descriptors.map(descriptor => {\n const run = entity.handlers[descriptor.key];\n if (run === undefined) {\n throw new EntifixLogicError(\n `The use case \"${descriptor.key}\" on ${name} reaches the command palette but this host registered no handler for it. ` +\n 'Add one to the entity’s `handlers`, or stop declaring the verb `unbound`.',\n undefined,\n { entity: name, key: descriptor.key },\n );\n }\n\n return {\n id: `use-case:${name}:${descriptor.key}`,\n label: translateKey(descriptor.labelKey),\n keywords: parseKeywords(\n descriptor.keywordsKey === undefined\n ? undefined\n : translateKey(descriptor.keywordsKey),\n ),\n run,\n ...(descriptor.confirm === undefined\n ? {}\n : {\n confirm: {\n tone: descriptor.confirm.tone,\n message: translateKey(descriptor.confirm.messageKey),\n },\n }),\n } satisfies CommandOption;\n });\n\n return {\n key: `use-cases:${name}`,\n groups: [\n {\n key: `use-cases:${name}`,\n label: t('commandPalette.groups.actions'),\n options: options.filter(option => matchesCommand(term, option)),\n isLoading,\n },\n ],\n } satisfies CommandSource;\n }, [entity, descriptors, isLoading, term, t, translateKey]);\n}\n\n/**\n * Every declared entity's palette verbs, as one source per entity.\n *\n * **Why the rule is disabled below.** `entities` is declared once at module\n * scope by the host and closed over, so it is the same array on every render and\n * the hook count is fixed — the invariant `react-hooks/rules-of-hooks` protects\n * and cannot see, because it reasons about the loop syntactically rather than\n * about the array's provenance. It is isolated here so the disable is written\n * once, with its reason, exactly as `useEntityLinkSources` does for pickers.\n */\nexport function useUseCaseSources(\n entities: readonly UseCaseCommandEntity[],\n term: string,\n): CommandSource[] {\n const sources: CommandSource[] = [];\n for (const entity of entities) {\n // eslint-disable-next-line react-hooks/rules-of-hooks -- fixed-length loop over a module-scope array; see the note above.\n sources.push(useUseCasesSource(entity, term));\n }\n return sources;\n}\n"],"names":["EntifixLogicError","envelopeEntityName","matchesCommand","parseKeywords","useCasesForSurface","useT","useTranslateKey","useEntityUseCases","useMemo","useUseCasesSource","entity","term","t","translateKey","useSource","useMetadataSource","metadataSource","metadata","isLoading","entityConstructor","descriptors","useCases","name","options","map","descriptor","run","handlers","key","undefined","id","label","labelKey","keywords","keywordsKey","confirm","tone","message","messageKey","groups","filter","option","useUseCaseSources","entities","sources","push"],"mappings":"AAAA;;AAEA,SAGEA,iBAAiB,EAIjBC,kBAAkB,EAClBC,cAAc,EACdC,aAAa,QACR,gBAAgB;AACvB,SACEC,kBAAkB,EAClBC,IAAI,EACJC,eAAe,QACV,0BAA0B;AACjC,SAASC,iBAAiB,QAAQ,6BAA6B;AAC/D,SAASC,OAAO,QAAQ,QAAQ;AA6BhC;;;;;;;;;;;;;;;;;;CAkBC,GACD,SAASC,kBACPC,MAA4B,EAC5BC,IAAY;QAMMD;IAJlB,MAAME,IAAIP,KAAK;IACf,MAAMQ,eAAeP;IACrB,8EAA8E;IAC9E,kDAAkD;IAClD,MAAMQ,aAAYJ,4BAAAA,OAAOK,iBAAiB,YAAxBL,4BAA6B,IAAMA,OAAOM,cAAc;IAC1E,MAAM,EAAEC,QAAQ,EAAEC,SAAS,EAAE,GAAGX,kBAC9BG,OAAOS,iBAAiB,EACxBL;IAGF,+EAA+E;IAC/E,4EAA4E;IAC5E,qDAAqD;IACrD,MAAMM,cAAchB,mBAAmB,mBAAmBa,4BAAAA,SAAUI,QAAQ;IAE5E,OAAOb,QAAQ;QACb,MAAMc,OAAOrB,mBAAmBS,OAAOS,iBAAiB;QAExD,MAAMI,UAA2BH,YAAYI,GAAG,CAACC,CAAAA;YAC/C,MAAMC,MAAMhB,OAAOiB,QAAQ,CAACF,WAAWG,GAAG,CAAC;YAC3C,IAAIF,QAAQG,WAAW;gBACrB,MAAM,IAAI7B,kBACR,CAAC,cAAc,EAAEyB,WAAWG,GAAG,CAAC,KAAK,EAAEN,KAAK,yEAAyE,CAAC,GACpH,6EACFO,WACA;oBAAEnB,QAAQY;oBAAMM,KAAKH,WAAWG,GAAG;gBAAC;YAExC;YAEA,OAAO;gBACLE,IAAI,CAAC,SAAS,EAAER,KAAK,CAAC,EAAEG,WAAWG,GAAG,EAAE;gBACxCG,OAAOlB,aAAaY,WAAWO,QAAQ;gBACvCC,UAAU9B,cACRsB,WAAWS,WAAW,KAAKL,YACvBA,YACAhB,aAAaY,WAAWS,WAAW;gBAEzCR;eACID,WAAWU,OAAO,KAAKN,YACvB,CAAC,IACD;gBACEM,SAAS;oBACPC,MAAMX,WAAWU,OAAO,CAACC,IAAI;oBAC7BC,SAASxB,aAAaY,WAAWU,OAAO,CAACG,UAAU;gBACrD;YACF;QAER;QAEA,OAAO;YACLV,KAAK,CAAC,UAAU,EAAEN,MAAM;YACxBiB,QAAQ;gBACN;oBACEX,KAAK,CAAC,UAAU,EAAEN,MAAM;oBACxBS,OAAOnB,EAAE;oBACTW,SAASA,QAAQiB,MAAM,CAACC,CAAAA,SAAUvC,eAAeS,MAAM8B;oBACvDvB;gBACF;aACD;QACH;IACF,GAAG;QAACR;QAAQU;QAAaF;QAAWP;QAAMC;QAAGC;KAAa;AAC5D;AAEA;;;;;;;;;CASC,GACD,OAAO,SAAS6B,kBACdC,QAAyC,EACzChC,IAAY;IAEZ,MAAMiC,UAA2B,EAAE;IACnC,KAAK,MAAMlC,UAAUiC,SAAU;QAC7B,0HAA0H;QAC1HC,QAAQC,IAAI,CAACpC,kBAAkBC,QAAQC;IACzC;IACA,OAAOiC;AACT"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConfigurationSource } from './configuration-source.js';
|
|
1
2
|
/** A group of parameters as `ConfigurationPlain` carries them. */
|
|
2
3
|
type PlainConfiguration = Record<string, Array<{
|
|
3
4
|
key: string;
|
|
@@ -21,31 +22,17 @@ type PlainConfiguration = Record<string, Array<{
|
|
|
21
22
|
* domains it actually proxies.
|
|
22
23
|
*/
|
|
23
24
|
export declare const rewriteServiceDomains: (plain: PlainConfiguration, map: Readonly<Record<string, string>>) => PlainConfiguration;
|
|
24
|
-
/**
|
|
25
|
-
* Where config-service answers, for the callers that reach it directly.
|
|
26
|
-
*
|
|
27
|
-
* One declaration rather than two: this route is no longer the only server-side
|
|
28
|
-
* caller — the storefront's server components read their own configuration from
|
|
29
|
-
* config-service without a route in between, because a Next app cannot fetch
|
|
30
|
-
* its own `/api/config` while prerendering itself.
|
|
31
|
-
*/
|
|
32
|
-
export declare const configApiUrl: () => string;
|
|
33
25
|
export interface ConfigRouteOptions {
|
|
34
|
-
/**
|
|
35
|
-
readonly
|
|
26
|
+
/** Where this app's configuration is fetched from, and with what headers. */
|
|
27
|
+
readonly source: ConfigurationSource;
|
|
36
28
|
/** Service domains to rewrite to same-origin proxy paths. */
|
|
37
29
|
readonly proxies?: Readonly<Record<string, string>>;
|
|
38
|
-
/** Overrides `CONFIG_API_URL`; mainly for tests. */
|
|
39
|
-
readonly configApiUrl?: string;
|
|
40
30
|
}
|
|
41
31
|
/**
|
|
42
32
|
* Builds the `GET /api/config` route handler every Next app exposes: fetch this
|
|
43
|
-
* app's configuration from
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* Shared because all three apps did the same thing, and because the shared-token
|
|
47
|
-
* header is the kind of detail that goes stale in one copy out of three.
|
|
33
|
+
* app's configuration from its source, rewrite the proxied domains, and hand the
|
|
34
|
+
* result to the browser.
|
|
48
35
|
*/
|
|
49
|
-
export declare const createConfigRoute: ({
|
|
36
|
+
export declare const createConfigRoute: ({ source, proxies, }: ConfigRouteOptions) => () => Promise<Response>;
|
|
50
37
|
export {};
|
|
51
38
|
//# sourceMappingURL=config-route.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-route.d.ts","sourceRoot":"","sources":["../../../src/lib/config/config-route.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config-route.d.ts","sourceRoot":"","sources":["../../../src/lib/config/config-route.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,kEAAkE;AAClE,KAAK,kBAAkB,GAAG,MAAM,CAC9B,MAAM,EACN,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CACvC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,kBAAkB,EACzB,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KACpC,kBAOD,CAAC;AAEH,MAAM,WAAW,kBAAkB;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,sBAG/B,kBAAkB,WACG,OAAO,CAAC,QAAQ,CAerC,CAAC"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
2
|
import { NextResponse } from 'next/server';
|
|
3
|
-
import { SERVICE_TOKEN_HEADER, serviceToken } from './service-token.js';
|
|
4
3
|
/**
|
|
5
4
|
* Rewrites backend addresses to the app's own same-origin proxy paths.
|
|
6
5
|
*
|
|
@@ -25,31 +24,15 @@ import { SERVICE_TOKEN_HEADER, serviceToken } from './service-token.js';
|
|
|
25
24
|
}) : parameter)
|
|
26
25
|
});
|
|
27
26
|
};
|
|
28
|
-
/**
|
|
29
|
-
* Where config-service answers, for the callers that reach it directly.
|
|
30
|
-
*
|
|
31
|
-
* One declaration rather than two: this route is no longer the only server-side
|
|
32
|
-
* caller — the storefront's server components read their own configuration from
|
|
33
|
-
* config-service without a route in between, because a Next app cannot fetch
|
|
34
|
-
* its own `/api/config` while prerendering itself.
|
|
35
|
-
*/ export const configApiUrl = ()=>{
|
|
36
|
-
var _process_env_CONFIG_API_URL;
|
|
37
|
-
return (_process_env_CONFIG_API_URL = process.env['CONFIG_API_URL']) != null ? _process_env_CONFIG_API_URL : 'http://localhost:3190';
|
|
38
|
-
};
|
|
39
27
|
/**
|
|
40
28
|
* Builds the `GET /api/config` route handler every Next app exposes: fetch this
|
|
41
|
-
* app's configuration from
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*/ export const createConfigRoute = ({ service, proxies = {}, configApiUrl: configApiUrlOverride })=>async function GET() {
|
|
47
|
-
const baseUrl = configApiUrlOverride != null ? configApiUrlOverride : configApiUrl();
|
|
48
|
-
const response = await fetch(`${baseUrl}/api/config/${service}`, {
|
|
29
|
+
* app's configuration from its source, rewrite the proxied domains, and hand the
|
|
30
|
+
* result to the browser.
|
|
31
|
+
*/ export const createConfigRoute = ({ source, proxies = {} })=>async function GET() {
|
|
32
|
+
const { url, headers } = source();
|
|
33
|
+
const response = await fetch(url, {
|
|
49
34
|
cache: 'no-store',
|
|
50
|
-
headers
|
|
51
|
-
[SERVICE_TOKEN_HEADER]: serviceToken()
|
|
52
|
-
}
|
|
35
|
+
headers
|
|
53
36
|
});
|
|
54
37
|
if (!response.ok) {
|
|
55
38
|
return NextResponse.json({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/config/config-route.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/config/config-route.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\n\nimport type { ConfigurationSource } from './configuration-source.js';\n\n/** A group of parameters as `ConfigurationPlain` carries them. */\ntype PlainConfiguration = Record<\n string,\n Array<{ key: string; value: unknown }>\n>;\n\n/**\n * Rewrites backend addresses to the app's own same-origin proxy paths.\n *\n * config-service holds the real address, which is what the *server* needs. A\n * browser pointed at it would be making a cross-origin request that carries no\n * session cookie and gets a `401` — `entifix_at` is httpOnly and same-origin, and\n * host-scoping it does not help, since that governs which host stores the cookie\n * rather than which cross-origin requests send it.\n *\n * So each service domain is swapped for a path on this app's own origin before\n * the browser ever sees it, which leaves the entity adapters unaware that a proxy\n * exists at all. `map` is keyed by the configuration key, e.g.\n * `{ 'config-service-domain': '/api/system' }`.\n *\n * Keys absent from `map` are passed through untouched, so an app only names the\n * domains it actually proxies.\n */\nexport const rewriteServiceDomains = (\n plain: PlainConfiguration,\n map: Readonly<Record<string, string>>,\n): PlainConfiguration => ({\n ...plain,\n uri: (plain['uri'] ?? []).map(parameter =>\n parameter.key in map\n ? { ...parameter, value: map[parameter.key] }\n : parameter,\n ),\n});\n\nexport interface ConfigRouteOptions {\n /** Where this app's configuration is fetched from, and with what headers. */\n readonly source: ConfigurationSource;\n /** Service domains to rewrite to same-origin proxy paths. */\n readonly proxies?: Readonly<Record<string, string>>;\n}\n\n/**\n * Builds the `GET /api/config` route handler every Next app exposes: fetch this\n * app's configuration from its source, rewrite the proxied domains, and hand the\n * result to the browser.\n */\nexport const createConfigRoute = ({\n source,\n proxies = {},\n}: ConfigRouteOptions) =>\n async function GET(): Promise<Response> {\n const { url, headers } = source();\n\n const response = await fetch(url, { cache: 'no-store', headers });\n\n if (!response.ok) {\n return NextResponse.json(\n { error: 'Failed to load configuration' },\n { status: 502 },\n );\n }\n\n const plain = (await response.json()) as PlainConfiguration;\n\n return NextResponse.json(rewriteServiceDomains(plain, proxies));\n };\n"],"names":["NextResponse","rewriteServiceDomains","plain","map","uri","parameter","key","value","createConfigRoute","source","proxies","GET","url","headers","response","fetch","cache","ok","json","error","status"],"mappings":";AAAA,SAASA,YAAY,QAAQ,cAAc;AAU3C;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMC,wBAAwB,CACnCC,OACAC;QAGMD;WAFkB,aACrBA;QACHE,KAAK,EAACF,aAAAA,KAAK,CAAC,MAAM,YAAZA,aAAgB,EAAE,EAAEC,GAAG,CAACE,CAAAA,YAC5BA,UAAUC,GAAG,IAAIH,MACb,aAAKE;gBAAWE,OAAOJ,GAAG,CAACE,UAAUC,GAAG,CAAC;iBACzCD;;EAEL;AASH;;;;CAIC,GACD,OAAO,MAAMG,oBAAoB,CAAC,EAChCC,MAAM,EACNC,UAAU,CAAC,CAAC,EACO,GACnB,eAAeC;QACb,MAAM,EAAEC,GAAG,EAAEC,OAAO,EAAE,GAAGJ;QAEzB,MAAMK,WAAW,MAAMC,MAAMH,KAAK;YAAEI,OAAO;YAAYH;QAAQ;QAE/D,IAAI,CAACC,SAASG,EAAE,EAAE;YAChB,OAAOjB,aAAakB,IAAI,CACtB;gBAAEC,OAAO;YAA+B,GACxC;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMlB,QAAS,MAAMY,SAASI,IAAI;QAElC,OAAOlB,aAAakB,IAAI,CAACjB,sBAAsBC,OAAOQ;IACxD,EAAE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an app's configuration is fetched from on the server, and what the
|
|
3
|
+
* request carries.
|
|
4
|
+
*
|
|
5
|
+
* Both halves are the host's: the URL a configuration service answers on and
|
|
6
|
+
* the credential it asks for are that deployment's protocol, so the routes here
|
|
7
|
+
* take them as a value rather than composing either. A function rather than an
|
|
8
|
+
* object so a rotated secret — or a test that sets one — is read on each
|
|
9
|
+
* request.
|
|
10
|
+
*
|
|
11
|
+
* Server-only: the headers usually carry a secret, and nothing here is
|
|
12
|
+
* exported from the client entry.
|
|
13
|
+
*/
|
|
14
|
+
export type ConfigurationSource = () => {
|
|
15
|
+
readonly url: string;
|
|
16
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=configuration-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration-source.d.ts","sourceRoot":"","sources":["../../../src/lib/config/configuration-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where an app's configuration is fetched from on the server, and what the
|
|
3
|
+
* request carries.
|
|
4
|
+
*
|
|
5
|
+
* Both halves are the host's: the URL a configuration service answers on and
|
|
6
|
+
* the credential it asks for are that deployment's protocol, so the routes here
|
|
7
|
+
* take them as a value rather than composing either. A function rather than an
|
|
8
|
+
* object so a rotated secret — or a test that sets one — is read on each
|
|
9
|
+
* request.
|
|
10
|
+
*
|
|
11
|
+
* Server-only: the headers usually carry a secret, and nothing here is
|
|
12
|
+
* exported from the client entry.
|
|
13
|
+
*/ export { };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=configuration-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/config/configuration-source.ts"],"sourcesContent":["/**\n * Where an app's configuration is fetched from on the server, and what the\n * request carries.\n *\n * Both halves are the host's: the URL a configuration service answers on and\n * the credential it asks for are that deployment's protocol, so the routes here\n * take them as a value rather than composing either. A function rather than an\n * object so a rotated secret — or a test that sets one — is read on each\n * request.\n *\n * Server-only: the headers usually carry a secret, and nothing here is\n * exported from the client entry.\n */\nexport type ConfigurationSource = () => {\n readonly url: string;\n readonly headers?: Readonly<Record<string, string>>;\n};\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;CAYC,GACD,WAGE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type EntityRepositoryTag } from '@entifix/business';
|
|
2
2
|
import { type Entity, type EntityConstructor } from '@entifix/core';
|
|
3
3
|
import type { TransactionSink } from '@entifix/transactions';
|
|
4
4
|
import { TransactionSinkTag } from '@entifix/transactions';
|
|
@@ -12,17 +12,15 @@ import type { EntityCrud, EntityCrudOptions } from './make-entity-crud.types.js'
|
|
|
12
12
|
* rebuilding the union at each such call site is how one of them ends up
|
|
13
13
|
* missing `TransactionSinkTag` and quietly losing its optimistic treatment.
|
|
14
14
|
*/
|
|
15
|
-
export type CrudContext = EntityRepositoryTag |
|
|
15
|
+
export type CrudContext = EntityRepositoryTag | TransactionSinkTag;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* Adds the pending-write sink to one repository adapter, giving the context the
|
|
18
|
+
* use-cases run against.
|
|
19
19
|
*
|
|
20
|
-
* The
|
|
20
|
+
* The cast is the price of naming adapters by key: `AdapterKey` narrows which
|
|
21
21
|
* names may be *offered*, but TypeScript will not carry that narrowing through
|
|
22
|
-
* an index access on a generic record.
|
|
23
|
-
* passing two `Context` values per entity — the duplication this factory exists
|
|
24
|
-
* to remove.
|
|
22
|
+
* an index access on a generic record.
|
|
25
23
|
*/
|
|
26
|
-
export declare function mergeCrudContext<TAdapters>(adapters: TAdapters,
|
|
24
|
+
export declare function mergeCrudContext<TAdapters>(adapters: TAdapters, repository: keyof TAdapters, sink: TransactionSink): Context.Context<CrudContext>;
|
|
27
25
|
export declare function makeEntityCrud<TEntity extends Entity, TAdapters>(entityConstructor: EntityConstructor<TEntity>, options: EntityCrudOptions<TAdapters, TEntity>): EntityCrud<TEntity>;
|
|
28
26
|
//# sourceMappingURL=make-entity-crud.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-entity-crud.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/make-entity-crud.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"make-entity-crud.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/make-entity-crud.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,mBAAmB,EAIzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAKL,KAAK,MAAM,EACX,KAAK,iBAAiB,EAIvB,MAAM,eAAe,CAAC;AAQvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAajC,OAAO,KAAK,EACV,UAAU,EAEV,iBAAiB,EAElB,MAAM,6BAA6B,CAAC;AAUrC;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAEnE;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EACxC,QAAQ,EAAE,SAAS,EACnB,UAAU,EAAE,MAAM,SAAS,EAC3B,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAM9B;AAwCD,wBAAgB,cAAc,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,EAC9D,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAC7C,OAAO,EAAE,iBAAiB,CAAC,SAAS,EAAE,OAAO,CAAC,GAC7C,UAAU,CAAC,OAAO,CAAC,CA4WrB"}
|
|
@@ -22,16 +22,14 @@ import { useEntityAffordances } from './use-entity-affordances.js';
|
|
|
22
22
|
import { useEntityBulk } from './use-entity-bulk.js';
|
|
23
23
|
/** What a picker defaults to reading off its target. */ const TARGET_NAME_PROPERTY = 'name';
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Adds the pending-write sink to one repository adapter, giving the context the
|
|
26
|
+
* use-cases run against.
|
|
27
27
|
*
|
|
28
|
-
* The
|
|
28
|
+
* The cast is the price of naming adapters by key: `AdapterKey` narrows which
|
|
29
29
|
* names may be *offered*, but TypeScript will not carry that narrowing through
|
|
30
|
-
* an index access on a generic record.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*/ export function mergeCrudContext(adapters, configuration, repository, sink) {
|
|
34
|
-
return Context.add(Context.merge(adapters[configuration], adapters[repository]), TransactionSinkTag, sink);
|
|
30
|
+
* an index access on a generic record.
|
|
31
|
+
*/ export function mergeCrudContext(adapters, repository, sink) {
|
|
32
|
+
return Context.add(adapters[repository], TransactionSinkTag, sink);
|
|
35
33
|
}
|
|
36
34
|
/**
|
|
37
35
|
* Builds the list page and the single-record page for one entity.
|
|
@@ -64,7 +62,13 @@ import { useEntityBulk } from './use-entity-bulk.js';
|
|
|
64
62
|
return failure instanceof EntifixLogicError || failure instanceof EntifixBuildError ? failure : new EntifixLogicError(failure instanceof Error ? failure.message : String(failure));
|
|
65
63
|
}
|
|
66
64
|
export function makeEntityCrud(entityConstructor, options) {
|
|
67
|
-
const { useAdapters, basePath, catalogKey, repository,
|
|
65
|
+
const { useAdapters, basePath, catalogKey, repository, hiddenFields = [], columns, toolbar, links = [], metadataSource: givenMetadataSource, metadata, runBulkUseCase, runUseCase } = options;
|
|
66
|
+
if (givenMetadataSource !== undefined && metadata !== undefined) {
|
|
67
|
+
throw new EntifixBuildError(`${entityConstructor.name} was given both metadataSource and metadata; pass one`);
|
|
68
|
+
}
|
|
69
|
+
// An adapter value, so it is resolved per render from the same `useAdapters()`
|
|
70
|
+
// the records come from — the host's composition, never the caller's module.
|
|
71
|
+
const metadataSourceOf = (adapters)=>metadata === undefined ? givenMetadataSource : adapters[metadata];
|
|
68
72
|
const meta = extractMetaEntity(entityConstructor);
|
|
69
73
|
if (meta.key !== catalogKey) {
|
|
70
74
|
throw new EntifixBuildError(`${entityConstructor.name} declares key "${String(meta.key)}" but was given catalog key "${catalogKey}"`);
|
|
@@ -112,6 +116,7 @@ export function makeEntityCrud(entityConstructor, options) {
|
|
|
112
116
|
const withLocale = useLocaleHref();
|
|
113
117
|
const queryClient = useQueryClient();
|
|
114
118
|
const scope = entityQueryScope(entityConstructor);
|
|
119
|
+
const metadataSource = metadataSourceOf(adapters);
|
|
115
120
|
const affordances = useEntityAffordances(entityConstructor, metadataSource);
|
|
116
121
|
const renderLink = useCrudRenderLink(catalogKey);
|
|
117
122
|
// Records this browser has created but the service has not finished
|
|
@@ -120,7 +125,7 @@ export function makeEntityCrud(entityConstructor, options) {
|
|
|
120
125
|
const optimistic = pendingRecordsFor(pending, entityName);
|
|
121
126
|
const pager = useDataLoading({
|
|
122
127
|
uc: loadUCFactory(),
|
|
123
|
-
ctx: mergeCrudContext(adapters,
|
|
128
|
+
ctx: mergeCrudContext(adapters, repository, pending),
|
|
124
129
|
// Scoped rather than left to the per-instance fallback, which is correct
|
|
125
130
|
// but unshared: with the entity's own scope one invalidation refreshes
|
|
126
131
|
// every page and filter of it, which is what a bulk run needs — and it is
|
|
@@ -211,6 +216,7 @@ export function makeEntityCrud(entityConstructor, options) {
|
|
|
211
216
|
*/ function SingleViewPage({ slug, onSaved, onDeleted, draft } = {}) {
|
|
212
217
|
var _ref, _ref1;
|
|
213
218
|
const adapters = useAdapters();
|
|
219
|
+
const metadataSource = metadataSourceOf(adapters);
|
|
214
220
|
const pending = usePendingTransactions();
|
|
215
221
|
const router = useRouter();
|
|
216
222
|
const withLocale = useLocaleHref();
|
|
@@ -219,7 +225,7 @@ export function makeEntityCrud(entityConstructor, options) {
|
|
|
219
225
|
// so it needs a slot of its own to reach the form's error area.
|
|
220
226
|
const [useCaseError, setUseCaseError] = useState(undefined);
|
|
221
227
|
const id = slugToEntityId(slug != null ? slug : params.slug);
|
|
222
|
-
const ctx = mergeCrudContext(adapters,
|
|
228
|
+
const ctx = mergeCrudContext(adapters, repository, pending);
|
|
223
229
|
const { entity, isLoading, error: loadError, reload } = useEntityRecord({
|
|
224
230
|
uc: getUCFactory(),
|
|
225
231
|
ctx,
|
|
@@ -240,7 +246,7 @@ export function makeEntityCrud(entityConstructor, options) {
|
|
|
240
246
|
entityConstructor: plan.entityConstructor,
|
|
241
247
|
loadUc: loadUCFactory(),
|
|
242
248
|
getUc: getUCFactory(),
|
|
243
|
-
ctx: mergeCrudContext(adapters,
|
|
249
|
+
ctx: mergeCrudContext(adapters, plan.repository, pending)
|
|
244
250
|
}
|
|
245
251
|
}));
|
|
246
252
|
// Inside a workspace tab, "back to the list" is the list's tab, not a URL
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/crud/make-entity-crud.tsx"],"sourcesContent":["'use client';\n\nimport {\n type ConfigurationRepositoryTag,\n deleteUCFactory,\n type EntityRepositoryTag,\n getUCFactory,\n loadUCFactory,\n saveUCFactory,\n} from '@entifix/business';\nimport {\n describeEntityColumns,\n EntifixBuildError,\n type EntifixError,\n EntifixLogicError,\n type Entity,\n type EntityConstructor,\n envelopeEntityName,\n extractMetaEntity,\n} from '@entifix/core';\nimport { EntityTable, useCasesForSurface } from '@entifix/react-controls';\nimport {\n entityQueryScope,\n useDataLoading,\n useEntityMutation,\n useEntityRecord,\n} from '@entifix/react-integration';\nimport type { TransactionSink } from '@entifix/transactions';\nimport { TransactionSinkTag } from '@entifix/transactions';\nimport { useQueryClient } from '@tanstack/react-query';\nimport { Context } from 'effect';\nimport { useParams, useRouter } from 'next/navigation';\nimport { useState } from 'react';\n\nimport { useLocaleHref } from '../i18n/index.js';\nimport { useEntityNavHost } from '../workspace/entity-nav.js';\nimport {\n pendingFor,\n pendingRecordsFor,\n usePendingTransactions,\n} from '../workspace/pending-transactions.js';\nimport { EntityCrudForm } from './entity-crud-form.js';\nimport { handOffWrite } from './hand-off-write.js';\nimport type {\n EntityCrud,\n EntityCrudLinkSource,\n EntityCrudOptions,\n EntityCrudSingleViewProps,\n} from './make-entity-crud.types.js';\nimport { PendingNotice } from './pending-notice.js';\nimport { CATALOG_NEW_SLUG, slugToEntityId } from './slug.js';\nimport { useCrudRenderLink } from './use-crud-render-link.js';\nimport { useEntityAffordances } from './use-entity-affordances.js';\nimport { useEntityBulk } from './use-entity-bulk.js';\n\n/** What a picker defaults to reading off its target. */\nconst TARGET_NAME_PROPERTY = 'name';\n\n/**\n * What the generated pages' use-cases run against.\n *\n * Exported because a hand-built screen needs the same three: a wizard reads\n * records, resolves pickers and writes through exactly these ports, and\n * rebuilding the union at each such call site is how one of them ends up\n * missing `TransactionSinkTag` and quietly losing its optimistic treatment.\n */\nexport type CrudContext =\n EntityRepositoryTag | ConfigurationRepositoryTag | TransactionSinkTag;\n\n/**\n * Merges the configuration adapter with one repository adapter into the context\n * the use-cases run against.\n *\n * The casts are the price of naming adapters by key: `AdapterKey` narrows which\n * names may be *offered*, but TypeScript will not carry that narrowing through\n * an index access on a generic record. The alternative is every call site\n * passing two `Context` values per entity — the duplication this factory exists\n * to remove.\n */\nexport function mergeCrudContext<TAdapters>(\n adapters: TAdapters,\n configuration: keyof TAdapters,\n repository: keyof TAdapters,\n sink: TransactionSink,\n): Context.Context<CrudContext> {\n return Context.add(\n Context.merge(\n adapters[configuration] as Context.Context<ConfigurationRepositoryTag>,\n adapters[repository] as Context.Context<EntityRepositoryTag>,\n ),\n TransactionSinkTag,\n sink,\n );\n}\n\n/**\n * Builds the list page and the single-record page for one entity.\n *\n * Every catalog entity used to cost ~10 files and ~300 lines whose only variable\n * was a class name: two organisms with a `.types.ts` each, two client pages, two\n * route files. The route files stay (Next needs a module per path); everything\n * between them is derived from the entity's own metadata, so a new reference\n * entity is this call plus a nav entry.\n *\n * Two things are checked here rather than at the call site, because both fail\n * silently otherwise. The catalog key must be the entity's own `@entity({ key })`\n * — they are the same string by convention, and a drifted one titles the form\n * after a different entity in a way only a reader of both catalogs would catch.\n * And a `links` entry must name a real member — a picker aimed at nothing renders\n * identically to a read-only field.\n *\n * **Serves compile-time `@entity()` classes only.** Operator reference data ships\n * with the code, so making this spec-driven would trade type safety for nothing;\n * a vendor-authored `EntitySpecification` renders through a separate path\n * (ADR 0014).\n */\n/**\n * A rejected verb, as something the form's error slot can render.\n *\n * `useErrorMessage` resolves `details.code` through the shared `errors`\n * catalog, so a shell that throws an `EntifixError` carrying one gets its\n * translated sentence; anything else — a network failure, a thrown string —\n * keeps its message rather than being relabelled with a code it never had.\n */\nfunction asEntifixError(failure: unknown): EntifixError {\n return failure instanceof EntifixLogicError ||\n failure instanceof EntifixBuildError\n ? failure\n : new EntifixLogicError(\n failure instanceof Error ? failure.message : String(failure),\n );\n}\n\nexport function makeEntityCrud<TEntity extends Entity, TAdapters>(\n entityConstructor: EntityConstructor<TEntity>,\n options: EntityCrudOptions<TAdapters, TEntity>,\n): EntityCrud<TEntity> {\n const {\n useAdapters,\n basePath,\n catalogKey,\n repository,\n configuration,\n hiddenFields = [],\n columns,\n toolbar,\n links = [],\n metadataSource,\n runBulkUseCase,\n runUseCase,\n } = options;\n\n const meta = extractMetaEntity(entityConstructor);\n if (meta.key !== catalogKey) {\n throw new EntifixBuildError(\n `${entityConstructor.name} declares key \"${String(meta.key)}\" but was given catalog key \"${catalogKey}\"`,\n );\n }\n // Read off `@entity()` rather than rebuilt from `catalogKey`. Both spellings\n // produce the same string today, and that is exactly the problem: a second\n // place that knows how an entity's catalog subtree is laid out is a second\n // place to fix when one moves. `MetaEntityOptions` makes them optional, so an\n // entity that never declared them fails here — where a screen is generated —\n // rather than on the render that first tries to title a tab with `undefined`.\n const { labelKey: entityLabelKey, pluralKey: entityPluralKey } = meta;\n if (entityLabelKey === undefined || entityPluralKey === undefined) {\n throw new EntifixBuildError(\n `${entityConstructor.name} must declare labelKey and pluralKey on @entity() to be generated`,\n );\n }\n\n // Computed once: a descriptor is a property of the class, so nothing about it\n // depends on the record being edited. `linkLabelProperty`/`linkSearchProperty`\n // are overridden here because a scalar foreign key's `@accessor()` cannot name\n // the target's members — it may not import the target at all.\n // The wire name, which is also what a pending entry records and what\n // `entityQueryScopeFor` keys on — one derivation, so a notice and an\n // invalidation can never disagree about which entity they mean.\n const entityName = envelopeEntityName(entityConstructor);\n const descriptors = describeEntityColumns(entityConstructor);\n const linkPlans = links.map(link => {\n const descriptor = descriptors.find(entry => entry.name === link.field);\n if (descriptor === undefined) {\n throw new EntifixBuildError(\n `${entityConstructor.name} has no member \"${link.field}\" for a link source`,\n );\n }\n return {\n ...link,\n descriptor: {\n ...descriptor,\n linkLabelProperty: link.labelProperty ?? TARGET_NAME_PROPERTY,\n linkSearchProperty: link.searchProperty ?? TARGET_NAME_PROPERTY,\n },\n };\n });\n\n function ListPage() {\n const adapters = useAdapters();\n // Outside a `PendingTransactionsProvider` this watches nothing, so a host\n // that has not mounted one behaves exactly as it did before.\n const pending = usePendingTransactions();\n // Every internal href carries the locale. An unprefixed one still resolves —\n // the middleware redirects it — but the visitor pays a round trip per click.\n const withLocale = useLocaleHref();\n\n const queryClient = useQueryClient();\n const scope = entityQueryScope(entityConstructor);\n const affordances = useEntityAffordances(entityConstructor, metadataSource);\n const renderLink = useCrudRenderLink(catalogKey);\n\n // Records this browser has created but the service has not finished\n // writing. Prepended below rather than patched into the cache, which a\n // refetch would undo.\n const optimistic = pendingRecordsFor<TEntity>(pending, entityName);\n\n const pager = useDataLoading<TEntity, CrudContext>({\n uc: loadUCFactory<TEntity>(),\n ctx: mergeCrudContext(adapters, configuration, repository, pending),\n // Scoped rather than left to the per-instance fallback, which is correct\n // but unshared: with the entity's own scope one invalidation refreshes\n // every page and filter of it, which is what a bulk run needs — and it is\n // the same prefix `useReactiveInvalidation`already targets, so a generated\n // list now also refreshes on a reactive change event.\n queryKey: scope,\n });\n\n // Only when this caller may actually run something over a selection.\n //\n // Measured live: an `admin` holds `catalog-reference:*:read` and no\n // `retire`, so the service filters the verb out of `$metadata` — but the\n // selection column still rendered, offering a set that no action could be\n // taken on. A checkbox that can lead nowhere is worse than no checkbox: it\n // reads as a permission the user does not have.\n //\n // Decided from the served document rather than from the runner, because\n // the runner is a property of the *shell* (it knows the route) and the\n // grant is a property of the *caller*. The column therefore appears when\n // the document lands, which is the asynchrony ADR 0026 already accepts for\n // every action surface.\n const canRunBulk =\n useCasesForSurface('bulk-bar', affordances.metadata?.useCases).length > 0;\n\n const bulk = useEntityBulk<TEntity>({\n run: canRunBulk ? runBulkUseCase : undefined,\n // A bulk write changed the rows underneath the listing, so the page is\n // re-read; the selection deliberately survives it (#121 — \"the selection\n // is still there afterwards\"), because the operator's next act is usually\n // to retry the failures or run a second verb on the same rows.\n onCompleted: () => {\n void queryClient.invalidateQueries({ queryKey: scope });\n },\n });\n\n // What a row's overflow menu runs, and the one place the list reports a\n // verb that failed.\n //\n // ⚠️ **A row verb and the form's are the same runner with different\n // arguments.** `EntityTable` hands back the whole record, because that is\n // what it has; `runUseCase` takes an id, because that is what a route needs\n // (#216). The list has no error slot of its own, so a failure lands in the\n // table's own error region — the same one a failed page read uses.\n const [useCaseError, setUseCaseError] = useState<EntifixError | undefined>(\n undefined,\n );\n // Called unconditionally and the *result* gated, the way the surfaces are\n // read in `EntityForm`: the name begins with `use`, so short-circuiting it\n // reads to `react-hooks/rules-of-hooks` as a conditional hook.\n const rowUseCases = useCasesForSurface(\n 'row-menu',\n affordances.metadata?.useCases,\n );\n const canRunRowUseCase = runUseCase !== undefined && rowUseCases.length > 0;\n\n const handleRowUseCase = canRunRowUseCase\n ? (key: string, item: TEntity) => {\n if (item.id == null) return;\n setUseCaseError(undefined);\n void runUseCase(key, item.id).then(\n () => queryClient.invalidateQueries({ queryKey: scope }),\n (failure: unknown) => setUseCaseError(asEntifixError(failure)),\n );\n }\n : undefined;\n\n return (\n <div className=\"flex flex-col gap-s\">\n {/* Above the table, because this is where `afterSave()` leaves the\n operator after a create — and a create is the only transactional\n write there is, so this is the surface the pending state has. */}\n <PendingNotice\n entries={pendingFor(pending, entityName)}\n onDismiss={pending.dismiss}\n />\n <EntityTable\n entityConstructor={entityConstructor}\n {...pager}\n items={[...optimistic, ...pager.items]}\n totalItems={pager.totalItems + optimistic.length}\n hrefFor={id => withLocale(`${basePath}/${String(id)}`)}\n newHref={withLocale(`${basePath}/${CATALOG_NEW_SLUG}`)}\n renderLink={renderLink}\n {...affordances}\n {...bulk.tableProps}\n onUseCase={handleRowUseCase}\n error={useCaseError ?? pager.error}\n >\n {columns}\n {toolbar}\n </EntityTable>\n </div>\n );\n }\n\n /**\n * Dual-host: as a route it reads its slug from the URL and returns to the list\n * on save; in a workspace tab it takes the slug and the post-save action as\n * props.\n */\n function SingleViewPage({\n slug,\n onSaved,\n onDeleted,\n draft,\n }: EntityCrudSingleViewProps = {}) {\n const adapters = useAdapters();\n const pending = usePendingTransactions();\n const router = useRouter();\n const withLocale = useLocaleHref();\n const params = useParams<{ slug: string }>();\n // A verb's failure is this page's, not the record fetch's or the mutation's,\n // so it needs a slot of its own to reach the form's error area.\n const [useCaseError, setUseCaseError] = useState<EntifixError | undefined>(\n undefined,\n );\n const id = slugToEntityId(slug ?? params.slug);\n\n const ctx = mergeCrudContext(adapters, configuration, repository, pending);\n\n const {\n entity,\n isLoading,\n error: loadError,\n reload,\n } = useEntityRecord<TEntity, CrudContext>({\n uc: getUCFactory<TEntity>(),\n ctx,\n id,\n });\n\n const {\n save,\n remove,\n isSaving,\n isDeleting,\n error: writeError,\n } = useEntityMutation<TEntity, CrudContext>({\n saveUc: saveUCFactory<TEntity>(),\n deleteUc: deleteUCFactory<TEntity>(),\n ctx,\n });\n\n // Rebuilt inline on every render on purpose: `useEntityLinkSource` holds\n // these in a ref and keeps them out of its query keys, precisely so a caller\n // does not have to memoise them.\n const linkSources: EntityCrudLinkSource[] = linkPlans.map(plan => ({\n field: plan.field,\n descriptor: plan.descriptor,\n config: {\n entityConstructor: plan.entityConstructor,\n loadUc: loadUCFactory(),\n getUc: getUCFactory(),\n ctx: mergeCrudContext(\n adapters,\n configuration,\n plan.repository,\n pending,\n ),\n },\n }));\n\n // Inside a workspace tab, \"back to the list\" is the list's tab, not a URL\n // that would leave the workspace. A host's own callbacks still win.\n const host = useEntityNavHost();\n const renderLink = useCrudRenderLink(catalogKey);\n const queryClient = useQueryClient();\n // The list this page returns to has its page cached, and it is no longer\n // true. Invalidated rather than patched, for the reason `handOffWrite`\n // gives: a transactional create is not in the service yet, and the pending\n // set, not the cache, is what keeps it visible until it is.\n //\n // ⚠️ Invisible while every Open was a document load, which threw the cache\n // away anyway (#20): the first client-side navigation back to the list\n // showed the record as it was before the save.\n const refreshList = () =>\n queryClient.invalidateQueries({\n queryKey: entityQueryScope(entityConstructor),\n });\n const toList = () =>\n host === undefined\n ? router.push(withLocale(basePath))\n : host.toList(catalogKey);\n const afterSave = onSaved ?? toList;\n const afterDelete = onDeleted ?? toList;\n\n // The draft is spent once the write *commits*, and only here is that known:\n // `useEntityForm` neither fetches nor saves, so it cannot clear its own.\n // A failed mutation deliberately keeps the draft — the edit is still the\n // user's only copy of what they typed.\n const handleSave = async (next: TEntity) => {\n const saved = await save(next);\n if (saved === undefined) return;\n\n // ⚠️ Deliberately *not* a `setQueriesData` patch. The list refetches on\n // mount — precisely when the operator arrives, having just been navigated\n // here — and the server legitimately does not hold the record yet, so the\n // refetch would replace the patched page and the row would vanish a\n // moment after appearing. The pending set outlives refetches.\n handOffWrite({ id: String(saved.id), record: saved, pending, draft });\n void refreshList();\n afterSave();\n };\n\n const handleDelete = async () => {\n if (await remove(id)) {\n draft?.clear();\n void refreshList();\n afterDelete();\n }\n };\n\n /**\n * An `entity`-bound verb, on the record being shown.\n *\n * It **reloads** rather than patching what it has: the service decides the\n * resulting state — a `publish` may land on `published` from four different\n * places — and a client that assumed the new value would show the wrong one\n * the first time the rule changed. `id == null` means the create form,\n * where there is no record to act on and the form renders no verbs anyway.\n *\n * ⚠️ The rejection is **caught and shown**, never left to the promise.\n * `EntityForm`'s `onUseCase` returns `void`, so an uncaught rejection here\n * reaches the console and nothing else — measured on an offering refusing\n * `unpublish` from `draft`: the service answered `409` with a coded body,\n * the record correctly did not move, and the operator saw a button that\n * appeared to do nothing at all.\n */\n const handleUseCase =\n runUseCase === undefined || id == null\n ? undefined\n : (key: string) => {\n setUseCaseError(undefined);\n void runUseCase(key, id).then(\n () => reload(),\n (failure: unknown) => setUseCaseError(asEntifixError(failure)),\n );\n };\n\n return (\n <EntityCrudForm<TEntity>\n // Remounts (and reseeds the fields) once the record arrives.\n key={String(entity?.id ?? CATALOG_NEW_SLUG)}\n entityConstructor={entityConstructor}\n catalogKey={catalogKey}\n metadataSource={metadataSource}\n hiddenFields={hiddenFields}\n links={linkSources}\n entity={entity}\n isLoading={isLoading}\n isSaving={isSaving}\n isDeleting={isDeleting}\n error={loadError ?? writeError ?? useCaseError}\n onSave={handleSave}\n onDelete={id == null ? undefined : handleDelete}\n onUseCase={handleUseCase}\n backHref={withLocale(basePath)}\n renderLink={renderLink}\n draft={draft}\n />\n );\n }\n\n return {\n entityConstructor,\n entityKey: catalogKey,\n entityLabelKey,\n entityPluralKey,\n basePath,\n ListPage,\n SingleViewPage,\n };\n}\n"],"names":["deleteUCFactory","getUCFactory","loadUCFactory","saveUCFactory","describeEntityColumns","EntifixBuildError","EntifixLogicError","envelopeEntityName","extractMetaEntity","EntityTable","useCasesForSurface","entityQueryScope","useDataLoading","useEntityMutation","useEntityRecord","TransactionSinkTag","useQueryClient","Context","useParams","useRouter","useState","useLocaleHref","useEntityNavHost","pendingFor","pendingRecordsFor","usePendingTransactions","EntityCrudForm","handOffWrite","PendingNotice","CATALOG_NEW_SLUG","slugToEntityId","useCrudRenderLink","useEntityAffordances","useEntityBulk","TARGET_NAME_PROPERTY","mergeCrudContext","adapters","configuration","repository","sink","add","merge","asEntifixError","failure","Error","message","String","makeEntityCrud","entityConstructor","options","useAdapters","basePath","catalogKey","hiddenFields","columns","toolbar","links","metadataSource","runBulkUseCase","runUseCase","meta","key","name","labelKey","entityLabelKey","pluralKey","entityPluralKey","undefined","entityName","descriptors","linkPlans","map","link","descriptor","find","entry","field","linkLabelProperty","labelProperty","linkSearchProperty","searchProperty","ListPage","affordances","pending","withLocale","queryClient","scope","renderLink","optimistic","pager","uc","ctx","queryKey","canRunBulk","metadata","useCases","length","bulk","run","onCompleted","invalidateQueries","useCaseError","setUseCaseError","rowUseCases","canRunRowUseCase","handleRowUseCase","item","id","then","div","className","entries","onDismiss","dismiss","items","totalItems","hrefFor","newHref","tableProps","onUseCase","error","SingleViewPage","slug","onSaved","onDeleted","draft","loadError","router","params","entity","isLoading","reload","save","remove","isSaving","isDeleting","writeError","saveUc","deleteUc","linkSources","plan","config","loadUc","getUc","host","refreshList","toList","push","afterSave","afterDelete","handleSave","next","saved","record","handleDelete","clear","handleUseCase","onSave","onDelete","backHref","entityKey"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/crud/make-entity-crud.tsx"],"sourcesContent":["'use client';\n\nimport {\n deleteUCFactory,\n type EntityRepositoryTag,\n getUCFactory,\n loadUCFactory,\n saveUCFactory,\n} from '@entifix/business';\nimport {\n describeEntityColumns,\n EntifixBuildError,\n type EntifixError,\n EntifixLogicError,\n type Entity,\n type EntityConstructor,\n type EntityMetadataSource,\n envelopeEntityName,\n extractMetaEntity,\n} from '@entifix/core';\nimport { EntityTable, useCasesForSurface } from '@entifix/react-controls';\nimport {\n entityQueryScope,\n useDataLoading,\n useEntityMutation,\n useEntityRecord,\n} from '@entifix/react-integration';\nimport type { TransactionSink } from '@entifix/transactions';\nimport { TransactionSinkTag } from '@entifix/transactions';\nimport { useQueryClient } from '@tanstack/react-query';\nimport { Context } from 'effect';\nimport { useParams, useRouter } from 'next/navigation';\nimport { useState } from 'react';\n\nimport { useLocaleHref } from '../i18n/index.js';\nimport { useEntityNavHost } from '../workspace/entity-nav.js';\nimport {\n pendingFor,\n pendingRecordsFor,\n usePendingTransactions,\n} from '../workspace/pending-transactions.js';\nimport { EntityCrudForm } from './entity-crud-form.js';\nimport { handOffWrite } from './hand-off-write.js';\nimport type {\n EntityCrud,\n EntityCrudLinkSource,\n EntityCrudOptions,\n EntityCrudSingleViewProps,\n} from './make-entity-crud.types.js';\nimport { PendingNotice } from './pending-notice.js';\nimport { CATALOG_NEW_SLUG, slugToEntityId } from './slug.js';\nimport { useCrudRenderLink } from './use-crud-render-link.js';\nimport { useEntityAffordances } from './use-entity-affordances.js';\nimport { useEntityBulk } from './use-entity-bulk.js';\n\n/** What a picker defaults to reading off its target. */\nconst TARGET_NAME_PROPERTY = 'name';\n\n/**\n * What the generated pages' use-cases run against.\n *\n * Exported because a hand-built screen needs the same three: a wizard reads\n * records, resolves pickers and writes through exactly these ports, and\n * rebuilding the union at each such call site is how one of them ends up\n * missing `TransactionSinkTag` and quietly losing its optimistic treatment.\n */\nexport type CrudContext = EntityRepositoryTag | TransactionSinkTag;\n\n/**\n * Adds the pending-write sink to one repository adapter, giving the context the\n * use-cases run against.\n *\n * The cast is the price of naming adapters by key: `AdapterKey` narrows which\n * names may be *offered*, but TypeScript will not carry that narrowing through\n * an index access on a generic record.\n */\nexport function mergeCrudContext<TAdapters>(\n adapters: TAdapters,\n repository: keyof TAdapters,\n sink: TransactionSink,\n): Context.Context<CrudContext> {\n return Context.add(\n adapters[repository] as Context.Context<EntityRepositoryTag>,\n TransactionSinkTag,\n sink,\n );\n}\n\n/**\n * Builds the list page and the single-record page for one entity.\n *\n * Every catalog entity used to cost ~10 files and ~300 lines whose only variable\n * was a class name: two organisms with a `.types.ts` each, two client pages, two\n * route files. The route files stay (Next needs a module per path); everything\n * between them is derived from the entity's own metadata, so a new reference\n * entity is this call plus a nav entry.\n *\n * Two things are checked here rather than at the call site, because both fail\n * silently otherwise. The catalog key must be the entity's own `@entity({ key })`\n * — they are the same string by convention, and a drifted one titles the form\n * after a different entity in a way only a reader of both catalogs would catch.\n * And a `links` entry must name a real member — a picker aimed at nothing renders\n * identically to a read-only field.\n *\n * **Serves compile-time `@entity()` classes only.** Operator reference data ships\n * with the code, so making this spec-driven would trade type safety for nothing;\n * a vendor-authored `EntitySpecification` renders through a separate path\n * (ADR 0014).\n */\n/**\n * A rejected verb, as something the form's error slot can render.\n *\n * `useErrorMessage` resolves `details.code` through the shared `errors`\n * catalog, so a shell that throws an `EntifixError` carrying one gets its\n * translated sentence; anything else — a network failure, a thrown string —\n * keeps its message rather than being relabelled with a code it never had.\n */\nfunction asEntifixError(failure: unknown): EntifixError {\n return failure instanceof EntifixLogicError ||\n failure instanceof EntifixBuildError\n ? failure\n : new EntifixLogicError(\n failure instanceof Error ? failure.message : String(failure),\n );\n}\n\nexport function makeEntityCrud<TEntity extends Entity, TAdapters>(\n entityConstructor: EntityConstructor<TEntity>,\n options: EntityCrudOptions<TAdapters, TEntity>,\n): EntityCrud<TEntity> {\n const {\n useAdapters,\n basePath,\n catalogKey,\n repository,\n hiddenFields = [],\n columns,\n toolbar,\n links = [],\n metadataSource: givenMetadataSource,\n metadata,\n runBulkUseCase,\n runUseCase,\n } = options;\n\n if (givenMetadataSource !== undefined && metadata !== undefined) {\n throw new EntifixBuildError(\n `${entityConstructor.name} was given both metadataSource and metadata; pass one`,\n );\n }\n // An adapter value, so it is resolved per render from the same `useAdapters()`\n // the records come from — the host's composition, never the caller's module.\n const metadataSourceOf = (adapters: TAdapters) =>\n metadata === undefined\n ? givenMetadataSource\n : (adapters[metadata] as EntityMetadataSource);\n\n const meta = extractMetaEntity(entityConstructor);\n if (meta.key !== catalogKey) {\n throw new EntifixBuildError(\n `${entityConstructor.name} declares key \"${String(meta.key)}\" but was given catalog key \"${catalogKey}\"`,\n );\n }\n // Read off `@entity()` rather than rebuilt from `catalogKey`. Both spellings\n // produce the same string today, and that is exactly the problem: a second\n // place that knows how an entity's catalog subtree is laid out is a second\n // place to fix when one moves. `MetaEntityOptions` makes them optional, so an\n // entity that never declared them fails here — where a screen is generated —\n // rather than on the render that first tries to title a tab with `undefined`.\n const { labelKey: entityLabelKey, pluralKey: entityPluralKey } = meta;\n if (entityLabelKey === undefined || entityPluralKey === undefined) {\n throw new EntifixBuildError(\n `${entityConstructor.name} must declare labelKey and pluralKey on @entity() to be generated`,\n );\n }\n\n // Computed once: a descriptor is a property of the class, so nothing about it\n // depends on the record being edited. `linkLabelProperty`/`linkSearchProperty`\n // are overridden here because a scalar foreign key's `@accessor()` cannot name\n // the target's members — it may not import the target at all.\n // The wire name, which is also what a pending entry records and what\n // `entityQueryScopeFor` keys on — one derivation, so a notice and an\n // invalidation can never disagree about which entity they mean.\n const entityName = envelopeEntityName(entityConstructor);\n const descriptors = describeEntityColumns(entityConstructor);\n const linkPlans = links.map(link => {\n const descriptor = descriptors.find(entry => entry.name === link.field);\n if (descriptor === undefined) {\n throw new EntifixBuildError(\n `${entityConstructor.name} has no member \"${link.field}\" for a link source`,\n );\n }\n return {\n ...link,\n descriptor: {\n ...descriptor,\n linkLabelProperty: link.labelProperty ?? TARGET_NAME_PROPERTY,\n linkSearchProperty: link.searchProperty ?? TARGET_NAME_PROPERTY,\n },\n };\n });\n\n function ListPage() {\n const adapters = useAdapters();\n // Outside a `PendingTransactionsProvider` this watches nothing, so a host\n // that has not mounted one behaves exactly as it did before.\n const pending = usePendingTransactions();\n // Every internal href carries the locale. An unprefixed one still resolves —\n // the middleware redirects it — but the visitor pays a round trip per click.\n const withLocale = useLocaleHref();\n\n const queryClient = useQueryClient();\n const scope = entityQueryScope(entityConstructor);\n const metadataSource = metadataSourceOf(adapters);\n const affordances = useEntityAffordances(entityConstructor, metadataSource);\n const renderLink = useCrudRenderLink(catalogKey);\n\n // Records this browser has created but the service has not finished\n // writing. Prepended below rather than patched into the cache, which a\n // refetch would undo.\n const optimistic = pendingRecordsFor<TEntity>(pending, entityName);\n\n const pager = useDataLoading<TEntity, CrudContext>({\n uc: loadUCFactory<TEntity>(),\n ctx: mergeCrudContext(adapters, repository, pending),\n // Scoped rather than left to the per-instance fallback, which is correct\n // but unshared: with the entity's own scope one invalidation refreshes\n // every page and filter of it, which is what a bulk run needs — and it is\n // the same prefix `useReactiveInvalidation`already targets, so a generated\n // list now also refreshes on a reactive change event.\n queryKey: scope,\n });\n\n // Only when this caller may actually run something over a selection.\n //\n // Measured live: an `admin` holds `catalog-reference:*:read` and no\n // `retire`, so the service filters the verb out of `$metadata` — but the\n // selection column still rendered, offering a set that no action could be\n // taken on. A checkbox that can lead nowhere is worse than no checkbox: it\n // reads as a permission the user does not have.\n //\n // Decided from the served document rather than from the runner, because\n // the runner is a property of the *shell* (it knows the route) and the\n // grant is a property of the *caller*. The column therefore appears when\n // the document lands, which is the asynchrony ADR 0026 already accepts for\n // every action surface.\n const canRunBulk =\n useCasesForSurface('bulk-bar', affordances.metadata?.useCases).length > 0;\n\n const bulk = useEntityBulk<TEntity>({\n run: canRunBulk ? runBulkUseCase : undefined,\n // A bulk write changed the rows underneath the listing, so the page is\n // re-read; the selection deliberately survives it (#121 — \"the selection\n // is still there afterwards\"), because the operator's next act is usually\n // to retry the failures or run a second verb on the same rows.\n onCompleted: () => {\n void queryClient.invalidateQueries({ queryKey: scope });\n },\n });\n\n // What a row's overflow menu runs, and the one place the list reports a\n // verb that failed.\n //\n // ⚠️ **A row verb and the form's are the same runner with different\n // arguments.** `EntityTable` hands back the whole record, because that is\n // what it has; `runUseCase` takes an id, because that is what a route needs\n // (#216). The list has no error slot of its own, so a failure lands in the\n // table's own error region — the same one a failed page read uses.\n const [useCaseError, setUseCaseError] = useState<EntifixError | undefined>(\n undefined,\n );\n // Called unconditionally and the *result* gated, the way the surfaces are\n // read in `EntityForm`: the name begins with `use`, so short-circuiting it\n // reads to `react-hooks/rules-of-hooks` as a conditional hook.\n const rowUseCases = useCasesForSurface(\n 'row-menu',\n affordances.metadata?.useCases,\n );\n const canRunRowUseCase = runUseCase !== undefined && rowUseCases.length > 0;\n\n const handleRowUseCase = canRunRowUseCase\n ? (key: string, item: TEntity) => {\n if (item.id == null) return;\n setUseCaseError(undefined);\n void runUseCase(key, item.id).then(\n () => queryClient.invalidateQueries({ queryKey: scope }),\n (failure: unknown) => setUseCaseError(asEntifixError(failure)),\n );\n }\n : undefined;\n\n return (\n <div className=\"flex flex-col gap-s\">\n {/* Above the table, because this is where `afterSave()` leaves the\n operator after a create — and a create is the only transactional\n write there is, so this is the surface the pending state has. */}\n <PendingNotice\n entries={pendingFor(pending, entityName)}\n onDismiss={pending.dismiss}\n />\n <EntityTable\n entityConstructor={entityConstructor}\n {...pager}\n items={[...optimistic, ...pager.items]}\n totalItems={pager.totalItems + optimistic.length}\n hrefFor={id => withLocale(`${basePath}/${String(id)}`)}\n newHref={withLocale(`${basePath}/${CATALOG_NEW_SLUG}`)}\n renderLink={renderLink}\n {...affordances}\n {...bulk.tableProps}\n onUseCase={handleRowUseCase}\n error={useCaseError ?? pager.error}\n >\n {columns}\n {toolbar}\n </EntityTable>\n </div>\n );\n }\n\n /**\n * Dual-host: as a route it reads its slug from the URL and returns to the list\n * on save; in a workspace tab it takes the slug and the post-save action as\n * props.\n */\n function SingleViewPage({\n slug,\n onSaved,\n onDeleted,\n draft,\n }: EntityCrudSingleViewProps = {}) {\n const adapters = useAdapters();\n const metadataSource = metadataSourceOf(adapters);\n const pending = usePendingTransactions();\n const router = useRouter();\n const withLocale = useLocaleHref();\n const params = useParams<{ slug: string }>();\n // A verb's failure is this page's, not the record fetch's or the mutation's,\n // so it needs a slot of its own to reach the form's error area.\n const [useCaseError, setUseCaseError] = useState<EntifixError | undefined>(\n undefined,\n );\n const id = slugToEntityId(slug ?? params.slug);\n\n const ctx = mergeCrudContext(adapters, repository, pending);\n\n const {\n entity,\n isLoading,\n error: loadError,\n reload,\n } = useEntityRecord<TEntity, CrudContext>({\n uc: getUCFactory<TEntity>(),\n ctx,\n id,\n });\n\n const {\n save,\n remove,\n isSaving,\n isDeleting,\n error: writeError,\n } = useEntityMutation<TEntity, CrudContext>({\n saveUc: saveUCFactory<TEntity>(),\n deleteUc: deleteUCFactory<TEntity>(),\n ctx,\n });\n\n // Rebuilt inline on every render on purpose: `useEntityLinkSource` holds\n // these in a ref and keeps them out of its query keys, precisely so a caller\n // does not have to memoise them.\n const linkSources: EntityCrudLinkSource[] = linkPlans.map(plan => ({\n field: plan.field,\n descriptor: plan.descriptor,\n config: {\n entityConstructor: plan.entityConstructor,\n loadUc: loadUCFactory(),\n getUc: getUCFactory(),\n ctx: mergeCrudContext(adapters, plan.repository, pending),\n },\n }));\n\n // Inside a workspace tab, \"back to the list\" is the list's tab, not a URL\n // that would leave the workspace. A host's own callbacks still win.\n const host = useEntityNavHost();\n const renderLink = useCrudRenderLink(catalogKey);\n const queryClient = useQueryClient();\n // The list this page returns to has its page cached, and it is no longer\n // true. Invalidated rather than patched, for the reason `handOffWrite`\n // gives: a transactional create is not in the service yet, and the pending\n // set, not the cache, is what keeps it visible until it is.\n //\n // ⚠️ Invisible while every Open was a document load, which threw the cache\n // away anyway (#20): the first client-side navigation back to the list\n // showed the record as it was before the save.\n const refreshList = () =>\n queryClient.invalidateQueries({\n queryKey: entityQueryScope(entityConstructor),\n });\n const toList = () =>\n host === undefined\n ? router.push(withLocale(basePath))\n : host.toList(catalogKey);\n const afterSave = onSaved ?? toList;\n const afterDelete = onDeleted ?? toList;\n\n // The draft is spent once the write *commits*, and only here is that known:\n // `useEntityForm` neither fetches nor saves, so it cannot clear its own.\n // A failed mutation deliberately keeps the draft — the edit is still the\n // user's only copy of what they typed.\n const handleSave = async (next: TEntity) => {\n const saved = await save(next);\n if (saved === undefined) return;\n\n // ⚠️ Deliberately *not* a `setQueriesData` patch. The list refetches on\n // mount — precisely when the operator arrives, having just been navigated\n // here — and the server legitimately does not hold the record yet, so the\n // refetch would replace the patched page and the row would vanish a\n // moment after appearing. The pending set outlives refetches.\n handOffWrite({ id: String(saved.id), record: saved, pending, draft });\n void refreshList();\n afterSave();\n };\n\n const handleDelete = async () => {\n if (await remove(id)) {\n draft?.clear();\n void refreshList();\n afterDelete();\n }\n };\n\n /**\n * An `entity`-bound verb, on the record being shown.\n *\n * It **reloads** rather than patching what it has: the service decides the\n * resulting state — a `publish` may land on `published` from four different\n * places — and a client that assumed the new value would show the wrong one\n * the first time the rule changed. `id == null` means the create form,\n * where there is no record to act on and the form renders no verbs anyway.\n *\n * ⚠️ The rejection is **caught and shown**, never left to the promise.\n * `EntityForm`'s `onUseCase` returns `void`, so an uncaught rejection here\n * reaches the console and nothing else — measured on an offering refusing\n * `unpublish` from `draft`: the service answered `409` with a coded body,\n * the record correctly did not move, and the operator saw a button that\n * appeared to do nothing at all.\n */\n const handleUseCase =\n runUseCase === undefined || id == null\n ? undefined\n : (key: string) => {\n setUseCaseError(undefined);\n void runUseCase(key, id).then(\n () => reload(),\n (failure: unknown) => setUseCaseError(asEntifixError(failure)),\n );\n };\n\n return (\n <EntityCrudForm<TEntity>\n // Remounts (and reseeds the fields) once the record arrives.\n key={String(entity?.id ?? CATALOG_NEW_SLUG)}\n entityConstructor={entityConstructor}\n catalogKey={catalogKey}\n metadataSource={metadataSource}\n hiddenFields={hiddenFields}\n links={linkSources}\n entity={entity}\n isLoading={isLoading}\n isSaving={isSaving}\n isDeleting={isDeleting}\n error={loadError ?? writeError ?? useCaseError}\n onSave={handleSave}\n onDelete={id == null ? undefined : handleDelete}\n onUseCase={handleUseCase}\n backHref={withLocale(basePath)}\n renderLink={renderLink}\n draft={draft}\n />\n );\n }\n\n return {\n entityConstructor,\n entityKey: catalogKey,\n entityLabelKey,\n entityPluralKey,\n basePath,\n ListPage,\n SingleViewPage,\n };\n}\n"],"names":["deleteUCFactory","getUCFactory","loadUCFactory","saveUCFactory","describeEntityColumns","EntifixBuildError","EntifixLogicError","envelopeEntityName","extractMetaEntity","EntityTable","useCasesForSurface","entityQueryScope","useDataLoading","useEntityMutation","useEntityRecord","TransactionSinkTag","useQueryClient","Context","useParams","useRouter","useState","useLocaleHref","useEntityNavHost","pendingFor","pendingRecordsFor","usePendingTransactions","EntityCrudForm","handOffWrite","PendingNotice","CATALOG_NEW_SLUG","slugToEntityId","useCrudRenderLink","useEntityAffordances","useEntityBulk","TARGET_NAME_PROPERTY","mergeCrudContext","adapters","repository","sink","add","asEntifixError","failure","Error","message","String","makeEntityCrud","entityConstructor","options","useAdapters","basePath","catalogKey","hiddenFields","columns","toolbar","links","metadataSource","givenMetadataSource","metadata","runBulkUseCase","runUseCase","undefined","name","metadataSourceOf","meta","key","labelKey","entityLabelKey","pluralKey","entityPluralKey","entityName","descriptors","linkPlans","map","link","descriptor","find","entry","field","linkLabelProperty","labelProperty","linkSearchProperty","searchProperty","ListPage","affordances","pending","withLocale","queryClient","scope","renderLink","optimistic","pager","uc","ctx","queryKey","canRunBulk","useCases","length","bulk","run","onCompleted","invalidateQueries","useCaseError","setUseCaseError","rowUseCases","canRunRowUseCase","handleRowUseCase","item","id","then","div","className","entries","onDismiss","dismiss","items","totalItems","hrefFor","newHref","tableProps","onUseCase","error","SingleViewPage","slug","onSaved","onDeleted","draft","loadError","router","params","entity","isLoading","reload","save","remove","isSaving","isDeleting","writeError","saveUc","deleteUc","linkSources","plan","config","loadUc","getUc","host","refreshList","toList","push","afterSave","afterDelete","handleSave","next","saved","record","handleDelete","clear","handleUseCase","onSave","onDelete","backHref","entityKey"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EntityRepositoryTag } from '@entifix/business';
|
|
2
2
|
import type { BulkOutcome, Entity, EntityConstructor, EntityFieldDescriptor, EntityId, EntityMetadataSource, EntitySelection } from '@entifix/core';
|
|
3
3
|
import type { EntityDraftStore, EntityLinkSourceConfig } from '@entifix/react-integration';
|
|
4
4
|
import type { Context } from 'effect/Context';
|
|
@@ -106,7 +106,6 @@ export interface EntityCrudOptions<TAdapters, TEntity extends Entity> {
|
|
|
106
106
|
readonly catalogKey: EntityCatalogKey;
|
|
107
107
|
/** Which adapter holds this entity's own repository. */
|
|
108
108
|
readonly repository: AdapterKey<TAdapters, Context<EntityRepositoryTag>>;
|
|
109
|
-
readonly configuration: AdapterKey<TAdapters, Context<ConfigurationRepositoryTag>>;
|
|
110
109
|
/**
|
|
111
110
|
* Members the form keeps but does not show — `id` always, plus anything a
|
|
112
111
|
* transaction assigns. A hidden member still round-trips: it is dropped from
|
|
@@ -142,6 +141,17 @@ export interface EntityCrudOptions<TAdapters, TEntity extends Entity> {
|
|
|
142
141
|
* one through a shell's own same-origin routes.
|
|
143
142
|
*/
|
|
144
143
|
readonly metadataSource?: EntityMetadataSource;
|
|
144
|
+
/**
|
|
145
|
+
* Which adapter holds this entity's {@link EntityMetadataSource} — the same
|
|
146
|
+
* source as {@link metadataSource}, resolved from the host's composition
|
|
147
|
+
* instead of built by the caller.
|
|
148
|
+
*
|
|
149
|
+
* Prefer it: a source is a transport, and naming it by key keeps the module
|
|
150
|
+
* that generates the screens free of the adapter that fetches, exactly as
|
|
151
|
+
* {@link repository} already does for records. Supplying both is a build
|
|
152
|
+
* error — two answers to where the document comes from.
|
|
153
|
+
*/
|
|
154
|
+
readonly metadata?: AdapterKey<TAdapters, EntityMetadataSource>;
|
|
145
155
|
/**
|
|
146
156
|
* Runs a `collection`-bound verb over a selection, and reports **per row**.
|
|
147
157
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"make-entity-crud.types.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/make-entity-crud.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"make-entity-crud.types.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/make-entity-crud.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,EACV,WAAW,EACX,MAAM,EACN,iBAAiB,EACjB,qBAAqB,EACrB,QAAQ,EACR,oBAAoB,EACpB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,gBAAgB,CAC1B,cAAc,GAAG,eAAe,CAAC,oBAAoB,CAAC,IACpD,CAAC,cAAc,CAAC,SAAS,CAAC,KAAK,CAAC,GAIhC,MAAM,GACN;KACG,CAAC,IAAI,MAAM,cAAc,GAAG,cAAc,CAAC,CAAC,CAAC,SAAS;QACrD,IAAI,EAAE;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAC9C,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,GACG,CAAC,GACD,KAAK;CACV,CAAC,MAAM,cAAc,CAAC,GACrB,MAAM,CAAC;AAEb;;;;;;;;;;;GAWG;AACH,KAAK,oBAAoB,GAAG,iBAAiB,SAAS;IAAE,SAAS,EAAE,MAAM,CAAC,CAAA;CAAE,GACxE,CAAC,GACD,KAAK,CAAC;AAEV,KAAK,eAAe,CAAC,UAAU,IAAI,UAAU,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACrE,CAAC,GACD,KAAK,CAAC;AAEV;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,cAAc,CAAC,SAAS;IACvC,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACtD;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACzE,4BAA4B;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,gEAAgE;IAChE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,qEAAqE;AACrE,MAAM,MAAM,UAAU,CAAC,SAAS,EAAE,MAAM,IAAI;KACzC,CAAC,IAAI,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK;CAChE,CAAC,MAAM,SAAS,CAAC,CAAC;AAEnB,MAAM,WAAW,iBAAiB,CAAC,SAAS,EAAE,OAAO,SAAS,MAAM;IAClE,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,CAAC;IACtC,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACzE;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,yFAAyF;IACzF,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;IACtD;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAC/C;;;;;;;;;OASG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;IAChE;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CACxB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,KAChC,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;IACrC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU,CAAC,OAAO,SAAS,MAAM;IAChD,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACvD,kFAAkF;IAClF,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,kFAAkF;IAClF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,YAAY,CAAC;IACtC,QAAQ,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,EAAE,yBAAyB,KAAK,YAAY,CAAC;CAC9E;AAED;;;;;;;GAOG;AAEH,MAAM,MAAM,yBAAyB,GAAG,sBAAsB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE5E;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;CAC5C"}
|