lanka 1.0.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/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/ALankaGateway-ExlRGT3D.d.ts +122 -0
- package/dist/ALankaSharedStore-B7uepuuk.d.ts +29 -0
- package/dist/ILankaApiError-zI_2bNs0.d.ts +16 -0
- package/dist/ILankaRuntimeConfig-Vl436GWK.d.ts +62 -0
- package/dist/ILankaScenarioMetadata-Bu-yggTZ.d.ts +14 -0
- package/dist/ILankaScenarioVM-DuCyPoyT.d.ts +260 -0
- package/dist/LankaError-B1HtuIkw.d.ts +86 -0
- package/dist/LankaScenarioLocator-BGQHwf3n.d.ts +144 -0
- package/dist/LankaSharedStoreLocator-MvCpav5F.d.ts +126 -0
- package/dist/TLankaErrorHandler-Yfqtdh1M.d.ts +3 -0
- package/dist/_extend/index.d.ts +34 -0
- package/dist/_extend/index.js +36 -0
- package/dist/_extend/index.js.map +1 -0
- package/dist/_internal/index.d.ts +43 -0
- package/dist/_internal/index.js +34 -0
- package/dist/_internal/index.js.map +1 -0
- package/dist/activeRuntime-FcsSJvUg.d.ts +84 -0
- package/dist/bootstrap/index.d.ts +242 -0
- package/dist/bootstrap/index.js +22 -0
- package/dist/bootstrap/index.js.map +1 -0
- package/dist/chunk-73IVH3C6.js +52 -0
- package/dist/chunk-73IVH3C6.js.map +1 -0
- package/dist/chunk-BGVDPDX4.js +29 -0
- package/dist/chunk-BGVDPDX4.js.map +1 -0
- package/dist/chunk-C2HP7CRD.js +224 -0
- package/dist/chunk-C2HP7CRD.js.map +1 -0
- package/dist/chunk-D27MREPB.js +13 -0
- package/dist/chunk-D27MREPB.js.map +1 -0
- package/dist/chunk-DTO27QFR.js +59 -0
- package/dist/chunk-DTO27QFR.js.map +1 -0
- package/dist/chunk-EWVDJYCC.js +296 -0
- package/dist/chunk-EWVDJYCC.js.map +1 -0
- package/dist/chunk-FIR4XTBL.js +136 -0
- package/dist/chunk-FIR4XTBL.js.map +1 -0
- package/dist/chunk-ILQNYQY5.js +200 -0
- package/dist/chunk-ILQNYQY5.js.map +1 -0
- package/dist/chunk-J5LA3Q6D.js +14 -0
- package/dist/chunk-J5LA3Q6D.js.map +1 -0
- package/dist/chunk-MDZ42XRH.js +10 -0
- package/dist/chunk-MDZ42XRH.js.map +1 -0
- package/dist/chunk-Q3SOVBIJ.js +176 -0
- package/dist/chunk-Q3SOVBIJ.js.map +1 -0
- package/dist/chunk-Q7QESSYF.js +49 -0
- package/dist/chunk-Q7QESSYF.js.map +1 -0
- package/dist/chunk-RKYKK6MN.js +19 -0
- package/dist/chunk-RKYKK6MN.js.map +1 -0
- package/dist/chunk-RYFZCAQ3.js +637 -0
- package/dist/chunk-RYFZCAQ3.js.map +1 -0
- package/dist/chunk-UJEC7H6K.js +39 -0
- package/dist/chunk-UJEC7H6K.js.map +1 -0
- package/dist/chunk-XESL274R.js +23 -0
- package/dist/chunk-XESL274R.js.map +1 -0
- package/dist/chunk-YR4MZXMU.js +13 -0
- package/dist/chunk-YR4MZXMU.js.map +1 -0
- package/dist/config/index.d.ts +63 -0
- package/dist/config/index.js +16 -0
- package/dist/config/index.js.map +1 -0
- package/dist/createLankaScope-Bc_vChRs.d.ts +30 -0
- package/dist/errors/index.d.ts +37 -0
- package/dist/errors/index.js +16 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/gateway/index.d.ts +258 -0
- package/dist/gateway/index.js +438 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/lankaHttpInFlight-Bk1eIuSx.d.ts +43 -0
- package/dist/lankaRequestMiddleware-DAC5kCb7.d.ts +62 -0
- package/dist/lankaStandardValidator-CL-r-zEV.d.ts +34 -0
- package/dist/locator/index.d.ts +282 -0
- package/dist/locator/index.js +67 -0
- package/dist/locator/index.js.map +1 -0
- package/dist/logger/index.d.ts +146 -0
- package/dist/logger/index.js +11 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/mock/index.d.ts +16 -0
- package/dist/mock/index.js +58 -0
- package/dist/mock/index.js.map +1 -0
- package/dist/role/index.d.ts +48 -0
- package/dist/role/index.js +7 -0
- package/dist/role/index.js.map +1 -0
- package/dist/scenario/index.d.ts +204 -0
- package/dist/scenario/index.js +28 -0
- package/dist/scenario/index.js.map +1 -0
- package/dist/validation/index.d.ts +18 -0
- package/dist/validation/index.js +53 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/viewmodel/index.d.ts +478 -0
- package/dist/viewmodel/index.js +701 -0
- package/dist/viewmodel/index.js.map +1 -0
- package/package.json +99 -0
- package/skills/lanka-core/SKILL.md +241 -0
- package/skills/lanka-core/reference.md +1090 -0
- package/skills/lanka-packages/SKILL.md +70 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/validation/lanka-validation-error/LankaValidationError.ts
|
|
2
|
+
var LankaValidationError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
errors;
|
|
5
|
+
constructor(message, errors = []) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "LankaValidationError";
|
|
8
|
+
this.status = 422;
|
|
9
|
+
this.errors = errors.length > 0 ? errors : [message];
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/validation/lanka-standard-validator/lankaStandardValidator.ts
|
|
14
|
+
function describeIssue(issue) {
|
|
15
|
+
const path = (issue.path ?? []).map(
|
|
16
|
+
(segment) => typeof segment === "object" && segment !== null && "key" in segment ? String(segment.key) : String(segment)
|
|
17
|
+
).join(".");
|
|
18
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
19
|
+
}
|
|
20
|
+
function runSync(schema, data) {
|
|
21
|
+
const result = schema["~standard"].validate(data);
|
|
22
|
+
if (result instanceof Promise) {
|
|
23
|
+
throw new LankaValidationError(
|
|
24
|
+
"The schema is asynchronous and the validation port is synchronous. Parse such data by hand: passing it silently would be worse than failing.",
|
|
25
|
+
[]
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
var lankaStandardValidator = Object.freeze({
|
|
31
|
+
validate(schema, data, context) {
|
|
32
|
+
const result = runSync(schema, data);
|
|
33
|
+
if (result.issues) {
|
|
34
|
+
throw new LankaValidationError(
|
|
35
|
+
`Validation failed for ${context}`,
|
|
36
|
+
result.issues.map(describeIssue)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return result.value;
|
|
40
|
+
},
|
|
41
|
+
validateSafe(schema, data) {
|
|
42
|
+
const result = runSync(schema, data);
|
|
43
|
+
if (result.issues) {
|
|
44
|
+
return { success: false, errors: result.issues.map(describeIssue) };
|
|
45
|
+
}
|
|
46
|
+
return { success: true, data: result.value };
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
export {
|
|
50
|
+
LankaValidationError,
|
|
51
|
+
lankaStandardValidator
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/validation/lanka-validation-error/LankaValidationError.ts","../../src/validation/lanka-standard-validator/lankaStandardValidator.ts"],"sourcesContent":["import { ILankaApiError } from \"../../errors/_interfaces/ILankaApiError\";\n\n/**\n * What a schema refused, as an error a screen can catch by type.\n *\n * The one case where a class is the answer by itself: `instanceof` needs a\n * prototype. It carries the field errors a form renders, so a caller branches on\n * the type rather than parsing a message.\n */\nexport class LankaValidationError extends Error implements ILankaApiError {\n\tstatus: number;\n\terrors: string[];\n\n\tconstructor(message: string, errors: string[] = []) {\n\t\tsuper(message);\n\t\tthis.name = \"LankaValidationError\";\n\t\tthis.status = 422;\n\t\tthis.errors = errors.length > 0 ? errors : [message];\n\t}\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { LankaValidationError } from \"../lanka-validation-error/LankaValidationError\";\nimport type { TLankaValidationResult } from \"../_types/TLankaValidationResult\";\n\n/**\n * Response body validation with any Standard Schema implementation.\n *\n * An abstraction typed by one library cannot be implemented by another, which is\n * the whole reason a validation port exists. Standard Schema is the shared\n * interface implemented by zod 4, valibot, arktype and others.\n *\n * `@standard-schema/spec` contains TYPES ONLY and adds no bytes to a build, so it\n * is a regular dependency rather than another peer.\n *\n * No adapter classes: a schema describes itself, and\n * `schema[\"~standard\"].validate(data)` is the whole protocol.\n */\n\n/** A schema the port understands: any Standard Schema implementation. */\nexport type TLankaSchema<TOutput = unknown> = StandardSchemaV1<unknown, TOutput>;\n\nexport interface ILankaValidator {\n\t/** Validates and returns the parsed value, or throws. */\n\tvalidate<TOutput>(schema: TLankaSchema<TOutput>, data: unknown, context: string): TOutput;\n\t/** Validates and returns an outcome, throwing nothing. */\n\tvalidateSafe<TOutput>(\n\t\tschema: TLankaSchema<TOutput>,\n\t\tdata: unknown,\n\t): TLankaValidationResult<TOutput>;\n}\n\n/**\n * A field path plus a message.\n *\n * The path is assembled WHOLE — `items.0.id`, not `id`. Without the index and the\n * parent the message points nowhere when the list has twenty items.\n */\nfunction describeIssue(issue: StandardSchemaV1.Issue): string {\n\tconst path = (issue.path ?? [])\n\t\t.map((segment) =>\n\t\t\ttypeof segment === \"object\" && segment !== null && \"key\" in segment\n\t\t\t\t? String(segment.key)\n\t\t\t\t: String(segment),\n\t\t)\n\t\t.join(\".\");\n\n\treturn path ? `${path}: ${issue.message}` : issue.message;\n}\n\nfunction runSync<TOutput>(\n\tschema: TLankaSchema<TOutput>,\n\tdata: unknown,\n): StandardSchemaV1.Result<TOutput> {\n\tconst result = schema[\"~standard\"].validate(data);\n\n\t// Standard Schema allows returning a promise. A synchronous port cannot await\n\t// it, and answering \"fine\" would let UNVALIDATED data through — a check that\n\t// cannot fail reporting success.\n\tif (result instanceof Promise) {\n\t\tthrow new LankaValidationError(\n\t\t\t\"The schema is asynchronous and the validation port is synchronous. Parse \" +\n\t\t\t\t\"such data by hand: passing it silently would be worse than failing.\",\n\t\t\t[],\n\t\t);\n\t}\n\n\treturn result;\n}\n\nexport const lankaStandardValidator: ILankaValidator = Object.freeze<ILankaValidator>({\n\tvalidate<TOutput>(schema: TLankaSchema<TOutput>, data: unknown, context: string): TOutput {\n\t\tconst result = runSync(schema, data);\n\n\t\tif (result.issues) {\n\t\t\tthrow new LankaValidationError(\n\t\t\t\t`Validation failed for ${context}`,\n\t\t\t\tresult.issues.map(describeIssue),\n\t\t\t);\n\t\t}\n\n\t\treturn result.value;\n\t},\n\n\tvalidateSafe<TOutput>(\n\t\tschema: TLankaSchema<TOutput>,\n\t\tdata: unknown,\n\t): TLankaValidationResult<TOutput> {\n\t\tconst result = runSync(schema, data);\n\n\t\tif (result.issues) {\n\t\t\treturn { success: false, errors: result.issues.map(describeIssue) };\n\t\t}\n\n\t\treturn { success: true, data: result.value };\n\t},\n});\n"],"mappings":";AASO,IAAM,uBAAN,cAAmC,MAAgC;AAAA,EACzE;AAAA,EACA;AAAA,EAEA,YAAY,SAAiB,SAAmB,CAAC,GAAG;AACnD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,SAAS,OAAO,SAAS,IAAI,SAAS,CAAC,OAAO;AAAA,EACpD;AACD;;;ACkBA,SAAS,cAAc,OAAuC;AAC7D,QAAM,QAAQ,MAAM,QAAQ,CAAC,GAC3B;AAAA,IAAI,CAAC,YACL,OAAO,YAAY,YAAY,YAAY,QAAQ,SAAS,UACzD,OAAO,QAAQ,GAAG,IAClB,OAAO,OAAO;AAAA,EAClB,EACC,KAAK,GAAG;AAEV,SAAO,OAAO,GAAG,IAAI,KAAK,MAAM,OAAO,KAAK,MAAM;AACnD;AAEA,SAAS,QACR,QACA,MACmC;AACnC,QAAM,SAAS,OAAO,WAAW,EAAE,SAAS,IAAI;AAKhD,MAAI,kBAAkB,SAAS;AAC9B,UAAM,IAAI;AAAA,MACT;AAAA,MAEA,CAAC;AAAA,IACF;AAAA,EACD;AAEA,SAAO;AACR;AAEO,IAAM,yBAA0C,OAAO,OAAwB;AAAA,EACrF,SAAkB,QAA+B,MAAe,SAA0B;AACzF,UAAM,SAAS,QAAQ,QAAQ,IAAI;AAEnC,QAAI,OAAO,QAAQ;AAClB,YAAM,IAAI;AAAA,QACT,yBAAyB,OAAO;AAAA,QAChC,OAAO,OAAO,IAAI,aAAa;AAAA,MAChC;AAAA,IACD;AAEA,WAAO,OAAO;AAAA,EACf;AAAA,EAEA,aACC,QACA,MACkC;AAClC,UAAM,SAAS,QAAQ,QAAQ,IAAI;AAEnC,QAAI,OAAO,QAAQ;AAClB,aAAO,EAAE,SAAS,OAAO,QAAQ,OAAO,OAAO,IAAI,aAAa,EAAE;AAAA,IACnE;AAEA,WAAO,EAAE,SAAS,MAAM,MAAM,OAAO,MAAM;AAAA,EAC5C;AACD,CAAC;","names":[]}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { StoreApi, StateCreator, UseBoundStore } from 'zustand';
|
|
2
|
+
import { a as ILankaScenario, T as TLankaReplayRequest, I as ILankaScenarioVM } from '../ILankaScenarioVM-DuCyPoyT.js';
|
|
3
|
+
import { A as ALankaSharedStore } from '../ALankaSharedStore-B7uepuuk.js';
|
|
4
|
+
import { StoreApi as StoreApi$1 } from 'zustand/vanilla';
|
|
5
|
+
|
|
6
|
+
interface ILankaVMContext<TState, TGateways extends object, TServices extends object> {
|
|
7
|
+
set: StoreApi<TState>["setState"];
|
|
8
|
+
get: () => TState;
|
|
9
|
+
gateways: TGateways;
|
|
10
|
+
services: TServices;
|
|
11
|
+
trigger: <T>(scenario: ILankaScenario<T>, data?: T) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A scenario handler, accepted bivariantly.
|
|
16
|
+
*
|
|
17
|
+
* The method-shorthand indirection is deliberate and cannot be simplified: under
|
|
18
|
+
* `strictFunctionTypes` an arrow-typed parameter is contravariant, so a handler
|
|
19
|
+
* declared for a specific payload would not satisfy the bus, which calls every
|
|
20
|
+
* subscriber as `(data?: unknown) => void`. A method position is checked
|
|
21
|
+
* bivariantly, which is what makes the two assignable.
|
|
22
|
+
*
|
|
23
|
+
* Written once here because it appeared identically in three binding types, and
|
|
24
|
+
* a reader meeting `bivarianceHack` for the first time deserves to find the
|
|
25
|
+
* reason rather than the trick.
|
|
26
|
+
*/
|
|
27
|
+
type TLankaScenarioHandler<TData> = {
|
|
28
|
+
bivarianceHack(data?: TData): void;
|
|
29
|
+
}["bivarianceHack"];
|
|
30
|
+
|
|
31
|
+
interface ILankaScenarioBinding<TData, TState extends object, TGateways extends object, TServices extends object> {
|
|
32
|
+
scenario: ILankaScenario<TData>;
|
|
33
|
+
/**
|
|
34
|
+
* Bivariant handler allows passing stricter callback signatures
|
|
35
|
+
* (e.g. data?: TFeatureGroupsListRefreshEventData) while the
|
|
36
|
+
* lankaEventBus expects (data?: unknown) => void.
|
|
37
|
+
*/
|
|
38
|
+
handler: (ctx: ILankaVMContext<TState, TGateways, TServices>) => TLankaScenarioHandler<TData>;
|
|
39
|
+
options?: {
|
|
40
|
+
priority?: number;
|
|
41
|
+
replay?: TLankaReplayRequest;
|
|
42
|
+
usedBy?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type TUnknownLankaScenarioBinding<TState extends object, TGateways extends object, TServices extends object> = ILankaScenarioBinding<unknown, TState & ILankaScenarioVM, TGateways, TServices>;
|
|
47
|
+
|
|
48
|
+
type TLankaAnyMutators = any;
|
|
49
|
+
|
|
50
|
+
type TLankaVMEnhancer<TState> = (creator: StateCreator<TState, TLankaAnyMutators, TLankaAnyMutators>) => StateCreator<TState, TLankaAnyMutators, TLankaAnyMutators>;
|
|
51
|
+
|
|
52
|
+
interface ILankaVMConfig<State extends object, Actions extends object, TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> {
|
|
53
|
+
name: string;
|
|
54
|
+
/**
|
|
55
|
+
* Enables access-tracking optimization for no-selector hook usage.
|
|
56
|
+
* Disable when the ViewModel is consumed by a single broad consumer
|
|
57
|
+
* that reads most fields, because proxy tracking overhead may outweigh gains.
|
|
58
|
+
*/
|
|
59
|
+
enableAccessTrackingOptimization?: boolean;
|
|
60
|
+
states?: State;
|
|
61
|
+
createActions: (ctx: ILankaVMContext<State & Actions & ILankaScenarioVM, TGateways, Services>) => Actions;
|
|
62
|
+
scenarioHandlers?: TUnknownLankaScenarioBinding<State & Actions, TGateways, Services>[];
|
|
63
|
+
/** Non-gateway services. */
|
|
64
|
+
services?: Services | (() => Services);
|
|
65
|
+
/** TGateways (data layer) separated from services. */
|
|
66
|
+
gateways?: TGateways | (() => TGateways);
|
|
67
|
+
enhancers?: TLankaVMEnhancer<State & Actions & ILankaScenarioVM>[];
|
|
68
|
+
onInit?: (ctx: ILankaVMContext<State & Actions & ILankaScenarioVM, TGateways, Services>) => void;
|
|
69
|
+
onReset?: (ctx: ILankaVMContext<State & Actions & ILankaScenarioVM, TGateways, Services>) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The functional style of `ALankaVM`: a ViewModel declared as an options object.
|
|
74
|
+
*
|
|
75
|
+
* Everything it can do, the class can do, because this IS the class — the hooks a
|
|
76
|
+
* subclass overrides arrive here as config fields of the same names, and the
|
|
77
|
+
* protected surface arrives as the `ctx` every hook is handed. What a ViewModel is
|
|
78
|
+
* and how it binds scenarios is documented once, on `ALankaVM`.
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ ACCESS-TRACKING BLIND SPOT. A consumer re-renders only for state keys it READ
|
|
81
|
+
* off the returned proxy. An action that DERIVES a value (`getSomeView()`) reads the
|
|
82
|
+
* store through `get()`, which the proxy never sees — so a component whose only link
|
|
83
|
+
* to a state key is such a getter will never re-render when that key changes.
|
|
84
|
+
*
|
|
85
|
+
* Set `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT patch it
|
|
86
|
+
* in the view by destructuring the underlying keys for their side effect only: that
|
|
87
|
+
* reads as dead code, so a refactor, an unused-variable cleanup or a lint autofix
|
|
88
|
+
* removes it and the screen silently freezes again. `MeetingReportViewModel` carries
|
|
89
|
+
* the worked example (its report toggles froze exactly that way, twice).
|
|
90
|
+
*
|
|
91
|
+
* In development the mismatch ANNOUNCES ITSELF: the framework sees that a key
|
|
92
|
+
* changed, that no re-render will follow, and that the component reads that key
|
|
93
|
+
* through a getter — and warns with the ViewModel and key names. "Remember to
|
|
94
|
+
* set the flag" is not a mechanism.
|
|
95
|
+
*/
|
|
96
|
+
declare function createLankaVM<State extends object, Actions extends object>(config: ILankaVMConfig<State, Actions, Record<string, never>, Record<string, never>>): UseBoundStore<StoreApi<State & Actions & ILankaScenarioVM>>;
|
|
97
|
+
declare function createLankaVM<State extends object, Actions extends object, Services extends object>(config: ILankaVMConfig<State, Actions, Record<string, never>, Services>): UseBoundStore<StoreApi<State & Actions & ILankaScenarioVM>>;
|
|
98
|
+
declare function createLankaVM<State extends object, Actions extends object, TGateways extends object>(config: ILankaVMConfig<State, Actions, TGateways, Record<string, never>>): UseBoundStore<StoreApi<State & Actions & ILankaScenarioVM>>;
|
|
99
|
+
declare function createLankaVM<State extends object, Actions extends object, TGateways extends object, Services extends object>(config: ILankaVMConfig<State, Actions, TGateways, Services>): UseBoundStore<StoreApi<State & Actions & ILankaScenarioVM>>;
|
|
100
|
+
|
|
101
|
+
declare function createLazyLankaVM<State extends object, Actions extends object>(config: ILankaVMConfig<State, Actions, Record<string, never>, Record<string, never>>): TLazyLankaVM<ReturnType<typeof createLankaVM<State, Actions, Record<string, never>, Record<string, never>>>, State & Actions & ILankaScenarioVM>;
|
|
102
|
+
declare function createLazyLankaVM<State extends object, Actions extends object, Services extends object>(config: ILankaVMConfig<State, Actions, Record<string, never>, Services>): TLazyLankaVM<ReturnType<typeof createLankaVM<State, Actions, Record<string, never>, Services>>, State & Actions & ILankaScenarioVM>;
|
|
103
|
+
declare function createLazyLankaVM<State extends object, Actions extends object, TGateways extends object>(config: ILankaVMConfig<State, Actions, TGateways, Record<string, never>>): TLazyLankaVM<ReturnType<typeof createLankaVM<State, Actions, TGateways, Record<string, never>>>, State & Actions & ILankaScenarioVM>;
|
|
104
|
+
declare function createLazyLankaVM<State extends object, Actions extends object, TGateways extends object, Services extends object>(config: ILankaVMConfig<State, Actions, TGateways, Services>): TLazyLankaVM<ReturnType<typeof createLankaVM<State, Actions, TGateways, Services>>, State & Actions & ILankaScenarioVM>;
|
|
105
|
+
/**
|
|
106
|
+
* A lazy ViewModel hook: everything an ordinary store does, plus `dispose`.
|
|
107
|
+
*/
|
|
108
|
+
type TLazyLankaVM<TStore, TFullState> = TStore & {
|
|
109
|
+
getState: () => TFullState;
|
|
110
|
+
dispose: () => void;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
type TLankaSetState<TState> = (partial: TState | Partial<TState> | ((state: TState) => TState | Partial<TState>), replace?: boolean) => void;
|
|
114
|
+
interface ILankaStatelessVMContext<TState, TGateways extends object, TServices extends object> {
|
|
115
|
+
set: TLankaSetState<TState>;
|
|
116
|
+
get: () => TState;
|
|
117
|
+
gateways: TGateways;
|
|
118
|
+
services: TServices;
|
|
119
|
+
trigger: <T>(scenario: ILankaScenario<T>, data?: T) => void;
|
|
120
|
+
}
|
|
121
|
+
interface ILankaStatelessScenarioBinding<TData, TState extends object, TGateways extends object, TServices extends object> {
|
|
122
|
+
scenario: ILankaScenario<TData>;
|
|
123
|
+
handler: (ctx: ILankaStatelessVMContext<TState, TGateways, TServices>) => TLankaScenarioHandler<TData>;
|
|
124
|
+
options?: {
|
|
125
|
+
priority?: number;
|
|
126
|
+
replay?: TLankaReplayRequest;
|
|
127
|
+
usedBy?: string;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
type TLankaStatelessVMConfig$1<Actions extends object, TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> = {
|
|
131
|
+
name: string;
|
|
132
|
+
createActions: (ctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>) => Actions;
|
|
133
|
+
scenarioHandlers?: ILankaStatelessScenarioBinding<unknown, Actions & ILankaScenarioVM, TGateways, Services>[];
|
|
134
|
+
services?: Services | (() => Services);
|
|
135
|
+
gateways?: TGateways | (() => TGateways);
|
|
136
|
+
onInit?: (ctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>) => void;
|
|
137
|
+
onReset?: (ctx: ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>) => void;
|
|
138
|
+
};
|
|
139
|
+
type TLankaStatelessVMHook<Actions extends object> = {
|
|
140
|
+
<TSelected = Actions & ILankaScenarioVM>(selector?: (full: Actions & ILankaScenarioVM) => TSelected): TSelected;
|
|
141
|
+
getState: () => Actions & ILankaScenarioVM;
|
|
142
|
+
};
|
|
143
|
+
declare function createStatelessLankaVM<Actions extends object>(config: TLankaStatelessVMConfig$1<Actions, Record<string, never>, Record<string, never>>): TLankaStatelessVMHook<Actions>;
|
|
144
|
+
declare function createStatelessLankaVM<Actions extends object, Services extends object>(config: TLankaStatelessVMConfig$1<Actions, Record<string, never>, Services>): TLankaStatelessVMHook<Actions>;
|
|
145
|
+
declare function createStatelessLankaVM<Actions extends object, TGateways extends object>(config: TLankaStatelessVMConfig$1<Actions, TGateways, Record<string, never>>): TLankaStatelessVMHook<Actions>;
|
|
146
|
+
declare function createStatelessLankaVM<Actions extends object, TGateways extends object, Services extends object>(config: TLankaStatelessVMConfig$1<Actions, TGateways, Services>): TLankaStatelessVMHook<Actions>;
|
|
147
|
+
|
|
148
|
+
type TLankaStatelessVMConfig<Actions extends object, TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> = Omit<ILankaVMConfig<object, Actions, TGateways, Services>, "states"> & {
|
|
149
|
+
states?: never;
|
|
150
|
+
};
|
|
151
|
+
type TLazyStatelessReturn<Actions extends object, TGateways extends object, Services extends object> = ReturnType<typeof createStatelessLankaVM<Actions, TGateways, Services>> & {
|
|
152
|
+
getState: () => Actions & ILankaScenarioVM;
|
|
153
|
+
dispose: () => void;
|
|
154
|
+
};
|
|
155
|
+
declare function createLazyStatelessLankaVM<Actions extends object>(config: TLankaStatelessVMConfig<Actions, Record<string, never>, Record<string, never>>): TLazyStatelessReturn<Actions, Record<string, never>, Record<string, never>>;
|
|
156
|
+
declare function createLazyStatelessLankaVM<Actions extends object, Services extends object>(config: TLankaStatelessVMConfig<Actions, Record<string, never>, Services>): TLazyStatelessReturn<Actions, Record<string, never>, Services>;
|
|
157
|
+
declare function createLazyStatelessLankaVM<Actions extends object, TGateways extends object>(config: TLankaStatelessVMConfig<Actions, TGateways, Record<string, never>>): TLazyStatelessReturn<Actions, TGateways, Record<string, never>>;
|
|
158
|
+
declare function createLazyStatelessLankaVM<Actions extends object, TGateways extends object, Services extends object>(config: TLankaStatelessVMConfig<Actions, TGateways, Services>): TLazyStatelessReturn<Actions, TGateways, Services>;
|
|
159
|
+
|
|
160
|
+
interface ILankaSharedStoreVMContext<TStoreState extends object, TFullState extends object, TStore extends ALankaSharedStore<TStoreState>, TGateways extends object, TServices extends object> {
|
|
161
|
+
set: StoreApi$1<TStoreState>["setState"];
|
|
162
|
+
getStore: () => TStoreState;
|
|
163
|
+
get: () => TFullState;
|
|
164
|
+
store: TStore;
|
|
165
|
+
gateways: TGateways;
|
|
166
|
+
services: TServices;
|
|
167
|
+
trigger: <T>(scenario: ILankaScenario<T>, data?: T) => void;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
type TLankaSharedStoreVMHook<TStoreState extends object, TActions extends object> = {
|
|
171
|
+
<TSelected = TStoreState & TActions & ILankaScenarioVM>(selector?: (full: TStoreState & TActions & ILankaScenarioVM) => TSelected): TSelected;
|
|
172
|
+
getState: () => TStoreState & TActions & ILankaScenarioVM;
|
|
173
|
+
getStoreState: () => TStoreState;
|
|
174
|
+
};
|
|
175
|
+
interface ILankaSharedStoreScenarioBinding<TData, TStoreState extends object, TActions extends object, TStore extends ALankaSharedStore<TStoreState>, TGateways extends object, TServices extends object> {
|
|
176
|
+
scenario: ILankaScenario<TData>;
|
|
177
|
+
/**
|
|
178
|
+
* Bivariant handler allows stricter callback signatures while lankaEventBus expects unknown.
|
|
179
|
+
*/
|
|
180
|
+
handler: (ctx: ILankaSharedStoreVMContext<TStoreState, TStoreState & TActions & ILankaScenarioVM, TStore, TGateways, TServices>) => TLankaScenarioHandler<TData>;
|
|
181
|
+
options?: {
|
|
182
|
+
priority?: number;
|
|
183
|
+
replay?: TLankaReplayRequest;
|
|
184
|
+
usedBy?: string;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
interface ILankaSharedStoreVMConfig<TStoreState extends object, TActions extends object, TStore extends ALankaSharedStore<TStoreState>, TGateways extends object = Record<string, never>, TServices extends object = Record<string, never>> {
|
|
188
|
+
name: string;
|
|
189
|
+
/**
|
|
190
|
+
* Enables access-tracking optimization for no-selector hook usage.
|
|
191
|
+
* Disable when one broad consumer reads most store fields and updates are frequent.
|
|
192
|
+
*/
|
|
193
|
+
enableAccessTrackingOptimization?: boolean;
|
|
194
|
+
store: TStore;
|
|
195
|
+
createActions: (ctx: ILankaSharedStoreVMContext<TStoreState, TStoreState & TActions & ILankaScenarioVM, TStore, TGateways, TServices>) => TActions;
|
|
196
|
+
scenarioHandlers?: ILankaSharedStoreScenarioBinding<unknown, TStoreState, TActions, TStore, TGateways, TServices>[];
|
|
197
|
+
services?: TServices | (() => TServices);
|
|
198
|
+
gateways?: TGateways | (() => TGateways);
|
|
199
|
+
onInit?: (ctx: ILankaSharedStoreVMContext<TStoreState, TStoreState & TActions & ILankaScenarioVM, TStore, TGateways, TServices>) => void;
|
|
200
|
+
onReset?: (ctx: ILankaSharedStoreVMContext<TStoreState, TStoreState & TActions & ILankaScenarioVM, TStore, TGateways, TServices>) => void;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Factory for ViewModels backed by an external shared store instance.
|
|
205
|
+
* Multiple ViewModels can be created on top of the same store instance.
|
|
206
|
+
*/
|
|
207
|
+
declare function createSharedStoreLankaVM<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, Record<string, never>, Record<string, never>>): TLankaSharedStoreVMHook<StoreState, Actions>;
|
|
208
|
+
declare function createSharedStoreLankaVM<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>, Services extends object>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, Record<string, never>, Services>): TLankaSharedStoreVMHook<StoreState, Actions>;
|
|
209
|
+
declare function createSharedStoreLankaVM<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>, TGateways extends object>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Record<string, never>>): TLankaSharedStoreVMHook<StoreState, Actions>;
|
|
210
|
+
declare function createSharedStoreLankaVM<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>, TGateways extends object, Services extends object>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>): TLankaSharedStoreVMHook<StoreState, Actions>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* What a lazy shared-store ViewModel is, in the type as well as at runtime.
|
|
214
|
+
*
|
|
215
|
+
* The overloads used to promise the eager factory's return and nothing else, so
|
|
216
|
+
* `dispose` and `getStoreState` existed on the object and not in the type: a
|
|
217
|
+
* consumer releasing a closed screen's ViewModel got a compile error for calling
|
|
218
|
+
* something that was there.
|
|
219
|
+
*/
|
|
220
|
+
type TLazySharedStoreReturn<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>, TGateways extends object, Services extends object> = ReturnType<typeof createSharedStoreLankaVM<StoreState, Actions, Store, TGateways, Services>> & {
|
|
221
|
+
getState: () => StoreState & Actions & ILankaScenarioVM;
|
|
222
|
+
getStoreState: () => StoreState;
|
|
223
|
+
dispose: () => void;
|
|
224
|
+
};
|
|
225
|
+
declare function createLazySharedStoreLankaVM<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, Record<string, never>, Record<string, never>>): TLazySharedStoreReturn<StoreState, Actions, Store, Record<string, never>, Record<string, never>>;
|
|
226
|
+
declare function createLazySharedStoreLankaVM<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>, TGateways extends object>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Record<string, never>>): TLazySharedStoreReturn<StoreState, Actions, Store, TGateways, Record<string, never>>;
|
|
227
|
+
declare function createLazySharedStoreLankaVM<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>, TGateways extends object, Services extends object>(config: ILankaSharedStoreVMConfig<StoreState, Actions, Store, TGateways, Services>): TLazySharedStoreReturn<StoreState, Actions, Store, TGateways, Services>;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* What every ViewModel is given, and the two moments it is told about.
|
|
231
|
+
*
|
|
232
|
+
* The three ViewModel shapes — stateful, stateless, over a shared store — differ
|
|
233
|
+
* in where their state lives and in nothing else about this: each is handed a
|
|
234
|
+
* data layer and a set of collaborators, and each is told when its scenarios are
|
|
235
|
+
* bound and when they are about to be unbound.
|
|
236
|
+
*
|
|
237
|
+
* Stated once because a fifth hook added to two of the three is exactly the
|
|
238
|
+
* divergence `skills/parity/SKILL.md` is written against, and three copies of a
|
|
239
|
+
* default is how that starts.
|
|
240
|
+
*
|
|
241
|
+
* It is not a role and nothing extends it directly: the three bases do, and a
|
|
242
|
+
* consumer extends one of them.
|
|
243
|
+
*/
|
|
244
|
+
declare abstract class ALankaVMEnvironment<TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> {
|
|
245
|
+
/** What `createGateways` answered: the data layer, kept apart from services. */
|
|
246
|
+
protected gateways: TGateways;
|
|
247
|
+
/** What `createServices` answered: everything that is not a gateway. */
|
|
248
|
+
protected services: Services;
|
|
249
|
+
/** The data layer, built once per ViewModel. */
|
|
250
|
+
protected createGateways(): TGateways;
|
|
251
|
+
/** Non-gateway collaborators, built once per ViewModel. */
|
|
252
|
+
protected createServices(): Services;
|
|
253
|
+
/** Runs after the scenarios are bound. */
|
|
254
|
+
protected onInit(): void;
|
|
255
|
+
/** Runs when the screen goes away, before the scenarios are unbound. */
|
|
256
|
+
protected onReset(): void;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* A ViewModel written as a class: the state a screen reads, and the only place a
|
|
261
|
+
* gateway is called from.
|
|
262
|
+
*
|
|
263
|
+
* This is the implementation of the role, and `createLankaVM` is the same thing
|
|
264
|
+
* reached the other way — a subclass built from an options object. Neither style
|
|
265
|
+
* can do what the other cannot, because there is nothing here to diverge from.
|
|
266
|
+
*
|
|
267
|
+
* The protected surface IS the functional context, member for member:
|
|
268
|
+
* `set`, `get`, `gateways`, `services`, `trigger`. What the config supplies as a
|
|
269
|
+
* value or a thunk, the class supplies by overriding a method of the same name —
|
|
270
|
+
* `states`, `scenarioHandlers`, `enhancers`, `onInit`, `onReset` — with the two
|
|
271
|
+
* dependency suppliers named `createGateways` and `createServices`, because
|
|
272
|
+
* `gateways` and `services` already name what they answer.
|
|
273
|
+
*
|
|
274
|
+
* ```ts
|
|
275
|
+
* class TodoVM extends ALankaVM<ITodoState, ITodoActions, ITodoGateways> {
|
|
276
|
+
* protected readonly name = "TodoVM";
|
|
277
|
+
*
|
|
278
|
+
* protected states(): ITodoState {
|
|
279
|
+
* return { todos: [], isLoading: false };
|
|
280
|
+
* }
|
|
281
|
+
*
|
|
282
|
+
* protected createGateways(): ITodoGateways {
|
|
283
|
+
* return { todo: new TodoGateway() };
|
|
284
|
+
* }
|
|
285
|
+
*
|
|
286
|
+
* protected createActions(): ITodoActions {
|
|
287
|
+
* return {
|
|
288
|
+
* load: async () => {
|
|
289
|
+
* this.set({ isLoading: true });
|
|
290
|
+
* this.set({ todos: await this.gateways.todo.list(), isLoading: false });
|
|
291
|
+
* },
|
|
292
|
+
* };
|
|
293
|
+
* }
|
|
294
|
+
* }
|
|
295
|
+
*
|
|
296
|
+
* export const useTodoVM = new TodoVM().build();
|
|
297
|
+
* ```
|
|
298
|
+
*
|
|
299
|
+
* The access-tracking blind spot the functional style documents is the same one
|
|
300
|
+
* here, and `enableAccessTrackingOptimization` is the same switch. A consumer
|
|
301
|
+
* re-renders only for state keys it READ off the returned proxy; an action that
|
|
302
|
+
* DERIVES a value reads the store through `get`, which the proxy never sees, so a
|
|
303
|
+
* component whose only link to a key is such a getter never re-renders for it. In
|
|
304
|
+
* development the mismatch announces itself by name rather than by a frozen
|
|
305
|
+
* screen. Canon: `skills/parity/SKILL.md`.
|
|
306
|
+
*/
|
|
307
|
+
declare abstract class ALankaVM<State extends object, Actions extends object, TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> extends ALankaVMEnvironment<TGateways, Services> {
|
|
308
|
+
/** Names the ViewModel in the logs, the scenario registry and the blind-spot warning. */
|
|
309
|
+
protected abstract readonly name: string;
|
|
310
|
+
/**
|
|
311
|
+
* Turn off when one broad consumer reads most fields, or when an action derives
|
|
312
|
+
* what the screen shows — proxy tracking then costs more than it saves, and in
|
|
313
|
+
* the second case it cannot see the read at all.
|
|
314
|
+
*/
|
|
315
|
+
protected readonly enableAccessTrackingOptimization: boolean;
|
|
316
|
+
/** Writes state. Available from `createActions` onwards, never before. */
|
|
317
|
+
protected set: StoreApi<State & Actions & ILankaScenarioVM>["setState"];
|
|
318
|
+
/** Reads state. The read a tracked hook cannot see — hence the switch above. */
|
|
319
|
+
protected get: () => State & Actions & ILankaScenarioVM;
|
|
320
|
+
/** Fires a scenario, which every ViewModel bound to it then hears. */
|
|
321
|
+
protected trigger: <TData>(scenario: ILankaScenario<TData>, data?: TData) => void;
|
|
322
|
+
/** The reactive fields the screen reads. */
|
|
323
|
+
protected states(): State;
|
|
324
|
+
/** The scenarios this ViewModel listens to, unsubscribed for it on reset. */
|
|
325
|
+
protected scenarioHandlers(): TUnknownLankaScenarioBinding<State & Actions, TGateways, Services>[];
|
|
326
|
+
/** Store middleware — `persist`, `lankaDevtools` — the last one applied outermost. */
|
|
327
|
+
protected enhancers(): TLankaVMEnhancer<State & Actions & ILankaScenarioVM>[];
|
|
328
|
+
/** The actions the screen calls. Written against `this.set` and `this.get`. */
|
|
329
|
+
protected abstract createActions(): Actions;
|
|
330
|
+
/**
|
|
331
|
+
* The protected surface as an object, for the functional style.
|
|
332
|
+
*
|
|
333
|
+
* Assembled INSIDE the class because that is the only place `protected` can be
|
|
334
|
+
* read — a context built from outside could carry only the public half, which is
|
|
335
|
+
* the wrong half. Canon: `skills/parity/SKILL.md` section 3a.
|
|
336
|
+
*/
|
|
337
|
+
protected toStyleContext(): ILankaVMContext<State & Actions & ILankaScenarioVM, TGateways, Services>;
|
|
338
|
+
/** Builds the hook a screen calls. One store per call. */
|
|
339
|
+
build(): UseBoundStore<StoreApi<State & Actions & ILankaScenarioVM>>;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* A ViewModel that holds no reactive state: actions, and what they orchestrate.
|
|
344
|
+
*
|
|
345
|
+
* The second rung of the ladder in `core/README.md` written as a class. A screen
|
|
346
|
+
* that reads nothing and only DOES things — a sign-out, a share sheet, a form
|
|
347
|
+
* whose fields live in the form library — pays for a zustand store it never
|
|
348
|
+
* reads; this is the same role without one.
|
|
349
|
+
*
|
|
350
|
+
* The protected surface IS the functional context, member for member: `set`,
|
|
351
|
+
* `get`, `gateways`, `services`, `trigger`. What the config supplies as a value
|
|
352
|
+
* or a thunk, the class supplies by overriding a method of the same name, with
|
|
353
|
+
* the two dependency suppliers named `createGateways` and `createServices`
|
|
354
|
+
* because `gateways` and `services` already name what they answer.
|
|
355
|
+
*
|
|
356
|
+
* ```ts
|
|
357
|
+
* class SessionVM extends ALankaStatelessVM<ISessionActions, ISessionGateways> {
|
|
358
|
+
* protected readonly name = "SessionVM";
|
|
359
|
+
*
|
|
360
|
+
* protected createGateways(): ISessionGateways {
|
|
361
|
+
* return { session: new SessionGateway() };
|
|
362
|
+
* }
|
|
363
|
+
*
|
|
364
|
+
* protected createActions(): ISessionActions {
|
|
365
|
+
* return {
|
|
366
|
+
* signOut: async () => {
|
|
367
|
+
* await this.gateways.session.signOut();
|
|
368
|
+
* this.trigger(sessionEnded);
|
|
369
|
+
* },
|
|
370
|
+
* };
|
|
371
|
+
* }
|
|
372
|
+
* }
|
|
373
|
+
*
|
|
374
|
+
* export const useSessionVM = new SessionVM().build();
|
|
375
|
+
* ```
|
|
376
|
+
*
|
|
377
|
+
* Canon: `skills/parity/SKILL.md`.
|
|
378
|
+
*/
|
|
379
|
+
declare abstract class ALankaStatelessVM<Actions extends object, TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> extends ALankaVMEnvironment<TGateways, Services> {
|
|
380
|
+
/** Names the ViewModel in the logs, the scenario registry and any warning. */
|
|
381
|
+
protected abstract readonly name: string;
|
|
382
|
+
/** Writes state. Available from `createActions` onwards, never before. */
|
|
383
|
+
protected set: TLankaSetState<Actions & ILankaScenarioVM>;
|
|
384
|
+
/** Reads what the actions have written, including the actions themselves. */
|
|
385
|
+
protected get: () => Actions & ILankaScenarioVM;
|
|
386
|
+
/** Fires a scenario, which every ViewModel bound to it then hears. */
|
|
387
|
+
protected trigger: <TData>(scenario: ILankaScenario<TData>, data?: TData) => void;
|
|
388
|
+
/** The scenarios this ViewModel listens to, unsubscribed for it on reset. */
|
|
389
|
+
protected scenarioHandlers(): ILankaStatelessScenarioBinding<unknown, Actions & ILankaScenarioVM, TGateways, Services>[];
|
|
390
|
+
/** The actions the screen calls. Written against `this.set` and `this.get`. */
|
|
391
|
+
protected abstract createActions(): Actions;
|
|
392
|
+
/**
|
|
393
|
+
* The protected surface as an object, for the functional style.
|
|
394
|
+
*
|
|
395
|
+
* Assembled INSIDE the class because that is the only place `protected` can be
|
|
396
|
+
* read. Canon: `skills/parity/SKILL.md` section 3a.
|
|
397
|
+
*/
|
|
398
|
+
protected toStyleContext(): ILankaStatelessVMContext<Actions & ILankaScenarioVM, TGateways, Services>;
|
|
399
|
+
/** Builds the hook a screen calls. One ViewModel per call. */
|
|
400
|
+
build(): TLankaStatelessVMHook<Actions>;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* A ViewModel over a store several ViewModels share, written as a class.
|
|
405
|
+
*
|
|
406
|
+
* The third rung of the ladder in `core/README.md`: reach for it only when two
|
|
407
|
+
* ViewModels must CO-EDIT one state — a list and the badge that counts it, a
|
|
408
|
+
* form and the header that says it is dirty. What the class adds over the
|
|
409
|
+
* stateful base is where the state lives: in the store it is given, so `set`
|
|
410
|
+
* writes there and `getStore` reads it.
|
|
411
|
+
*
|
|
412
|
+
* ```ts
|
|
413
|
+
* class BadgeVM extends ALankaSharedStoreVM<ISelection, IBadgeActions, TodoStore> {
|
|
414
|
+
* protected readonly name = "BadgeVM";
|
|
415
|
+
*
|
|
416
|
+
* public constructor(store: TodoStore) {
|
|
417
|
+
* super(store);
|
|
418
|
+
* }
|
|
419
|
+
*
|
|
420
|
+
* protected createActions(): IBadgeActions {
|
|
421
|
+
* return { clear: () => this.set({ selectedId: null }) };
|
|
422
|
+
* }
|
|
423
|
+
* }
|
|
424
|
+
* ```
|
|
425
|
+
*
|
|
426
|
+
* Unlike its two siblings this one is a thin adapter rather than the
|
|
427
|
+
* implementation: the store, the tracked hook and the two memoised state
|
|
428
|
+
* references are the factory's, and duplicating them here would be the second
|
|
429
|
+
* implementation the parity canon exists to prevent. What it gives a class-style
|
|
430
|
+
* consumer is the same protected surface under the same names.
|
|
431
|
+
*
|
|
432
|
+
* Canon: `skills/parity/SKILL.md`.
|
|
433
|
+
*/
|
|
434
|
+
declare abstract class ALankaSharedStoreVM<StoreState extends object, Actions extends object, Store extends ALankaSharedStore<StoreState>, TGateways extends object = Record<string, never>, Services extends object = Record<string, never>> extends ALankaVMEnvironment<TGateways, Services> {
|
|
435
|
+
/** Names the ViewModel in the logs and in the scenario registry. */
|
|
436
|
+
protected abstract readonly name: string;
|
|
437
|
+
/** The store this ViewModel and its siblings share. */
|
|
438
|
+
protected readonly store: Store;
|
|
439
|
+
/** Writes into the shared store, which every reader of it hears about. */
|
|
440
|
+
protected set: ILankaSharedStoreVMContext<StoreState, StoreState & Actions & ILankaScenarioVM, Store, TGateways, Services>["set"];
|
|
441
|
+
/** Reads the store's own state, without this ViewModel's actions on top. */
|
|
442
|
+
protected getStore: () => StoreState;
|
|
443
|
+
/** Reads the store's state WITH the actions, which is what a screen sees. */
|
|
444
|
+
protected get: () => StoreState & Actions & ILankaScenarioVM;
|
|
445
|
+
/** Fires a scenario, which every ViewModel bound to it then hears. */
|
|
446
|
+
protected trigger: ILankaSharedStoreVMContext<StoreState, StoreState & Actions & ILankaScenarioVM, Store, TGateways, Services>["trigger"];
|
|
447
|
+
constructor(store: Store);
|
|
448
|
+
/**
|
|
449
|
+
* Turn off when one broad consumer reads most fields of the store — proxy
|
|
450
|
+
* tracking then costs more than it saves.
|
|
451
|
+
*/
|
|
452
|
+
protected readonly enableAccessTrackingOptimization: boolean;
|
|
453
|
+
/** The scenarios this ViewModel listens to, unsubscribed for it on reset. */
|
|
454
|
+
protected scenarioHandlers(): ILankaSharedStoreScenarioBinding<unknown, StoreState, Actions, Store, TGateways, Services>[];
|
|
455
|
+
/** The actions the screen calls. Written against `this.set` and `this.get`. */
|
|
456
|
+
protected abstract createActions(): Actions;
|
|
457
|
+
/** Builds the hook a screen calls. One ViewModel per call. */
|
|
458
|
+
build(): TLankaSharedStoreVMHook<StoreState, Actions>;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* A shared store, without writing a class whose body is one function.
|
|
463
|
+
*
|
|
464
|
+
* A shared store is the third rung of the ladder in `core/README.md`: reach for
|
|
465
|
+
* it only when several ViewModels must CO-EDIT one state. Most stores have no
|
|
466
|
+
* behaviour of their own — the state and how to build it fresh is the whole
|
|
467
|
+
* declaration — and this is the shape for those.
|
|
468
|
+
*
|
|
469
|
+
* One implementation: what comes back is an `ALankaSharedStore`, so a reset, a
|
|
470
|
+
* subscription and the zustand api behave identically either way.
|
|
471
|
+
*/
|
|
472
|
+
declare const createLankaSharedStore: <TState extends object>(createInitialState: () => TState) => ALankaSharedStore<TState>;
|
|
473
|
+
|
|
474
|
+
type TLankaVMStateCreator<TFullState> = StateCreator<TFullState, TLankaAnyMutators, TLankaAnyMutators>;
|
|
475
|
+
|
|
476
|
+
type TLankaSharedStoreSetState<TState extends object> = (partial: TState | Partial<TState> | ((state: TState) => TState | Partial<TState>), replace?: boolean) => void;
|
|
477
|
+
|
|
478
|
+
export { ALankaSharedStore, ALankaSharedStoreVM, ALankaStatelessVM, ALankaVM, type ILankaScenarioBinding, type ILankaSharedStoreScenarioBinding, type ILankaSharedStoreVMConfig, type ILankaSharedStoreVMContext, type ILankaStatelessScenarioBinding, type ILankaStatelessVMContext, type ILankaVMConfig, type ILankaVMContext, type TLankaAnyMutators, type TLankaSetState, type TLankaSharedStoreSetState, type TLankaSharedStoreVMHook, type TLankaStatelessVMConfig$1 as TLankaStatelessVMConfig, type TLankaStatelessVMHook, type TLankaVMEnhancer, type TLankaVMStateCreator, type TLazyLankaVM, type TUnknownLankaScenarioBinding, createLankaSharedStore, createLankaVM, createLazyLankaVM, createLazySharedStoreLankaVM, createLazyStatelessLankaVM, createSharedStoreLankaVM, createStatelessLankaVM };
|