lanka 1.2.0 → 1.3.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.
Files changed (60) hide show
  1. package/README.md +3 -1
  2. package/dist/{ALankaGateway-CkW1LbKE.d.ts → ALankaGateway-BrVPaZN5.d.ts} +1 -1
  3. package/dist/ILankaFieldError-D5931-vT.d.ts +34 -0
  4. package/dist/{ILankaScenarioMetadata-GoWWNEQL.d.ts → ILankaScenarioMetadata-Dj4GCqmX.d.ts} +1 -1
  5. package/dist/{ILankaScenarioVM-DUsI-fSc.d.ts → ILankaScenarioVM-DpKFL3iE.d.ts} +5 -4
  6. package/dist/{LankaError-xpI-qj35.d.ts → LankaError-D6RWNyLM.d.ts} +18 -0
  7. package/dist/{LankaScenarioLocator-D86TIwiu.d.ts → LankaScenarioLocator-CAENLhDO.d.ts} +3 -3
  8. package/dist/_extend/index.d.ts +6 -5
  9. package/dist/_internal/index.d.ts +61 -8
  10. package/dist/_internal/index.js +25 -0
  11. package/dist/_internal/index.js.map +1 -1
  12. package/dist/{activeRuntime-B336NU5I.d.ts → activeRuntime-BYd2D6Lx.d.ts} +2 -2
  13. package/dist/bootstrap/index.d.ts +9 -8
  14. package/dist/bootstrap/index.js +2 -2
  15. package/dist/cache/index.d.ts +99 -0
  16. package/dist/cache/index.js +1 -0
  17. package/dist/cache/index.js.map +1 -0
  18. package/dist/{chunk-UGXSGQPW.js → chunk-B7EYIAW7.js} +39 -3
  19. package/dist/chunk-B7EYIAW7.js.map +1 -0
  20. package/dist/chunk-CRIRTOLB.js +27 -0
  21. package/dist/chunk-CRIRTOLB.js.map +1 -0
  22. package/dist/{chunk-Q7QESSYF.js → chunk-G32H73QY.js} +10 -1
  23. package/dist/chunk-G32H73QY.js.map +1 -0
  24. package/dist/chunk-HVSQOMRE.js +69 -0
  25. package/dist/chunk-HVSQOMRE.js.map +1 -0
  26. package/dist/{chunk-73IVH3C6.js → chunk-NDCJBO47.js} +2 -2
  27. package/dist/{chunk-G3I7QIZR.js → chunk-UBGXDTXC.js} +14 -5
  28. package/dist/chunk-UBGXDTXC.js.map +1 -0
  29. package/dist/{createLanka-DI1CSy2Q.d.ts → createLanka-NrlvN-WQ.d.ts} +1 -1
  30. package/dist/errors/index.d.ts +22 -3
  31. package/dist/errors/index.js +8 -3
  32. package/dist/errors/index.js.map +1 -1
  33. package/dist/gateway/index.d.ts +4 -3
  34. package/dist/gateway/index.js +6 -5
  35. package/dist/gateway/index.js.map +1 -1
  36. package/dist/index.d.ts +9 -8
  37. package/dist/index.js +3 -3
  38. package/dist/{lankaStandardValidator-CL-r-zEV.d.ts → lankaStandardValidator-BUFnysK0.d.ts} +10 -0
  39. package/dist/locator/index.d.ts +3 -2
  40. package/dist/scenario/index.d.ts +56 -6
  41. package/dist/scenario/index.js +1 -1
  42. package/dist/storage/index.d.ts +120 -0
  43. package/dist/storage/index.js +1 -0
  44. package/dist/storage/index.js.map +1 -0
  45. package/dist/stream/index.d.ts +8 -7
  46. package/dist/validation/index.d.ts +7 -4
  47. package/dist/validation/index.js +5 -3
  48. package/dist/viewmodel/index.d.ts +51 -5
  49. package/dist/viewmodel/index.js +75 -47
  50. package/dist/viewmodel/index.js.map +1 -1
  51. package/package.json +11 -3
  52. package/skills/lanka-core/SKILL.md +59 -1
  53. package/skills/lanka-core/reference.md +312 -6
  54. package/skills/lanka-packages/SKILL.md +1 -1
  55. package/dist/chunk-G3I7QIZR.js.map +0 -1
  56. package/dist/chunk-GV5DUYST.js +0 -64
  57. package/dist/chunk-GV5DUYST.js.map +0 -1
  58. package/dist/chunk-Q7QESSYF.js.map +0 -1
  59. package/dist/chunk-UGXSGQPW.js.map +0 -1
  60. /package/dist/{chunk-73IVH3C6.js.map → chunk-NDCJBO47.js.map} +0 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **◆ core** · Core
4
4
 
5
- > Twelve subsystems, two peer dependencies, five extension points.
5
+ > Fourteen subsystems, two peer dependencies, five extension points.
6
6
 
7
7
  One, unscoped. Everything depends on it; it depends on nothing.
8
8
 
@@ -44,6 +44,8 @@ the seal on `internal`, and catches a subsystem added as a folder and forgotten
44
44
  - `src/locator/`
45
45
  - `src/gateway/`
46
46
  - `src/validation/`
47
+ - `src/cache/`
48
+ - `src/storage/`
47
49
  - `src/mock/`
48
50
  - `src/errors/`
49
51
  - `src/scenario/`
@@ -1,4 +1,4 @@
1
- import { I as ILankaValidator } from './lankaStandardValidator-CL-r-zEV.js';
1
+ import { I as ILankaValidator } from './lankaStandardValidator-BUFnysK0.js';
2
2
 
3
3
  /**
4
4
  * `RequestInit`, with the body a JSON API actually sends.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A failure with an ADDRESS: which input it belongs to, and what to say there.
3
+ *
4
+ * `issues` on `LankaError` flattens the address into the text — `items.1.qty:
5
+ * only 2 left` — which is right for a banner and useless for a form, which has
6
+ * a place per input and must find it. The path here stays in segments because
7
+ * the two form libraries this was designed against spell the same address
8
+ * differently (`items.1.qty` and `items[1].qty`) and neither can be parsed back
9
+ * out of a string safely: a message may contain a colon, a key may contain a
10
+ * dot.
11
+ *
12
+ * Produced by the validation port from a schema's issues, and by an
13
+ * application's request policy from a server body. Consumed by a ViewModel,
14
+ * which hands it to whatever holds the fields — its own state, or a form.
15
+ */
16
+ interface ILankaFieldError {
17
+ /**
18
+ * Segments, never a joined string: `["items", 1, "qty"]`.
19
+ *
20
+ * A segment is a name or an index and nothing else — a producer holding a
21
+ * symbol stringifies it, because no form can address one. EMPTY means the
22
+ * value as a whole: a cross-field refusal ("the dates are in the wrong
23
+ * order"), or a body that was not the expected shape at all. That is the
24
+ * form's ROOT, and an adapter routes it there rather than to an input named
25
+ * `""`.
26
+ */
27
+ readonly path: readonly (string | number)[];
28
+ /** What to show at that address when the application does not translate. */
29
+ readonly message: string;
30
+ /** The machine-readable reason, when the producer had one — what i18n keys on. */
31
+ readonly code?: string;
32
+ }
33
+
34
+ export type { ILankaFieldError as I };
@@ -1,4 +1,4 @@
1
- import { a as ILankaScenario } from './ILankaScenarioVM-DUsI-fSc.js';
1
+ import { a as ILankaScenario } from './ILankaScenarioVM-DpKFL3iE.js';
2
2
 
