@continuum-dev/session 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -92,6 +92,7 @@ function hydrateOrCreate(options?: SessionOptions): Session;
92
92
  Persistence note:
93
93
 
94
94
  - When `options.persistence` is provided, snapshot writes are debounced by 200ms.
95
+ - Pending writes are flushed on `beforeunload` so tab closes do not drop recent updates.
95
96
  - `SessionPersistenceOptions.maxBytes` enforces a payload size cap before writes.
96
97
  - `SessionPersistenceOptions.onError` receives `size_limit` and `storage_error` events.
97
98
  - Browser `storage` events are consumed for cross-tab session synchronization.
@@ -118,6 +119,8 @@ const stopIssues = session.onIssues((issues) => {
118
119
  });
119
120
  ```
120
121
 
122
+ Snapshot listeners receive immutable top-level copies of `view` and `data`.
123
+
121
124
  ### 2) View and State Updates
122
125
 
123
126
  #### `session.pushView(view)`
@@ -148,6 +151,8 @@ session.recordIntent({
148
151
  });
149
152
  ```
150
153
 
154
+ `recordIntent` clones incoming payload objects before storing them and deduplicates issues by `nodeId + code`.
155
+
151
156
  #### Viewport APIs
152
157
 
153
158
  ```typescript
@@ -240,15 +245,54 @@ const log = session.getEventLog();
240
245
 
241
246
  ### 6) Actions Registry
242
247
 
243
- Register local handlers for semantic intent ids and dispatch them with current session snapshot data.
248
+ Register handlers for semantic intent ids and dispatch them with full session context.
249
+
250
+ #### `session.registerAction(intentId, registration, handler)`
244
251
 
245
252
  ```typescript
