@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 +50 -5
- package/lib/session/checkpoint-manager.d.ts.map +1 -1
- package/lib/session/checkpoint-manager.js +10 -0
- package/lib/session/event-log.d.ts.map +1 -1
- package/lib/session/event-log.js +16 -8
- package/lib/session/listeners.d.ts.map +1 -1
- package/lib/session/listeners.js +7 -1
- package/lib/session/persistence.d.ts.map +1 -1
- package/lib/session/persistence.js +38 -28
- package/lib/session.d.ts.map +1 -1
- package/lib/session.js +40 -6
- package/lib/types.d.ts +12 -2
- package/lib/types.d.ts.map +1 -1
- package/package.json +3 -3
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
|
|
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('
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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,
|
|
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;
|
|
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"}
|
package/lib/session/event-log.js
CHANGED
|
@@ -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]:
|
|
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,
|
|
119
|
+
const validationIssues = validateNodeValue(node, payload);
|
|
112
120
|
if (validationIssues.length > 0) {
|
|
113
|
-
internal.issues =
|
|
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,
|
|
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"}
|
package/lib/session/listeners.js
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
export function buildSnapshotFromCurrentState(internal) {
|
|
8
8
|
if (!internal.currentView || !internal.currentData)
|
|
9
9
|
return null;
|
|
10
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
}
|
package/lib/session.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
package/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../packages/session/src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,aAAa,EACb,UAAU,EACV,cAAc,EACd,SAAS,EACT,kBAAkB,EAClB,aAAa,
|
|
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.
|
|
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.
|
|
44
|
-
"@continuum-dev/runtime": "^0.1.
|
|
43
|
+
"@continuum-dev/contract": "^0.1.3",
|
|
44
|
+
"@continuum-dev/runtime": "^0.1.3"
|
|
45
45
|
}
|
|
46
46
|
}
|