3
3
  /**
4
4
  * Metadata about a registered scenario
@@ -345,10 +345,11 @@ interface ILankaScenario<TData = void> {
345
345
  /**
346
346
  * A ViewModel that participates in scenarios.
347
347
  *
348
- * Not every ViewModel does: one with no `scenarioHandlers` never subscribes to
349
- * anything and is never registered. This is the pair of methods
350
- * `LankaScenarioBootstrap` calls on those that do, which is why the name says
351
- * scenario rather than ViewModel.
348
+ * Not every ViewModel does: one with no `scenarioHandlers` and neither
349
+ * lifecycle hook has nothing for bootstrap to do and is never registered. This
350
+ * is the pair of methods `LankaScenarioBootstrap` calls on those that are, which
351
+ * is why the name says scenario rather than ViewModel — `onInit` and `onReset`
352
+ * ride on the same two calls.
352
353
  *
353
354
  * Both are idempotent by contract: bootstrap may run after a ViewModel has
354
355
  * already initialised itself, and a reset may arrive for one that never did.
@@ -1,3 +1,5 @@
1
+ import { I as ILankaFieldError } from './ILankaFieldError-D5931-vT.js';
2
+
1
3
  /**
2
4
  * Structural API-error shape read by consumer code.
3
5
  *
@@ -42,6 +44,14 @@ interface ILankaErrorInit {
42
44
  code?: string;
43
45
  /** Details: server messages, or paths to the fields that failed validation. */
44
46
  issues?: readonly string[];
47
+ /**
48
+ * The failures that have an input to be shown at, with the path in segments.
49
+ *
50
+ * Beside `issues` rather than instead of it: `issues` is the banner's list and
51
+ * is promised; this is the form's, and a failure may have either, both or
52
+ * neither. Absent means "nothing here has an address", not "unknown".
53
+ */
54
+ fields?: readonly ILankaFieldError[];
45
55
  /**
46
56
  * The parsed ERROR response body, when there was one and it was JSON.
47
57
  *
@@ -72,6 +82,14 @@ declare class LankaError extends Error implements ILankaApiError {
72
82
  readonly status?: number;
73
83
  readonly code?: string;
74
84
  readonly issues?: readonly string[];
85
+ /**
86
+ * The failures with an input to be shown at. See `ILankaErrorInit.fields`.
87
+ *
88
+ * The array given is the array kept — unlike `errors`, which copies `issues`
89
+ * on every read. `readonly` carries the promise, and this is read on the way
90
+ * to a form, once, where a copy would buy nothing.
91
+ */
92
+ readonly fields?: readonly ILankaFieldError[];
75
93
  /** The parsed error response body; core carries it without reading it. */
76
94
  readonly body?: unknown;
77
95
  constructor(init: ILankaErrorInit);
@@ -1,6 +1,6 @@
1
- import { a as ILankaScenario, I as ILankaScenarioVM } from './ILankaScenarioVM-DUsI-fSc.js';
2
- import { I as ILankaScenarioMetadata } from './ILankaScenarioMetadata-GoWWNEQL.js';
3
- import { A as ALankaGateway } from './ALankaGateway-CkW1LbKE.js';
1
+ import { a as ILankaScenario, I as ILankaScenarioVM } from './ILankaScenarioVM-DpKFL3iE.js';
2
+ import { I as ILankaScenarioMetadata } from './ILankaScenarioMetadata-Dj4GCqmX.js';
3
+ import { A as ALankaGateway } from './ALankaGateway-BrVPaZN5.js';
4
4
  import { A as ALankaLocator } from './LankaSharedStoreLocator-zS2kLu-S.js';
5
5
 
6
6
  /**
@@ -1,13 +1,14 @@
1
- export { L as LankaGatewayLocator, a as LankaScenarioLocator, b as LankaScenarioVMRegistry, c as LankaScenariosRegistry } from '../LankaScenarioLocator-D86TIwiu.js';
1
+ export { L as LankaGatewayLocator, a as LankaScenarioLocator, b as LankaScenarioVMRegistry, c as LankaScenariosRegistry } from '../LankaScenarioLocator-CAENLhDO.js';
2
2
  import { a as ILankaLocator } from '../LankaSharedStoreLocator-zS2kLu-S.js';
3
3
  export { A as ALankaLocator, L as LankaSharedStoreLocator, b as LankaSingletonLocator } from '../LankaSharedStoreLocator-zS2kLu-S.js';
4
4
  export { c as createLankaScope } from '../createLankaScope-BiFxNQgl.js';
5
5
  export { c as composeLankaRequestMiddleware } from '../lankaRequestMiddleware-DAC5kCb7.js';
6
- import '../ILankaScenarioVM-DUsI-fSc.js';
7
- import '../ILankaScenarioMetadata-GoWWNEQL.js';
8
- import '../ALankaGateway-CkW1LbKE.js';
9
- import '../lankaStandardValidator-CL-r-zEV.js';
6
+ import '../ILankaScenarioVM-DpKFL3iE.js';
7
+ import '../ILankaScenarioMetadata-Dj4GCqmX.js';
8
+ import '../ALankaGateway-BrVPaZN5.js';
9
+ import '../lankaStandardValidator-BUFnysK0.js';
10
10
  import '@standard-schema/spec';
11
+ import '../ILankaFieldError-D5931-vT.js';
11
12
  import '../ALankaSharedStore-B7uepuuk.js';
12
13
  import 'zustand/vanilla';
13
14
 
@@ -1,19 +1,72 @@
1
- export { I as ILankaRuntime, T as TLankaRuntimeResolver, s as setLankaRuntimeResolver } from '../activeRuntime-B336NU5I.js';
2
- import '../ILankaScenarioVM-DUsI-fSc.js';
3
- import '../LankaScenarioLocator-D86TIwiu.js';
4
- import '../ILankaScenarioMetadata-GoWWNEQL.js';
5
- import '../ALankaGateway-CkW1LbKE.js';
6
- import '../lankaStandardValidator-CL-r-zEV.js';
7
- import '@standard-schema/spec';
1
+ export { I as ILankaRuntime, T as TLankaRuntimeResolver, s as setLankaRuntimeResolver } from '../activeRuntime-BYd2D6Lx.js';
2
+ import { a as TLankaValidationResult } from '../lankaStandardValidator-BUFnysK0.js';
3
+ import '../ILankaScenarioVM-DpKFL3iE.js';
4
+ import '../LankaScenarioLocator-CAENLhDO.js';
5
+ import '../ILankaScenarioMetadata-Dj4GCqmX.js';
6
+ import '../ALankaGateway-BrVPaZN5.js';
8
7
  import '../LankaSharedStoreLocator-zS2kLu-S.js';
9
8
  import '../ALankaSharedStore-B7uepuuk.js';
10
9
  import 'zustand/vanilla';
11
10
  import '../ILankaRuntimeConfig-Vl436GWK.js';
12
11
  import '../lankaHttpInFlight-Bk1eIuSx.js';
13
12
  import '../lankaRequestMiddleware-DAC5kCb7.js';
13
+ import '@standard-schema/spec';
14
+ import '../ILankaFieldError-D5931-vT.js';
14
15
 
15
16
  declare function generateUuid(): string;
16
17
 
18
+ /**
19
+ * What to say when a validator is handed a schema from another library.
20
+ *
21
+ * ## Why this is not three sentences in three packages
22
+ *
23
+ * `@lankajs/yup`, `@lankajs/typebox` and `@lankajs/effect` each refuse a schema
24
+ * they do not own, and each has to answer the same follow-up: is this a schema at
25
+ * all, and if so whose? The answer is identical in all three because the
26
+ * SITUATION is identical — only the lead differs, because only the marker each
27
+ * looked for differs.
28
+ *
29
+ * Written three times it was three copies, and `check:composition` said so. Made
30
+ * deliberately different to satisfy the gate it would have been worse: three
31
+ * wordings of one fact, and a consumer who met two of them would be entitled to
32
+ * think they meant different things.
33
+ *
34
+ * ## Why `lanka/internal` and not the facade
35
+ *
36
+ * This tier exists for exactly this: "a sibling package needs these and must not
37
+ * reach into another package's `src/`". It promises nothing beyond a patch, which
38
+ * is the right promise for a sentence.
39
+ *
40
+ * A facade `isStandardSchema` was proposed first and refused, for a reason worth
41
+ * keeping: it would answer `true` for every yup schema, and `lankaStandardValidator`
42
+ * throws on every yup schema. A consumer writing `if (isStandardSchema(s))
43
+ * validate(s, …)` would have written the exact bug the family works to prevent,
44
+ * and the name would have told them it was safe.
45
+ */
46
+ declare const lankaForeignSchemaMessage: (schema: unknown, { lead }: {
47
+ lead: string;
48
+ }) => string;
49
+
50
+ /**
51
+ * The strict path, built from the safe one.
52
+ *
53
+ * `ILankaValidator` publishes two methods over one answer: `validateSafe`
54
+ * returns an outcome, and `validate` is that outcome with the failure raised.
55
+ * Every implementation of the port therefore writes the same five lines, and
56
+ * three of them in this repository did — `check:composition` counted them.
57
+ *
58
+ * It is a GENERIC over the outcome rather than a helper returning `unknown`,
59
+ * which is the whole reason it can be shared: `@lankajs/typebox` returns
60
+ * `Static<TSchema>` and `@lankajs/effect` returns `Schema.Schema.Type<TAlias>`,
61
+ * and a helper that erased those would have cost each package its inference —
62
+ * a worse trade than the duplication it removed.
63
+ *
64
+ * `lanka/internal` because a sibling package needs it and must not reach into
65
+ * core's `src/`. It promises nothing beyond a patch, which is right for five
66
+ * lines that only restate what the port already says.
67
+ */
68
+ declare const lankaValueOrThrow: <TOutput>(result: TLankaValidationResult<TOutput>, context: string) => TOutput;
69
+
17
70
  /**
18
71
  * Reads `key` from a record and returns it only if the value is a string.
19
72
  * Returns `undefined` for missing or non-string values.
@@ -40,4 +93,4 @@ declare const getStringField: (data: Record<string, unknown>, key: string) => st
40
93
  */