246
- session.registerAction('fetch_data', { label: 'Fetch Data' }, async ({ snapshot, nodeId }) => {
247
- void snapshot;
248
- void nodeId;
253
+ session.registerAction('submit_form', { label: 'Submit' }, async (context) => {
254
+ const response = await fetch('/api/submit', {
255
+ method: 'POST',
256
+ body: JSON.stringify(context.snapshot.values),
257
+ });
258
+ const data = await response.json();
259
+ context.session.updateState('status', { value: 'submitted' });
260
+ return { success: true, data };
249
261
  });
262
+ ```
263
+
264
+ Handlers receive an `ActionContext` with:
265
+
266
+ - `intentId` -- the dispatched intent identifier
267
+ - `snapshot` -- current `DataSnapshot` at dispatch time
268
+ - `nodeId` -- the node that triggered the action
269
+ - `session` -- an `ActionSessionRef` for post-action mutations (`pushView`, `updateState`, `getSnapshot`, `proposeValue`)
270
+
271
+ #### `session.dispatchAction(intentId, nodeId)`
272
+
273
+ Returns a `Promise<ActionResult>`. Catches handler errors automatically.
250
274
 
251
- await session.dispatchAction('fetch_data', 'btn_1');
275
+ ```typescript
276
+ const result = await session.dispatchAction('submit_form', 'btn_submit');
277
+ if (result.success) {
278
+ console.log('Submitted:', result.data);
279
+ } else {
280
+ console.error('Failed:', result.error);
281
+ }
282
+ ```
283
+
284
+ If no handler is registered, a warning is logged and `{ success: false }` is returned.
285
+
286
+ #### `session.executeIntent(intent)`
287
+
288
+ Bridges the intent lifecycle and action dispatch in a single call: submits a pending intent, dispatches the registered action, and marks the intent as validated on success or cancelled on failure.
289
+
290
+ ```typescript
291
+ const result = await session.executeIntent({
292
+ nodeId: 'btn_submit',
293
+ intentName: 'submit_form',
294
+ payload: { source: 'user' },
295
+ });
252
296
  ```
253
297
 
254
298
  Also available:
@@ -271,6 +315,7 @@ Persistence behavior:
271
315
 
272
316
  - `serialize()` returns a JSON-safe payload with `formatVersion: 1`.
273
317
  - Automatic persistence writes are debounced (200ms) to reduce storage churn.
318
+ - Pending writes are flushed on `beforeunload` to reduce data loss risk during tab close.
274
319
  - If `maxBytes` is exceeded, the write is skipped and `onError` is invoked.
275
320
  - Remote storage updates can rehydrate in-memory state for cross-tab continuity.
276
321
 
@@ -1 +1 @@
1
- {"version":3,"file":"checkpoint-manager.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/checkpoint-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAOvD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAqB3D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,CAezE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,CAalF;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAkBzE"}
1
+ {"version":3,"file":"checkpoint-manager.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/checkpoint-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAOvD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAqB3D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,CAyBzE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,UAAU,GAAG,IAAI,CAalF;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAkBzE"}
@@ -59,6 +59,16 @@ export function createManualCheckpoint(internal) {
59
59
  trigger: 'manual',
60
60
  };
61
61
  internal.checkpoints.push(checkpoint);
62
+ if (internal.checkpoints.length > internal.maxCheckpoints) {
63
+ const overflow = internal.checkpoints.length - internal.maxCheckpoints;
64
+ for (let index = 0; index < overflow; index += 1) {
65
+ const removableIndex = internal.checkpoints.findIndex((cp) => cp.trigger === 'manual');
66
+ if (removableIndex === -1) {
67
+ break;
68
+ }
69
+ internal.checkpoints.splice(removableIndex, 1);
70
+ }
71
+ }
62
72
  return checkpoint;
63
73
  }
64
74
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/event-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,WAAW,EAA2B,MAAM,qBAAqB,CAAC;AAE1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAsDvD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,GAAG,WAAW,GAAG,WAAW,GAAG,aAAa,CAAC,GACtF,IAAI,CAiFN"}
1
+ {"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/event-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,WAAW,EAA2B,MAAM,qBAAqB,CAAC;AAE1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAoEvD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,GAAG,WAAW,GAAG,WAAW,GAAG,aAAa,CAAC,GACtF,IAAI,CA+EN"}
@@ -3,6 +3,16 @@ import { generateId } from './session-state.js';
3
3
  import { buildSnapshotFromCurrentState, notifySnapshotAndIssueListeners } from './listeners.js';
4
4
  import { cloneCheckpointSnapshot } from './checkpoint-manager.js';
5
5
  import { validateNodeValue } from '@continuum-dev/runtime';
6
+ function dedupeIssues(existing, incoming) {
7
+ if (incoming.length === 0) {
8
+ return existing;
9
+ }
10
+ const nextKeys = new Set(incoming.map((issue) => `${issue.nodeId ?? ''}:${issue.code}`));
11
+ return [
12
+ ...existing.filter((issue) => !nextKeys.has(`${issue.nodeId ?? ''}:${issue.code}`)),
13
+ ...incoming,
14
+ ];
15
+ }
6
16
  function collectNodesByCanonicalId(nodes) {
7
17
  const byId = new Map();
8
18
  const walk = (items, parentPath) => {
@@ -75,24 +85,22 @@ export function recordIntent(internal, partial) {
75
85
  }
76
86
  const resolvedEntry = resolveNodeLookupEntry(internal.currentView.nodes, partial.nodeId);
77
87
  if (!resolvedEntry) {
78
- internal.issues = [
79
- ...internal.issues,
80
- {
88
+ internal.issues = dedupeIssues(internal.issues, [{
81
89
  severity: ISSUE_SEVERITY.WARNING,
82
90
  nodeId: partial.nodeId,
83
91
  message: `Node ${partial.nodeId} not found in current view`,
84
92
  code: ISSUE_CODES.UNKNOWN_NODE,
85
- },
86
- ];
93
+ }]);
87
94
  notifySnapshotAndIssueListeners(internal);
88
95
  return;
89
96
  }
90
97
  const { canonicalId, node } = resolvedEntry;
98
+ const payload = { ...partial.payload };
91
99
  internal.currentData = {
92
100
  ...internal.currentData,
93
101
  values: {
94
102
  ...internal.currentData.values,
95
- [canonicalId]: partial.payload,
103
+ [canonicalId]: payload,
96
104
  },
97
105
  lineage: {
98
106
  ...internal.currentData.lineage,
@@ -108,9 +116,9 @@ export function recordIntent(internal, partial) {
108
116
  },
109
117
  };
110
118
  if (internal.validateOnUpdate) {
111
- const validationIssues = validateNodeValue(node, partial.payload);
119
+ const validationIssues = validateNodeValue(node, payload);
112
120
  if (validationIssues.length > 0) {
113
- internal.issues = [...internal.issues, ...validationIssues];
121
+ internal.issues = dedupeIssues(internal.issues, validationIssues);
114
122
  }
115
123
  }
116
124
  const lastAutoCheckpoint = [...internal.checkpoints]
@@ -1 +1 @@
1
- {"version":3,"file":"listeners.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/listeners.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,YAAY,GAAG,kBAAkB,GAAG,IAAI,CAG/F;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CASpE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAQjE;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAG5E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,GACtD,MAAM,IAAI,CAGZ;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,IAAI,GAChD,MAAM,IAAI,CAGZ"}
1
+ {"version":3,"file":"listeners.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/listeners.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,YAAY,GAAG,kBAAkB,GAAG,IAAI,CAS/F;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CASpE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAQjE;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAG5E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,GACtD,MAAM,IAAI,CAGZ;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,IAAI,GAChD,MAAM,IAAI,CAGZ"}
@@ -7,7 +7,13 @@
7
7
  export function buildSnapshotFromCurrentState(internal) {
8
8
  if (!internal.currentView || !internal.currentData)
9
9
  return null;
10
- return { view: internal.currentView, data: internal.currentData };
10
+ const snapshot = {
11
+ view: { ...internal.currentView },
12
+ data: { ...internal.currentData },
13
+ };
14
+ Object.freeze(snapshot.view);
15
+ Object.freeze(snapshot.data);
16
+ return Object.freeze(snapshot);
11
17
  }
12
18
  /**
13
19
  * Notifies all snapshot listeners with the latest snapshot value.
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAkB7D;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,yBAAyB,GACjC,MAAM,IAAI,CAuEZ"}
1
+ {"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIvD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAkB7D;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,yBAAyB,GACjC,MAAM,IAAI,CAmFZ"}
@@ -31,36 +31,43 @@ export function attachPersistence(internal, options) {
31
31
  let timeout;
32
32
  let isApplyingRemote = false;
33
33
  const encoder = new TextEncoder();
34
+ const flushNow = () => {
35
+ if (timeout) {
36
+ clearTimeout(timeout);
37
+ timeout = undefined;
38
+ }
39
+ try {
40
+ const payload = JSON.stringify(serializeSession(internal));
41
+ const attemptedBytes = encoder.encode(payload).byteLength;
42
+ if (typeof options.maxBytes === 'number'
43
+ && Number.isFinite(options.maxBytes)
44
+ && options.maxBytes >= 0
45
+ && attemptedBytes > options.maxBytes) {
46
+ options.onError?.({
47
+ reason: 'size_limit',
48
+ key,
49
+ attemptedBytes,
50
+ maxBytes: options.maxBytes,
51
+ });
52
+ return;
53
+ }
54
+ storage.setItem(key, payload);
55
+ }
56
+ catch (cause) {
57
+ options.onError?.({
58
+ reason: 'storage_error',
59
+ key,
60
+ cause,
61
+ });
62
+ }
63
+ };
34
64
  const unsubscribe = subscribeSnapshot(internal, () => {
35
65
  if (isApplyingRemote)
36
66
  return;
37
67
  if (timeout)
38
68
  clearTimeout(timeout);
39
69
  timeout = setTimeout(() => {
40
- try {
41
- const payload = JSON.stringify(serializeSession(internal));
42
- const attemptedBytes = encoder.encode(payload).byteLength;
43
- if (typeof options.maxBytes === 'number'
44
- && Number.isFinite(options.maxBytes)
45
- && options.maxBytes >= 0
46
- && attemptedBytes > options.maxBytes) {
47
- options.onError?.({
48
- reason: 'size_limit',
49
- key,
50
- attemptedBytes,
51
- maxBytes: options.maxBytes,
52
- });
53
- return;
54
- }
55
- storage.setItem(key, payload);
56
- }
57
- catch (cause) {
58
- options.onError?.({
59
- reason: 'storage_error',
60
- key,
61
- cause,
62
- });
63
- }
70
+ flushNow();
64
71
  }, 200);
65
72
  });
66
73
  const onStorage = (event) => {
@@ -75,26 +82,29 @@ export function attachPersistence(internal, options) {
75
82
  });
76
83
  isApplyingRemote = true;
77
84
  replaceInternalState(internal, next);
85
+ isApplyingRemote = false;
78
86
  notifySnapshotAndIssueListeners(internal);
79
87
  }
80
88
  catch {
81
- return;
82
- }
83
- finally {
84
89
  isApplyingRemote = false;
90
+ return;
85
91
  }
86
92
  };
87
93
  const maybeAdd = globalThis.addEventListener;
88
94
  const maybeRemove = globalThis.removeEventListener;
89
95
  if (maybeAdd) {
90
96
  maybeAdd('storage', onStorage);
97
+ maybeAdd('beforeunload', flushNow);
91
98
  }
92
99
  return () => {
93
- if (timeout)
100
+ if (timeout) {
94
101
  clearTimeout(timeout);
102
+ timeout = undefined;
103
+ }
95
104
  unsubscribe();
96
105
  if (maybeRemove) {
97
106
  maybeRemove('storage', onStorage);
107
+ maybeRemove('beforeunload', flushNow);
98
108
  }
99
109
  };
100
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../../packages/session/src/lib/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA2M1E;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAqB/D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAuB5E;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAWjE;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAA+C,CAAC"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../../packages/session/src/lib/session.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA4O1E;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAqB/D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAuB5E;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAWjE;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAA+C,CAAC"}
package/lib/session.js CHANGED
@@ -175,15 +175,49 @@ function assembleSessionFromInternalState(internal, cleanupPersistence) {
175
175
  }
176
176
  return result;
177
177
  },
178
- dispatchAction(intentId, nodeId) {
178
+ async dispatchAction(intentId, nodeId) {
179
179
  assertNotDestroyed(internal);
180
180
  const entry = internal.actionRegistry.get(intentId);
181
- if (!entry)
182
- return;
181
+ if (!entry) {
182
+ console.warn(`[Continuum] dispatchAction: no handler registered for intentId "${intentId}"`);
183
+ return { success: false, error: `No handler registered for intentId "${intentId}"` };
184
+ }
183
185
  const snapshot = buildSnapshotFromCurrentState(internal);
184
- if (!snapshot)
185
- return;
186
- return entry.handler({ intentId, snapshot: snapshot.data, nodeId });
186
+ if (!snapshot) {
187
+ return { success: false, error: 'No active snapshot' };
188
+ }
189
+ const sessionRef = {
190
+ pushView: (v) => session.pushView(v),
191
+ updateState: (id, p) => session.updateState(id, p),
192
+ getSnapshot: () => session.getSnapshot(),
193
+ proposeValue: (id, v, s) => session.proposeValue(id, v, s),
194
+ };
195
+ try {
196
+ const raw = await entry.handler({ intentId, snapshot: snapshot.data, nodeId, session: sessionRef });
197
+ return raw ?? { success: true };
198
+ }
199
+ catch (error) {
200
+ return { success: false, error };
201
+ }
202
+ },
203
+ async executeIntent(partial) {
204
+ assertNotDestroyed(internal);
205
+ submitIntent(internal, partial);
206
+ const intent = internal.pendingIntents[internal.pendingIntents.length - 1];
207
+ try {
208
+ const result = await session.dispatchAction(partial.intentName, partial.nodeId);
209
+ if (result.success) {
210
+ validateIntent(internal, intent.intentId);
211
+ }
212
+ else {
213
+ cancelIntent(internal, intent.intentId);
214
+ }
215
+ return result;
216
+ }
217
+ catch (error) {
218
+ cancelIntent(internal, intent.intentId);
219
+ return { success: false, error };
220
+ }
187
221
  },
188
222
  };
189
223
  return session;
package/lib/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ContinuitySnapshot, Interaction, DetachedValue, DetachedValuePolicy, ProposedValue, ViewportState, PendingIntent, Checkpoint, ViewDefinition, NodeValue, ActionRegistration, ActionHandler } from '@continuum-dev/contract';
1
+ import type { ContinuitySnapshot, Interaction, DetachedValue, DetachedValuePolicy, ProposedValue, ViewportState, PendingIntent, Checkpoint, ViewDefinition, NodeValue, ActionRegistration, ActionHandler, ActionResult } from '@continuum-dev/contract';
2
2
  import type { ReconciliationIssue, ReconciliationOptions, ReconciliationResolution, StateDiff } from '@continuum-dev/runtime';
3
3
  /**
4
4
  * Minimal storage adapter used by session persistence.
@@ -246,8 +246,18 @@ export interface Session {
246
246
  getRegisteredActions(): Record<string, ActionRegistration>;
247
247
  /**
248
248
  * Dispatches a registered action handler with current snapshot context.
249
+ *
250
+ * Returns `{ success: false }` when no handler is registered or when no
251
+ * active snapshot exists.
252
+ */
253
+ dispatchAction(intentId: string, nodeId: string): Promise<ActionResult>;
254
+ /**
255
+ * Submits a pending intent, dispatches the registered action, and updates
256
+ * intent status based on the result (validated on success, cancelled on failure).
257
+ *
258
+ * Returns the same `ActionResult` shape as `dispatchAction`.
249
259
  */
250
- dispatchAction(intentId: string, nodeId: string): void | Promise<void>;
260
+ executeIntent(intent: Omit<PendingIntent, 'intentId' | 'queuedAt' | 'status' | 'viewVersion'>): Promise<ActionResult>;
251
261
  }
252
262
  /**
253
263
  * Helper interface for dependency injection and test doubles.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../packages/session/src/lib/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../packages/session/src/lib/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@continuum-dev/session",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -40,7 +40,7 @@
40
40
  "LICENSE*"
41
41
  ],
42
42
  "dependencies": {
43
- "@continuum-dev/contract": "^0.1.1",
44
- "@continuum-dev/runtime": "^0.1.1"
43
+ "@continuum-dev/contract": "^0.1.3",
44
+ "@continuum-dev/runtime": "^0.1.3"
45
45
  }
46
46
  }