@entifix/core 0.5.0 → 0.7.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/command-palette/command-matching.d.ts +1 -1
- package/dist/command-palette/command-matching.js +1 -1
- package/dist/command-palette/command-matching.js.map +1 -1
- package/dist/command-palette/command-source.d.ts +3 -3
- package/dist/command-palette/command-source.js.map +1 -1
- package/dist/entity-definition/decorators/use-case/use-case.d.ts +1 -1
- package/dist/entity-definition/decorators/use-case/use-case.js +1 -1
- package/dist/entity-definition/decorators/use-case/use-case.js.map +1 -1
- package/dist/entity-definition/describe/describe-child-columns.d.ts +1 -1
- package/dist/entity-definition/describe/describe-child-columns.js +1 -1
- package/dist/entity-definition/describe/describe-child-columns.js.map +1 -1
- package/dist/entity-definition/describe/describe-entity-columns.d.ts +24 -0
- package/dist/entity-definition/describe/describe-entity-columns.d.ts.map +1 -1
- package/dist/entity-definition/describe/describe-entity-columns.js +36 -2
- package/dist/entity-definition/describe/describe-entity-columns.js.map +1 -1
- package/dist/entity-definition/describe/describe-entity-use-cases.d.ts +2 -1
- package/dist/entity-definition/describe/describe-entity-use-cases.d.ts.map +1 -1
- package/dist/entity-definition/describe/describe-entity-use-cases.js +2 -1
- package/dist/entity-definition/describe/describe-entity-use-cases.js.map +1 -1
- package/dist/entity-definition/helpers/entity-definition-helpers.d.ts +2 -2
- package/dist/entity-definition/helpers/entity-definition-helpers.js +2 -2
- package/dist/entity-definition/helpers/entity-definition-helpers.js.map +1 -1
- package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.d.ts +17 -4
- package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.d.ts.map +1 -1
- package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.js +1 -0
- package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.js.map +1 -1
- package/dist/entity-definition/meta-entities/meta-use-case/meta-use-case.d.ts +1 -1
- package/dist/entity-definition/meta-entities/meta-use-case/meta-use-case.js.map +1 -1
- package/dist/entity-definition/reconstruct/clone-entity-draft.d.ts +1 -1
- package/dist/entity-definition/reconstruct/clone-entity-draft.d.ts.map +1 -1
- package/dist/entity-definition/reconstruct/clone-entity-draft.js +5 -2
- package/dist/entity-definition/reconstruct/clone-entity-draft.js.map +1 -1
- package/dist/entity-definition/reconstruct/reconstruct-entity.js +1 -1
- package/dist/entity-definition/reconstruct/reconstruct-entity.js.map +1 -1
- package/dist/envelope/types.d.ts +5 -7
- package/dist/envelope/types.d.ts.map +1 -1
- package/dist/envelope/types.js.map +1 -1
- package/dist/hierarchy/hierarchy.d.ts +77 -0
- package/dist/hierarchy/hierarchy.d.ts.map +1 -0
- package/dist/hierarchy/hierarchy.js +165 -0
- package/dist/hierarchy/hierarchy.js.map +1 -0
- package/dist/hierarchy/index.d.ts +2 -0
- package/dist/hierarchy/index.d.ts.map +1 -0
- package/dist/hierarchy/index.js +3 -0
- package/dist/hierarchy/index.js.map +1 -0
- package/dist/http/session-cookies.d.ts +2 -3
- package/dist/http/session-cookies.d.ts.map +1 -1
- package/dist/http/session-cookies.js +2 -3
- package/dist/http/session-cookies.js.map +1 -1
- package/dist/i18n/locales.js +2 -2
- package/dist/i18n/locales.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types/BulkOutcome.d.ts +1 -2
- package/dist/types/BulkOutcome.d.ts.map +1 -1
- package/dist/types/BulkOutcome.js.map +1 -1
- package/dist/types/EntityAction.d.ts +1 -2
- package/dist/types/EntityAction.d.ts.map +1 -1
- package/dist/types/EntityAction.js +1 -2
- package/dist/types/EntityAction.js.map +1 -1
- package/dist/types/EntityDraft.d.ts +2 -3
- package/dist/types/EntityDraft.d.ts.map +1 -1
- package/dist/types/EntityDraft.js.map +1 -1
- package/dist/types/EntityRowDraft.d.ts +2 -3
- package/dist/types/EntityRowDraft.d.ts.map +1 -1
- package/dist/types/EntityRowDraft.js +1 -1
- package/dist/types/EntityRowDraft.js.map +1 -1
- package/dist/types/EntitySelection.d.ts +2 -3
- package/dist/types/EntitySelection.d.ts.map +1 -1
- package/dist/types/EntitySelection.js.map +1 -1
- package/dist/wizard/wizard-definition.d.ts +5 -8
- package/dist/wizard/wizard-definition.d.ts.map +1 -1
- package/dist/wizard/wizard-definition.js +3 -5
- package/dist/wizard/wizard-definition.js.map +1 -1
- package/dist/wizard/wizard-state.d.ts +4 -7
- package/dist/wizard/wizard-state.d.ts.map +1 -1
- package/dist/wizard/wizard-state.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hierarchy/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/hierarchy/index.ts"],"sourcesContent":["export * from './hierarchy.js';\n"],"names":[],"mappings":"AAAA,cAAc,iBAAiB"}
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
* configured in one bundle would never reach the others: the middleware would
|
|
10
10
|
* look for one name while a route wrote another, and the only symptom is being
|
|
11
11
|
* signed out. So the names are fixed and shared, and every reader imports them
|
|
12
|
-
* from here instead of re-declaring a literal — which is how
|
|
13
|
-
* separate copies of
|
|
14
|
-
* ([ADR 0059](../../../../../../docs/adr/0059-entifix-leaves-the-repo.md)).
|
|
12
|
+
* from here instead of re-declaring a literal — which is how one host once had
|
|
13
|
+
* five separate copies of its access-cookie name that nothing kept in step.
|
|
15
14
|
*
|
|
16
15
|
* Host-scoped in dev, where localhost shares cookies across ports, so every app
|
|
17
16
|
* and service in a local fleet reads the same session.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-cookies.d.ts","sourceRoot":"","sources":["../../src/http/session-cookies.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-cookies.d.ts","sourceRoot":"","sources":["../../src/http/session-cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,qEAAqE;AACrE,eAAO,MAAM,aAAa,eAAe,CAAC;AAE1C,kFAAkF;AAClF,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAE5C,6EAA6E;AAC7E,eAAO,MAAM,aAAa,gBAAgB,CAAC"}
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
* configured in one bundle would never reach the others: the middleware would
|
|
10
10
|
* look for one name while a route wrote another, and the only symptom is being
|
|
11
11
|
* signed out. So the names are fixed and shared, and every reader imports them
|
|
12
|
-
* from here instead of re-declaring a literal — which is how
|
|
13
|
-
* separate copies of
|
|
14
|
-
* ([ADR 0059](../../../../../../docs/adr/0059-entifix-leaves-the-repo.md)).
|
|
12
|
+
* from here instead of re-declaring a literal — which is how one host once had
|
|
13
|
+
* five separate copies of its access-cookie name that nothing kept in step.
|
|
15
14
|
*
|
|
16
15
|
* Host-scoped in dev, where localhost shares cookies across ports, so every app
|
|
17
16
|
* and service in a local fleet reads the same session.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/http/session-cookies.ts"],"sourcesContent":["/**\n * The names of the cookies an entifix application's session travels in.\n *\n * ⚠️ **Constants in one place, deliberately not a runtime option.** Three\n * packages read or write these — the Effect service shell verifies the access\n * token, the Next shell sets and clears all three, and the Playwright helpers\n * seed them — and a Next application runs its middleware, its server and its\n * client as separate bundles, each with its own module state. A prefix a host\n * configured in one bundle would never reach the others: the middleware would\n * look for one name while a route wrote another, and the only symptom is being\n * signed out. So the names are fixed and shared, and every reader imports them\n * from here instead of re-declaring a literal — which is how
|
|
1
|
+
{"version":3,"sources":["../../src/http/session-cookies.ts"],"sourcesContent":["/**\n * The names of the cookies an entifix application's session travels in.\n *\n * ⚠️ **Constants in one place, deliberately not a runtime option.** Three\n * packages read or write these — the Effect service shell verifies the access\n * token, the Next shell sets and clears all three, and the Playwright helpers\n * seed them — and a Next application runs its middleware, its server and its\n * client as separate bundles, each with its own module state. A prefix a host\n * configured in one bundle would never reach the others: the middleware would\n * look for one name while a route wrote another, and the only symptom is being\n * signed out. So the names are fixed and shared, and every reader imports them\n * from here instead of re-declaring a literal — which is how one host once had\n * five separate copies of its access-cookie name that nothing kept in step.\n *\n * Host-scoped in dev, where localhost shares cookies across ports, so every app\n * and service in a local fleet reads the same session.\n */\n\n/** Short-lived signed access token the services verify. httpOnly. */\nexport const ACCESS_COOKIE = 'entifix_at';\n\n/** Opaque session id — the revocation handle and the refresh source. httpOnly. */\nexport const SESSION_COOKIE = 'entifix_sid';\n\n/** Long-lived, opaque device id. Cleared with cookies — that is intended. */\nexport const DEVICE_COOKIE = 'entifix_did';\n"],"names":["ACCESS_COOKIE","SESSION_COOKIE","DEVICE_COOKIE"],"mappings":"AAAA;;;;;;;;;;;;;;;;CAgBC,GAED,mEAAmE,GACnE,OAAO,MAAMA,gBAAgB,aAAa;AAE1C,gFAAgF,GAChF,OAAO,MAAMC,iBAAiB,cAAc;AAE5C,2EAA2E,GAC3E,OAAO,MAAMC,gBAAgB,cAAc"}
|
package/dist/i18n/locales.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { EntifixBuildError } from '../base-entities/entifix-error/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* What a host gets when it declares nothing: English alone. Neutral rather than
|
|
4
|
-
* any one adopter's choice —
|
|
5
|
-
* now declares it like everyone else (#35).
|
|
4
|
+
* any one adopter's choice — the adopter that once supplied the `es` default
|
|
5
|
+
* here now declares it like everyone else (#35).
|
|
6
6
|
*/ const UNDECLARED = {
|
|
7
7
|
locales: [
|
|
8
8
|
'en'
|
package/dist/i18n/locales.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/i18n/locales.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\n\n/**\n * Where a host narrows {@link Locale} to the locales it ships:\n *\n * ```ts\n * declare module '@entifix/core' {\n * interface LocaleRegister {\n * locale: 'es' | 'en';\n * }\n * }\n * ```\n *\n * The same mechanism i18next's `CustomTypeOptions` uses. Without it a locale is\n * any string, which is honest for a framework that does not know which\n * languages its host speaks.\n */\n// An empty interface is the point: a host merges its declaration into it.\n// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type\nexport interface LocaleRegister {}\n\n/** A locale the host declared, or any string when it declared none. */\nexport type Locale = LocaleRegister extends { locale: infer L extends string }\n ? L\n : string;\n\n/** What a host declares through {@link defineLocales}. */\nexport interface LocaleDefinition {\n /** Every locale the host ships, in the order a switcher offers them. */\n readonly locales: readonly Locale[];\n /**\n * The locale a visitor gets when nothing else decides, and the one a missing\n * catalog key falls back to.\n */\n readonly defaultLocale: Locale;\n}\n\n/**\n * What a host gets when it declares nothing: English alone. Neutral rather than\n * any one adopter's choice —
|
|
1
|
+
{"version":3,"sources":["../../src/i18n/locales.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\n\n/**\n * Where a host narrows {@link Locale} to the locales it ships:\n *\n * ```ts\n * declare module '@entifix/core' {\n * interface LocaleRegister {\n * locale: 'es' | 'en';\n * }\n * }\n * ```\n *\n * The same mechanism i18next's `CustomTypeOptions` uses. Without it a locale is\n * any string, which is honest for a framework that does not know which\n * languages its host speaks.\n */\n// An empty interface is the point: a host merges its declaration into it.\n// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-empty-object-type\nexport interface LocaleRegister {}\n\n/** A locale the host declared, or any string when it declared none. */\nexport type Locale = LocaleRegister extends { locale: infer L extends string }\n ? L\n : string;\n\n/** What a host declares through {@link defineLocales}. */\nexport interface LocaleDefinition {\n /** Every locale the host ships, in the order a switcher offers them. */\n readonly locales: readonly Locale[];\n /**\n * The locale a visitor gets when nothing else decides, and the one a missing\n * catalog key falls back to.\n */\n readonly defaultLocale: Locale;\n}\n\n/**\n * What a host gets when it declares nothing: English alone. Neutral rather than\n * any one adopter's choice — the adopter that once supplied the `es` default\n * here now declares it like everyone else (#35).\n */\nconst UNDECLARED: LocaleDefinition = {\n locales: ['en' as Locale],\n defaultLocale: 'en' as Locale,\n};\n\nlet declared: LocaleDefinition = UNDECLARED;\n\n/**\n * Declares the host's locales. Call it once, from a module every entry point\n * imports — including a Next middleware, which is bundled on its own and shares\n * no module state with the server.\n *\n * Read at call time by every helper below and by `@entifix/i18n`,\n * `@entifix/react-controls` and `@entifix/next-i18n`, so a host declaring\n * `['en', 'es']` with `en` first gets English as the fallback everywhere, and a\n * third locale needs no change to entifix.\n */\nexport function defineLocales(definition: LocaleDefinition): void {\n const { locales: list, defaultLocale: fallback } = definition;\n if (list.length === 0) {\n throw new EntifixBuildError('defineLocales needs at least one locale.');\n }\n const duplicate = list.find(\n (locale, index) => list.indexOf(locale) !== index,\n );\n if (duplicate !== undefined) {\n throw new EntifixBuildError(\n `defineLocales was given \"${duplicate}\" more than once.`,\n );\n }\n if (!list.includes(fallback)) {\n throw new EntifixBuildError(\n `The default locale \"${fallback}\" is not one of ${JSON.stringify(list)}.`,\n );\n }\n declared = { locales: [...list], defaultLocale: fallback };\n}\n\n/** The locales the host declared. */\nexport function locales(): readonly Locale[] {\n return declared.locales;\n}\n\n/** The host's default locale. */\nexport function defaultLocale(): Locale {\n return declared.defaultLocale;\n}\n\n/** Forgets the host's declaration. For specs. */\nexport function resetLocales(): void {\n declared = UNDECLARED;\n}\n\n/**\n * Host-scoped so the choice is shared across the fleet's localhost ports in dev,\n * exactly like `entifix_at`/`entifix_sid`. Written by the locale middleware on every\n * redirect, so a visitor who lands on `/en/...` keeps English on the next visit.\n */\nexport const LOCALE_COOKIE = 'entifix_locale';\n\n/**\n * Request header the locale middleware sets when it rewrites a prefixed path.\n * Server components read it instead of a route param, which is what lets the\n * route tree stay free of an `app/[locale]` segment.\n */\nexport const LOCALE_HEADER = 'x-entifix-locale';\n\nexport function isLocale(value: unknown): value is Locale {\n return (\n typeof value === 'string' &&\n (declared.locales as readonly string[]).includes(value)\n );\n}\n"],"names":["EntifixBuildError","UNDECLARED","locales","defaultLocale","declared","defineLocales","definition","list","fallback","length","duplicate","find","locale","index","indexOf","undefined","includes","JSON","stringify","resetLocales","LOCALE_COOKIE","LOCALE_HEADER","isLocale","value"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,0CAA0C;AAqC5E;;;;CAIC,GACD,MAAMC,aAA+B;IACnCC,SAAS;QAAC;KAAe;IACzBC,eAAe;AACjB;AAEA,IAAIC,WAA6BH;AAEjC;;;;;;;;;CASC,GACD,OAAO,SAASI,cAAcC,UAA4B;IACxD,MAAM,EAAEJ,SAASK,IAAI,EAAEJ,eAAeK,QAAQ,EAAE,GAAGF;IACnD,IAAIC,KAAKE,MAAM,KAAK,GAAG;QACrB,MAAM,IAAIT,kBAAkB;IAC9B;IACA,MAAMU,YAAYH,KAAKI,IAAI,CACzB,CAACC,QAAQC,QAAUN,KAAKO,OAAO,CAACF,YAAYC;IAE9C,IAAIH,cAAcK,WAAW;QAC3B,MAAM,IAAIf,kBACR,CAAC,yBAAyB,EAAEU,UAAU,iBAAiB,CAAC;IAE5D;IACA,IAAI,CAACH,KAAKS,QAAQ,CAACR,WAAW;QAC5B,MAAM,IAAIR,kBACR,CAAC,oBAAoB,EAAEQ,SAAS,gBAAgB,EAAES,KAAKC,SAAS,CAACX,MAAM,CAAC,CAAC;IAE7E;IACAH,WAAW;QAAEF,SAAS;eAAIK;SAAK;QAAEJ,eAAeK;IAAS;AAC3D;AAEA,mCAAmC,GACnC,OAAO,SAASN;IACd,OAAOE,SAASF,OAAO;AACzB;AAEA,+BAA+B,GAC/B,OAAO,SAASC;IACd,OAAOC,SAASD,aAAa;AAC/B;AAEA,+CAA+C,GAC/C,OAAO,SAASgB;IACdf,WAAWH;AACb;AAEA;;;;CAIC,GACD,OAAO,MAAMmB,gBAAgB,iBAAiB;AAE9C;;;;CAIC,GACD,OAAO,MAAMC,gBAAgB,mBAAmB;AAEhD,OAAO,SAASC,SAASC,KAAc;IACrC,OACE,OAAOA,UAAU,YACjB,AAACnB,SAASF,OAAO,CAAuBc,QAAQ,CAACO;AAErD"}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './entity-definition/reconstruct/index.js';
|
|
|
22
22
|
export * from './entity-definition/serializer/index.js';
|
|
23
23
|
export * from './entity-definition/validate/index.js';
|
|
24
24
|
export * from './envelope/index.js';
|
|
25
|
+
export * from './hierarchy/index.js';
|
|
25
26
|
export * from './http/session-cookies.js';
|
|
26
27
|
export * from './i18n/format.js';
|
|
27
28
|
export * from './i18n/locales.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+CAA+C,CAAC;AAC9D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kDAAkD,CAAC;AACjE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,kDAAkD,CAAC;AACjE,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uDAAuD,CAAC;AACtE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uDAAuD,CAAC;AACtE,cAAc,iDAAiD,CAAC;AAChE,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,wDAAwD,CAAC;AACvE,cAAc,0DAA0D,CAAC;AACzE,cAAc,uCAAuC,CAAC;AACtD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+CAA+C,CAAC;AAC9D,cAAc,wCAAwC,CAAC;AACvD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kDAAkD,CAAC;AACjE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,gDAAgD,CAAC;AAC/D,cAAc,kDAAkD,CAAC;AACjE,cAAc,uCAAuC,CAAC;AACtD,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,uDAAuD,CAAC;AACtE,cAAc,2DAA2D,CAAC;AAC1E,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uDAAuD,CAAC;AACtE,cAAc,iDAAiD,CAAC;AAChE,cAAc,0DAA0D,CAAC;AACzE,cAAc,wDAAwD,CAAC;AACvE,cAAc,wDAAwD,CAAC;AACvE,cAAc,0DAA0D,CAAC;AACzE,cAAc,uCAAuC,CAAC;AACtD,cAAc,0CAA0C,CAAC;AACzD,cAAc,yCAAyC,CAAC;AACxD,cAAc,uCAAuC,CAAC;AACtD,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export * from './entity-definition/reconstruct/index.js';
|
|
|
22
22
|
export * from './entity-definition/serializer/index.js';
|
|
23
23
|
export * from './entity-definition/validate/index.js';
|
|
24
24
|
export * from './envelope/index.js';
|
|
25
|
+
export * from './hierarchy/index.js';
|
|
25
26
|
export * from './http/session-cookies.js';
|
|
26
27
|
export * from './i18n/format.js';
|
|
27
28
|
export * from './i18n/locales.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './base-entities/configuration-client/index.js';\nexport * from './base-entities/entifix-error/index.js';\nexport * from './command-palette/index.js';\nexport * from './entity-definition/decorators/accessor/index.js';\nexport * from './entity-definition/decorators/entity/index.js';\nexport * from './entity-definition/decorators/method/index.js';\nexport * from './entity-definition/decorators/use-case/index.js';\nexport * from './entity-definition/describe/index.js';\nexport * from './entity-definition/field-path.js';\nexport * from './entity-definition/helpers/index.js';\nexport * from './entity-definition/links/apply-entity-links/index.js';\nexport * from './entity-definition/links/entity-collection-link/index.js';\nexport * from './entity-definition/links/entity-link/index.js';\nexport * from './entity-definition/links/entity-link-source/index.js';\nexport * from './entity-definition/links/link-targets/index.js';\nexport * from './entity-definition/meta-entities/meta-accessor/index.js';\nexport * from './entity-definition/meta-entities/meta-entity/index.js';\nexport * from './entity-definition/meta-entities/meta-method/index.js';\nexport * from './entity-definition/meta-entities/meta-use-case/index.js';\nexport * from './entity-definition/metadata/index.js';\nexport * from './entity-definition/reconstruct/index.js';\nexport * from './entity-definition/serializer/index.js';\nexport * from './entity-definition/validate/index.js';\nexport * from './envelope/index.js';\nexport * from './http/session-cookies.js';\nexport * from './i18n/format.js';\nexport * from './i18n/locales.js';\nexport * from './i18n/localized-text.js';\nexport * from './i18n/negotiate.js';\nexport * from './query/in-memory-query.js';\nexport * from './rsql/index.js';\nexport * from './types/BulkOutcome.js';\nexport * from './types/Entity.js';\nexport * from './types/EntityAction.js';\nexport * from './types/EntityDraft.js';\nexport * from './types/EntityFiltering.js';\nexport * from './types/EntityLoadRequest.js';\nexport * from './types/EntityPage.js';\nexport * from './types/EntityRowDraft.js';\nexport * from './types/EntitySelection.js';\nexport * from './types/EntitySorting.js';\nexport * from './types/Json.js';\nexport * from './types/StandardSchema.js';\nexport * from './types/UseCase.js';\nexport * from './wizard/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,gDAAgD;AAC9D,cAAc,yCAAyC;AACvD,cAAc,6BAA6B;AAC3C,cAAc,mDAAmD;AACjE,cAAc,iDAAiD;AAC/D,cAAc,iDAAiD;AAC/D,cAAc,mDAAmD;AACjE,cAAc,wCAAwC;AACtD,cAAc,oCAAoC;AAClD,cAAc,uCAAuC;AACrD,cAAc,wDAAwD;AACtE,cAAc,4DAA4D;AAC1E,cAAc,iDAAiD;AAC/D,cAAc,wDAAwD;AACtE,cAAc,kDAAkD;AAChE,cAAc,2DAA2D;AACzE,cAAc,yDAAyD;AACvE,cAAc,yDAAyD;AACvE,cAAc,2DAA2D;AACzE,cAAc,wCAAwC;AACtD,cAAc,2CAA2C;AACzD,cAAc,0CAA0C;AACxD,cAAc,wCAAwC;AACtD,cAAc,sBAAsB;AACpC,cAAc,4BAA4B;AAC1C,cAAc,mBAAmB;AACjC,cAAc,oBAAoB;AAClC,cAAc,2BAA2B;AACzC,cAAc,sBAAsB;AACpC,cAAc,6BAA6B;AAC3C,cAAc,kBAAkB;AAChC,cAAc,yBAAyB;AACvC,cAAc,oBAAoB;AAClC,cAAc,0BAA0B;AACxC,cAAc,yBAAyB;AACvC,cAAc,6BAA6B;AAC3C,cAAc,+BAA+B;AAC7C,cAAc,wBAAwB;AACtC,cAAc,4BAA4B;AAC1C,cAAc,6BAA6B;AAC3C,cAAc,2BAA2B;AACzC,cAAc,kBAAkB;AAChC,cAAc,4BAA4B;AAC1C,cAAc,qBAAqB;AACnC,cAAc,oBAAoB"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from './base-entities/configuration-client/index.js';\nexport * from './base-entities/entifix-error/index.js';\nexport * from './command-palette/index.js';\nexport * from './entity-definition/decorators/accessor/index.js';\nexport * from './entity-definition/decorators/entity/index.js';\nexport * from './entity-definition/decorators/method/index.js';\nexport * from './entity-definition/decorators/use-case/index.js';\nexport * from './entity-definition/describe/index.js';\nexport * from './entity-definition/field-path.js';\nexport * from './entity-definition/helpers/index.js';\nexport * from './entity-definition/links/apply-entity-links/index.js';\nexport * from './entity-definition/links/entity-collection-link/index.js';\nexport * from './entity-definition/links/entity-link/index.js';\nexport * from './entity-definition/links/entity-link-source/index.js';\nexport * from './entity-definition/links/link-targets/index.js';\nexport * from './entity-definition/meta-entities/meta-accessor/index.js';\nexport * from './entity-definition/meta-entities/meta-entity/index.js';\nexport * from './entity-definition/meta-entities/meta-method/index.js';\nexport * from './entity-definition/meta-entities/meta-use-case/index.js';\nexport * from './entity-definition/metadata/index.js';\nexport * from './entity-definition/reconstruct/index.js';\nexport * from './entity-definition/serializer/index.js';\nexport * from './entity-definition/validate/index.js';\nexport * from './envelope/index.js';\nexport * from './hierarchy/index.js';\nexport * from './http/session-cookies.js';\nexport * from './i18n/format.js';\nexport * from './i18n/locales.js';\nexport * from './i18n/localized-text.js';\nexport * from './i18n/negotiate.js';\nexport * from './query/in-memory-query.js';\nexport * from './rsql/index.js';\nexport * from './types/BulkOutcome.js';\nexport * from './types/Entity.js';\nexport * from './types/EntityAction.js';\nexport * from './types/EntityDraft.js';\nexport * from './types/EntityFiltering.js';\nexport * from './types/EntityLoadRequest.js';\nexport * from './types/EntityPage.js';\nexport * from './types/EntityRowDraft.js';\nexport * from './types/EntitySelection.js';\nexport * from './types/EntitySorting.js';\nexport * from './types/Json.js';\nexport * from './types/StandardSchema.js';\nexport * from './types/UseCase.js';\nexport * from './wizard/index.js';\n"],"names":[],"mappings":"AAAA,cAAc,gDAAgD;AAC9D,cAAc,yCAAyC;AACvD,cAAc,6BAA6B;AAC3C,cAAc,mDAAmD;AACjE,cAAc,iDAAiD;AAC/D,cAAc,iDAAiD;AAC/D,cAAc,mDAAmD;AACjE,cAAc,wCAAwC;AACtD,cAAc,oCAAoC;AAClD,cAAc,uCAAuC;AACrD,cAAc,wDAAwD;AACtE,cAAc,4DAA4D;AAC1E,cAAc,iDAAiD;AAC/D,cAAc,wDAAwD;AACtE,cAAc,kDAAkD;AAChE,cAAc,2DAA2D;AACzE,cAAc,yDAAyD;AACvE,cAAc,yDAAyD;AACvE,cAAc,2DAA2D;AACzE,cAAc,wCAAwC;AACtD,cAAc,2CAA2C;AACzD,cAAc,0CAA0C;AACxD,cAAc,wCAAwC;AACtD,cAAc,sBAAsB;AACpC,cAAc,uBAAuB;AACrC,cAAc,4BAA4B;AAC1C,cAAc,mBAAmB;AACjC,cAAc,oBAAoB;AAClC,cAAc,2BAA2B;AACzC,cAAc,sBAAsB;AACpC,cAAc,6BAA6B;AAC3C,cAAc,kBAAkB;AAChC,cAAc,yBAAyB;AACvC,cAAc,oBAAoB;AAClC,cAAc,0BAA0B;AACxC,cAAc,yBAAyB;AACvC,cAAc,6BAA6B;AAC3C,cAAc,+BAA+B;AAC7C,cAAc,wBAAwB;AACtC,cAAc,4BAA4B;AAC1C,cAAc,6BAA6B;AAC3C,cAAc,2BAA2B;AACzC,cAAc,kBAAkB;AAChC,cAAc,4BAA4B;AAC1C,cAAc,qBAAqB;AACnC,cAAc,oBAAoB"}
|
|
@@ -11,8 +11,7 @@ import type { EntityId } from './Entity.js';
|
|
|
11
11
|
* `code` is an **error code**, never a sentence. Services answer
|
|
12
12
|
* `{ error, code, detail }` and the browser renders the code through the
|
|
13
13
|
* `errors` catalog, so a failure reason stays translatable and a new one fails
|
|
14
|
-
*
|
|
15
|
-
* ([docs/I18N.md](../../../../../../docs/I18N.md)).
|
|
14
|
+
* a host's catalog check rather than reaching a user in English.
|
|
16
15
|
*/
|
|
17
16
|
export interface BulkOutcome {
|
|
18
17
|
id: EntityId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BulkOutcome.d.ts","sourceRoot":"","sources":["../../src/types/BulkOutcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C
|
|
1
|
+
{"version":3,"file":"BulkOutcome.d.ts","sourceRoot":"","sources":["../../src/types/BulkOutcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,QAAQ,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,QAAQ,EAAE,CAEtE;AAED,kCAAkC;AAClC,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,MAAM,CAEvE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/BulkOutcome.ts"],"sourcesContent":["import type { EntityId } from './Entity.js';\n\n/**\n * What happened to **one** row of a bulk action.\n *\n * Partial failure is the normal case, not an edge one: 40 rows selected, 3\n * refused. A single success/failure result for the whole run lies in both\n * directions — reported as a failure it hides the 37 that were written, and\n * reported as a success it hides the 3 that were not, which is the worse half\n * because the user walks away believing the work is done.\n *\n * `code` is an **error code**, never a sentence. Services answer\n * `{ error, code, detail }` and the browser renders the code through the\n * `errors` catalog, so a failure reason stays translatable and a new one fails\n *
|
|
1
|
+
{"version":3,"sources":["../../src/types/BulkOutcome.ts"],"sourcesContent":["import type { EntityId } from './Entity.js';\n\n/**\n * What happened to **one** row of a bulk action.\n *\n * Partial failure is the normal case, not an edge one: 40 rows selected, 3\n * refused. A single success/failure result for the whole run lies in both\n * directions — reported as a failure it hides the 37 that were written, and\n * reported as a success it hides the 3 that were not, which is the worse half\n * because the user walks away believing the work is done.\n *\n * `code` is an **error code**, never a sentence. Services answer\n * `{ error, code, detail }` and the browser renders the code through the\n * `errors` catalog, so a failure reason stays translatable and a new one fails\n * a host's catalog check rather than reaching a user in English.\n */\nexport interface BulkOutcome {\n id: EntityId;\n ok: boolean;\n /** Present only when `ok` is false. */\n code?: string;\n}\n\n/** The ids that failed — what a retry re-runs, and nothing else. */\nexport function failedIds(outcomes: readonly BulkOutcome[]): EntityId[] {\n return outcomes.filter(outcome => !outcome.ok).map(outcome => outcome.id);\n}\n\n/** How many rows were written. */\nexport function succeededCount(outcomes: readonly BulkOutcome[]): number {\n return outcomes.filter(outcome => outcome.ok).length;\n}\n"],"names":["failedIds","outcomes","filter","outcome","ok","map","id","succeededCount","length"],"mappings":"AAuBA,kEAAkE,GAClE,OAAO,SAASA,UAAUC,QAAgC;IACxD,OAAOA,SAASC,MAAM,CAACC,CAAAA,UAAW,CAACA,QAAQC,EAAE,EAAEC,GAAG,CAACF,CAAAA,UAAWA,QAAQG,EAAE;AAC1E;AAEA,gCAAgC,GAChC,OAAO,SAASC,eAAeN,QAAgC;IAC7D,OAAOA,SAASC,MAAM,CAACC,CAAAA,UAAWA,QAAQC,EAAE,EAAEI,MAAM;AACtD"}
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
* the *permission* built from an action and re-exports these as `Actions` /
|
|
8
8
|
* `Action`; `@entifix/react-controls` reads the same triple off a served
|
|
9
9
|
* `EntityMetadataDocument` to decide whether to render Save and Delete, and
|
|
10
|
-
* a T3 package may not depend on `@entifix/authz` (T4)
|
|
11
|
-
* ([ADR 0026](../../../../../../docs/adr/0026-the-use-case-descriptor-and-served-entity-metadata.md)).
|
|
10
|
+
* a T3 package may not depend on `@entifix/authz` (T4).
|
|
12
11
|
*/
|
|
13
12
|
export declare const ENTITY_ACTIONS: readonly ["read", "write", "delete"];
|
|
14
13
|
export type EntityAction = (typeof ENTITY_ACTIONS)[number];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityAction.d.ts","sourceRoot":"","sources":["../../src/types/EntityAction.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"EntityAction.d.ts","sourceRoot":"","sources":["../../src/types/EntityAction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,sCAAuC,CAAC;AACnE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
* the *permission* built from an action and re-exports these as `Actions` /
|
|
8
8
|
* `Action`; `@entifix/react-controls` reads the same triple off a served
|
|
9
9
|
* `EntityMetadataDocument` to decide whether to render Save and Delete, and
|
|
10
|
-
* a T3 package may not depend on `@entifix/authz` (T4)
|
|
11
|
-
* ([ADR 0026](../../../../../../docs/adr/0026-the-use-case-descriptor-and-served-entity-metadata.md)).
|
|
10
|
+
* a T3 package may not depend on `@entifix/authz` (T4).
|
|
12
11
|
*/ export const ENTITY_ACTIONS = [
|
|
13
12
|
'read',
|
|
14
13
|
'write',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/EntityAction.ts"],"sourcesContent":["/**\n * What a caller wants to do with a resource: the CRUD triple every entity has\n * by construction, as opposed to a verb a domain declares with `@useCase()`.\n *\n * It lives in `core` rather than in the authorization vocabulary because two\n * packages that may not import each other both need it. `@entifix/authz` owns\n * the *permission* built from an action and re-exports these as `Actions` /\n * `Action`; `@entifix/react-controls` reads the same triple off a served\n * `EntityMetadataDocument` to decide whether to render Save and Delete, and\n * a T3 package may not depend on `@entifix/authz` (T4)
|
|
1
|
+
{"version":3,"sources":["../../src/types/EntityAction.ts"],"sourcesContent":["/**\n * What a caller wants to do with a resource: the CRUD triple every entity has\n * by construction, as opposed to a verb a domain declares with `@useCase()`.\n *\n * It lives in `core` rather than in the authorization vocabulary because two\n * packages that may not import each other both need it. `@entifix/authz` owns\n * the *permission* built from an action and re-exports these as `Actions` /\n * `Action`; `@entifix/react-controls` reads the same triple off a served\n * `EntityMetadataDocument` to decide whether to render Save and Delete, and\n * a T3 package may not depend on `@entifix/authz` (T4).\n */\nexport const ENTITY_ACTIONS = ['read', 'write', 'delete'] as const;\nexport type EntityAction = (typeof ENTITY_ACTIONS)[number];\n"],"names":["ENTITY_ACTIONS"],"mappings":"AAAA;;;;;;;;;;CAUC,GACD,OAAO,MAAMA,iBAAiB;IAAC;IAAQ;IAAS;CAAS,CAAU"}
|
|
@@ -10,14 +10,13 @@ import type { EntityRowDraft } from './EntityRowDraft.js';
|
|
|
10
10
|
* A **`composition`** member's value is an `EntityRowDraft[]` — rows the master
|
|
11
11
|
* owns, each a string map carrying its own `ROW_KEY`. That is the whole reason
|
|
12
12
|
* this is a union and not `string`: rows have no lossless string form, and the
|
|
13
|
-
* two shapes a draft can hold are exactly the two
|
|
13
|
+
* two shapes a draft can hold are exactly the two composition metadata names.
|
|
14
14
|
*
|
|
15
15
|
* A **`localized`** member's value is an {@link EntityLocalizedDraft}, a text per
|
|
16
16
|
* locale.
|
|
17
17
|
*
|
|
18
18
|
* **Exactly three shapes, and the union says so rather than `JsonValue`.** Both
|
|
19
|
-
* are JSON — a workspace autosaves a draft through `createJSONStorage
|
|
20
|
-
* ([ADR 0032](../../../../../../docs/adr/0032-what-may-live-in-an-autosaved-draft.md)),
|
|
19
|
+
* are JSON — a workspace autosaves a draft through `createJSONStorage`,
|
|
21
20
|
* so a class instance, an `EntityLink` or a `Date` does not degrade, it returns
|
|
22
21
|
* as something else, silently — but `JsonValue` would say more than is true. A
|
|
23
22
|
* member whose value is neither a string nor a row list has no editor, no
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityDraft.d.ts","sourceRoot":"","sources":["../../src/types/EntityDraft.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D
|
|
1
|
+
{"version":3,"file":"EntityDraft.d.ts","sourceRoot":"","sources":["../../src/types/EntityDraft.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,MAAM,gBAAgB,GAC1B,MAAM,GAAG,SAAS,cAAc,EAAE,GAAG,oBAAoB,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEpE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAE3D;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,GACX,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,GACX,oBAAoB,CActB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/EntityDraft.ts"],"sourcesContent":["import type { EntityRowDraft } from './EntityRowDraft.js';\n\n/**\n * A form's in-progress values, keyed by accessor name.\n *\n * A scalar member's value is a **string**, because that is what a native input\n * round-trips: a `scalarCollection` drafts as a comma list, a `date` as\n * `yyyy-mm-dd`, a relation as its foreign key. Reading one is therefore still\n * `values[name] ?? ''` in every place that only edits scalars.\n *\n * A **`composition`** member's value is an `EntityRowDraft[]` — rows the master\n * owns, each a string map carrying its own `ROW_KEY`. That is the whole reason\n * this is a union and not `string`: rows have no lossless string form, and the\n * two shapes a draft can hold are exactly the two
|
|
1
|
+
{"version":3,"sources":["../../src/types/EntityDraft.ts"],"sourcesContent":["import type { EntityRowDraft } from './EntityRowDraft.js';\n\n/**\n * A form's in-progress values, keyed by accessor name.\n *\n * A scalar member's value is a **string**, because that is what a native input\n * round-trips: a `scalarCollection` drafts as a comma list, a `date` as\n * `yyyy-mm-dd`, a relation as its foreign key. Reading one is therefore still\n * `values[name] ?? ''` in every place that only edits scalars.\n *\n * A **`composition`** member's value is an `EntityRowDraft[]` — rows the master\n * owns, each a string map carrying its own `ROW_KEY`. That is the whole reason\n * this is a union and not `string`: rows have no lossless string form, and the\n * two shapes a draft can hold are exactly the two composition metadata names.\n *\n * A **`localized`** member's value is an {@link EntityLocalizedDraft}, a text per\n * locale.\n *\n * **Exactly three shapes, and the union says so rather than `JsonValue`.** Both\n * are JSON — a workspace autosaves a draft through `createJSONStorage`,\n * so a class instance, an `EntityLink` or a `Date` does not degrade, it returns\n * as something else, silently — but `JsonValue` would say more than is true. A\n * member whose value is neither a string nor a row list has no editor, no\n * coercion and no restore rule: `reconstructEntity` would carry it through\n * untouched and the serializer would write it.\n *\n * The narrower union is also what keeps the form engine compiling. `JsonValue`\n * is **recursive**, and TanStack Form derives a field-path type from the value\n * it is given, so a draft typed that way makes that derivation unbounded —\n * `Type instantiation is excessively deep and possibly infinite`. A row is a\n * flat string map, so this union has a bottom and the derivation terminates.\n *\n * The alias exists in **one** place on purpose. The same structural type was\n * declared four times — `EntityFormValues`, `EntityFormDraft`, `EntityLinkDraft`\n * and `EntityCrudDraft` — and collapsing them is what made this widening a\n * single edit rather than four found by search.\n */\nexport type EntityDraftValue =\n string | readonly EntityRowDraft[] | EntityLocalizedDraft;\n\n/**\n * A `localized` member's draft: one string per locale, the text each input\n * holds. Flat like a row, so the union still has a bottom for TanStack Form to\n * derive paths from, and JSON-safe for the same autosave a row has to survive.\n * Told apart from a row list by not being an array.\n */\nexport type EntityLocalizedDraft = Readonly<Record<string, string>>;\n\nexport type EntityDraft = Record<string, EntityDraftValue>;\n\n/**\n * The string a draft holds for a scalar member.\n *\n * A draft value is `JsonValue` now, but every scalar editor still writes and\n * reads a string, so this is the read every one of them performs — in one\n * place, so a member that came back from storage as a number or `null` renders\n * as an empty input instead of flipping React from controlled to uncontrolled\n * mid-render.\n */\nexport function readDraftString(\n values: EntityDraft | undefined,\n name: string,\n): string {\n const value = values?.[name];\n return typeof value === 'string' ? value : '';\n}\n\n/**\n * The per-locale texts a draft holds for a `localized` member, or an empty map\n * for anything else — a member never seeded, or one that came back from\n * storage in another shape.\n */\nexport function readLocalizedDraft(\n values: EntityDraft | undefined,\n name: string,\n): EntityLocalizedDraft {\n const value = values?.[name];\n if (\n value === undefined ||\n typeof value === 'string' ||\n Array.isArray(value)\n ) {\n return {};\n }\n return Object.fromEntries(\n Object.entries(value as EntityLocalizedDraft).filter(\n ([, text]) => typeof text === 'string',\n ),\n );\n}\n"],"names":["readDraftString","values","name","value","readLocalizedDraft","undefined","Array","isArray","Object","fromEntries","entries","filter","text"],"mappings":"AAkDA;;;;;;;;CAQC,GACD,OAAO,SAASA,gBACdC,MAA+B,EAC/BC,IAAY;IAEZ,MAAMC,QAAQF,0BAAAA,MAAQ,CAACC,KAAK;IAC5B,OAAO,OAAOC,UAAU,WAAWA,QAAQ;AAC7C;AAEA;;;;CAIC,GACD,OAAO,SAASC,mBACdH,MAA+B,EAC/BC,IAAY;IAEZ,MAAMC,QAAQF,0BAAAA,MAAQ,CAACC,KAAK;IAC5B,IACEC,UAAUE,aACV,OAAOF,UAAU,YACjBG,MAAMC,OAAO,CAACJ,QACd;QACA,OAAO,CAAC;IACV;IACA,OAAOK,OAAOC,WAAW,CACvBD,OAAOE,OAAO,CAACP,OAA+BQ,MAAM,CAClD,CAAC,GAAGC,KAAK,GAAK,OAAOA,SAAS;AAGpC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* A `composition`'s rows are editable before the record they belong to has ever
|
|
5
5
|
* been saved, and the child is a **value** — `OrderItem` has no `id`, no domain
|
|
6
6
|
* and no permission namespace
|
|
7
|
-
|
|
7
|
+
*. So the
|
|
8
8
|
* only identity a row can have is one the browser mints, and it exists for
|
|
9
9
|
* exactly two jobs: a stable React key, and stable focus across a re-render.
|
|
10
10
|
*
|
|
@@ -20,8 +20,7 @@ export declare const ROW_KEY = "$key";
|
|
|
20
20
|
*
|
|
21
21
|
* Members are strings for the reason every draft value is a string — that is
|
|
22
22
|
* what a native input round-trips — and the whole row is JSON, because the
|
|
23
|
-
* workspace autosaves the master's draft through `createJSONStorage
|
|
24
|
-
* ([ADR 0032](../../../../../../docs/adr/0032-what-may-live-in-an-autosaved-draft.md)).
|
|
23
|
+
* workspace autosaves the master's draft through `createJSONStorage`.
|
|
25
24
|
* The key rides **inside** the row rather than in a parallel array precisely so
|
|
26
25
|
* that it survives that round trip attached to the row it identifies; a parallel
|
|
27
26
|
* structure re-introduces the index identity this exists to avoid.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityRowDraft.d.ts","sourceRoot":"","sources":["../../src/types/EntityRowDraft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,OAAO,SAAS,CAAC;AAE9B
|
|
1
|
+
{"version":3,"file":"EntityRowDraft.d.ts","sourceRoot":"","sources":["../../src/types/EntityRowDraft.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,OAAO,SAAS,CAAC;AAE9B;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,cAAc,GAAG;IAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,EAAE,CAczE;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,EAAE,CAE9D"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* A `composition`'s rows are editable before the record they belong to has ever
|
|
5
5
|
* been saved, and the child is a **value** — `OrderItem` has no `id`, no domain
|
|
6
6
|
* and no permission namespace
|
|
7
|
-
|
|
7
|
+
*. So the
|
|
8
8
|
* only identity a row can have is one the browser mints, and it exists for
|
|
9
9
|
* exactly two jobs: a stable React key, and stable focus across a re-render.
|
|
10
10
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/EntityRowDraft.ts"],"sourcesContent":["/**\n * The member a row draft carries its client-side identity in.\n *\n * A `composition`'s rows are editable before the record they belong to has ever\n * been saved, and the child is a **value** — `OrderItem` has no `id`, no domain\n * and no permission namespace\n
|
|
1
|
+
{"version":3,"sources":["../../src/types/EntityRowDraft.ts"],"sourcesContent":["/**\n * The member a row draft carries its client-side identity in.\n *\n * A `composition`'s rows are editable before the record they belong to has ever\n * been saved, and the child is a **value** — `OrderItem` has no `id`, no domain\n * and no permission namespace\n *. So the\n * only identity a row can have is one the browser mints, and it exists for\n * exactly two jobs: a stable React key, and stable focus across a re-render.\n *\n * `$` is not a legal first character for any member this repo declares, but that\n * is a convention rather than a rule, so `describeChildColumns` **throws** on a\n * child that declares this name instead of trusting it. A silent collision would\n * be a row whose key is overwritten by its own data — it would re-key on every\n * keystroke, lose focus mid-word, and address the wrong row's errors.\n */\nexport const ROW_KEY = '$key';\n\n/**\n * One row of an owned collection, as a form holds it.\n *\n * Members are strings for the reason every draft value is a string — that is\n * what a native input round-trips — and the whole row is JSON, because the\n * workspace autosaves the master's draft through `createJSONStorage`.\n * The key rides **inside** the row rather than in a parallel array precisely so\n * that it survives that round trip attached to the row it identifies; a parallel\n * structure re-introduces the index identity this exists to avoid.\n *\n * {@link ROW_KEY} never reaches the wire: `reconstructChild` drops it when it\n * rebuilds the child, so the serializer never sees a member the entity does not\n * declare.\n */\nexport type EntityRowDraft = { [member: string]: string };\n\n/**\n * Mints a key for a row the user has just added.\n *\n * `crypto.randomUUID` rather than a counter, because two drafts restored into\n * one session must not collide, and a counter that resets on reload would\n * re-issue keys a persisted draft is still using.\n */\nexport function newRowKey(): string {\n return crypto.randomUUID();\n}\n\n/**\n * The key of the *n*th row as the record supplied it.\n *\n * Deterministic, and that is load-bearing rather than tidy. Seeding is not a\n * one-time event: `useEntityForm` re-seeds whenever its `entity` changes\n * identity, and a caller that builds the record inline re-seeds on **every\n * render**. Minting a fresh random key there produces a draft that differs from\n * the last one on every pass, so the form engine sees its defaults change\n * forever and React stops with `Maximum update depth exceeded` — a live hang,\n * not a test artifact.\n *\n * Positional is safe *here* precisely because it describes the record rather\n * than the live list: re-seeding the same record yields the same keys, and once\n * the user adds or removes anything the keys already in the draft are carried\n * through untouched. A row added at runtime takes a {@link newRowKey} instead,\n * which can never collide with this shape.\n */\nexport function seededRowKey(index: number): string {\n return `row-${index}`;\n}\n\n/**\n * Whether a draft value is a usable list of rows.\n *\n * A restored draft is `JsonValue`, so a composition member can come back as\n * anything — a string written by a build before this existed, `null`, or an\n * array of the wrong shape. Every caller must ask before reading, and the answer\n * for a value that fails is an empty grid rather than a crash: an unreadable\n * draft is an unfinished edit, and `restoreEntityDraft` already discards those\n * per entry rather than losing the whole form.\n *\n * A row missing its {@link ROW_KEY} fails, deliberately. Minting one here would\n * hide the case where a row was written by something that does not know the\n * contract, and the missing key would then be minted afresh on every render.\n */\nexport function isRowDraftArray(value: unknown): value is EntityRowDraft[] {\n return (\n Array.isArray(value) &&\n value.every(\n row =>\n typeof row === 'object' &&\n row !== null &&\n !Array.isArray(row) &&\n typeof (row as Record<string, unknown>)[ROW_KEY] === 'string' &&\n Object.values(row as Record<string, unknown>).every(\n member => typeof member === 'string',\n ),\n )\n );\n}\n\n/** The rows a draft value holds, or an empty list when it holds none usable. */\nexport function readRowDrafts(value: unknown): EntityRowDraft[] {\n return isRowDraftArray(value) ? value : [];\n}\n"],"names":["ROW_KEY","newRowKey","crypto","randomUUID","seededRowKey","index","isRowDraftArray","value","Array","isArray","every","row","Object","values","member","readRowDrafts"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMA,UAAU,OAAO;AAkB9B;;;;;;CAMC,GACD,OAAO,SAASC;IACd,OAAOC,OAAOC,UAAU;AAC1B;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,aAAaC,KAAa;IACxC,OAAO,CAAC,IAAI,EAAEA,OAAO;AACvB;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,gBAAgBC,KAAc;IAC5C,OACEC,MAAMC,OAAO,CAACF,UACdA,MAAMG,KAAK,CACTC,CAAAA,MACE,OAAOA,QAAQ,YACfA,QAAQ,QACR,CAACH,MAAMC,OAAO,CAACE,QACf,OAAO,AAACA,GAA+B,CAACX,QAAQ,KAAK,YACrDY,OAAOC,MAAM,CAACF,KAAgCD,KAAK,CACjDI,CAAAA,SAAU,OAAOA,WAAW;AAItC;AAEA,8EAA8E,GAC9E,OAAO,SAASC,cAAcR,KAAc;IAC1C,OAAOD,gBAAgBC,SAASA,QAAQ,EAAE;AAC5C"}
|
|
@@ -20,7 +20,7 @@ import type { FilterGroup } from './EntityFiltering.js';
|
|
|
20
20
|
* `total` is carried rather than derived because only the server knows it, and
|
|
21
21
|
* the count is the whole affordance — "Retirar 3.200 marcas" is a different
|
|
22
22
|
* sentence from "Retirar", and the user has to read the number *before* they
|
|
23
|
-
* confirm
|
|
23
|
+
* confirm.
|
|
24
24
|
*/
|
|
25
25
|
export type EntitySelection<TEntity extends Entity> = {
|
|
26
26
|
mode: 'ids';
|
|
@@ -73,8 +73,7 @@ export declare function selectionIds<TEntity extends Entity>(selection: EntitySe
|
|
|
73
73
|
*
|
|
74
74
|
* `Set` does not survive `JSON.stringify` — it serializes to `{}`, silently,
|
|
75
75
|
* and arrives as an empty object that reads as "no rows excluded" rather than
|
|
76
|
-
* as an error. That is the same class of fault
|
|
77
|
-
* drafts, and it is worse here: a `matching` selection whose exclusions
|
|
76
|
+
* as an error. That is the same class of fault an autosaved draft meets, and it is worse here: a `matching` selection whose exclusions
|
|
78
77
|
* evaporate acts on rows the operator deliberately took out.
|
|
79
78
|
*
|
|
80
79
|
* So the wire form is arrays and the in-memory form is sets, named separately
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntitySelection.d.ts","sourceRoot":"","sources":["../../src/types/EntitySelection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,eAAe,CAAC,OAAO,SAAS,MAAM,IAC9C;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;CAC5B,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,gFAAgF;IAChF,SAAS,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;CACjC,CAAC;AAEN,yEAAyE;AACzE,eAAO,MAAM,cAAc,GACzB,OAAO,SAAS,MAAM,OACnB,eAAe,CAAC,OAAO,CAAgD,CAAC;AAE7E;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,SAAS,MAAM,EAClD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,GAClC,MAAM,CAIR;AAED,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,OAAO,SAAS,MAAM,EAC/C,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,EACnC,EAAE,EAAE,QAAQ,GACX,OAAO,CAIT;AAED,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,MAAM,EACrD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,GAClC,OAAO,CAET;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS,MAAM,EACnD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,EACnC,EAAE,EAAE,QAAQ,EACZ,QAAQ,EAAE,OAAO,GAChB,eAAe,CAAC,OAAO,CAAC,CAY1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,MAAM,EACjD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,GAClC,QAAQ,EAAE,GAAG,SAAS,CAExB;AAED
|
|
1
|
+
{"version":3,"file":"EntitySelection.d.ts","sourceRoot":"","sources":["../../src/types/EntitySelection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,eAAe,CAAC,OAAO,SAAS,MAAM,IAC9C;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,GAAG,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;CAC5B,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,gFAAgF;IAChF,SAAS,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;CACjC,CAAC;AAEN,yEAAyE;AACzE,eAAO,MAAM,cAAc,GACzB,OAAO,SAAS,MAAM,OACnB,eAAe,CAAC,OAAO,CAAgD,CAAC;AAE7E;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,SAAS,MAAM,EAClD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,GAClC,MAAM,CAIR;AAED,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,OAAO,SAAS,MAAM,EAC/C,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,EACnC,EAAE,EAAE,QAAQ,GACX,OAAO,CAIT;AAED,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,MAAM,EACrD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,GAClC,OAAO,CAET;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS,MAAM,EACnD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,EACnC,EAAE,EAAE,QAAQ,EACZ,QAAQ,EAAE,OAAO,GAChB,eAAe,CAAC,OAAO,CAAC,CAY1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,MAAM,EACjD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,GAClC,QAAQ,EAAE,GAAG,SAAS,CAExB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,mBAAmB,CAAC,OAAO,SAAS,MAAM,IAClD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,QAAQ,EAAE,CAAA;CAAE,GAChC;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB,CAAC;AAEN;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,EACpD,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,GAClC,mBAAmB,CAAC,OAAO,CAAC,CAI9B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,MAAM,EACtD,KAAK,EAAE,OAAO,GACb,eAAe,CAAC,OAAO,CAAC,GAAG,SAAS,CA0BtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/EntitySelection.ts"],"sourcesContent":["import type { Entity, EntityId } from './Entity.js';\nimport type { FilterGroup } from './EntityFiltering.js';\n\n/**\n * What a collection-bound action acts on.\n *\n * The two members are **not** two shapes of one idea, and collapsing them is\n * the defect this type exists to prevent. \"Select all on this page\" is a list\n * of ids the browser already holds; \"select all 3.200 matching the filter\" is a\n * *filter expression the server evaluates* — the set is by definition larger\n * than the page, so there is nothing to enumerate. Written as one optional\n * field (`ids?: …` beside `all?: boolean`) the second quietly becomes the\n * first, and a bulk action that reads as \"the 25 rows I can see\" runs over\n * every row in the store.\n *\n * `excluded` is what makes the second member usable rather than all-or-nothing:\n * an operator takes everything matching and then drops the two rows they know\n * about. It is a subtraction from a server-side set, never an addition to a\n * client-side one.\n *\n * `total` is carried rather than derived because only the server knows it, and\n * the count is the whole affordance — \"Retirar 3.200 marcas\" is a different\n * sentence from \"Retirar\", and the user has to read the number *before* they\n * confirm
|
|
1
|
+
{"version":3,"sources":["../../src/types/EntitySelection.ts"],"sourcesContent":["import type { Entity, EntityId } from './Entity.js';\nimport type { FilterGroup } from './EntityFiltering.js';\n\n/**\n * What a collection-bound action acts on.\n *\n * The two members are **not** two shapes of one idea, and collapsing them is\n * the defect this type exists to prevent. \"Select all on this page\" is a list\n * of ids the browser already holds; \"select all 3.200 matching the filter\" is a\n * *filter expression the server evaluates* — the set is by definition larger\n * than the page, so there is nothing to enumerate. Written as one optional\n * field (`ids?: …` beside `all?: boolean`) the second quietly becomes the\n * first, and a bulk action that reads as \"the 25 rows I can see\" runs over\n * every row in the store.\n *\n * `excluded` is what makes the second member usable rather than all-or-nothing:\n * an operator takes everything matching and then drops the two rows they know\n * about. It is a subtraction from a server-side set, never an addition to a\n * client-side one.\n *\n * `total` is carried rather than derived because only the server knows it, and\n * the count is the whole affordance — \"Retirar 3.200 marcas\" is a different\n * sentence from \"Retirar\", and the user has to read the number *before* they\n * confirm.\n */\nexport type EntitySelection<TEntity extends Entity> =\n | {\n mode: 'ids';\n ids: ReadonlySet<EntityId>;\n }\n | {\n mode: 'matching';\n /** The filtering in effect when the set was taken. Absent means \"every row\". */\n filtering?: FilterGroup<TEntity>;\n /** How many rows the server said match. */\n total: number;\n /** Rows the user removed from the matching set. */\n excluded: ReadonlySet<EntityId>;\n };\n\n/** An empty `ids` selection — the state a table starts and clears to. */\nexport const emptySelection = <\n TEntity extends Entity,\n>(): EntitySelection<TEntity> => ({ mode: 'ids', ids: new Set<EntityId>() });\n\n/**\n * How many rows the action will touch.\n *\n * For `matching` this is the server's count minus what the user removed, which\n * is the number the confirmation must show. It can only ever be an estimate —\n * rows may have been written since the count was taken — and that is inherent\n * to the mode rather than a defect in it.\n */\nexport function selectionSize<TEntity extends Entity>(\n selection: EntitySelection<TEntity>,\n): number {\n return selection.mode === 'ids'\n ? selection.ids.size\n : Math.max(0, selection.total - selection.excluded.size);\n}\n\n/** Whether one row is part of the selection. */\nexport function isSelected<TEntity extends Entity>(\n selection: EntitySelection<TEntity>,\n id: EntityId,\n): boolean {\n return selection.mode === 'ids'\n ? selection.ids.has(id)\n : !selection.excluded.has(id);\n}\n\n/** Whether the action would touch nothing. */\nexport function isSelectionEmpty<TEntity extends Entity>(\n selection: EntitySelection<TEntity>,\n): boolean {\n return selectionSize(selection) === 0;\n}\n\n/**\n * Add or remove one row, in whichever direction the current mode means.\n *\n * In `ids` mode a toggle writes the id set; in `matching` mode it writes the\n * *exclusion* set, because the selection is the server's answer and the only\n * thing the browser owns is what was taken back out of it. Both return a new\n * value — the selection is controlled state held above the table.\n */\nexport function toggleSelected<TEntity extends Entity>(\n selection: EntitySelection<TEntity>,\n id: EntityId,\n selected: boolean,\n): EntitySelection<TEntity> {\n if (selection.mode === 'ids') {\n const ids = new Set(selection.ids);\n if (selected) ids.add(id);\n else ids.delete(id);\n return { mode: 'ids', ids };\n }\n\n const excluded = new Set(selection.excluded);\n if (selected) excluded.delete(id);\n else excluded.add(id);\n return { ...selection, excluded };\n}\n\n/**\n * The ids a bulk request should carry, or `undefined` when it must carry the\n * filter instead.\n *\n * A caller that treats `undefined` as \"no rows\" has made exactly the mistake\n * the two modes exist to prevent, so this returns `undefined` rather than an\n * empty array: an empty array is a plausible payload and would silently do\n * nothing, where `undefined` cannot be sent at all.\n */\nexport function selectionIds<TEntity extends Entity>(\n selection: EntitySelection<TEntity>,\n): EntityId[] | undefined {\n return selection.mode === 'ids' ? Array.from(selection.ids) : undefined;\n}\n\n/**\n * The selection as it crosses the wire.\n *\n * `Set` does not survive `JSON.stringify` — it serializes to `{}`, silently,\n * and arrives as an empty object that reads as \"no rows excluded\" rather than\n * as an error. That is the same class of fault an autosaved draft meets, and it is worse here: a `matching` selection whose exclusions\n * evaporate acts on rows the operator deliberately took out.\n *\n * So the wire form is arrays and the in-memory form is sets, named separately\n * rather than one type used loosely. The set is the right in-memory shape\n * because it dedupes by construction and answers `isSelected` in constant time;\n * the array is the only JSON shape there is.\n */\nexport type EntitySelectionWire<TEntity extends Entity> =\n | { mode: 'ids'; ids: EntityId[] }\n | {\n mode: 'matching';\n filtering?: FilterGroup<TEntity>;\n total: number;\n excluded: EntityId[];\n };\n\n/**\n * Convert a selection to its wire form.\n *\n * ⚠️ **`Array.from(set)`, never `[...set]`.** Every package here compiles with\n * `\"loose\": true` in its `.swcrc`, and SWC's loose spread helper skips the\n * iterable protocol: it treats the operand as array-like, finds no `length` on\n * a `Set`, and **wraps it as a single element** — `[...someSet]` becomes\n * `[Set]`, silently, with the right `.size` still on the inner value. Measured\n * against the running service, where it turned a two-row selection into one\n * outcome whose `id` serialized as `{}`. Vitest and Turbopack transpile\n * differently, so it passes every unit test and only fails in the built\n * bundle.\n */\nexport function toWireSelection<TEntity extends Entity>(\n selection: EntitySelection<TEntity>,\n): EntitySelectionWire<TEntity> {\n return selection.mode === 'ids'\n ? { mode: 'ids', ids: Array.from(selection.ids) }\n : { ...selection, excluded: Array.from(selection.excluded) };\n}\n\n/**\n * Read a wire selection back, rejecting anything that is not one.\n *\n * Untrusted input: this arrives in a request body and decides which rows a\n * bulk write touches. A missing `mode` must not fall through to a default —\n * defaulting to `ids` with no ids would act on nothing (merely confusing),\n * while defaulting to `matching` would act on **everything**.\n */\nexport function readWireSelection<TEntity extends Entity>(\n value: unknown,\n): EntitySelection<TEntity> | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const candidate = value as Partial<EntitySelectionWire<TEntity>>;\n\n if (candidate.mode === 'ids') {\n return Array.isArray(candidate.ids)\n ? { mode: 'ids', ids: new Set(candidate.ids) }\n : undefined;\n }\n\n if (candidate.mode === 'matching') {\n const matching = candidate as Extract<\n EntitySelectionWire<TEntity>,\n { mode: 'matching' }\n >;\n if (typeof matching.total !== 'number') return undefined;\n if (!Array.isArray(matching.excluded)) return undefined;\n return {\n mode: 'matching',\n filtering: matching.filtering,\n total: matching.total,\n excluded: new Set(matching.excluded),\n };\n }\n\n return undefined;\n}\n"],"names":["emptySelection","mode","ids","Set","selectionSize","selection","size","Math","max","total","excluded","isSelected","id","has","isSelectionEmpty","toggleSelected","selected","add","delete","selectionIds","Array","from","undefined","toWireSelection","readWireSelection","value","candidate","isArray","matching","filtering"],"mappings":";AAwCA,uEAAuE,GACvE,OAAO,MAAMA,iBAAiB,IAEI,CAAA;QAAEC,MAAM;QAAOC,KAAK,IAAIC;IAAgB,CAAA,EAAG;AAE7E;;;;;;;CAOC,GACD,OAAO,SAASC,cACdC,SAAmC;IAEnC,OAAOA,UAAUJ,IAAI,KAAK,QACtBI,UAAUH,GAAG,CAACI,IAAI,GAClBC,KAAKC,GAAG,CAAC,GAAGH,UAAUI,KAAK,GAAGJ,UAAUK,QAAQ,CAACJ,IAAI;AAC3D;AAEA,8CAA8C,GAC9C,OAAO,SAASK,WACdN,SAAmC,EACnCO,EAAY;IAEZ,OAAOP,UAAUJ,IAAI,KAAK,QACtBI,UAAUH,GAAG,CAACW,GAAG,CAACD,MAClB,CAACP,UAAUK,QAAQ,CAACG,GAAG,CAACD;AAC9B;AAEA,4CAA4C,GAC5C,OAAO,SAASE,iBACdT,SAAmC;IAEnC,OAAOD,cAAcC,eAAe;AACtC;AAEA;;;;;;;CAOC,GACD,OAAO,SAASU,eACdV,SAAmC,EACnCO,EAAY,EACZI,QAAiB;IAEjB,IAAIX,UAAUJ,IAAI,KAAK,OAAO;QAC5B,MAAMC,MAAM,IAAIC,IAAIE,UAAUH,GAAG;QACjC,IAAIc,UAAUd,IAAIe,GAAG,CAACL;aACjBV,IAAIgB,MAAM,CAACN;QAChB,OAAO;YAAEX,MAAM;YAAOC;QAAI;IAC5B;IAEA,MAAMQ,WAAW,IAAIP,IAAIE,UAAUK,QAAQ;IAC3C,IAAIM,UAAUN,SAASQ,MAAM,CAACN;SACzBF,SAASO,GAAG,CAACL;IAClB,OAAO,aAAKP;QAAWK;;AACzB;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASS,aACdd,SAAmC;IAEnC,OAAOA,UAAUJ,IAAI,KAAK,QAAQmB,MAAMC,IAAI,CAAChB,UAAUH,GAAG,IAAIoB;AAChE;AAwBA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,gBACdlB,SAAmC;IAEnC,OAAOA,UAAUJ,IAAI,KAAK,QACtB;QAAEA,MAAM;QAAOC,KAAKkB,MAAMC,IAAI,CAAChB,UAAUH,GAAG;IAAE,IAC9C,aAAKG;QAAWK,UAAUU,MAAMC,IAAI,CAAChB,UAAUK,QAAQ;;AAC7D;AAEA;;;;;;;CAOC,GACD,OAAO,SAASc,kBACdC,KAAc;IAEd,IAAI,OAAOA,UAAU,YAAYA,UAAU,MAAM,OAAOH;IACxD,MAAMI,YAAYD;IAElB,IAAIC,UAAUzB,IAAI,KAAK,OAAO;QAC5B,OAAOmB,MAAMO,OAAO,CAACD,UAAUxB,GAAG,IAC9B;YAAED,MAAM;YAAOC,KAAK,IAAIC,IAAIuB,UAAUxB,GAAG;QAAE,IAC3CoB;IACN;IAEA,IAAII,UAAUzB,IAAI,KAAK,YAAY;QACjC,MAAM2B,WAAWF;QAIjB,IAAI,OAAOE,SAASnB,KAAK,KAAK,UAAU,OAAOa;QAC/C,IAAI,CAACF,MAAMO,OAAO,CAACC,SAASlB,QAAQ,GAAG,OAAOY;QAC9C,OAAO;YACLrB,MAAM;YACN4B,WAAWD,SAASC,SAAS;YAC7BpB,OAAOmB,SAASnB,KAAK;YACrBC,UAAU,IAAIP,IAAIyB,SAASlB,QAAQ;QACrC;IACF;IAEA,OAAOY;AACT"}
|
|
@@ -15,9 +15,8 @@ export type WizardStepKind = (typeof WizardStepKinds)[number];
|
|
|
15
15
|
* one may lead to, so reachability, unknown targets, cycles and terminality are
|
|
16
16
|
* decidable by reading the definition — no probing, no enumeration of the state
|
|
17
17
|
* space, and no rule that "only appears to hold" because a `next` reading a form
|
|
18
|
-
* value could never be enumerated. It is the move
|
|
19
|
-
*
|
|
20
|
-
* made for saga steps, for the same reason: a walkable definition is checkable
|
|
18
|
+
* value could never be enumerated. It is the move a saga definition makes for
|
|
19
|
+
* its steps, for the same reason: a walkable definition is checkable
|
|
21
20
|
* at load, and a function is not.
|
|
22
21
|
*
|
|
23
22
|
* A step with an empty `to` is **terminal**, and
|
|
@@ -84,8 +83,7 @@ export declare const findStep: (definition: WizardDefinition, stepId: string) =>
|
|
|
84
83
|
* branch names a step that does not exist, must fail on the first render of any
|
|
85
84
|
* surface rather than on the render of the step nobody reached — otherwise the
|
|
86
85
|
* failure of the longest flow in the product arrives at the end of it. That is
|
|
87
|
-
* the posture `assertLinkSourcesAreEditable` and `surfaceFor` already take
|
|
88
|
-
* ([ADR 0035](../../../../../../docs/adr/0035-entity-actions-selection-and-bulk.md)).
|
|
86
|
+
* the posture `assertLinkSourcesAreEditable` and `surfaceFor` already take.
|
|
89
87
|
*
|
|
90
88
|
* Six faults, each of which is silent otherwise:
|
|
91
89
|
*
|
|
@@ -96,9 +94,8 @@ export declare const findStep: (definition: WizardDefinition, stepId: string) =>
|
|
|
96
94
|
* - **a step nothing reaches**, which is either a dead branch or a missing edge;
|
|
97
95
|
* - **the wrong terminal step** — the flow must end in the summary, and there
|
|
98
96
|
* must be exactly one of those. A wizard ending anywhere else commits without
|
|
99
|
-
* the operator ever seeing what they answered, which
|
|
100
|
-
*
|
|
101
|
-
* optional.
|
|
97
|
+
* the operator ever seeing what they answered, which is not optional in a
|
|
98
|
+
* long flow.
|
|
102
99
|
*
|
|
103
100
|
* A cycle needs no separate check: every step is reachable from the entry and
|
|
104
101
|
* exactly one is terminal, so a cycle would leave the steps on it unable to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wizard-definition.d.ts","sourceRoot":"","sources":["../../src/wizard/wizard-definition.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,+DAMlB,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D
|
|
1
|
+
{"version":3,"file":"wizard-definition.d.ts","sourceRoot":"","sources":["../../src/wizard/wizard-definition.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,+DAMlB,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC;CACvC;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CACxD;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC;AAED,qDAAqD;AACrD,eAAO,MAAM,SAAS,GAAI,YAAY,gBAAgB,KAAG,UAMxD,CAAC;AAEF,0EAA0E;AAC1E,eAAO,MAAM,QAAQ,GACnB,YAAY,gBAAgB,EAC5B,QAAQ,MAAM,KACb,UAAU,GAAG,SAA8D,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAiCzE"}
|
|
@@ -29,8 +29,7 @@ import { EntifixBuildError } from '../base-entities/entifix-error/index.js';
|
|
|
29
29
|
* branch names a step that does not exist, must fail on the first render of any
|
|
30
30
|
* surface rather than on the render of the step nobody reached — otherwise the
|
|
31
31
|
* failure of the longest flow in the product arrives at the end of it. That is
|
|
32
|
-
* the posture `assertLinkSourcesAreEditable` and `surfaceFor` already take
|
|
33
|
-
* ([ADR 0035](../../../../../../docs/adr/0035-entity-actions-selection-and-bulk.md)).
|
|
32
|
+
* the posture `assertLinkSourcesAreEditable` and `surfaceFor` already take.
|
|
34
33
|
*
|
|
35
34
|
* Six faults, each of which is silent otherwise:
|
|
36
35
|
*
|
|
@@ -41,9 +40,8 @@ import { EntifixBuildError } from '../base-entities/entifix-error/index.js';
|
|
|
41
40
|
* - **a step nothing reaches**, which is either a dead branch or a missing edge;
|
|
42
41
|
* - **the wrong terminal step** — the flow must end in the summary, and there
|
|
43
42
|
* must be exactly one of those. A wizard ending anywhere else commits without
|
|
44
|
-
* the operator ever seeing what they answered, which
|
|
45
|
-
*
|
|
46
|
-
* optional.
|
|
43
|
+
* the operator ever seeing what they answered, which is not optional in a
|
|
44
|
+
* long flow.
|
|
47
45
|
*
|
|
48
46
|
* A cycle needs no separate check: every step is reachable from the entry and
|
|
49
47
|
* exactly one is terminal, so a cycle would leave the steps on it unable to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/wizard/wizard-definition.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\n\n/**\n * What a step holds, which decides how its value is stored and edited.\n *\n * `form` is one `useEntityForm` instance over part of an entity; `selection` is\n * a table step whose value is a set of ids; `choice` is a branch point offering\n * a fixed list of options; `summary` is the read-only recap; `custom` is\n * anything else the host renders and reads itself.\n */\nexport const WizardStepKinds = [\n 'form',\n 'selection',\n 'choice',\n 'summary',\n 'custom',\n] as const;\nexport type WizardStepKind = (typeof WizardStepKinds)[number];\n\n/**\n * One step, and the edges out of it.\n *\n * **The graph is data; `next` only chooses along it.** `to` lists the steps this\n * one may lead to, so reachability, unknown targets, cycles and terminality are\n * decidable by reading the definition — no probing, no enumeration of the state\n * space, and no rule that \"only appears to hold\" because a `next` reading a form\n * value could never be enumerated. It is the move\n * [ADR 0039](../../../../../../docs/adr/0039-multi-step-sagas-are-orchestrated.md)\n * made for saga steps, for the same reason: a walkable definition is checkable\n * at load, and a function is not.\n *\n * A step with an empty `to` is **terminal**, and\n * {@link assertWizardDefinition} requires that to be the summary.\n */\nexport interface WizardStep {\n readonly id: string;\n readonly kind: WizardStepKind;\n /** Catalog key for the stepper's label. Resolved by the renderer, never here. */\n readonly labelKey: string;\n /**\n * The options a `choice` step offers, as stable identifiers.\n *\n * Required on `choice` and forbidden elsewhere: the value of a choice step is\n * one of these strings, so a choice with no options can never be answered and\n * options on a form step describe nothing.\n */\n readonly options?: readonly string[];\n /** Every step this one may lead to. Empty means the flow ends here. */\n readonly to: readonly string[];\n /**\n * Which of {@link WizardStep.to} comes next, given everything answered so far.\n *\n * Optional, and omitting it is the ordinary case: a step with exactly one edge\n * needs no function to pick it. A step with several **must** declare one, or\n * the definition cannot say where it goes.\n *\n * It returns a step id and never `undefined`: **the end of the flow is an\n * empty `to`, not a `next` that declines**. Two ways of saying the same thing\n * would let a step be terminal in one of them and not the other, and it is\n * `to` that {@link assertWizardDefinition} can read.\n */\n next?(state: WizardStateLike): string;\n}\n\n/**\n * The part of the state a `next` may read.\n *\n * Declared structurally rather than importing `WizardState`, so the definition\n * module stays below the state module and neither has to import the other.\n */\nexport interface WizardStateLike {\n readonly activeStep: string;\n readonly steps: { readonly [stepId: string]: unknown };\n}\n\n/**\n * A whole wizard: its key — which is also its screen key in a workspace address\n * — and its steps, the first of which is where the flow begins.\n */\nexport interface WizardDefinition {\n readonly key: string;\n readonly steps: readonly WizardStep[];\n}\n\n/** The step a flow starts at: the first declared. */\nexport const entryStep = (definition: WizardDefinition): WizardStep => {\n const [first] = definition.steps;\n if (first === undefined) {\n throw new EntifixBuildError(`wizard \"${definition.key}\" declares no steps`);\n }\n return first;\n};\n\n/** The step with this id, or `undefined` when the definition has none. */\nexport const findStep = (\n definition: WizardDefinition,\n stepId: string,\n): WizardStep | undefined => definition.steps.find(step => step.id === stepId);\n\n/**\n * Checks a definition the moment it is declared, and throws rather than\n * degrading.\n *\n * **Load time, not step time.** A wizard whose last step is missing, or whose\n * branch names a step that does not exist, must fail on the first render of any\n * surface rather than on the render of the step nobody reached — otherwise the\n * failure of the longest flow in the product arrives at the end of it. That is\n * the posture `assertLinkSourcesAreEditable` and `surfaceFor` already take\n * ([ADR 0035](../../../../../../docs/adr/0035-entity-actions-selection-and-bulk.md)).\n *\n * Six faults, each of which is silent otherwise:\n *\n * - **no steps**, which renders an empty frame with a disabled button;\n * - **a duplicate id**, which makes one step's stored value overwrite another's;\n * - **an unknown `to` target**, a typo that strands the flow mid-way;\n * - **several edges and no `next`**, which cannot say where the step goes;\n * - **a step nothing reaches**, which is either a dead branch or a missing edge;\n * - **the wrong terminal step** — the flow must end in the summary, and there\n * must be exactly one of those. A wizard ending anywhere else commits without\n * the operator ever seeing what they answered, which\n * [#111](https://github.com/r10c-technologies/r10c/issues/111) records as not\n * optional.\n *\n * A cycle needs no separate check: every step is reachable from the entry and\n * exactly one is terminal, so a cycle would leave the steps on it unable to\n * reach that terminal, which the walk below reports as unreachable-from-here.\n */\nexport function assertWizardDefinition(definition: WizardDefinition): void {\n const first = entryStep(definition);\n const ids = new Set<string>();\n\n for (const step of definition.steps) {\n if (ids.has(step.id)) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" declares step \"${step.id}\" twice`,\n );\n }\n ids.add(step.id);\n }\n\n for (const step of definition.steps) {\n assertStepOptions(definition, step);\n\n for (const target of step.to) {\n if (!ids.has(target)) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" leads to unknown step \"${target}\"`,\n );\n }\n }\n\n if (step.to.length > 1 && step.next === undefined) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" has ${step.to.length} edges and no next()`,\n );\n }\n }\n\n assertOneSummaryAtTheEnd(definition);\n assertEveryStepIsReachable(definition, first);\n}\n\n/** `options` belongs to `choice` and to nothing else. */\nfunction assertStepOptions(\n definition: WizardDefinition,\n step: WizardStep,\n): void {\n if (step.kind === 'choice') {\n if (step.options === undefined || step.options.length === 0) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" choice step \"${step.id}\" offers no options`,\n );\n }\n return;\n }\n if (step.options !== undefined) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" is a ${step.kind} step and cannot declare options`,\n );\n }\n}\n\n/** Exactly one summary, and it is the only step the flow may end on. */\nfunction assertOneSummaryAtTheEnd(definition: WizardDefinition): void {\n const summaries = definition.steps.filter(step => step.kind === 'summary');\n if (summaries.length !== 1) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" declares ${summaries.length} summary steps, and must declare exactly one`,\n );\n }\n\n for (const step of definition.steps) {\n const terminal = step.to.length === 0;\n if (terminal && step.kind !== 'summary') {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" ends on \"${step.id}\", which is not its summary`,\n );\n }\n if (!terminal && step.kind === 'summary') {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" summary step \"${step.id}\" leads onward, and must end the flow`,\n );\n }\n }\n}\n\n/** Every declared step is on some path out of the entry step. */\nfunction assertEveryStepIsReachable(\n definition: WizardDefinition,\n first: WizardStep,\n): void {\n const seen = new Set<string>([first.id]);\n\n // A fixpoint over `seen` rather than a queue of resolved steps: every target\n // has already been checked to name a declared step, so a walk that looked one\n // up would carry an `undefined` arm nothing can reach — and an unreachable\n // arm is indistinguishable from an untested one.\n for (let grew = true; grew;) {\n grew = false;\n for (const step of definition.steps) {\n if (!seen.has(step.id)) continue;\n for (const target of step.to) {\n if (seen.has(target)) continue;\n seen.add(target);\n grew = true;\n }\n }\n }\n\n const stranded = definition.steps\n .filter(step => !seen.has(step.id))\n .map(step => step.id);\n\n if (stranded.length > 0) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" cannot reach ${stranded.map(id => `\"${id}\"`).join(', ')} from \"${first.id}\"`,\n );\n }\n}\n"],"names":["EntifixBuildError","WizardStepKinds","entryStep","definition","first","steps","undefined","key","findStep","stepId","find","step","id","assertWizardDefinition","ids","Set","has","add","assertStepOptions","target","to","length","next","assertOneSummaryAtTheEnd","assertEveryStepIsReachable","kind","options","summaries","filter","terminal","seen","grew","stranded","map","join"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,0CAA0C;AAE5E;;;;;;;CAOC,GACD,OAAO,MAAMC,kBAAkB;IAC7B;IACA;IACA;IACA;IACA;CACD,CAAU;AAoEX,mDAAmD,GACnD,OAAO,MAAMC,YAAY,CAACC;IACxB,MAAM,CAACC,MAAM,GAAGD,WAAWE,KAAK;IAChC,IAAID,UAAUE,WAAW;QACvB,MAAM,IAAIN,kBAAkB,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,mBAAmB,CAAC;IAC5E;IACA,OAAOH;AACT,EAAE;AAEF,wEAAwE,GACxE,OAAO,MAAMI,WAAW,CACtBL,YACAM,SAC2BN,WAAWE,KAAK,CAACK,IAAI,CAACC,CAAAA,OAAQA,KAAKC,EAAE,KAAKH,QAAQ;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,GACD,OAAO,SAASI,uBAAuBV,UAA4B;IACjE,MAAMC,QAAQF,UAAUC;IACxB,MAAMW,MAAM,IAAIC;IAEhB,KAAK,MAAMJ,QAAQR,WAAWE,KAAK,CAAE;QACnC,IAAIS,IAAIE,GAAG,CAACL,KAAKC,EAAE,GAAG;YACpB,MAAM,IAAIZ,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,iBAAiB,EAAEI,KAAKC,EAAE,CAAC,OAAO,CAAC;QAEjE;QACAE,IAAIG,GAAG,CAACN,KAAKC,EAAE;IACjB;IAEA,KAAK,MAAMD,QAAQR,WAAWE,KAAK,CAAE;QACnCa,kBAAkBf,YAAYQ;QAE9B,KAAK,MAAMQ,UAAUR,KAAKS,EAAE,CAAE;YAC5B,IAAI,CAACN,IAAIE,GAAG,CAACG,SAAS;gBACpB,MAAM,IAAInB,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,QAAQ,EAAEI,KAAKC,EAAE,CAAC,yBAAyB,EAAEO,OAAO,CAAC,CAAC;YAEpF;QACF;QAEA,IAAIR,KAAKS,EAAE,CAACC,MAAM,GAAG,KAAKV,KAAKW,IAAI,KAAKhB,WAAW;YACjD,MAAM,IAAIN,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,QAAQ,EAAEI,KAAKC,EAAE,CAAC,MAAM,EAAED,KAAKS,EAAE,CAACC,MAAM,CAAC,oBAAoB,CAAC;QAE5F;IACF;IAEAE,yBAAyBpB;IACzBqB,2BAA2BrB,YAAYC;AACzC;AAEA,uDAAuD,GACvD,SAASc,kBACPf,UAA4B,EAC5BQ,IAAgB;IAEhB,IAAIA,KAAKc,IAAI,KAAK,UAAU;QAC1B,IAAId,KAAKe,OAAO,KAAKpB,aAAaK,KAAKe,OAAO,CAACL,MAAM,KAAK,GAAG;YAC3D,MAAM,IAAIrB,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,eAAe,EAAEI,KAAKC,EAAE,CAAC,mBAAmB,CAAC;QAE3E;QACA;IACF;IACA,IAAID,KAAKe,OAAO,KAAKpB,WAAW;QAC9B,MAAM,IAAIN,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,QAAQ,EAAEI,KAAKC,EAAE,CAAC,OAAO,EAAED,KAAKc,IAAI,CAAC,gCAAgC,CAAC;IAEpG;AACF;AAEA,sEAAsE,GACtE,SAASF,yBAAyBpB,UAA4B;IAC5D,MAAMwB,YAAYxB,WAAWE,KAAK,CAACuB,MAAM,CAACjB,CAAAA,OAAQA,KAAKc,IAAI,KAAK;IAChE,IAAIE,UAAUN,MAAM,KAAK,GAAG;QAC1B,MAAM,IAAIrB,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,WAAW,EAAEoB,UAAUN,MAAM,CAAC,4CAA4C,CAAC;IAEzG;IAEA,KAAK,MAAMV,QAAQR,WAAWE,KAAK,CAAE;QACnC,MAAMwB,WAAWlB,KAAKS,EAAE,CAACC,MAAM,KAAK;QACpC,IAAIQ,YAAYlB,KAAKc,IAAI,KAAK,WAAW;YACvC,MAAM,IAAIzB,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,WAAW,EAAEI,KAAKC,EAAE,CAAC,2BAA2B,CAAC;QAE/E;QACA,IAAI,CAACiB,YAAYlB,KAAKc,IAAI,KAAK,WAAW;YACxC,MAAM,IAAIzB,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,gBAAgB,EAAEI,KAAKC,EAAE,CAAC,qCAAqC,CAAC;QAE9F;IACF;AACF;AAEA,+DAA+D,GAC/D,SAASY,2BACPrB,UAA4B,EAC5BC,KAAiB;IAEjB,MAAM0B,OAAO,IAAIf,IAAY;QAACX,MAAMQ,EAAE;KAAC;IAEvC,6EAA6E;IAC7E,8EAA8E;IAC9E,2EAA2E;IAC3E,iDAAiD;IACjD,IAAK,IAAImB,OAAO,MAAMA,MAAO;QAC3BA,OAAO;QACP,KAAK,MAAMpB,QAAQR,WAAWE,KAAK,CAAE;YACnC,IAAI,CAACyB,KAAKd,GAAG,CAACL,KAAKC,EAAE,GAAG;YACxB,KAAK,MAAMO,UAAUR,KAAKS,EAAE,CAAE;gBAC5B,IAAIU,KAAKd,GAAG,CAACG,SAAS;gBACtBW,KAAKb,GAAG,CAACE;gBACTY,OAAO;YACT;QACF;IACF;IAEA,MAAMC,WAAW7B,WAAWE,KAAK,CAC9BuB,MAAM,CAACjB,CAAAA,OAAQ,CAACmB,KAAKd,GAAG,CAACL,KAAKC,EAAE,GAChCqB,GAAG,CAACtB,CAAAA,OAAQA,KAAKC,EAAE;IAEtB,IAAIoB,SAASX,MAAM,GAAG,GAAG;QACvB,MAAM,IAAIrB,kBACR,CAAC,QAAQ,EAAEG,WAAWI,GAAG,CAAC,eAAe,EAAEyB,SAASC,GAAG,CAACrB,CAAAA,KAAM,CAAC,CAAC,EAAEA,GAAG,CAAC,CAAC,EAAEsB,IAAI,CAAC,MAAM,OAAO,EAAE9B,MAAMQ,EAAE,CAAC,CAAC,CAAC;IAE5G;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../src/wizard/wizard-definition.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\n\n/**\n * What a step holds, which decides how its value is stored and edited.\n *\n * `form` is one `useEntityForm` instance over part of an entity; `selection` is\n * a table step whose value is a set of ids; `choice` is a branch point offering\n * a fixed list of options; `summary` is the read-only recap; `custom` is\n * anything else the host renders and reads itself.\n */\nexport const WizardStepKinds = [\n 'form',\n 'selection',\n 'choice',\n 'summary',\n 'custom',\n] as const;\nexport type WizardStepKind = (typeof WizardStepKinds)[number];\n\n/**\n * One step, and the edges out of it.\n *\n * **The graph is data; `next` only chooses along it.** `to` lists the steps this\n * one may lead to, so reachability, unknown targets, cycles and terminality are\n * decidable by reading the definition — no probing, no enumeration of the state\n * space, and no rule that \"only appears to hold\" because a `next` reading a form\n * value could never be enumerated. It is the move a saga definition makes for\n * its steps, for the same reason: a walkable definition is checkable\n * at load, and a function is not.\n *\n * A step with an empty `to` is **terminal**, and\n * {@link assertWizardDefinition} requires that to be the summary.\n */\nexport interface WizardStep {\n readonly id: string;\n readonly kind: WizardStepKind;\n /** Catalog key for the stepper's label. Resolved by the renderer, never here. */\n readonly labelKey: string;\n /**\n * The options a `choice` step offers, as stable identifiers.\n *\n * Required on `choice` and forbidden elsewhere: the value of a choice step is\n * one of these strings, so a choice with no options can never be answered and\n * options on a form step describe nothing.\n */\n readonly options?: readonly string[];\n /** Every step this one may lead to. Empty means the flow ends here. */\n readonly to: readonly string[];\n /**\n * Which of {@link WizardStep.to} comes next, given everything answered so far.\n *\n * Optional, and omitting it is the ordinary case: a step with exactly one edge\n * needs no function to pick it. A step with several **must** declare one, or\n * the definition cannot say where it goes.\n *\n * It returns a step id and never `undefined`: **the end of the flow is an\n * empty `to`, not a `next` that declines**. Two ways of saying the same thing\n * would let a step be terminal in one of them and not the other, and it is\n * `to` that {@link assertWizardDefinition} can read.\n */\n next?(state: WizardStateLike): string;\n}\n\n/**\n * The part of the state a `next` may read.\n *\n * Declared structurally rather than importing `WizardState`, so the definition\n * module stays below the state module and neither has to import the other.\n */\nexport interface WizardStateLike {\n readonly activeStep: string;\n readonly steps: { readonly [stepId: string]: unknown };\n}\n\n/**\n * A whole wizard: its key — which is also its screen key in a workspace address\n * — and its steps, the first of which is where the flow begins.\n */\nexport interface WizardDefinition {\n readonly key: string;\n readonly steps: readonly WizardStep[];\n}\n\n/** The step a flow starts at: the first declared. */\nexport const entryStep = (definition: WizardDefinition): WizardStep => {\n const [first] = definition.steps;\n if (first === undefined) {\n throw new EntifixBuildError(`wizard \"${definition.key}\" declares no steps`);\n }\n return first;\n};\n\n/** The step with this id, or `undefined` when the definition has none. */\nexport const findStep = (\n definition: WizardDefinition,\n stepId: string,\n): WizardStep | undefined => definition.steps.find(step => step.id === stepId);\n\n/**\n * Checks a definition the moment it is declared, and throws rather than\n * degrading.\n *\n * **Load time, not step time.** A wizard whose last step is missing, or whose\n * branch names a step that does not exist, must fail on the first render of any\n * surface rather than on the render of the step nobody reached — otherwise the\n * failure of the longest flow in the product arrives at the end of it. That is\n * the posture `assertLinkSourcesAreEditable` and `surfaceFor` already take.\n *\n * Six faults, each of which is silent otherwise:\n *\n * - **no steps**, which renders an empty frame with a disabled button;\n * - **a duplicate id**, which makes one step's stored value overwrite another's;\n * - **an unknown `to` target**, a typo that strands the flow mid-way;\n * - **several edges and no `next`**, which cannot say where the step goes;\n * - **a step nothing reaches**, which is either a dead branch or a missing edge;\n * - **the wrong terminal step** — the flow must end in the summary, and there\n * must be exactly one of those. A wizard ending anywhere else commits without\n * the operator ever seeing what they answered, which is not optional in a\n * long flow.\n *\n * A cycle needs no separate check: every step is reachable from the entry and\n * exactly one is terminal, so a cycle would leave the steps on it unable to\n * reach that terminal, which the walk below reports as unreachable-from-here.\n */\nexport function assertWizardDefinition(definition: WizardDefinition): void {\n const first = entryStep(definition);\n const ids = new Set<string>();\n\n for (const step of definition.steps) {\n if (ids.has(step.id)) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" declares step \"${step.id}\" twice`,\n );\n }\n ids.add(step.id);\n }\n\n for (const step of definition.steps) {\n assertStepOptions(definition, step);\n\n for (const target of step.to) {\n if (!ids.has(target)) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" leads to unknown step \"${target}\"`,\n );\n }\n }\n\n if (step.to.length > 1 && step.next === undefined) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" has ${step.to.length} edges and no next()`,\n );\n }\n }\n\n assertOneSummaryAtTheEnd(definition);\n assertEveryStepIsReachable(definition, first);\n}\n\n/** `options` belongs to `choice` and to nothing else. */\nfunction assertStepOptions(\n definition: WizardDefinition,\n step: WizardStep,\n): void {\n if (step.kind === 'choice') {\n if (step.options === undefined || step.options.length === 0) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" choice step \"${step.id}\" offers no options`,\n );\n }\n return;\n }\n if (step.options !== undefined) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" is a ${step.kind} step and cannot declare options`,\n );\n }\n}\n\n/** Exactly one summary, and it is the only step the flow may end on. */\nfunction assertOneSummaryAtTheEnd(definition: WizardDefinition): void {\n const summaries = definition.steps.filter(step => step.kind === 'summary');\n if (summaries.length !== 1) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" declares ${summaries.length} summary steps, and must declare exactly one`,\n );\n }\n\n for (const step of definition.steps) {\n const terminal = step.to.length === 0;\n if (terminal && step.kind !== 'summary') {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" ends on \"${step.id}\", which is not its summary`,\n );\n }\n if (!terminal && step.kind === 'summary') {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" summary step \"${step.id}\" leads onward, and must end the flow`,\n );\n }\n }\n}\n\n/** Every declared step is on some path out of the entry step. */\nfunction assertEveryStepIsReachable(\n definition: WizardDefinition,\n first: WizardStep,\n): void {\n const seen = new Set<string>([first.id]);\n\n // A fixpoint over `seen` rather than a queue of resolved steps: every target\n // has already been checked to name a declared step, so a walk that looked one\n // up would carry an `undefined` arm nothing can reach — and an unreachable\n // arm is indistinguishable from an untested one.\n for (let grew = true; grew;) {\n grew = false;\n for (const step of definition.steps) {\n if (!seen.has(step.id)) continue;\n for (const target of step.to) {\n if (seen.has(target)) continue;\n seen.add(target);\n grew = true;\n }\n }\n }\n\n const stranded = definition.steps\n .filter(step => !seen.has(step.id))\n .map(step => step.id);\n\n if (stranded.length > 0) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" cannot reach ${stranded.map(id => `\"${id}\"`).join(', ')} from \"${first.id}\"`,\n );\n }\n}\n"],"names":["EntifixBuildError","WizardStepKinds","entryStep","definition","first","steps","undefined","key","findStep","stepId","find","step","id","assertWizardDefinition","ids","Set","has","add","assertStepOptions","target","to","length","next","assertOneSummaryAtTheEnd","assertEveryStepIsReachable","kind","options","summaries","filter","terminal","seen","grew","stranded","map","join"],"mappings":"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"}
|
|
@@ -8,9 +8,8 @@ import type { EntityDraft } from '../types/EntityDraft.js';
|
|
|
8
8
|
* members, which would let a form step carry ids nothing would ever read.
|
|
9
9
|
*
|
|
10
10
|
* ⚠️ A selection holds `readonly string[]`, **never a `ReadonlySet`**. A `Set`
|
|
11
|
-
* serializes to `{}` and does it silently, which is the fault
|
|
12
|
-
*
|
|
13
|
-
* named for the wire — and a wizard's state goes through `createJSONStorage` on
|
|
11
|
+
* serializes to `{}` and does it silently, which is the fault a bulk
|
|
12
|
+
* selection meets on the wire — and a wizard's state goes through `createJSONStorage` on
|
|
14
13
|
* every keystroke, so it would be met far more often here than there.
|
|
15
14
|
*/
|
|
16
15
|
export type WizardStepValue = {
|
|
@@ -32,13 +31,11 @@ export type WizardStepValue = {
|
|
|
32
31
|
* **A `type`, never an `interface`.** This is persisted through
|
|
33
32
|
* `createJSONStorage` and therefore has to satisfy `JsonValue`, and TypeScript
|
|
34
33
|
* gives an interface no implicit index signature — the resulting "Index
|
|
35
|
-
* signature for type 'string' is missing" explains nothing about the real cause
|
|
36
|
-
* ([ADR 0032](../../../../../../docs/adr/0032-what-may-live-in-an-autosaved-draft.md)).
|
|
34
|
+
* signature for type 'string' is missing" explains nothing about the real cause.
|
|
37
35
|
*
|
|
38
36
|
* `history` is **the path actually taken**, and it is what Back pops. A branch's
|
|
39
37
|
* inverse is ambiguous once an earlier answer changes, so a computed `previous`
|
|
40
|
-
* would walk the operator back through a path they were never on
|
|
41
|
-
* ([ADR 0045](../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).
|
|
38
|
+
* would walk the operator back through a path they were never on.
|
|
42
39
|
*
|
|
43
40
|
* `steps` is deliberately **not** pruned when a branch is abandoned. It is the
|
|
44
41
|
* record of what was typed, and {@link wizardPath} decides what counts — so
|