41
94
  declare const isRecord: (value: unknown) => value is Record<string, unknown>;
42
95
 
43
- export { generateUuid, getStringField, isRecord };
96
+ export { generateUuid, getStringField, isRecord, lankaForeignSchemaMessage, lankaValueOrThrow };
@@ -1,7 +1,11 @@
1
+ import {
2
+ LankaValidationError
3
+ } from "../chunk-CRIRTOLB.js";
1
4
  import {
2
5
  getStringField,
3
6
  isRecord
4
7
  } from "../chunk-J5LA3Q6D.js";
8
+ import "../chunk-G32H73QY.js";
5
9
  import {
6
10
  setLankaRuntimeResolver
7
11
  } from "../chunk-BGVDPDX4.js";
@@ -25,10 +29,31 @@ function generateUuid() {
25
29
  return (c === "x" ? r : r & 3 | 8).toString(16);
26
30
  });
27
31
  }
32
+
33
+ // src/_internal/lanka-foreign-schema-message/lankaForeignSchemaMessage.ts
34
+ var lankaForeignSchemaMessage = (schema, { lead }) => {
35
+ const indexable = schema !== null && (typeof schema === "object" || typeof schema === "function");
36
+ if (indexable && "~standard" in schema) {
37
+ return `${lead} It does carry \`~standard\`, so it belongs to another library in \`modules/validators/\` \u2014 validate it with that package's validator, or with \`lankaStandardValidator\`.`;
38
+ }
39
+ return `${lead} Either it is not a schema at all, or it belongs to a library with its own package in \`modules/validators/\`.`;
40
+ };
41
+
42
+ // src/_internal/lanka-value-or-throw/lankaValueOrThrow.ts
43
+ var lankaValueOrThrow = (result, context) => {
44
+ if (result.success) return result.data;
45
+ throw new LankaValidationError(
46
+ `Validation failed for ${context}`,
47
+ result.errors,
48
+ result.fields
49
+ );
50
+ };
28
51
  export {
29
52
  generateUuid,
30
53
  getStringField,
31
54
  isRecord,
55
+ lankaForeignSchemaMessage,
56
+ lankaValueOrThrow,
32
57
  setLankaRuntimeResolver
33
58
  };
