@ankhorage/runtime 5.0.6 → 5.1.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 (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/dist/ManifestContext.d.ts +7 -3
  4. package/dist/ManifestContext.d.ts.map +1 -1
  5. package/dist/ManifestContext.js +9 -2
  6. package/dist/ManifestContext.js.map +1 -1
  7. package/dist/RuntimeRenderer.d.ts.map +1 -1
  8. package/dist/RuntimeRenderer.js +36 -26
  9. package/dist/RuntimeRenderer.js.map +1 -1
  10. package/dist/RuntimeScreen.d.ts.map +1 -1
  11. package/dist/RuntimeScreen.js +6 -3
  12. package/dist/RuntimeScreen.js.map +1 -1
  13. package/dist/capabilities/index.d.ts +47 -0
  14. package/dist/capabilities/index.d.ts.map +1 -1
  15. package/dist/capabilities/index.js +34 -0
  16. package/dist/capabilities/index.js.map +1 -1
  17. package/dist/runtimeCapabilityExecutor.d.ts +3 -1
  18. package/dist/runtimeCapabilityExecutor.d.ts.map +1 -1
  19. package/dist/runtimeCapabilityExecutor.js +91 -2
  20. package/dist/runtimeCapabilityExecutor.js.map +1 -1
  21. package/dist/runtimeStateAdapter.d.ts.map +1 -1
  22. package/dist/runtimeStateAdapter.js +25 -13
  23. package/dist/runtimeStateAdapter.js.map +1 -1
  24. package/dist/useRuntimeStateBindings.d.ts +9 -0
  25. package/dist/useRuntimeStateBindings.d.ts.map +1 -0
  26. package/dist/useRuntimeStateBindings.js +50 -0
  27. package/dist/useRuntimeStateBindings.js.map +1 -0
  28. package/package.json +70 -3
  29. package/paradox/badges/npm.svg +3 -3
  30. package/paradox/components.md +8 -5
  31. package/paradox/diagrams/architecture-overview.mmd +13 -0
  32. package/paradox/diagrams/export-graph.mmd +2 -0
  33. package/paradox/diagrams/module-relationships.mmd +11 -0
  34. package/paradox/diagrams/sequences/runtime-screen.mmd +5 -0
  35. package/paradox/exports.json +25 -18
  36. package/paradox/exports.md +23 -14
  37. package/paradox/index.html +259 -92
  38. package/paradox/paradox.json +534 -99
@@ -1,8 +1,37 @@
1
- /*** Routes manifest-owned API capabilities before delegating application-owned capabilities. */
1
+ import { isRecord } from '@ankhorage/utility/object';
2
+ /*** Routes API and state capabilities before delegating application-owned capabilities. */
2
3
  export function composeRuntimeCapabilityExecutor(composition) {
3
4
  return async (args) => args.capability.startsWith('api.')
4
5
  ? composition.apiCapabilityExecutor(args)
5
- : executeApplicationCapabilityAsync(args, composition.capabilityExecutor);
6
+ : args.capability === 'state.set'
7
+ ? executeStateSetCapabilityAsync(args, composition.stateAdapter)
8
+ : executeApplicationCapabilityAsync(args, composition.capabilityExecutor);
9
+ }
10
+ /*** Applies one serializable state value through the configured application state adapter. */
11
+ function executeStateSetCapabilityAsync(args, stateAdapter) {
12
+ if (stateAdapter === undefined)
13
+ return Promise.resolve(missingStateAdapterResult());
14
+ const input = resolveStateSetInput(args.input);
15
+ if (input === undefined)
16
+ return Promise.resolve(invalidStateSetInputResult());
17
+ try {
18
+ const result = stateAdapter.set(input.path, input.value);
19
+ return Promise.resolve(result.ok
20
+ ? { data: { path: input.path, value: input.value }, ok: true }
21
+ : {
22
+ diagnostics: [
23
+ {
24
+ code: result.error.code,
25
+ message: result.error.message,
26
+ severity: 'error',
27
+ },
28
+ ],
29
+ ok: false,
30
+ });
31
+ }
32
+ catch (error) {
33
+ return Promise.resolve(stateAdapterSetErrorResult(error));
34
+ }
6
35
  }
7
36
  async function executeApplicationCapabilityAsync(args, capabilityExecutor) {
8
37
  if (capabilityExecutor !== undefined)
@@ -18,4 +47,64 @@ async function executeApplicationCapabilityAsync(args, capabilityExecutor) {
18
47
  ok: false,
19
48
  };
20
49
  }
50
+ function resolveStateSetInput(input) {
51
+ if (!isRecord(input) || !('value' in input))
52
+ return undefined;
53
+ const { path, value } = input;
54
+ if (!isStatePath(path) || !isStateValue(value))
55
+ return undefined;
56
+ return { path, value };
57
+ }
58
+ function isStatePath(value) {
59
+ return ((typeof value === 'string' && value.trim().length > 0) ||
60
+ (Array.isArray(value) &&
61
+ value.length > 0 &&
62
+ value.every((part) => typeof part === 'string' && part.trim().length > 0)));
63
+ }
64
+ function isStateValue(value) {
65
+ if (value === null || typeof value === 'boolean' || typeof value === 'string')
66
+ return true;
67
+ if (typeof value === 'number')
68
+ return Number.isFinite(value);
69
+ if (Array.isArray(value))
70
+ return value.every(isStateValue);
71
+ return isRecord(value) && Object.values(value).every(isStateValue);
72
+ }
73
+ /*** Converts thrown adapter failures into capability diagnostics at the public Runtime boundary. */
74
+ function stateAdapterSetErrorResult(error) {
75
+ return {
76
+ diagnostics: [
77
+ {
78
+ code: 'state-adapter-error',
79
+ message: error instanceof Error ? error.message : 'StateAdapter.set failed.',
80
+ severity: 'error',
81
+ },
82
+ ],
83
+ ok: false,
84
+ };
85
+ }
86
+ function missingStateAdapterResult() {
87
+ return {
88
+ diagnostics: [
89
+ {
90
+ code: 'missing-adapter',
91
+ message: "Capability 'state.set' requires a configured StateAdapter.",
92
+ severity: 'error',
93
+ },
94
+ ],
95
+ ok: false,
96
+ };
97
+ }
98
+ function invalidStateSetInputResult() {
99
+ return {
100
+ diagnostics: [
101
+ {
102
+ code: 'invalid-input',
103
+ message: "Capability 'state.set' requires a non-empty path and serializable value.",
104
+ severity: 'error',
105
+ },
106
+ ],
107
+ ok: false,
108
+ };
109
+ }
21
110
  //# sourceMappingURL=runtimeCapabilityExecutor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeCapabilityExecutor.js","sourceRoot":"","sources":["../src/runtimeCapabilityExecutor.ts"],"names":[],"mappings":"AAWA,gGAAgG;AAChG,MAAM,UAAU,gCAAgC,CAC9C,WAAiD;IAEjD,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE,CACpB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC;QAChC,CAAC,CAAC,WAAW,CAAC,qBAAqB,CAAC,IAAI,CAAC;QACzC,CAAC,CAAC,iCAAiC,CAAC,IAAI,EAAE,WAAW,CAAC,kBAAkB,CAAC,CAAC;AAChF,CAAC;AAED,KAAK,UAAU,iCAAiC,CAC9C,IAAoC,EACpC,kBAAyD;IAEzD,IAAI,kBAAkB,KAAK,SAAS;QAAE,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACtE,OAAO;QACL,WAAW,EAAE;YACX;gBACE,IAAI,EAAE,mBAAmB;gBACzB,OAAO,EAAE,eAAe,IAAI,CAAC,UAAU,+BAA+B;gBACtE,QAAQ,EAAE,OAAO;aAClB;SACF;QACD,EAAE,EAAE,KAAK;KACV,CAAC;AACJ,CAAC","sourcesContent":["import type {\n RuntimeCapabilityExecutionArgs,\n RuntimeCapabilityExecutionResult,\n RuntimeCapabilityExecutor,\n} from './runtimeBindings';\n\nexport interface RuntimeCapabilityExecutorComposition {\n readonly apiCapabilityExecutor: RuntimeCapabilityExecutor;\n readonly capabilityExecutor?: RuntimeCapabilityExecutor;\n}\n\n/*** Routes manifest-owned API capabilities before delegating application-owned capabilities. */\nexport function composeRuntimeCapabilityExecutor(\n composition: RuntimeCapabilityExecutorComposition,\n): RuntimeCapabilityExecutor {\n return async (args) =>\n args.capability.startsWith('api.')\n ? composition.apiCapabilityExecutor(args)\n : executeApplicationCapabilityAsync(args, composition.capabilityExecutor);\n}\n\nasync function executeApplicationCapabilityAsync(\n args: RuntimeCapabilityExecutionArgs,\n capabilityExecutor: RuntimeCapabilityExecutor | undefined,\n): Promise<RuntimeCapabilityExecutionResult> {\n if (capabilityExecutor !== undefined) return capabilityExecutor(args);\n return {\n diagnostics: [\n {\n code: 'missing-operation',\n message: `Capability '${args.capability}' has no configured executor.`,\n severity: 'error',\n },\n ],\n ok: false,\n };\n}\n"]}
1
+ {"version":3,"file":"runtimeCapabilityExecutor.js","sourceRoot":"","sources":["../src/runtimeCapabilityExecutor.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateAdapter, StatePath, StateValue } from '@ankhorage/contracts';\nimport { isRecord } from '@ankhorage/utility/object';\n\nimport type {\n RuntimeCapabilityExecutionArgs,\n RuntimeCapabilityExecutionResult,\n RuntimeCapabilityExecutor,\n} from './runtimeBindings';\n\nexport interface RuntimeCapabilityExecutorComposition {\n readonly apiCapabilityExecutor: RuntimeCapabilityExecutor;\n readonly capabilityExecutor?: RuntimeCapabilityExecutor;\n readonly stateAdapter?: StateAdapter;\n}\n\n/*** Routes API and state capabilities before delegating application-owned capabilities. */\nexport function composeRuntimeCapabilityExecutor(\n composition: RuntimeCapabilityExecutorComposition,\n): RuntimeCapabilityExecutor {\n return async (args) =>\n args.capability.startsWith('api.')\n ? composition.apiCapabilityExecutor(args)\n : args.capability === 'state.set'\n ? executeStateSetCapabilityAsync(args, composition.stateAdapter)\n : executeApplicationCapabilityAsync(args, composition.capabilityExecutor);\n}\n\n/*** Applies one serializable state value through the configured application state adapter. */\nfunction executeStateSetCapabilityAsync(\n args: RuntimeCapabilityExecutionArgs,\n stateAdapter: StateAdapter | undefined,\n): Promise<RuntimeCapabilityExecutionResult> {\n if (stateAdapter === undefined) return Promise.resolve(missingStateAdapterResult());\n\n const input = resolveStateSetInput(args.input);\n if (input === undefined) return Promise.resolve(invalidStateSetInputResult());\n\n try {\n const result = stateAdapter.set(input.path, input.value);\n return Promise.resolve(\n result.ok\n ? { data: { path: input.path, value: input.value }, ok: true }\n : {\n diagnostics: [\n {\n code: result.error.code,\n message: result.error.message,\n severity: 'error',\n },\n ],\n ok: false,\n },\n );\n } catch (error) {\n return Promise.resolve(stateAdapterSetErrorResult(error));\n }\n}\n\nasync function executeApplicationCapabilityAsync(\n args: RuntimeCapabilityExecutionArgs,\n capabilityExecutor: RuntimeCapabilityExecutor | undefined,\n): Promise<RuntimeCapabilityExecutionResult> {\n if (capabilityExecutor !== undefined) return capabilityExecutor(args);\n return {\n diagnostics: [\n {\n code: 'missing-operation',\n message: `Capability '${args.capability}' has no configured executor.`,\n severity: 'error',\n },\n ],\n ok: false,\n };\n}\n\nfunction resolveStateSetInput(\n input: RuntimeCapabilityExecutionArgs['input'],\n): { readonly path: StatePath; readonly value: StateValue } | undefined {\n if (!isRecord(input) || !('value' in input)) return undefined;\n const { path, value } = input;\n if (!isStatePath(path) || !isStateValue(value)) return undefined;\n return { path, value };\n}\n\nfunction isStatePath(value: unknown): value is StatePath {\n return (\n (typeof value === 'string' && value.trim().length > 0) ||\n (Array.isArray(value) &&\n value.length > 0 &&\n value.every((part) => typeof part === 'string' && part.trim().length > 0))\n );\n}\n\nfunction isStateValue(value: unknown): value is StateValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return true;\n if (typeof value === 'number') return Number.isFinite(value);\n if (Array.isArray(value)) return value.every(isStateValue);\n return isRecord(value) && Object.values(value).every(isStateValue);\n}\n\n/*** Converts thrown adapter failures into capability diagnostics at the public Runtime boundary. */\nfunction stateAdapterSetErrorResult(error: unknown): RuntimeCapabilityExecutionResult {\n return {\n diagnostics: [\n {\n code: 'state-adapter-error',\n message: error instanceof Error ? error.message : 'StateAdapter.set failed.',\n severity: 'error',\n },\n ],\n ok: false,\n };\n}\n\nfunction missingStateAdapterResult(): RuntimeCapabilityExecutionResult {\n return {\n diagnostics: [\n {\n code: 'missing-adapter',\n message: \"Capability 'state.set' requires a configured StateAdapter.\",\n severity: 'error',\n },\n ],\n ok: false,\n };\n}\n\nfunction invalidStateSetInputResult(): RuntimeCapabilityExecutionResult {\n return {\n diagnostics: [\n {\n code: 'invalid-input',\n message: \"Capability 'state.set' requires a non-empty path and serializable value.\",\n severity: 'error',\n },\n ],\n ok: false,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeStateAdapter.d.ts","sourceRoot":"","sources":["../src/runtimeStateAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAMZ,UAAU,EACX,MAAM,sBAAsB,CAAC;AAK9B,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;CAC9D;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,gCAAqC,GAC7C,YAAY,CA+Ed"}
1
+ {"version":3,"file":"runtimeStateAdapter.d.ts","sourceRoot":"","sources":["../src/runtimeStateAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAMZ,UAAU,EACX,MAAM,sBAAsB,CAAC;AAS9B,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;CAC9D;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,gCAAqC,GAC7C,YAAY,CAmFd"}
@@ -1,15 +1,17 @@
1
1
  export function createRuntimeMemoryStateAdapter(options = {}) {
2
2
  let rootState = { ...(options.initialState ?? {}) };
3
3
  const listeners = new Map();
4
- const emit = (path, value) => {
4
+ const emit = (path) => {
5
5
  const pathResult = normalizeStatePath(path);
6
6
  if (!pathResult.ok)
7
7
  return;
8
- const listenersForPath = listeners.get(pathPartsToKey(pathResult.parts));
9
- if (listenersForPath === undefined)
10
- return;
11
- for (const listener of listenersForPath) {
12
- listener(value);
8
+ for (const entry of listeners.values()) {
9
+ if (!areStatePathsRelated(pathResult.parts, entry.parts))
10
+ continue;
11
+ const value = readStatePath(rootState, entry.parts);
12
+ for (const listener of entry.listeners) {
13
+ listener(value);
14
+ }
13
15
  }
14
16
  };
15
17
  return {
@@ -32,7 +34,7 @@ export function createRuntimeMemoryStateAdapter(options = {}) {
32
34
  if (!nextStateResult.ok)
33
35
  return nextStateResult;
34
36
  rootState = nextStateResult.data;
35
- emit(path, value);
37
+ emit(path);
36
38
  return { ok: true };
37
39
  },
38
40
  subscribe(path, listener) {
@@ -40,16 +42,19 @@ export function createRuntimeMemoryStateAdapter(options = {}) {
40
42
  if (!pathResult.ok)
41
43
  return pathResult;
42
44
  const key = pathPartsToKey(pathResult.parts);
43
- const listenersForPath = listeners.get(key) ?? new Set();
45
+ const entry = listeners.get(key) ?? {
46
+ listeners: new Set(),
47
+ parts: pathResult.parts,
48
+ };
44
49
  const wrappedListener = (value) => {
45
50
  listener({ path, value: value });
46
51
  };
47
- listenersForPath.add(wrappedListener);
48
- listeners.set(key, listenersForPath);
52
+ entry.listeners.add(wrappedListener);
53
+ listeners.set(key, entry);
49
54
  return createStateDataResult({
50
55
  unsubscribe() {
51
- listenersForPath.delete(wrappedListener);
52
- if (listenersForPath.size === 0) {
56
+ entry.listeners.delete(wrappedListener);
57
+ if (entry.listeners.size === 0) {
53
58
  listeners.delete(key);
54
59
  }
55
60
  },
@@ -63,7 +68,7 @@ export function createRuntimeMemoryStateAdapter(options = {}) {
63
68
  if (!nextStateResult.ok)
64
69
  return nextStateResult;
65
70
  rootState = nextStateResult.data;
66
- emit(path, undefined);
71
+ emit(path);
67
72
  return { ok: true };
68
73
  },
69
74
  };
@@ -82,6 +87,13 @@ function normalizeStatePath(path) {
82
87
  function pathPartsToKey(parts) {
83
88
  return parts.join('.');
84
89
  }
90
+ /*** Determines whether a state mutation changes an ancestor, descendant, or exact subscription. */
91
+ function areStatePathsRelated(changedParts, subscribedParts) {
92
+ const sharedLength = Math.min(changedParts.length, subscribedParts.length);
93
+ return changedParts
94
+ .slice(0, sharedLength)
95
+ .every((part, index) => part === subscribedParts[index]);
96
+ }
85
97
  function readStatePath(source, parts) {
86
98
  let currentValue = source;
87
99
  for (const part of parts) {
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeStateAdapter.js","sourceRoot":"","sources":["../src/runtimeStateAdapter.ts"],"names":[],"mappings":"AAiBA,MAAM,UAAU,+BAA+B,CAC7C,UAA4C,EAAE;IAE9C,IAAI,SAAS,GAAuB,EAAE,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,EAAE,CAAC;IACxE,MAAM,SAAS,GAAG,IAAI,GAAG,EAA4B,CAAC;IAEtD,MAAM,IAAI,GAAG,CAAC,IAAe,EAAE,KAA6B,EAAE,EAAE;QAC9D,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,UAAU,CAAC,EAAE;YAAE,OAAO;QAE3B,MAAM,gBAAgB,GAAG,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QACzE,IAAI,gBAAgB,KAAK,SAAS;YAAE,OAAO;QAE3C,KAAK,MAAM,QAAQ,IAAI,gBAAgB,EAAE,CAAC;YACxC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACL,YAAY,EAAE;YACZ,aAAa,EAAE,IAAI;YACnB,QAAQ,EAAE,KAAK;YACf,WAAW,EAAE,KAAK;SACnB;QACD,GAAG,CAAyC,IAAe;YACzD,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,UAAU,CAAC,EAAE;gBAAE,OAAO,UAAU,CAAC;YAEtC,OAAO,qBAAqB,CAC1B,aAAa,CAAC,SAAS,EAAE,UAAU,CAAC,KAAK,CAAuB,CACjE,CAAC;QACJ,CAAC;QACD,GAAG,CAAyC,IAAe,EAAE,KAAa;YACxE,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,UAAU,CAAC,EAAE;gBAAE,OAAO,UAAU,CAAC;YAEtC,MAAM,eAAe,GAAG,YAAY,CAAC,SAAS,EAAE,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACzE,IAAI,CAAC,eAAe,CAAC,EAAE;gBAAE,OAAO,eAAe,CAAC;YAEhD,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC;YACjC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAClB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC;QACD,SAAS,CACP,IAAe,EACf,QAA+B;YAE/B,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,UAAU,CAAC,EAAE;gBAAE,OAAO,UAAU,CAAC;YAEtC,MAAM,GAAG,GAAG,cAAc,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC7C,MAAM,gBAAgB,GACpB,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAA2C,CAAC;YAC3E,MAAM,eAAe,GAAG,CAAC,KAA6B,EAAE,EAAE;gBACxD,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAA2B,EAAE,CAAC,CAAC;YACzD,CAAC,CAAC;YAEF,gBAAgB,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YACtC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;YAErC,OAAO,qBAAqB,CAAC;gBAC3B,WAAW;oBACT,gBAAgB,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;oBACzC,IAAI,gBAAgB,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;wBAChC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBACxB,CAAC;gBACH,CAAC;aACF,CAAC,CAAC;QACL,CAAC;QACD,MAAM,CAAC,IAAe;YACpB,MAAM,UAAU,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,UAAU,CAAC,EAAE;gBAAE,OAAO,UAAU,CAAC;YAEtC,MAAM,eAAe,GAAG,eAAe,CAAC,SAAS,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;YACrE,IAAI,CAAC,eAAe,CAAC,EAAE;gBAAE,OAAO,eAAe,CAAC;YAEhD,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC;YACjC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACtB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC;AAcD,SAAS,kBAAkB,CAAC,IAAe;IACzC,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACrE,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEpE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,gBAAgB,CAAC,cAAc,EAAE,+CAA+C,CAAC,CAAC;IAC3F,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,KAAK,EAAE,UAAU;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAwB;IAC9C,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,aAAa,CACpB,MAA0B,EAC1B,KAAwB;IAExB,IAAI,YAAY,GAA2B,MAAM,CAAC;IAElD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,YAAY,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,YAAY,CACnB,MAA0B,EAC1B,KAAwB,EACxB,KAAiB;IAEjB,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IAC9B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,gBAAgB,CAAC,cAAc,EAAE,+CAA+C,CAAC,CAAC;IAC3F,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,qBAAqB,CAAC;YAC3B,GAAG,MAAM;YACT,CAAC,IAAI,CAAC,EAAE,KAAK;SACd,CAAC,CAAC;IACL,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrD,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,gBAAgB,CACrB,eAAe,EACf,0DAA0D,IAAI,IAAI,CACnE,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACrD,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IAExC,OAAO,qBAAqB,CAAC;QAC3B,GAAG,MAAM;QACT,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,IAAI;KACzB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CACtB,MAA0B,EAC1B,KAAwB;IAExB,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IAC9B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,gBAAgB,CAAC,cAAc,EAAE,+CAA+C,CAAC,CAAC;IAC3F,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,SAAS,GAAuB,EAAE,CAAC;QACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;gBACjB,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACzB,CAAC;QACH,CAAC;QAED,OAAO,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,gBAAgB,CACrB,eAAe,EACf,6DAA6D,IAAI,IAAI,CACtE,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACpD,IAAI,CAAC,WAAW,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC;IAExC,OAAO,qBAAqB,CAAC;QAC3B,GAAG,MAAM;QACT,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,IAAI;KACzB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,KAA6B;IAClD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,OAAe;IACrD,MAAM,KAAK,GAAsB,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACnD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,qBAAqB,CAAS,IAAY;IACjD,MAAM,MAAM,GAAG;QACb,EAAE,EAAE,IAAI;QACR,IAAI;KACL,CAAC;IAEF,OAAO,MAA6B,CAAC;AACvC,CAAC","sourcesContent":["import type {\n StateAdapter,\n StateAdapterError,\n StateListener,\n StatePath,\n StateResult,\n StateSubscription,\n StateValue,\n} from '@ankhorage/contracts';\n\ntype MutableStateRecord = Record<string, StateValue>;\ntype StateListenerSet = Set<(value: StateValue | undefined) => void>;\n\nexport interface RuntimeMemoryStateAdapterOptions {\n readonly initialState?: Readonly<Record<string, StateValue>>;\n}\n\nexport function createRuntimeMemoryStateAdapter(\n options: RuntimeMemoryStateAdapterOptions = {},\n): StateAdapter {\n let rootState: MutableStateRecord = { ...(options.initialState ?? {}) };\n const listeners = new Map<string, StateListenerSet>();\n\n const emit = (path: StatePath, value: StateValue | undefined) => {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return;\n\n const listenersForPath = listeners.get(pathPartsToKey(pathResult.parts));\n if (listenersForPath === undefined) return;\n\n for (const listener of listenersForPath) {\n listener(value);\n }\n };\n\n return {\n capabilities: {\n subscriptions: true,\n computed: false,\n persistence: false,\n },\n get<TValue extends StateValue = StateValue>(path: StatePath): StateResult<TValue | undefined> {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return pathResult;\n\n return createStateDataResult(\n readStatePath(rootState, pathResult.parts) as TValue | undefined,\n );\n },\n set<TValue extends StateValue = StateValue>(path: StatePath, value: TValue): StateResult {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return pathResult;\n\n const nextStateResult = setStatePath(rootState, pathResult.parts, value);\n if (!nextStateResult.ok) return nextStateResult;\n\n rootState = nextStateResult.data;\n emit(path, value);\n return { ok: true };\n },\n subscribe<TValue extends StateValue = StateValue>(\n path: StatePath,\n listener: StateListener<TValue>,\n ): StateResult<StateSubscription> {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return pathResult;\n\n const key = pathPartsToKey(pathResult.parts);\n const listenersForPath =\n listeners.get(key) ?? new Set<(value: StateValue | undefined) => void>();\n const wrappedListener = (value: StateValue | undefined) => {\n listener({ path, value: value as TValue | undefined });\n };\n\n listenersForPath.add(wrappedListener);\n listeners.set(key, listenersForPath);\n\n return createStateDataResult({\n unsubscribe() {\n listenersForPath.delete(wrappedListener);\n if (listenersForPath.size === 0) {\n listeners.delete(key);\n }\n },\n });\n },\n delete(path: StatePath): StateResult {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return pathResult;\n\n const nextStateResult = deleteStatePath(rootState, pathResult.parts);\n if (!nextStateResult.ok) return nextStateResult;\n\n rootState = nextStateResult.data;\n emit(path, undefined);\n return { ok: true };\n },\n };\n}\n\ninterface ResolvedStatePath {\n readonly ok: true;\n readonly parts: readonly string[];\n}\n\ninterface StatePathError {\n readonly ok: false;\n readonly error: StateAdapterError;\n}\n\ntype StatePathResult = ResolvedStatePath | StatePathError;\n\nfunction normalizeStatePath(path: StatePath): StatePathResult {\n const parts = typeof path === 'string' ? path.split('.') : [...path];\n const normalized = parts.map((part) => part.trim()).filter(Boolean);\n\n if (normalized.length === 0) {\n return createStateError('invalid_path', 'State path must contain at least one segment.');\n }\n\n return {\n ok: true,\n parts: normalized,\n };\n}\n\nfunction pathPartsToKey(parts: readonly string[]): string {\n return parts.join('.');\n}\n\nfunction readStatePath(\n source: MutableStateRecord,\n parts: readonly string[],\n): StateValue | undefined {\n let currentValue: StateValue | undefined = source;\n\n for (const part of parts) {\n if (!isStateRecord(currentValue)) {\n return undefined;\n }\n\n currentValue = currentValue[part];\n }\n\n return currentValue;\n}\n\nfunction setStatePath(\n source: MutableStateRecord,\n parts: readonly string[],\n value: StateValue,\n): StateResult<MutableStateRecord> {\n const [head, ...tail] = parts;\n if (head === undefined) {\n return createStateError('invalid_path', 'State path must contain at least one segment.');\n }\n\n if (tail.length === 0) {\n return createStateDataResult({\n ...source,\n [head]: value,\n });\n }\n\n const existing = source[head];\n const child = existing === undefined ? {} : existing;\n if (!isStateRecord(child)) {\n return createStateError(\n 'path_conflict',\n `Cannot set nested state below non-object path segment \"${head}\".`,\n );\n }\n\n const childResult = setStatePath(child, tail, value);\n if (!childResult.ok) return childResult;\n\n return createStateDataResult({\n ...source,\n [head]: childResult.data,\n });\n}\n\nfunction deleteStatePath(\n source: MutableStateRecord,\n parts: readonly string[],\n): StateResult<MutableStateRecord> {\n const [head, ...tail] = parts;\n if (head === undefined) {\n return createStateError('invalid_path', 'State path must contain at least one segment.');\n }\n\n if (tail.length === 0) {\n const nextState: MutableStateRecord = {};\n for (const [key, value] of Object.entries(source)) {\n if (key !== head) {\n nextState[key] = value;\n }\n }\n\n return createStateDataResult(nextState);\n }\n\n const existing = source[head];\n if (existing === undefined) {\n return createStateDataResult(source);\n }\n\n if (!isStateRecord(existing)) {\n return createStateError(\n 'path_conflict',\n `Cannot delete nested state below non-object path segment \"${head}\".`,\n );\n }\n\n const childResult = deleteStatePath(existing, tail);\n if (!childResult.ok) return childResult;\n\n return createStateDataResult({\n ...source,\n [head]: childResult.data,\n });\n}\n\nfunction isStateRecord(value: StateValue | undefined): value is MutableStateRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction createStateError(code: string, message: string): StatePathError {\n const error: StateAdapterError = { code, message };\n return { ok: false, error };\n}\n\nfunction createStateDataResult<TValue>(data: TValue): StateResult<TValue> {\n const result = {\n ok: true,\n data,\n };\n\n return result as StateResult<TValue>;\n}\n"]}
1
+ {"version":3,"file":"runtimeStateAdapter.js","sourceRoot":"","sources":["../src/runtimeStateAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n StateAdapter,\n StateAdapterError,\n StateListener,\n StatePath,\n StateResult,\n StateSubscription,\n StateValue,\n} from '@ankhorage/contracts';\n\ntype MutableStateRecord = Record<string, StateValue>;\ntype StateListenerSet = Set<(value: StateValue | undefined) => void>;\ninterface StateListenerEntry {\n readonly listeners: StateListenerSet;\n readonly parts: readonly string[];\n}\n\nexport interface RuntimeMemoryStateAdapterOptions {\n readonly initialState?: Readonly<Record<string, StateValue>>;\n}\n\nexport function createRuntimeMemoryStateAdapter(\n options: RuntimeMemoryStateAdapterOptions = {},\n): StateAdapter {\n let rootState: MutableStateRecord = { ...(options.initialState ?? {}) };\n const listeners = new Map<string, StateListenerEntry>();\n\n const emit = (path: StatePath) => {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return;\n\n for (const entry of listeners.values()) {\n if (!areStatePathsRelated(pathResult.parts, entry.parts)) continue;\n\n const value = readStatePath(rootState, entry.parts);\n for (const listener of entry.listeners) {\n listener(value);\n }\n }\n };\n\n return {\n capabilities: {\n subscriptions: true,\n computed: false,\n persistence: false,\n },\n get<TValue extends StateValue = StateValue>(path: StatePath): StateResult<TValue | undefined> {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return pathResult;\n\n return createStateDataResult(\n readStatePath(rootState, pathResult.parts) as TValue | undefined,\n );\n },\n set<TValue extends StateValue = StateValue>(path: StatePath, value: TValue): StateResult {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return pathResult;\n\n const nextStateResult = setStatePath(rootState, pathResult.parts, value);\n if (!nextStateResult.ok) return nextStateResult;\n\n rootState = nextStateResult.data;\n emit(path);\n return { ok: true };\n },\n subscribe<TValue extends StateValue = StateValue>(\n path: StatePath,\n listener: StateListener<TValue>,\n ): StateResult<StateSubscription> {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return pathResult;\n\n const key = pathPartsToKey(pathResult.parts);\n const entry = listeners.get(key) ?? {\n listeners: new Set<(value: StateValue | undefined) => void>(),\n parts: pathResult.parts,\n };\n const wrappedListener = (value: StateValue | undefined) => {\n listener({ path, value: value as TValue | undefined });\n };\n\n entry.listeners.add(wrappedListener);\n listeners.set(key, entry);\n\n return createStateDataResult({\n unsubscribe() {\n entry.listeners.delete(wrappedListener);\n if (entry.listeners.size === 0) {\n listeners.delete(key);\n }\n },\n });\n },\n delete(path: StatePath): StateResult {\n const pathResult = normalizeStatePath(path);\n if (!pathResult.ok) return pathResult;\n\n const nextStateResult = deleteStatePath(rootState, pathResult.parts);\n if (!nextStateResult.ok) return nextStateResult;\n\n rootState = nextStateResult.data;\n emit(path);\n return { ok: true };\n },\n };\n}\n\ninterface ResolvedStatePath {\n readonly ok: true;\n readonly parts: readonly string[];\n}\n\ninterface StatePathError {\n readonly ok: false;\n readonly error: StateAdapterError;\n}\n\ntype StatePathResult = ResolvedStatePath | StatePathError;\n\nfunction normalizeStatePath(path: StatePath): StatePathResult {\n const parts = typeof path === 'string' ? path.split('.') : [...path];\n const normalized = parts.map((part) => part.trim()).filter(Boolean);\n\n if (normalized.length === 0) {\n return createStateError('invalid_path', 'State path must contain at least one segment.');\n }\n\n return {\n ok: true,\n parts: normalized,\n };\n}\n\nfunction pathPartsToKey(parts: readonly string[]): string {\n return parts.join('.');\n}\n\n/*** Determines whether a state mutation changes an ancestor, descendant, or exact subscription. */\nfunction areStatePathsRelated(\n changedParts: readonly string[],\n subscribedParts: readonly string[],\n): boolean {\n const sharedLength = Math.min(changedParts.length, subscribedParts.length);\n return changedParts\n .slice(0, sharedLength)\n .every((part, index) => part === subscribedParts[index]);\n}\n\nfunction readStatePath(\n source: MutableStateRecord,\n parts: readonly string[],\n): StateValue | undefined {\n let currentValue: StateValue | undefined = source;\n\n for (const part of parts) {\n if (!isStateRecord(currentValue)) {\n return undefined;\n }\n\n currentValue = currentValue[part];\n }\n\n return currentValue;\n}\n\nfunction setStatePath(\n source: MutableStateRecord,\n parts: readonly string[],\n value: StateValue,\n): StateResult<MutableStateRecord> {\n const [head, ...tail] = parts;\n if (head === undefined) {\n return createStateError('invalid_path', 'State path must contain at least one segment.');\n }\n\n if (tail.length === 0) {\n return createStateDataResult({\n ...source,\n [head]: value,\n });\n }\n\n const existing = source[head];\n const child = existing === undefined ? {} : existing;\n if (!isStateRecord(child)) {\n return createStateError(\n 'path_conflict',\n `Cannot set nested state below non-object path segment \"${head}\".`,\n );\n }\n\n const childResult = setStatePath(child, tail, value);\n if (!childResult.ok) return childResult;\n\n return createStateDataResult({\n ...source,\n [head]: childResult.data,\n });\n}\n\nfunction deleteStatePath(\n source: MutableStateRecord,\n parts: readonly string[],\n): StateResult<MutableStateRecord> {\n const [head, ...tail] = parts;\n if (head === undefined) {\n return createStateError('invalid_path', 'State path must contain at least one segment.');\n }\n\n if (tail.length === 0) {\n const nextState: MutableStateRecord = {};\n for (const [key, value] of Object.entries(source)) {\n if (key !== head) {\n nextState[key] = value;\n }\n }\n\n return createStateDataResult(nextState);\n }\n\n const existing = source[head];\n if (existing === undefined) {\n return createStateDataResult(source);\n }\n\n if (!isStateRecord(existing)) {\n return createStateError(\n 'path_conflict',\n `Cannot delete nested state below non-object path segment \"${head}\".`,\n );\n }\n\n const childResult = deleteStatePath(existing, tail);\n if (!childResult.ok) return childResult;\n\n return createStateDataResult({\n ...source,\n [head]: childResult.data,\n });\n}\n\nfunction isStateRecord(value: StateValue | undefined): value is MutableStateRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction createStateError(code: string, message: string): StatePathError {\n const error: StateAdapterError = { code, message };\n return { ok: false, error };\n}\n\nfunction createStateDataResult<TValue>(data: TValue): StateResult<TValue> {\n const result = {\n ok: true,\n data,\n };\n\n return result as StateResult<TValue>;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { BindingExpression, ComponentDataBindingRegistry, StateAdapter } from '@ankhorage/contracts';
2
+ /*** Re-renders a Runtime node when one of its state-backed presentation bindings changes. */
3
+ export declare function useRuntimeStateBindings(args: {
4
+ readonly dataBindings?: ComponentDataBindingRegistry;
5
+ readonly nodeId: string;
6
+ readonly repeatSource?: BindingExpression;
7
+ readonly stateAdapter?: StateAdapter;
8
+ }): void;
9
+ //# sourceMappingURL=useRuntimeStateBindings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRuntimeStateBindings.d.ts","sourceRoot":"","sources":["../src/useRuntimeStateBindings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,4BAA4B,EAC5B,YAAY,EACb,MAAM,sBAAsB,CAAC;AAG9B,6FAA6F;AAC7F,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,QAAQ,CAAC,YAAY,CAAC,EAAE,4BAA4B,CAAC;IACrD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;CACtC,GAAG,IAAI,CAsBP"}
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ /*** Re-renders a Runtime node when one of its state-backed presentation bindings changes. */
3
+ export function useRuntimeStateBindings(args) {
4
+ const { dataBindings, nodeId, repeatSource, stateAdapter } = args;
5
+ const paths = React.useMemo(() => collectStateBindingPaths(findDataBindingProps(dataBindings, nodeId), repeatSource), [dataBindings, nodeId, repeatSource]);
6
+ const [, setVersion] = React.useState(0);
7
+ React.useEffect(() => {
8
+ if (stateAdapter === undefined || paths.length === 0)
9
+ return undefined;
10
+ const subscriptions = paths.flatMap((path) => {
11
+ const result = stateAdapter.subscribe(path, () => {
12
+ setVersion((version) => version + 1);
13
+ });
14
+ return result.ok ? [result.data] : [];
15
+ });
16
+ return () => {
17
+ for (const subscription of subscriptions)
18
+ void subscription.unsubscribe();
19
+ };
20
+ }, [paths, stateAdapter]);
21
+ }
22
+ function findDataBindingProps(dataBindings, nodeId) {
23
+ return Object.values(dataBindings ?? {}).find((binding) => binding.componentId === nodeId)?.props;
24
+ }
25
+ function collectStateBindingPaths(props, repeatSource) {
26
+ const paths = [
27
+ ...Object.values(props ?? {}).flatMap((binding) => collectStateExpressionPaths(binding.value)),
28
+ ...(repeatSource ? collectStateExpressionPaths(repeatSource) : []),
29
+ ];
30
+ return [...new Set(paths)].sort();
31
+ }
32
+ function collectStateExpressionPaths(expression) {
33
+ if ('capability' in expression)
34
+ return expression.capability.startsWith('state.')
35
+ ? [expression.capability.slice('state.'.length)]
36
+ : [];
37
+ if (expression.kind === 'literal')
38
+ return [];
39
+ if (expression.kind === 'array')
40
+ return expression.items.flatMap(collectStateExpressionPaths);
41
+ if (expression.kind === 'object')
42
+ return Object.values(expression.fields).flatMap(collectStateExpressionPaths);
43
+ if (expression.kind === 'transform')
44
+ return collectStateExpressionPaths(expression.value);
45
+ return [
46
+ ...collectStateExpressionPaths(expression.value),
47
+ ...collectStateExpressionPaths(expression.fallback),
48
+ ];
49
+ }
50
+ //# sourceMappingURL=useRuntimeStateBindings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRuntimeStateBindings.js","sourceRoot":"","sources":["../src/useRuntimeStateBindings.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n BindingExpression,\n ComponentDataBindingRegistry,\n StateAdapter,\n} from '@ankhorage/contracts';\nimport React from 'react';\n\n/*** Re-renders a Runtime node when one of its state-backed presentation bindings changes. */\nexport function useRuntimeStateBindings(args: {\n readonly dataBindings?: ComponentDataBindingRegistry;\n readonly nodeId: string;\n readonly repeatSource?: BindingExpression;\n readonly stateAdapter?: StateAdapter;\n}): void {\n const { dataBindings, nodeId, repeatSource, stateAdapter } = args;\n const paths = React.useMemo(\n () => collectStateBindingPaths(findDataBindingProps(dataBindings, nodeId), repeatSource),\n [dataBindings, nodeId, repeatSource],\n );\n const [, setVersion] = React.useState(0);\n\n React.useEffect(() => {\n if (stateAdapter === undefined || paths.length === 0) return undefined;\n\n const subscriptions = paths.flatMap((path) => {\n const result = stateAdapter.subscribe(path, () => {\n setVersion((version) => version + 1);\n });\n return result.ok ? [result.data] : [];\n });\n\n return () => {\n for (const subscription of subscriptions) void subscription.unsubscribe();\n };\n }, [paths, stateAdapter]);\n}\n\nfunction findDataBindingProps(\n dataBindings: ComponentDataBindingRegistry | undefined,\n nodeId: string,\n): ComponentDataBindingRegistry[string]['props'] | undefined {\n return Object.values(dataBindings ?? {}).find((binding) => binding.componentId === nodeId)?.props;\n}\n\nfunction collectStateBindingPaths(\n props: ComponentDataBindingRegistry[string]['props'] | undefined,\n repeatSource: BindingExpression | undefined,\n): readonly string[] {\n const paths = [\n ...Object.values(props ?? {}).flatMap((binding) => collectStateExpressionPaths(binding.value)),\n ...(repeatSource ? collectStateExpressionPaths(repeatSource) : []),\n ];\n return [...new Set(paths)].sort();\n}\n\nfunction collectStateExpressionPaths(expression: BindingExpression): readonly string[] {\n if ('capability' in expression)\n return expression.capability.startsWith('state.')\n ? [expression.capability.slice('state.'.length)]\n : [];\n if (expression.kind === 'literal') return [];\n if (expression.kind === 'array') return expression.items.flatMap(collectStateExpressionPaths);\n if (expression.kind === 'object')\n return Object.values(expression.fields).flatMap(collectStateExpressionPaths);\n if (expression.kind === 'transform') return collectStateExpressionPaths(expression.value);\n return [\n ...collectStateExpressionPaths(expression.value),\n ...collectStateExpressionPaths(expression.fallback),\n ];\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/runtime",
3
- "version": "5.0.6",
3
+ "version": "5.1.0",
4
4
  "description": "Platform-neutral runtime renderer, contracts, and helpers for Ankhorage generated apps.",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -101,6 +101,71 @@
101
101
  },
102
102
  "label": "Runtime bindings"
103
103
  },
104
+ {
105
+ "id": "state.set",
106
+ "owner": "@ankhorage/runtime",
107
+ "access": [
108
+ "invoke",
109
+ "write"
110
+ ],
111
+ "binding": {
112
+ "kind": "state",
113
+ "bindableAs": [
114
+ "target"
115
+ ]
116
+ },
117
+ "label": "Set runtime state",
118
+ "input": {
119
+ "schema": {
120
+ "type": "object",
121
+ "required": [
122
+ "path",
123
+ "value"
124
+ ],
125
+ "properties": {
126
+ "path": {
127
+ "anyOf": [
128
+ {
129
+ "type": "string"
130
+ },
131
+ {
132
+ "type": "array",
133
+ "items": {
134
+ "type": "string"
135
+ }
136
+ }
137
+ ]
138
+ },
139
+ "value": {}
140
+ }
141
+ }
142
+ },
143
+ "output": {
144
+ "schema": {
145
+ "type": "object",
146
+ "required": [
147
+ "path",
148
+ "value"
149
+ ],
150
+ "properties": {
151
+ "path": {
152
+ "anyOf": [
153
+ {
154
+ "type": "string"
155
+ },
156
+ {
157
+ "type": "array",
158
+ "items": {
159
+ "type": "string"
160
+ }
161
+ }
162
+ ]
163
+ },
164
+ "value": {}
165
+ }
166
+ }
167
+ }
168
+ },
104
169
  {
105
170
  "id": "runtime.eventOperation",
106
171
  "owner": "@ankhorage/runtime",
@@ -198,7 +263,7 @@
198
263
  "@ankhorage/capability": "^0.2.2",
199
264
  "@ankhorage/contracts": "^26.0.1",
200
265
  "@ankhorage/data-sources": "^2.1.2",
201
- "@ankhorage/utility": "^1.9.86"
266
+ "@ankhorage/utility": "^1.10.0"
202
267
  },
203
268
  "peerDependencies": {
204
269
  "react": ">=19.2.0 <20",
@@ -211,14 +276,16 @@
211
276
  }
212
277
  },
213
278
  "devDependencies": {
214
- "@ankhorage/devtools": "^2.3.21",
279
+ "@ankhorage/devtools": "^2.3.23",
215
280
  "@ankhorage/paradox": "^0.3.5",
216
281
  "@types/bun": "^1.4.2",
217
282
  "@types/node": "^26.6.4",
218
283
  "@types/react": "^19.3.0",
219
284
  "@types/react-dom": "^19.3.0",
285
+ "@types/react-test-renderer": "^19.1.0",
220
286
  "react": "19.3.0",
221
287
  "react-dom": "19.3.0",
288
+ "react-test-renderer": "19.3.0",
222
289
  "react-native": "0.87.1",
223
290
  "react-native-web": "~0.21.3",
224
291
  "typescript": "~6.0.3"
@@ -1,7 +1,7 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="90" height="20" role="img" aria-label="npm: v5.0.6">
2
- <title>npm: v5.0.6</title>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="90" height="20" role="img" aria-label="npm: v5.1.0">
2
+ <title>npm: v5.1.0</title>
3
3
  <rect width="38" height="20" fill="#374151"/>
4
4
  <rect x="38" width="52" height="20" fill="#cb3837"/>
5
5
  <text x="19" y="14" fill="#ffffff" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11" text-anchor="middle">npm</text>
6
- <text x="64" y="14" fill="#ffffff" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11" text-anchor="middle">v5.0.6</text>
6
+ <text x="64" y="14" fill="#ffffff" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11" text-anchor="middle">v5.1.0</text>
7
7
  </svg>
@@ -2,18 +2,20 @@
2
2
 
3
3
  ## ManifestContext
4
4
 
5
- Source: `src/ManifestContext.tsx:10:14`
5
+ Source: `src/ManifestContext.tsx:13:14`
6
6
 
7
7
  Export paths: `src/index.ts`
8
8
 
9
9
  | Prop | Type | Required | Default | Description |
10
10
  | --- | --- | --- | --- | --- |
11
11
  | children | `ReactNode` | no | — | |
12
- | value | `ManifestContextValue \| null` | yes | — | |
12
+ | value | `import("../../../src/ManifestContext").ManifestContextValue \| null` | yes | — | |
13
13
 
14
14
  ## ManifestProvider
15
15
 
16
- Source: `src/ManifestContext.tsx:12:1`
16
+ Source: `src/ManifestContext.tsx:16:1`
17
+
18
+ Owns one configured state adapter for every Runtime screen in an application tree.
17
19
 
18
20
  Export paths: `src/index.ts`
19
21
 
@@ -23,10 +25,11 @@ Export paths: `src/index.ts`
23
25
  | children | `React.ReactNode \| undefined` | no | — | |
24
26
  | manifest | `AppManifest` | yes | — | |
25
27
  | onScreenChange | `(screenId: string) => void \| undefined` | no | — | |
28
+ | stateAdapter | `StateAdapter \| undefined` | no | — | |
26
29
 
27
30
  ## RuntimeRenderer
28
31
 
29
- Source: `src/RuntimeRenderer.tsx:73:1`
32
+ Source: `src/RuntimeRenderer.tsx:74:1`
30
33
 
31
34
  Export paths: `src/index.ts`
32
35
 
@@ -62,7 +65,7 @@ Export paths: `src/index.ts`
62
65
 
63
66
  ## RuntimeScreen
64
67
 
65
- Source: `src/RuntimeScreen.tsx:19:1`
68
+ Source: `src/RuntimeScreen.tsx:20:1`
66
69
 
67
70
  Export paths: `src/index.ts`
68
71
 
@@ -19,6 +19,7 @@ graph TD
19
19
  module_src_index_ts["src/index.ts"]
20
20
  module_src_ManifestContext_tsx["src/ManifestContext.tsx"]
21
21
  package__ankhorage_runtime -.-> module_src_ManifestContext_tsx
22
+ module_src_ManifestContext_tsx --> module_src_runtimeStateAdapter_ts
22
23
  module_src_react19Compatibility_test_tsx["src/react19Compatibility.test.tsx"]
23
24
  package__ankhorage_runtime -.-> module_src_react19Compatibility_test_tsx
24
25
  module_src_react19Compatibility_test_tsx --> module_src_ManifestContext_tsx
@@ -55,8 +56,11 @@ graph TD
55
56
  package__ankhorage_runtime -.-> module_src_runtimeBindings_ts
56
57
  module_src_runtimeCapabilityExecutor_test_ts["src/runtimeCapabilityExecutor.test.ts"]
57
58
  package__ankhorage_runtime -.-> module_src_runtimeCapabilityExecutor_test_ts
59
+ module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeActionRegistry_ts
58
60
  module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeCapabilityExecutor_ts
61
+ module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeNodeProps_ts
59
62
  module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeScreenLoaders_ts
63
+ module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeStateAdapter_ts
60
64
  module_src_runtimeCapabilityExecutor_ts["src/runtimeCapabilityExecutor.ts"]
61
65
  package__ankhorage_runtime -.-> module_src_runtimeCapabilityExecutor_ts
62
66
  module_src_runtimeCapabilityExecutor_ts --> module_src_runtimeBindings_ts
@@ -107,6 +111,7 @@ graph TD
107
111
  module_src_RuntimeRenderer_tsx --> module_src_runtimeRepeatDiagnostics_ts
108
112
  module_src_RuntimeRenderer_tsx --> module_src_runtimeRepeatEmptyState_ts
109
113
  module_src_RuntimeRenderer_tsx --> module_src_useRuntimeMediaProps_ts
114
+ module_src_RuntimeRenderer_tsx --> module_src_useRuntimeStateBindings_ts
110
115
  module_src_RuntimeRendererConfig_tsx["src/RuntimeRendererConfig.tsx"]
111
116
  package__ankhorage_runtime -.-> module_src_RuntimeRendererConfig_tsx
112
117
  module_src_RuntimeRendererConfig_tsx --> module_src_registry_tsx
@@ -123,6 +128,7 @@ graph TD
123
128
  package__ankhorage_runtime -.-> module_src_runtimeRepeatEmptyState_ts
124
129
  module_src_RuntimeScreen_tsx["src/RuntimeScreen.tsx"]
125
130
  package__ankhorage_runtime -.-> module_src_RuntimeScreen_tsx
131
+ module_src_RuntimeScreen_tsx --> module_src_ManifestContext_tsx
126
132
  module_src_RuntimeScreen_tsx --> module_src_registry_tsx
127
133
  module_src_RuntimeScreen_tsx --> module_src_runtimeApiCapabilities_ts
128
134
  module_src_RuntimeScreen_tsx --> module_src_runtimeCapabilityExecutor_ts
@@ -138,9 +144,16 @@ graph TD
138
144
  module_src_runtimeScreenLoaders_ts --> module_src_runtimeBindings_ts
139
145
  module_src_runtimeStateAdapter_ts["src/runtimeStateAdapter.ts"]
140
146
  package__ankhorage_runtime -.-> module_src_runtimeStateAdapter_ts
147
+ module_src_runtimeStateReactivity_test_tsx["src/runtimeStateReactivity.test.tsx"]
148
+ package__ankhorage_runtime -.-> module_src_runtimeStateReactivity_test_tsx
149
+ module_src_runtimeStateReactivity_test_tsx --> module_src_registry_tsx
150
+ module_src_runtimeStateReactivity_test_tsx --> module_src_runtimeBindings_ts
151
+ module_src_runtimeStateReactivity_test_tsx --> module_src_runtimeStateAdapter_ts
141
152
  module_src_useRuntimeMediaProps_ts["src/useRuntimeMediaProps.ts"]
142
153
  package__ankhorage_runtime -.-> module_src_useRuntimeMediaProps_ts
143
154
  module_src_useRuntimeMediaProps_ts --> module_src_runtimeMedia_ts
155
+ module_src_useRuntimeStateBindings_ts["src/useRuntimeStateBindings.ts"]
156
+ package__ankhorage_runtime -.-> module_src_useRuntimeStateBindings_ts
144
157
  module_tests_reactNativeWebCompatibility_fixture_tsx["tests/reactNativeWebCompatibility.fixture.tsx"]
145
158
  package__ankhorage_runtime -.-> module_tests_reactNativeWebCompatibility_fixture_tsx
146
159
  module_tests_reactNativeWebCompatibility_fixture_tsx --> module_src_RuntimeRenderer_tsx
@@ -39,7 +39,9 @@ graph LR
39
39
  module_src_runtimeScreenLoaders_test_ts["src/runtimeScreenLoaders.test.ts"]
40
40
  module_src_runtimeScreenLoaders_ts["src/runtimeScreenLoaders.ts"]
41
41
  module_src_runtimeStateAdapter_ts["src/runtimeStateAdapter.ts"]
42
+ module_src_runtimeStateReactivity_test_tsx["src/runtimeStateReactivity.test.tsx"]
42
43
  module_src_useRuntimeMediaProps_ts["src/useRuntimeMediaProps.ts"]
44
+ module_src_useRuntimeStateBindings_ts["src/useRuntimeStateBindings.ts"]
43
45
  module_tests_reactNativeWebCompatibility_fixture_tsx["tests/reactNativeWebCompatibility.fixture.tsx"]
44
46
  module_tests_reactNativeWebCompatibility_test_ts["tests/reactNativeWebCompatibility.test.ts"]
45
47
  module_tests_runtime_test_ts["tests/runtime.test.ts"]
@@ -39,12 +39,15 @@ graph LR
39
39
  module_src_runtimeScreenLoaders_test_ts["src/runtimeScreenLoaders.test.ts"]
40
40
  module_src_runtimeScreenLoaders_ts["src/runtimeScreenLoaders.ts"]
41
41
  module_src_runtimeStateAdapter_ts["src/runtimeStateAdapter.ts"]
42
+ module_src_runtimeStateReactivity_test_tsx["src/runtimeStateReactivity.test.tsx"]
42
43
  module_src_useRuntimeMediaProps_ts["src/useRuntimeMediaProps.ts"]
44
+ module_src_useRuntimeStateBindings_ts["src/useRuntimeStateBindings.ts"]
43
45
  module_tests_reactNativeWebCompatibility_fixture_tsx["tests/reactNativeWebCompatibility.fixture.tsx"]
44
46
  module_tests_reactNativeWebCompatibility_test_ts["tests/reactNativeWebCompatibility.test.ts"]
45
47
  module_tests_runtime_test_ts["tests/runtime.test.ts"]
46
48
  module_src_capabilities_resolveRuntimeContextCapability_test_ts --> module_src_capabilities_resolveRuntimeContextCapability_ts
47
49
  module_src_capabilities_resolveRuntimeContextCapability_ts --> module_src_capabilities_index_ts
50
+ module_src_ManifestContext_tsx --> module_src_runtimeStateAdapter_ts
48
51
  module_src_react19Compatibility_test_tsx --> module_src_ManifestContext_tsx
49
52
  module_src_react19Compatibility_test_tsx --> module_src_RuntimeRendererConfig_tsx
50
53
  module_src_registry_test_ts --> module_src_registry_tsx
@@ -59,8 +62,11 @@ graph LR
59
62
  module_src_runtimeBindings_contracts26_test_ts --> module_src_runtimeBindings_ts
60
63
  module_src_runtimeBindings_contracts26_test_ts --> module_src_runtimeRepeat_ts
61
64
  module_src_runtimeBindings_contracts26_test_ts --> module_src_runtimeScreenLoaders_ts
65
+ module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeActionRegistry_ts
62
66
  module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeCapabilityExecutor_ts
67
+ module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeNodeProps_ts
63
68
  module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeScreenLoaders_ts
69
+ module_src_runtimeCapabilityExecutor_test_ts --> module_src_runtimeStateAdapter_ts
64
70
  module_src_runtimeCapabilityExecutor_ts --> module_src_runtimeBindings_ts
65
71
  module_src_runtimeEventExecution_ts --> module_src_runtimeActionRegistry_ts
66
72
  module_src_runtimeImageMedia_test_ts --> module_src_runtimeMedia_ts
@@ -85,12 +91,14 @@ graph LR
85
91
  module_src_RuntimeRenderer_tsx --> module_src_runtimeRepeatDiagnostics_ts
86
92
  module_src_RuntimeRenderer_tsx --> module_src_runtimeRepeatEmptyState_ts
87
93
  module_src_RuntimeRenderer_tsx --> module_src_useRuntimeMediaProps_ts
94
+ module_src_RuntimeRenderer_tsx --> module_src_useRuntimeStateBindings_ts
88
95
  module_src_RuntimeRendererConfig_tsx --> module_src_registry_tsx
89
96
  module_src_RuntimeRendererConfig_tsx --> module_src_runtimeActionRegistry_ts
90
97
  module_src_RuntimeRendererConfig_tsx --> module_src_runtimeBindings_ts
91
98
  module_src_RuntimeRendererConfig_tsx --> module_src_runtimeEventOperationLifecycle_ts
92
99
  module_src_RuntimeRendererConfig_tsx --> module_src_runtimeMedia_ts
93
100
  module_src_runtimeRepeat_ts --> module_src_runtimeBindings_ts
101
+ module_src_RuntimeScreen_tsx --> module_src_ManifestContext_tsx
94
102
  module_src_RuntimeScreen_tsx --> module_src_registry_tsx
95
103
  module_src_RuntimeScreen_tsx --> module_src_runtimeApiCapabilities_ts
96
104
  module_src_RuntimeScreen_tsx --> module_src_runtimeCapabilityExecutor_ts
@@ -100,6 +108,9 @@ graph LR
100
108
  module_src_RuntimeScreen_tsx --> module_src_runtimeStateAdapter_ts
101
109
  module_src_runtimeScreenLoaders_test_ts --> module_src_runtimeScreenLoaders_ts
102
110
  module_src_runtimeScreenLoaders_ts --> module_src_runtimeBindings_ts
111
+ module_src_runtimeStateReactivity_test_tsx --> module_src_registry_tsx
112
+ module_src_runtimeStateReactivity_test_tsx --> module_src_runtimeBindings_ts
113
+ module_src_runtimeStateReactivity_test_tsx --> module_src_runtimeStateAdapter_ts
103
114
  module_src_useRuntimeMediaProps_ts --> module_src_runtimeMedia_ts
104
115
  module_tests_reactNativeWebCompatibility_fixture_tsx --> module_src_RuntimeRenderer_tsx
105
116
  module_tests_runtime_test_ts --> module_src_capabilities_index_ts