34
59
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/_internal/generate-uuid/generateUuid.ts"],"sourcesContent":["export function generateUuid() {\n\tif (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n\t\treturn crypto.randomUUID();\n\t}\n\n\tlet d = new Date().getTime();\n\n\tlet d2 =\n\t\t(typeof performance !== \"undefined\" && performance.now && performance.now() * 1000) || 0;\n\n\treturn \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n\t\tlet r = Math.random() * 16;\n\n\t\tif (d > 0) {\n\t\t\tr = ((d + r) % 16) | 0;\n\t\t\td = Math.floor(d / 16);\n\t\t} else {\n\t\t\tr = ((d2 + r) % 16) | 0;\n\t\t\td2 = Math.floor(d2 / 16);\n\t\t}\n\n\t\treturn (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n\t});\n}\n"],"mappings":";;;;;;;;;AAAO,SAAS,eAAe;AAC9B,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC7E,WAAO,OAAO,WAAW;AAAA,EAC1B;AAEA,MAAI,KAAI,oBAAI,KAAK,GAAE,QAAQ;AAE3B,MAAI,KACF,OAAO,gBAAgB,eAAe,YAAY,OAAO,YAAY,IAAI,IAAI,OAAS;AAExF,SAAO,uCAAuC,QAAQ,SAAS,SAAU,GAAG;AAC3E,QAAI,IAAI,KAAK,OAAO,IAAI;AAExB,QAAI,IAAI,GAAG;AACV,WAAM,IAAI,KAAK,KAAM;AACrB,UAAI,KAAK,MAAM,IAAI,EAAE;AAAA,IACtB,OAAO;AACN,WAAM,KAAK,KAAK,KAAM;AACtB,WAAK,KAAK,MAAM,KAAK,EAAE;AAAA,IACxB;AAEA,YAAQ,MAAM,MAAM,IAAK,IAAI,IAAO,GAAK,SAAS,EAAE;AAAA,EACrD,CAAC;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/_internal/generate-uuid/generateUuid.ts","../../src/_internal/lanka-foreign-schema-message/lankaForeignSchemaMessage.ts","../../src/_internal/lanka-value-or-throw/lankaValueOrThrow.ts"],"sourcesContent":["export function generateUuid() {\n\tif (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n\t\treturn crypto.randomUUID();\n\t}\n\n\tlet d = new Date().getTime();\n\n\tlet d2 =\n\t\t(typeof performance !== \"undefined\" && performance.now && performance.now() * 1000) || 0;\n\n\treturn \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n\t\tlet r = Math.random() * 16;\n\n\t\tif (d > 0) {\n\t\t\tr = ((d + r) % 16) | 0;\n\t\t\td = Math.floor(d / 16);\n\t\t} else {\n\t\t\tr = ((d2 + r) % 16) | 0;\n\t\t\td2 = Math.floor(d2 / 16);\n\t\t}\n\n\t\treturn (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n\t});\n}\n","/**\n * What to say when a validator is handed a schema from another library.\n *\n * ## Why this is not three sentences in three packages\n *\n * `@lankajs/yup`, `@lankajs/typebox` and `@lankajs/effect` each refuse a schema\n * they do not own, and each has to answer the same follow-up: is this a schema at\n * all, and if so whose? The answer is identical in all three because the\n * SITUATION is identical — only the lead differs, because only the marker each\n * looked for differs.\n *\n * Written three times it was three copies, and `check:composition` said so. Made\n * deliberately different to satisfy the gate it would have been worse: three\n * wordings of one fact, and a consumer who met two of them would be entitled to\n * think they meant different things.\n *\n * ## Why `lanka/internal` and not the facade\n *\n * This tier exists for exactly this: \"a sibling package needs these and must not\n * reach into another package's `src/`\". It promises nothing beyond a patch, which\n * is the right promise for a sentence.\n *\n * A facade `isStandardSchema` was proposed first and refused, for a reason worth\n * keeping: it would answer `true` for every yup schema, and `lankaStandardValidator`\n * throws on every yup schema. A consumer writing `if (isStandardSchema(s))\n * validate(s, …)` would have written the exact bug the family works to prevent,\n * and the name would have told them it was safe.\n */\nexport const lankaForeignSchemaMessage = (schema: unknown, { lead }: { lead: string }): string => {\n\t// Object OR function: arktype's schema is callable, with `~standard` on its\n\t// prototype, and a check reading only objects would tell an arktype user\n\t// their schema is not a schema.\n\tconst indexable =\n\t\tschema !== null && (typeof schema === \"object\" || typeof schema === \"function\");\n\n\tif (indexable && \"~standard\" in schema) {\n\t\treturn (\n\t\t\t`${lead} It does carry \\`~standard\\`, so it belongs to another library in ` +\n\t\t\t\"`modules/validators/` — validate it with that package's validator, or with \" +\n\t\t\t\"`lankaStandardValidator`.\"\n\t\t);\n\t}\n\n\treturn (\n\t\t`${lead} Either it is not a schema at all, or it belongs to a library with its ` +\n\t\t\"own package in `modules/validators/`.\"\n\t);\n};\n","import { LankaValidationError } from \"../../validation/lanka-validation-error/LankaValidationError\";\nimport type { TLankaValidationResult } from \"../../validation/_types/TLankaValidationResult\";\n\n/**\n * The strict path, built from the safe one.\n *\n * `ILankaValidator` publishes two methods over one answer: `validateSafe`\n * returns an outcome, and `validate` is that outcome with the failure raised.\n * Every implementation of the port therefore writes the same five lines, and\n * three of them in this repository did — `check:composition` counted them.\n *\n * It is a GENERIC over the outcome rather than a helper returning `unknown`,\n * which is the whole reason it can be shared: `@lankajs/typebox` returns\n * `Static<TSchema>` and `@lankajs/effect` returns `Schema.Schema.Type<TAlias>`,\n * and a helper that erased those would have cost each package its inference —\n * a worse trade than the duplication it removed.\n *\n * `lanka/internal` because a sibling package needs it and must not reach into\n * core's `src/`. It promises nothing beyond a patch, which is right for five\n * lines that only restate what the port already says.\n */\nexport const lankaValueOrThrow = <TOutput>(\n\tresult: TLankaValidationResult<TOutput>,\n\tcontext: string,\n): TOutput => {\n\tif (result.success) return result.data;\n\n\tthrow new LankaValidationError(\n\t\t`Validation failed for ${context}`,\n\t\tresult.errors,\n\t\tresult.fields,\n\t);\n};\n"],"mappings":";;;;;;;;;;;;;AAAO,SAAS,eAAe;AAC9B,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC7E,WAAO,OAAO,WAAW;AAAA,EAC1B;AAEA,MAAI,KAAI,oBAAI,KAAK,GAAE,QAAQ;AAE3B,MAAI,KACF,OAAO,gBAAgB,eAAe,YAAY,OAAO,YAAY,IAAI,IAAI,OAAS;AAExF,SAAO,uCAAuC,QAAQ,SAAS,SAAU,GAAG;AAC3E,QAAI,IAAI,KAAK,OAAO,IAAI;AAExB,QAAI,IAAI,GAAG;AACV,WAAM,IAAI,KAAK,KAAM;AACrB,UAAI,KAAK,MAAM,IAAI,EAAE;AAAA,IACtB,OAAO;AACN,WAAM,KAAK,KAAK,KAAM;AACtB,WAAK,KAAK,MAAM,KAAK,EAAE;AAAA,IACxB;AAEA,YAAQ,MAAM,MAAM,IAAK,IAAI,IAAO,GAAK,SAAS,EAAE;AAAA,EACrD,CAAC;AACF;;;ACKO,IAAM,4BAA4B,CAAC,QAAiB,EAAE,KAAK,MAAgC;AAIjG,QAAM,YACL,WAAW,SAAS,OAAO,WAAW,YAAY,OAAO,WAAW;AAErE,MAAI,aAAa,eAAe,QAAQ;AACvC,WACC,GAAG,IAAI;AAAA,EAIT;AAEA,SACC,GAAG,IAAI;AAGT;;;AC1BO,IAAM,oBAAoB,CAChC,QACA,YACa;AACb,MAAI,OAAO,QAAS,QAAO,OAAO;AAElC,QAAM,IAAI;AAAA,IACT,yBAAyB,OAAO;AAAA,IAChC,OAAO;AAAA,IACP,OAAO;AAAA,EACR;AACD;","names":[]}
@@ -1,5 +1,5 @@
1
- import { L as LankaEventBusInstance, I as ILankaScenarioVM } from './ILankaScenarioVM-DUsI-fSc.js';
2
- import { c as LankaScenariosRegistry, b as LankaScenarioVMRegistry, L as LankaGatewayLocator, a as LankaScenarioLocator } from './LankaScenarioLocator-D86TIwiu.js';
1
+ import { L as LankaEventBusInstance, I as ILankaScenarioVM } from './ILankaScenarioVM-DpKFL3iE.js';
2
+ import { c as LankaScenariosRegistry, b as LankaScenarioVMRegistry, L as LankaGatewayLocator, a as LankaScenarioLocator } from './LankaScenarioLocator-CAENLhDO.js';
3
3
  import { b as ILankaRuntimeConfig } from './ILankaRuntimeConfig-Vl436GWK.js';
4
4
  import { I as ILankaInFlightCounter } from './lankaHttpInFlight-Bk1eIuSx.js';
5
5
  import { T as TLankaRequestMiddleware } from './lankaRequestMiddleware-DAC5kCb7.js';
@@ -1,14 +1,15 @@
1
- import { c as ILankaPlugin, e as ILankaServiceConfig, I as ILankaBootstrapConfig, a as ILankaInstance } from '../createLanka-DI1CSy2Q.js';
2
- export { b as ILankaInstanceConfig, d as ILankaScenarioBootstrapConfig, f as createLanka } from '../createLanka-DI1CSy2Q.js';
1
+ import { c as ILankaPlugin, e as ILankaServiceConfig, I as ILankaBootstrapConfig, a as ILankaInstance } from '../createLanka-NrlvN-WQ.js';
2
+ export { b as ILankaInstanceConfig, d as ILankaScenarioBootstrapConfig, f as createLanka } from '../createLanka-NrlvN-WQ.js';
3
3
  import { I as ILankaFlags, a as ILankaHost } from '../ILankaRuntimeConfig-Vl436GWK.js';
4
4
  export { I as ILankaScope } from '../createLankaScope-BiFxNQgl.js';
5
- import '../activeRuntime-B336NU5I.js';
6
- import '../ILankaScenarioVM-DUsI-fSc.js';
7
- import '../LankaScenarioLocator-D86TIwiu.js';
8
- import '../ILankaScenarioMetadata-GoWWNEQL.js';
9
- import '../ALankaGateway-CkW1LbKE.js';
10
- import '../lankaStandardValidator-CL-r-zEV.js';
5
+ import '../activeRuntime-BYd2D6Lx.js';
6
+ import '../ILankaScenarioVM-DpKFL3iE.js';
7
+ import '../LankaScenarioLocator-CAENLhDO.js';
8
+ import '../ILankaScenarioMetadata-Dj4GCqmX.js';
9
+ import '../ALankaGateway-BrVPaZN5.js';
10
+ import '../lankaStandardValidator-BUFnysK0.js';
11
11
  import '@standard-schema/spec';
12
+ import '../ILankaFieldError-D5931-vT.js';
12
13
  import '../LankaSharedStoreLocator-zS2kLu-S.js';
13
14
  import '../ALankaSharedStore-B7uepuuk.js';
14
15
  import 'zustand/vanilla';
@@ -3,9 +3,9 @@ import {
3
3
  createLanka,
4
4
  resetActiveLanka,
5
5
  startLanka
6
- } from "../chunk-G3I7QIZR.js";
6
+ } from "../chunk-UBGXDTXC.js";
7
+ import "../chunk-B7EYIAW7.js";
7
8
  import "../chunk-5MAQVBI2.js";
8
- import "../chunk-UGXSGQPW.js";
9
9
  import "../chunk-O5ROO7QF.js";
10
10
  import "../chunk-XESL274R.js";
11
11
  import "../chunk-UJEC7H6K.js";
@@ -0,0 +1,99 @@
1
+ /**
2
+ * A resource's address in a read cache: `["order", 7]`, never a joined string.
3
+ *
4
+ * Strings, numbers and booleans only — deliberately narrower than `unknown[]`.
5
+ * `@nanostores/query` accepts `string | number | true` as a key part and cannot
6
+ * carry an object at all, so a wider type here would be a promise the port
7
+ * cannot keep on every implementation. An application that wants to key by a
8
+ * filter object states its parts: `["orders", status, page]`.
9
+ *
10
+ * Two structurally equal keys are the same key. Segments are ordered, so
11
+ * `["order", 1]` and `[1, "order"]` are two resources.
12
+ */
13
+ type TLankaCacheKey = readonly (string | number | boolean)[];
14
+
15
+ /**
16
+ * What a ViewModel calls to avoid asking the server twice.
17
+ *
18
+ * A PORT and nothing else: core declares the shape and ships no cache, because
19
+ * a host framework already carries one and two caches disagree on the first
20
+ * mutation. Where there is no host — a plain Vite SPA — the slot is empty rather
21
+ * than taken, and an application fills it with `@lankajs/tanstack-query`,
22
+ * `@lankajs/nanostores-query`, or an implementation of its own.
23
+ *
24
+ * It sits UNDER the ViewModel: the ViewModel calls it, hands it the loader it
25
+ * got from a gateway, and keeps deciding what a failure means. The screen still
26
+ * reads one hook.
27
+ *
28
+ * ## The clauses an implementation must keep
29
+ *
30
+ * Behaviour, not signatures, is where two honest implementations diverge, so
31
+ * each clause below is a promise and most of them are assertions in
32
+ * `@lankajs/tool-testing`'s `lankaReadCacheConformance`. Run it; do not read a
33
+ * member's source.
34
+ *
35
+ * 1. `subscribe` does NOT deliver the current value. Use `peek` for that.
36
+ * 2. `subscribe` delivers only SETTLED data — never a pending or failed state.
37
+ * 3. The release it answers removes exactly its own listener; a second call
38
+ * does nothing.
39
+ * 4. `peek` answers what is cached, stale or not. It never fetches and never
40
+ * throws.
41
+ * 5. `read` answers from memory while fresh; two concurrent reads of one key
42
+ * call `load` once.
43
+ * 6. `read` rejects with EXACTLY what `load` threw. Wrapping it breaks the
44
+ * caller: a ViewModel branches on `LankaError.kind`, and a wrapper turns
45
+ * every failure into the screen's.
46
+ * 7. A failed `read` is not remembered as data; the next one tries again.
47
+ * 8. `invalidate` resolves after the refetch settles when somebody is
48
+ * subscribed, and marks stale without fetching when nobody is.
49
+ * 9. `write` notifies that key's subscribers before it returns.
50
+ * 10. `clear` empties everything and notifies nobody.
51
+ * 11. `cancel` is declared only if it really aborts.
52
+ * 12. Nothing refetches on its own: only `read` and `invalidate` fetch. A
53
+ * background revalidation would change what a form was opened on.
54
+ *
55
+ * Four more cannot be checked from inside an implementation, and belong to
56
+ * whoever wires it: a subscriber must not write the key it observes; on a server
57
+ * the cache is per REQUEST, never a module-level singleton; one client instance
58
+ * per application; and the cache never fetches by itself — the loader comes from
59
+ * a gateway, through a ViewModel.
60
+ */
61
+ interface ILankaReadCache {
62
+ /**
63
+ * The resource: from memory while fresh, otherwise through `load`.
64
+ *
65
+ * `load` receives a signal only from an implementation that can cancel —
66
+ * hence the optional parameter. One that cannot passes nothing, rather than a
67
+ * signal that can never fire, and `TLankaExecuteOptions.signal` already
68
+ * accepts `undefined`, so the gateway call is written the same way either way.
69
+ */
70
+ read<TData>(key: TLankaCacheKey, load: (signal?: AbortSignal) => Promise<TData>, options?: {
71
+ staleMs?: number;
72
+ }): Promise<TData>;
73
+ /** What the server just answered, in the DOMAIN shape. Subscribers hear before this returns. */
74
+ write<TData>(key: TLankaCacheKey, data: TData): void;
75
+ /** Marks the resource stale, and reloads it while somebody is listening. */
76
+ invalidate(key: TLankaCacheKey): Promise<void>;
77
+ /** Hears every later change to the resource — not the current value. Answers the release. */
78
+ subscribe(key: TLankaCacheKey, onData: (data: unknown) => void): () => void;
79
+ /** What is cached right now, stale or not. Never fetches. */
80
+ peek<TData>(key: TLankaCacheKey): TData | undefined;
81
+ /** Empties everything — the end of a session. Notifies nobody. */
82
+ clear(): void;
83
+ /**
84
+ * Stops a load in flight: the screen went away.
85
+ *
86
+ * OPTIONAL, because three of the four libraries measured for this port cannot
87
+ * do it — `@nanostores/query` structurally, since no signal reaches its
88
+ * fetcher. Absent means "the request finishes and its answer is discarded",
89
+ * which is wasteful and never wrong. Declaring it as a no-op is worse than
90
+ * omitting it: a caller would believe the request stopped.
91
+ *
92
+ * It belongs to the cache rather than to the ViewModel because the cache owns
93
+ * deduplication — a second reader joins the first's request in flight, and a
94
+ * cancellation by the first would tear it out from under the second.
95
+ */
96
+ cancel?(key: TLankaCacheKey): void;
97
+ }
98
+
99
+ export type { ILankaReadCache, TLankaCacheKey };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -88,6 +88,7 @@ var LankaScenarioBootstrap = class {
88
88
  const scenarios = registry.getAllScenarios();
89
89
  this.registerScenarioEvents(scenarios);
90
90
  this.state.bootstrapped = true;
91
+ this.adoptDeclaredViewModels();
91
92
  this.initializeAlreadyCreatedViewModels();
92
93
  lankaLogger.printScenarioLog("BOOTSTRAP SCENARIOS FINISH");
93
94
  }
@@ -122,8 +123,27 @@ var LankaScenarioBootstrap = class {
122
123
  * to.
123
124
  */
124
125
  declaredViewModels = [];
125
- /** Registers everything declared into a NEW instance. Called by `createLanka`. */
126
+ /**
127
+ * Registers everything declared into a NEW instance.
128
+ *
129
+ * Called twice, and the second call is the one that makes this work on a
130
+ * server: `createLanka` calls it, and so does `bootstrap()`.
131
+ *
132
+ * The reason is an order nobody can change. Where "which instance is active"
133
+ * is answered by a RESOLVER — one instance per request, the shape
134
+ * `@lankajs/host` installs — an instance becomes findable only once the CALLER
135
+ * has put it in its store, and a caller can only do that after `createLanka`
136
+ * has returned. So during creation the answer is honestly "none", and asking
137
+ * for it threw: every server render of an application with a module-level
138
+ * ViewModel failed inside the call that was creating the scope, with a message
139
+ * about running outside one.
140
+ *
141
+ * Skipping is safe precisely because bootstrap adopts again. It is idempotent
142
+ * at the other end too: `attachViewModel` returns early for a ViewModel the
143
+ * registry already holds.
144
+ */
126
145
  adoptDeclaredViewModels() {
146
+ if (!getActiveRuntime()) return;
127
147
  for (const { viewModel, name } of [...this.declaredViewModels]) {
128
148
  this.attachViewModel(viewModel, name);
129
149
  }
@@ -162,14 +182,30 @@ var LankaScenarioBootstrap = class {
162
182
  * the ViewModel registry together with all their subscriptions, and the bus —
163
183
  * events and
164
184
  * middleware.
185
+ *
186
+ * What it does NOT clear by default is which ViewModels were DECLARED, and
187
+ * that default is load-bearing: a module-level ViewModel is built once per
188
+ * process, so the declaration is the only thing that lets a second instance
189
+ * find it. Drop it and its handlers bind to nothing, silently, for the rest of
190
+ * the process — see `declaredViewModels`.
191
+ *
192
+ * `withDeclarations` is for the case that default gets wrong: a suite that
193
+ * builds ViewModels inside test BODIES. Those are declared like any other and
194
+ * nothing un-declares them, so the next bootstrap re-adopts every one ever
195
+ * built and a finished test's handlers run again — against the gateway that
196
+ * test built, which is somebody else's double. Forgetting is not a tombstone:
197
+ * a ViewModel declared again afterwards is adopted again.
198
+ *
199
+ * @param config `withDeclarations` also forgets which ViewModels exist
165
200
  */
166
- reset() {
201
+ reset(config = {}) {
167
202
  ALankaScenario.clearAutoRegisteredScenarios();
168
203
  LankaScenariosRegistry.getInstance().clear();
169
204
  LankaScenarioVMRegistry.getInstance().resetAll();
170
205
  lankaEventBus.clearAllEvents();
171
206
  this.state.bootstrapped = false;
172
207
  this.state.initialized = /* @__PURE__ */ new WeakSet();
208
+ if (config.withDeclarations) this.declaredViewModels.length = 0;
173
209
  }
174
210
  };
175
211
  var lankaScenarioBootstrap = new LankaScenarioBootstrap();
@@ -177,4 +213,4 @@ var lankaScenarioBootstrap = new LankaScenarioBootstrap();
177
213
  export {
178
214
  lankaScenarioBootstrap
179
215
  };
180
- //# sourceMappingURL=chunk-UGXSGQPW.js.map
216
+ //# sourceMappingURL=chunk-B7EYIAW7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scenario/lanka-scenario-bootstrap/LankaScenarioBootstrap.ts"],"sourcesContent":["import type { ILankaScenario } from \"../_interfaces/ILankaScenario\";\nimport type { ILankaScenarioVM } from \"../_interfaces/ILankaScenarioVM\";\nimport type { ILankaScenarioResetConfig } from \"../_interfaces/ILankaScenarioResetConfig\";\nimport { LankaScenariosRegistry } from \"../_registries/lanka-scenarios-registry/LankaScenariosRegistry\";\nimport { LankaScenarioVMRegistry } from \"../_registries/lanka-scenario-vm-registry/LankaScenarioVMRegistry\";\nimport { ALankaScenario } from \"../_abstractions/lanka-scenario/ALankaScenario\";\nimport { lankaLogger } from \"../../logger/lanka-logger/LankaLogger\";\n\n/**\n * Brings up the scenario layer — deliberately React-free.\n *\n * Two jobs: declare every scenario's events on the bus as early as possible,\n * before the first ViewModel exists; and let a ViewModel raise its own\n * subscriptions at construction.\n *\n * Called from the application entry point before ViewModels are imported.\n */\n\n// The barrel of every scenario class: what keeps bootstrap in step with the list.\nimport * as ScenariosModule from \"@lanka_di/Scenarios\";\nimport { lankaEventBus } from \"../event-bus/_facades/lanka-event-bus/lankaEventBus\";\nimport {\n\tgetActiveRuntime,\n\trequireActiveRuntime,\n} from \"../../_internal/active-runtime/activeRuntime\";\n\n/**\n * Bootstrapping the scenario layer, for callers with no instance in hand.\n *\n * An ordinary class with an ordinary instance: the declared-ViewModel list below\n * is deliberately shared across instances — every test builds a new framework and\n * module-level ViewModels are declared once — so the single instance IS the\n * shared list, and a second one would be a second list nobody adopts.\n */\nexport class LankaScenarioBootstrap {\n\t/**\n\t * The bootstrapped flag and the set of initialised VMs live on the INSTANCE.\n\t *\n\t * As static fields, a second framework in the same process would exit\n\t * `bootstrap()` on someone else's flag — never starting at all, with no symptom\n\t * except that no scenario works.\n\t */\n\tprivate get state() {\n\t\treturn requireActiveRuntime().scenarioState;\n\t}\n\n\tpublic isScenarioLayerBootstrapped(): boolean {\n\t\treturn this.state.bootstrapped;\n\t}\n\n\tprivate forceInstantiateAllScenarios(): void {\n\t\t// Every class the pool already holds an instance of. Constructing it again\n\t\t// would add a second instance to a pool nothing ever drains: each bootstrap\n\t\t// — one per framework instance, one per test — grew it by the whole barrel,\n\t\t// and both `collectAutoRegisteredScenarios` and the locator's fallback walk\n\t\t// it in full.\n\t\tconst pooled = new Set(\n\t\t\tALankaScenario.getAutoRegisteredScenarios().map((scenario) => scenario.constructor),\n\t\t);\n\n\t\tfor (const exported of Object.values(ScenariosModule)) {\n\t\t\t// Only class constructors are relevant.\n\t\t\tif (typeof exported !== \"function\") continue;\n\t\t\tif (pooled.has(exported)) continue;\n\n\t\t\ttry {\n\t\t\t\t// Constructing an ALankaScenario subclass puts it into the\n\t\t\t\t// self-registration pool — see ALankaScenario.\n\t\t\t\tnew (exported as unknown as new () => unknown)();\n\t\t\t} catch {\n\t\t\t\t// Not every export is constructible; skip those.\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate registerScenarioEvents(scenarios: ILankaScenario<unknown>[]): void {\n\t\tconst registeredEventTypes = new Set<string>();\n\n\t\tscenarios.forEach((scenario) => {\n\t\t\tif (!registeredEventTypes.has(scenario.eventType)) {\n\t\t\t\tregisteredEventTypes.add(scenario.eventType);\n\n\t\t\t\tlankaLogger.printScenarioLog(\"REGISTER SCENARIO EVENT\", scenario.name);\n\t\t\t\tlankaEventBus.registerEvent(scenario.eventType, {\n\t\t\t\t\tdataType: scenario.dataTypeName,\n\t\t\t\t\tdescription: `Scenario: ${scenario.name}`,\n\t\t\t\t\tusedBy: [scenario.name],\n\t\t\t\t\tpriority: 0,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (scenario.initialize) {\n\t\t\t\tlankaLogger.printScenarioLog(\"INITIALIZE SCENARIO\", scenario.name);\n\t\t\t\tscenario.initialize();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate initializeAlreadyCreatedViewModels(): void {\n\t\tconst viewModels = LankaScenarioVMRegistry.getInstance().getAllViewModels();\n\t\tviewModels.forEach((vm) => {\n\t\t\tif (!this.state.initialized.has(vm)) {\n\t\t\t\tthis.state.initialized.add(vm);\n\t\t\t\tvm.initializeScenario();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Brings up the scenario layer.\n\t *\n\t * Constructs the scenario classes so they can be collected, declares their\n\t * events on the bus, calls `initialize()` where present, and initialises\n\t * ViewModels that already exist — which happens in tests and under unusual\n\t * import order.\n\t *\n\t * Idempotent.\n\t */\n\tpublic bootstrap(): void {\n\t\tif (this.state.bootstrapped) return;\n\n\t\tlankaLogger.printScenarioLog(\"BOOTSTRAP SCENARIOS START\");\n\n\t\t// Instances must exist, or the registry has nothing to collect.\n\t\tthis.forceInstantiateAllScenarios();\n\n\t\tconst registry = LankaScenariosRegistry.getInstance();\n\t\tregistry.collectAutoRegisteredScenarios();\n\n\t\tconst scenarios = registry.getAllScenarios();\n\t\tthis.registerScenarioEvents(scenarios);\n\n\t\tthis.state.bootstrapped = true;\n\n\t\t// The second adoption. On a server this is the FIRST one that can succeed:\n\t\t// the instance is in its caller's store by now, and creation's attempt was\n\t\t// made before it could be. In a browser it is a no-op — creation adopted\n\t\t// already, and attaching is idempotent.\n\t\tthis.adoptDeclaredViewModels();\n\n\t\t// Some ViewModels may predate bootstrap — in tests, or under unusual import\n\t\t// order; initialise them now.\n\t\tthis.initializeAlreadyCreatedViewModels();\n\n\t\tlankaLogger.printScenarioLog(\"BOOTSTRAP SCENARIOS FINISH\");\n\t}\n\n\t/**\n\t * Called by a ViewModel factory when the ViewModel declares scenario handlers.\n\t *\n\t * Registers the ViewModel and, if the layer is already up, initialises it\n\t * immediately. Duplicate registration and duplicate initialisation are\n\t * impossible.\n\t *\n\t * @param viewModel What to register\n\t * @param name Name for the log\n\t */\n\t/**\n\t * ViewModels DECLARED in this process.\n\t *\n\t * Module-level deliberately, the same case as the scenario pool: a registry of\n\t * DEFINITIONS, not runtime state. Splitting it between instances would be\n\t * divergence, not isolation — the classes come from the same modules and both\n\t * instances must see one list.\n\t *\n\t * Two reasons it exists:\n\t *\n\t * 1. Declaring a ViewModel is a declaration, not work. A screen file creates it\n\t * at module level, and import order decides whether that happens before or\n\t * after `createLanka()`. Requiring a live instance at declaration time\n\t * requires an import order the consumer does not control.\n\t * 2. Instances come one after another: every test creates its own. The list is\n\t * NOT drained by the first — otherwise the second instance would know no\n\t * module-level ViewModel and its `dispose()` would not remove their\n\t * subscriptions, so a test would receive events the previous one subscribed\n\t * to.\n\t */\n\tprivate readonly declaredViewModels: { viewModel: ILankaScenarioVM; name?: string }[] = [];\n\n\t/**\n\t * Registers everything declared into a NEW instance.\n\t *\n\t * Called twice, and the second call is the one that makes this work on a\n\t * server: `createLanka` calls it, and so does `bootstrap()`.\n\t *\n\t * The reason is an order nobody can change. Where \"which instance is active\"\n\t * is answered by a RESOLVER — one instance per request, the shape\n\t * `@lankajs/host` installs — an instance becomes findable only once the CALLER\n\t * has put it in its store, and a caller can only do that after `createLanka`\n\t * has returned. So during creation the answer is honestly \"none\", and asking\n\t * for it threw: every server render of an application with a module-level\n\t * ViewModel failed inside the call that was creating the scope, with a message\n\t * about running outside one.\n\t *\n\t * Skipping is safe precisely because bootstrap adopts again. It is idempotent\n\t * at the other end too: `attachViewModel` returns early for a ViewModel the\n\t * registry already holds.\n\t */\n\tpublic adoptDeclaredViewModels(): void {\n\t\tif (!getActiveRuntime()) return;\n\n\t\tfor (const { viewModel, name } of [...this.declaredViewModels]) {\n\t\t\tthis.attachViewModel(viewModel, name);\n\t\t}\n\t}\n\n\tpublic registerViewModel(viewModel: ILankaScenarioVM, name?: string): void {\n\t\tconst isKnown = this.declaredViewModels.some(\n\t\t\t(declared) => declared.viewModel === viewModel,\n\t\t);\n\t\tif (!isKnown) this.declaredViewModels.push({ viewModel, name });\n\n\t\t// No instance yet: the declaration came before bootstrap, and whoever\n\t\t// bootstraps will pick it up.\n\t\tif (!getActiveRuntime()) return;\n\n\t\tthis.attachViewModel(viewModel, name);\n\t}\n\n\tprivate attachViewModel(viewModel: ILankaScenarioVM, name?: string): void {\n\t\tconst registry = LankaScenarioVMRegistry.getInstance();\n\n\t\t// Already registered — nothing to do.\n\t\tif (registry.isRegistered(viewModel)) {\n\t\t\tif (name) {\n\t\t\t\tlankaLogger.printViewModelLog(\"VM Already Registered (skipped)\", name);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Register it.\n\t\tregistry.register(viewModel);\n\n\t\tif (name) {\n\t\t\tlankaLogger.printViewModelLog(\"Register Scenario VM\", name);\n\t\t}\n\n\t\t// The layer is already up — initialise it now rather than at the next\n\t\t// bootstrap.\n\t\tif (this.state.bootstrapped) {\n\t\t\tif (!this.state.initialized.has(viewModel)) {\n\t\t\t\tthis.state.initialized.add(viewModel);\n\t\t\t\tviewModel.initializeScenario();\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Resets the whole scenario layer. Without it tests are not isolated.\n\t *\n\t * Clears the self-registration list, the scenario registry with its metadata,\n\t * the ViewModel registry together with all their subscriptions, and the bus —\n\t * events and\n\t * middleware.\n\t *\n\t * What it does NOT clear by default is which ViewModels were DECLARED, and\n\t * that default is load-bearing: a module-level ViewModel is built once per\n\t * process, so the declaration is the only thing that lets a second instance\n\t * find it. Drop it and its handlers bind to nothing, silently, for the rest of\n\t * the process — see `declaredViewModels`.\n\t *\n\t * `withDeclarations` is for the case that default gets wrong: a suite that\n\t * builds ViewModels inside test BODIES. Those are declared like any other and\n\t * nothing un-declares them, so the next bootstrap re-adopts every one ever\n\t * built and a finished test's handlers run again — against the gateway that\n\t * test built, which is somebody else's double. Forgetting is not a tombstone:\n\t * a ViewModel declared again afterwards is adopted again.\n\t *\n\t * @param config `withDeclarations` also forgets which ViewModels exist\n\t */\n\tpublic reset(config: ILankaScenarioResetConfig = {}): void {\n\t\tALankaScenario.clearAutoRegisteredScenarios();\n\t\tLankaScenariosRegistry.getInstance().clear();\n\t\tLankaScenarioVMRegistry.getInstance().resetAll();\n\t\tlankaEventBus.clearAllEvents();\n\t\tthis.state.bootstrapped = false;\n\t\tthis.state.initialized = new WeakSet<ILankaScenarioVM>();\n\n\t\tif (config.withDeclarations) this.declaredViewModels.length = 0;\n\t}\n}\n\n/** The one every caller wants. */\nexport const lankaScenarioBootstrap = new LankaScenarioBootstrap();\n"],"mappings":"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","names":[]}
@@ -0,0 +1,27 @@
1
+ import {
2
+ LankaError
3
+ } from "./chunk-G32H73QY.js";
4
+
5
+ // src/validation/lanka-validation-error/LankaValidationError.ts
6
+ var LankaValidationError = class _LankaValidationError extends LankaError {
7
+ constructor(message, errors = [], fields = []) {
8
+ super({
9
+ kind: "schema",
10
+ message,
11
+ status: 422,
12
+ issues: errors.length > 0 ? errors : [message],
13
+ fields: fields.length > 0 ? fields : void 0
14
+ });
15
+ this.name = "LankaValidationError";
16
+ Object.setPrototypeOf(this, _LankaValidationError.prototype);
17
+ }
18
+ /** Always present here, unlike the base's optional list. */
19
+ get errors() {
20
+ return [...this.issues ?? []];
21
+ }
22
+ };
23
+
24
+ export {
25
+ LankaValidationError
26
+ };
27
+ //# sourceMappingURL=chunk-CRIRTOLB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/validation/lanka-validation-error/LankaValidationError.ts"],"sourcesContent":["import { LankaError } from \"../../errors/lanka-error/LankaError\";\nimport type { ILankaFieldError } from \"../../errors/_interfaces/ILankaFieldError\";\n\n/**\n * The validation port's failure: a body the schema refused.\n *\n * A `LankaError` of kind `schema`, so that `LankaError.is` and everything built\n * on it — the HTTP policy's error middleware, an application branching on\n * `kind` — see a refused body the way they see the request layer's own\n * \"200 that was not JSON\". It used to extend `Error` directly, and a consumer\n * that told a contract drift from a network failure by `kind` never saw it.\n *\n * `name` stays `LankaValidationError` and `status` stays 422: both are what a\n * consumer's existing `catch` reads, and the message list is `errors`. The same\n * issues arrive a second time as `fields`, with the path in segments, for the\n * consumer that has an input to show each one at.\n */\nexport class LankaValidationError extends LankaError {\n\tconstructor(message: string, errors: string[] = [], fields: readonly ILankaFieldError[] = []) {\n\t\tsuper({\n\t\t\tkind: \"schema\",\n\t\t\tmessage,\n\t\t\tstatus: 422,\n\t\t\tissues: errors.length > 0 ? errors : [message],\n\t\t\tfields: fields.length > 0 ? fields : undefined,\n\t\t});\n\t\tthis.name = \"LankaValidationError\";\n\t\tObject.setPrototypeOf(this, LankaValidationError.prototype);\n\t}\n\n\t/** Always present here, unlike the base's optional list. */\n\toverride get errors(): string[] {\n\t\treturn [...(this.issues ?? [])];\n\t}\n}\n"],"mappings":";;;;;AAiBO,IAAM,uBAAN,MAAM,8BAA6B,WAAW;AAAA,EACpD,YAAY,SAAiB,SAAmB,CAAC,GAAG,SAAsC,CAAC,GAAG;AAC7F,UAAM;AAAA,MACL,MAAM;AAAA,MACN;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ,OAAO,SAAS,IAAI,SAAS,CAAC,OAAO;AAAA,MAC7C,QAAQ,OAAO,SAAS,IAAI,SAAS;AAAA,IACtC,CAAC;AACD,SAAK,OAAO;AACZ,WAAO,eAAe,MAAM,sBAAqB,SAAS;AAAA,EAC3D;AAAA;AAAA,EAGA,IAAa,SAAmB;AAC/B,WAAO,CAAC,GAAI,KAAK,UAAU,CAAC,CAAE;AAAA,EAC/B;AACD;","names":[]}
@@ -4,6 +4,14 @@ var LankaError = class _LankaError extends Error {
4
4
  status;
5
5
  code;
6
6
  issues;
7
+ /**
8
+ * The failures with an input to be shown at. See `ILankaErrorInit.fields`.
9
+ *
10
+ * The array given is the array kept — unlike `errors`, which copies `issues`
11
+ * on every read. `readonly` carries the promise, and this is read on the way
12
+ * to a form, once, where a copy would buy nothing.
13
+ */
14
+ fields;
7
15
  /** The parsed error response body; core carries it without reading it. */
8
16
  body;
9
17
  constructor(init) {
@@ -13,6 +21,7 @@ var LankaError = class _LankaError extends Error {
13
21
  this.status = init.status;
14
22
  this.code = init.code;
15
23
  this.issues = init.issues;
24
+ this.fields = init.fields;
16
25
  this.body = init.body;
17
26
  Object.setPrototypeOf(this, _LankaError.prototype);
18
27
  }
@@ -46,4 +55,4 @@ var LankaError = class _LankaError extends Error {
46
55
  export {
47
56
  LankaError
48
57
  };
49
- //# sourceMappingURL=chunk-Q7QESSYF.js.map
58
+ //# sourceMappingURL=chunk-G32H73QY.js.map