@continuum-dev/session 0.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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +306 -0
  3. package/index.d.ts +3 -0
  4. package/index.d.ts.map +1 -0
  5. package/index.js +2 -0
  6. package/lib/session/action-manager.d.ts +7 -0
  7. package/lib/session/action-manager.d.ts.map +1 -0
  8. package/lib/session/checkpoint-manager.d.ts +39 -0
  9. package/lib/session/checkpoint-manager.d.ts.map +1 -0
  10. package/lib/session/checkpoint-manager.js +106 -0
  11. package/lib/session/destroyer.d.ts +12 -0
  12. package/lib/session/destroyer.d.ts.map +1 -0
  13. package/lib/session/destroyer.js +22 -0
  14. package/lib/session/event-log.d.ts +13 -0
  15. package/lib/session/event-log.d.ts.map +1 -0
  16. package/lib/session/event-log.js +126 -0
  17. package/lib/session/intent-manager.d.ts +34 -0
  18. package/lib/session/intent-manager.d.ts.map +1 -0
  19. package/lib/session/intent-manager.js +67 -0
  20. package/lib/session/listeners.d.ts +49 -0
  21. package/lib/session/listeners.d.ts.map +1 -0
  22. package/lib/session/listeners.js +77 -0
  23. package/lib/session/persistence.d.ts +14 -0
  24. package/lib/session/persistence.d.ts.map +1 -0
  25. package/lib/session/persistence.js +99 -0
  26. package/lib/session/schema-pusher.d.ts +4 -0
  27. package/lib/session/schema-pusher.d.ts.map +1 -0
  28. package/lib/session/serializer.d.ts +25 -0
  29. package/lib/session/serializer.d.ts.map +1 -0
  30. package/lib/session/serializer.js +126 -0
  31. package/lib/session/session-state.d.ts +65 -0
  32. package/lib/session/session-state.d.ts.map +1 -0
  33. package/lib/session/session-state.js +79 -0
  34. package/lib/session/view-pusher.d.ts +13 -0
  35. package/lib/session/view-pusher.d.ts.map +1 -0
  36. package/lib/session/view-pusher.js +104 -0
  37. package/lib/session.d.ts +33 -0
  38. package/lib/session.d.ts.map +1 -0
  39. package/lib/session.js +275 -0
  40. package/lib/types.d.ts +265 -0
  41. package/lib/types.d.ts.map +1 -0
  42. package/lib/types.js +1 -0
  43. package/package.json +46 -0
@@ -0,0 +1,34 @@
1
+ import type { PendingIntent } from '@continuum/contract';
2
+ import type { SessionState } from './session-state.js';
3
+ /**
4
+ * Queues a pending intent on the current view version.
5
+ *
6
+ * @param internal Mutable internal session state.
7
+ * @param partial Intent payload without generated metadata.
8
+ */
9
+ export declare function submitIntent(internal: SessionState, partial: Omit<PendingIntent, 'intentId' | 'queuedAt' | 'status' | 'viewVersion'>): void;
10
+ /**
11
+ * Marks an intent as validated.
12
+ *
13
+ * @param internal Mutable internal session state.
14
+ * @param intentId Target intent id.
15
+ * @returns True when the intent exists.
16
+ */
17
+ export declare function validateIntent(internal: SessionState, intentId: string): boolean;
18
+ /**
19
+ * Marks an intent as cancelled.
20
+ *
21
+ * @param internal Mutable internal session state.
22
+ * @param intentId Target intent id.
23
+ * @returns True when the intent exists.
24
+ */
25
+ export declare function cancelIntent(internal: SessionState, intentId: string): boolean;
26
+ /**
27
+ * Marks all currently pending intents as stale.
28
+ *
29
+ * Used when view version changes invalidate unresolved intents.
30
+ *
31
+ * @param internal Mutable internal session state.
32
+ */
33
+ export declare function markAllPendingIntentsAsStale(internal: SessionState): void;
34
+ //# sourceMappingURL=intent-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent-manager.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/intent-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,aAAa,CAAC,GAC/E,IAAI,CAiBN;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKhF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK9E;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAMzE"}
@@ -0,0 +1,67 @@
1
+ import { INTENT_STATUS } from '@continuum/contract';
2
+ import { generateId } from './session-state.js';
3
+ /**
4
+ * Queues a pending intent on the current view version.
5
+ *
6
+ * @param internal Mutable internal session state.
7
+ * @param partial Intent payload without generated metadata.
8
+ */
9
+ export function submitIntent(internal, partial) {
10
+ if (internal.destroyed || !internal.currentView)
11
+ return;
12
+ const intent = {
13
+ intentId: generateId('intent', internal.clock),
14
+ nodeId: partial.nodeId,
15
+ intentName: partial.intentName,
16
+ payload: partial.payload,
17
+ queuedAt: internal.clock(),
18
+ viewVersion: internal.currentView.version,
19
+ status: INTENT_STATUS.PENDING,
20
+ };
21
+ internal.pendingIntents.push(intent);
22
+ if (internal.pendingIntents.length > internal.maxPendingIntents) {
23
+ internal.pendingIntents.splice(0, internal.pendingIntents.length - internal.maxPendingIntents);
24
+ }
25
+ }
26
+ /**
27
+ * Marks an intent as validated.
28
+ *
29
+ * @param internal Mutable internal session state.
30
+ * @param intentId Target intent id.
31
+ * @returns True when the intent exists.
32
+ */
33
+ export function validateIntent(internal, intentId) {
34
+ const intent = internal.pendingIntents.find((a) => a.intentId === intentId);
35
+ if (!intent)
36
+ return false;
37
+ intent.status = INTENT_STATUS.VALIDATED;
38
+ return true;
39
+ }
40
+ /**
41
+ * Marks an intent as cancelled.
42
+ *
43
+ * @param internal Mutable internal session state.
44
+ * @param intentId Target intent id.
45
+ * @returns True when the intent exists.
46
+ */
47
+ export function cancelIntent(internal, intentId) {
48
+ const intent = internal.pendingIntents.find((a) => a.intentId === intentId);
49
+ if (!intent)
50
+ return false;
51
+ intent.status = INTENT_STATUS.CANCELLED;
52
+ return true;
53
+ }
54
+ /**
55
+ * Marks all currently pending intents as stale.
56
+ *
57
+ * Used when view version changes invalidate unresolved intents.
58
+ *
59
+ * @param internal Mutable internal session state.
60
+ */
61
+ export function markAllPendingIntentsAsStale(internal) {
62
+ for (const intent of internal.pendingIntents) {
63
+ if (intent.status === INTENT_STATUS.PENDING) {
64
+ intent.status = INTENT_STATUS.STALE;
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,49 @@
1
+ import type { ContinuitySnapshot } from '@continuum/contract';
2
+ import type { ReconciliationIssue } from '@continuum/runtime';
3
+ import type { SessionState } from './session-state.js';
4
+ /**
5
+ * Builds a continuity snapshot from internal view/data state.
6
+ *
7
+ * @param internal Mutable internal session state.
8
+ * @returns Snapshot when both view and data are present, otherwise null.
9
+ */
10
+ export declare function buildSnapshotFromCurrentState(internal: SessionState): ContinuitySnapshot | null;
11
+ /**
12
+ * Notifies all snapshot listeners with the latest snapshot value.
13
+ *
14
+ * Listener exceptions are swallowed so one faulty subscriber cannot block others.
15
+ *
16
+ * @param internal Mutable internal session state.
17
+ */
18
+ export declare function notifySnapshotListeners(internal: SessionState): void;
19
+ /**
20
+ * Notifies all issue listeners with a cloned issue array.
21
+ *
22
+ * Listener exceptions are swallowed so one faulty subscriber cannot block others.
23
+ *
24
+ * @param internal Mutable internal session state.
25
+ */
26
+ export declare function notifyIssueListeners(internal: SessionState): void;
27
+ /**
28
+ * Notifies snapshot listeners, then issue listeners.
29
+ *
30
+ * @param internal Mutable internal session state.
31
+ */
32
+ export declare function notifySnapshotAndIssueListeners(internal: SessionState): void;
33
+ /**
34
+ * Registers a snapshot listener.
35
+ *
36
+ * @param internal Mutable internal session state.
37
+ * @param listener Snapshot callback.
38
+ * @returns Unsubscribe function.
39
+ */
40
+ export declare function subscribeSnapshot(internal: SessionState, listener: (snapshot: ContinuitySnapshot | null) => void): () => void;
41
+ /**
42
+ * Registers an issue listener.
43
+ *
44
+ * @param internal Mutable internal session state.
45
+ * @param listener Issues callback.
46
+ * @returns Unsubscribe function.
47
+ */
48
+ export declare function subscribeIssues(internal: SessionState, listener: (issues: ReconciliationIssue[]) => void): () => void;
49
+ //# sourceMappingURL=listeners.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Builds a continuity snapshot from internal view/data state.
3
+ *
4
+ * @param internal Mutable internal session state.
5
+ * @returns Snapshot when both view and data are present, otherwise null.
6
+ */
7
+ export function buildSnapshotFromCurrentState(internal) {
8
+ if (!internal.currentView || !internal.currentData)
9
+ return null;
10
+ return { view: internal.currentView, data: internal.currentData };
11
+ }
12
+ /**
13
+ * Notifies all snapshot listeners with the latest snapshot value.
14
+ *
15
+ * Listener exceptions are swallowed so one faulty subscriber cannot block others.
16
+ *
17
+ * @param internal Mutable internal session state.
18
+ */
19
+ export function notifySnapshotListeners(internal) {
20
+ const snapshot = buildSnapshotFromCurrentState(internal);
21
+ for (const listener of internal.snapshotListeners) {
22
+ try {
23
+ listener(snapshot);
24
+ }
25
+ catch {
26
+ continue;
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Notifies all issue listeners with a cloned issue array.
32
+ *
33
+ * Listener exceptions are swallowed so one faulty subscriber cannot block others.
34
+ *
35
+ * @param internal Mutable internal session state.
36
+ */
37
+ export function notifyIssueListeners(internal) {
38
+ for (const listener of internal.issueListeners) {
39
+ try {
40
+ listener([...internal.issues]);
41
+ }
42
+ catch {
43
+ continue;
44
+ }
45
+ }
46
+ }
47
+ /**
48
+ * Notifies snapshot listeners, then issue listeners.
49
+ *
50
+ * @param internal Mutable internal session state.
51
+ */
52
+ export function notifySnapshotAndIssueListeners(internal) {
53
+ notifySnapshotListeners(internal);
54
+ notifyIssueListeners(internal);
55
+ }
56
+ /**
57
+ * Registers a snapshot listener.
58
+ *
59
+ * @param internal Mutable internal session state.
60
+ * @param listener Snapshot callback.
61
+ * @returns Unsubscribe function.
62
+ */
63
+ export function subscribeSnapshot(internal, listener) {
64
+ internal.snapshotListeners.add(listener);
65
+ return () => { internal.snapshotListeners.delete(listener); };
66
+ }
67
+ /**
68
+ * Registers an issue listener.
69
+ *
70
+ * @param internal Mutable internal session state.
71
+ * @param listener Issues callback.
72
+ * @returns Unsubscribe function.
73
+ */
74
+ export function subscribeIssues(internal, listener) {
75
+ internal.issueListeners.add(listener);
76
+ return () => { internal.issueListeners.delete(listener); };
77
+ }
@@ -0,0 +1,14 @@
1
+ import type { SessionState } from './session-state.js';
2
+ import type { SessionPersistenceOptions } from '../types.js';
3
+ /**
4
+ * Attaches persistence synchronization to a session.
5
+ *
6
+ * Persists snapshots to storage with debounce and applies remote `storage` events
7
+ * (for example, cross-tab updates) back into the active session state.
8
+ *
9
+ * @param internal Mutable internal session state.
10
+ * @param options Persistence storage options.
11
+ * @returns Cleanup function that unsubscribes listeners and removes event handlers.
12
+ */
13
+ export declare function attachPersistence(internal: SessionState, options: SessionPersistenceOptions): () => void;
14
+ //# sourceMappingURL=persistence.d.ts.map
@@ -0,0 +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,CAsEZ"}
@@ -0,0 +1,99 @@
1
+ import { replaceInternalState } from './session-state.js';
2
+ import { subscribeSnapshot, notifySnapshotAndIssueListeners } from './listeners.js';
3
+ import { serializeSession, deserializeToState } from './serializer.js';
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null;
6
+ }
7
+ function isMatchingStorageEvent(event, storage, key) {
8
+ if (!isRecord(event))
9
+ return false;
10
+ if (event.key !== key)
11
+ return false;
12
+ if (event.newValue === null || typeof event.newValue !== 'string')
13
+ return false;
14
+ if (event.storageArea !== undefined && event.storageArea !== storage)
15
+ return false;
16
+ return true;
17
+ }
18
+ /**
19
+ * Attaches persistence synchronization to a session.
20
+ *
21
+ * Persists snapshots to storage with debounce and applies remote `storage` events
22
+ * (for example, cross-tab updates) back into the active session state.
23
+ *
24
+ * @param internal Mutable internal session state.
25
+ * @param options Persistence storage options.
26
+ * @returns Cleanup function that unsubscribes listeners and removes event handlers.
27
+ */
28
+ export function attachPersistence(internal, options) {
29
+ const key = options.key ?? 'continuum_session';
30
+ const storage = options.storage;
31
+ let timeout;
32
+ let isApplyingRemote = false;
33
+ const encoder = new TextEncoder();
34
+ const unsubscribe = subscribeSnapshot(internal, () => {
35
+ if (isApplyingRemote)
36
+ return;
37
+ if (timeout)
38
+ clearTimeout(timeout);
39
+ 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
+ }
64
+ }, 200);
65
+ });
66
+ const onStorage = (event) => {
67
+ if (!isMatchingStorageEvent(event, storage, key))
68
+ return;
69
+ try {
70
+ const raw = JSON.parse(event.newValue);
71
+ const next = deserializeToState(raw, internal.clock, {
72
+ maxEventLogSize: internal.maxEventLogSize,
73
+ maxPendingIntents: internal.maxPendingIntents,
74
+ maxCheckpoints: internal.maxCheckpoints,
75
+ });
76
+ isApplyingRemote = true;
77
+ replaceInternalState(internal, next);
78
+ notifySnapshotAndIssueListeners(internal);
79
+ }
80
+ catch {
81
+ }
82
+ finally {
83
+ isApplyingRemote = false;
84
+ }
85
+ };
86
+ const maybeAdd = globalThis.addEventListener;
87
+ const maybeRemove = globalThis.removeEventListener;
88
+ if (maybeAdd) {
89
+ maybeAdd('storage', onStorage);
90
+ }
91
+ return () => {
92
+ if (timeout)
93
+ clearTimeout(timeout);
94
+ unsubscribe();
95
+ if (maybeRemove) {
96
+ maybeRemove('storage', onStorage);
97
+ }
98
+ };
99
+ }
@@ -0,0 +1,4 @@
1
+ import type { SchemaSnapshot } from '@continuum/contract';
2
+ import type { SessionState } from './session-state.js';
3
+ export declare function pushSchema(internal: SessionState, schema: SchemaSnapshot): void;
4
+ //# sourceMappingURL=schema-pusher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-pusher.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/schema-pusher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAiBvD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAgC/E"}
@@ -0,0 +1,25 @@
1
+ import type { SessionState } from './session-state.js';
2
+ /**
3
+ * Serializes internal session state into a JSON-compatible payload.
4
+ *
5
+ * @param internal Mutable internal session state.
6
+ * @returns Deep-cloned serialized payload.
7
+ */
8
+ export declare function serializeSession(internal: SessionState): unknown;
9
+ /**
10
+ * Deserializes a serialized session payload into internal session state.
11
+ *
12
+ * Accepts missing `formatVersion` for backward compatibility. When present,
13
+ * only version `1` is supported.
14
+ *
15
+ * @param data Serialized payload object.
16
+ * @param clock Clock source for resumed runtime operations.
17
+ * @param limits Optional caps for event log, pending intents, and checkpoints.
18
+ * @returns Reconstructed internal session state.
19
+ */
20
+ export declare function deserializeToState(data: unknown, clock: () => number, limits?: {
21
+ maxEventLogSize?: number;
22
+ maxPendingIntents?: number;
23
+ maxCheckpoints?: number;
24
+ }): SessionState;
25
+ //# sourceMappingURL=serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/serializer.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQvD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAmBhE;AAuFD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,MAAM,MAAM,EACnB,MAAM,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACzF,YAAY,CAoCd"}
@@ -0,0 +1,126 @@
1
+ import { isInteractionType } from '@continuum/contract';
2
+ const CURRENT_FORMAT_VERSION = 1;
3
+ function deepClone(value) {
4
+ return structuredClone(value);
5
+ }
6
+ /**
7
+ * Serializes internal session state into a JSON-compatible payload.
8
+ *
9
+ * @param internal Mutable internal session state.
10
+ * @returns Deep-cloned serialized payload.
11
+ */
12
+ export function serializeSession(internal) {
13
+ return deepClone({
14
+ formatVersion: CURRENT_FORMAT_VERSION,
15
+ sessionId: internal.sessionId,
16
+ currentView: internal.currentView,
17
+ currentData: internal.currentData,
18
+ priorView: internal.priorView,
19
+ eventLog: internal.eventLog,
20
+ pendingIntents: internal.pendingIntents,
21
+ checkpoints: internal.checkpoints,
22
+ issues: internal.issues,
23
+ diffs: internal.diffs,
24
+ resolutions: internal.resolutions,
25
+ settings: {
26
+ allowBlindCarry: internal.reconciliationOptions?.allowBlindCarry,
27
+ allowPartialRestore: internal.reconciliationOptions?.allowPartialRestore,
28
+ validateOnUpdate: internal.validateOnUpdate,
29
+ },
30
+ });
31
+ }
32
+ function isRecord(value) {
33
+ return typeof value === 'object' && value !== null;
34
+ }
35
+ function assertArrayField(data, field) {
36
+ const value = data[field];
37
+ if (value !== undefined && !Array.isArray(value)) {
38
+ throw new Error(`Invalid serialized session: "${String(field)}" must be an array`);
39
+ }
40
+ }
41
+ function assertObjectOrNullField(data, field) {
42
+ const value = data[field];
43
+ if (value !== undefined && value !== null && !isRecord(value)) {
44
+ throw new Error(`Invalid serialized session: "${field}" must be an object or null`);
45
+ }
46
+ }
47
+ function assertValidEventLogTypes(data) {
48
+ const value = data.eventLog;
49
+ if (!Array.isArray(value))
50
+ return;
51
+ for (let i = 0; i < value.length; i++) {
52
+ const interaction = value[i];
53
+ if (!isRecord(interaction)) {
54
+ throw new Error(`Invalid serialized session: "eventLog[${i}]" must be an object`);
55
+ }
56
+ if (!isInteractionType(interaction.type)) {
57
+ throw new Error(`Invalid serialized session: "eventLog[${i}].type" must be a valid interaction type`);
58
+ }
59
+ }
60
+ }
61
+ function validateSerializedSessionData(data) {
62
+ if (!isRecord(data)) {
63
+ throw new Error('Invalid serialized session: expected an object');
64
+ }
65
+ if (typeof data.sessionId !== 'string') {
66
+ throw new Error('Invalid serialized session: "sessionId" must be a string');
67
+ }
68
+ if (data.formatVersion !== undefined &&
69
+ typeof data.formatVersion !== 'number') {
70
+ throw new Error('Invalid serialized session: "formatVersion" must be a number');
71
+ }
72
+ assertObjectOrNullField(data, 'currentView');
73
+ assertObjectOrNullField(data, 'currentData');
74
+ assertObjectOrNullField(data, 'priorView');
75
+ assertArrayField(data, 'eventLog');
76
+ assertArrayField(data, 'pendingIntents');
77
+ assertArrayField(data, 'checkpoints');
78
+ assertArrayField(data, 'issues');
79
+ assertArrayField(data, 'diffs');
80
+ assertArrayField(data, 'resolutions');
81
+ assertValidEventLogTypes(data);
82
+ }
83
+ /**
84
+ * Deserializes a serialized session payload into internal session state.
85
+ *
86
+ * Accepts missing `formatVersion` for backward compatibility. When present,
87
+ * only version `1` is supported.
88
+ *
89
+ * @param data Serialized payload object.
90
+ * @param clock Clock source for resumed runtime operations.
91
+ * @param limits Optional caps for event log, pending intents, and checkpoints.
92
+ * @returns Reconstructed internal session state.
93
+ */
94
+ export function deserializeToState(data, clock, limits) {
95
+ validateSerializedSessionData(data);
96
+ const raw = data;
97
+ if (raw.formatVersion !== undefined && raw.formatVersion !== CURRENT_FORMAT_VERSION) {
98
+ throw new Error(`Unsupported format version ${raw.formatVersion}. This runtime supports version ${CURRENT_FORMAT_VERSION}.`);
99
+ }
100
+ return {
101
+ sessionId: raw.sessionId,
102
+ clock,
103
+ maxEventLogSize: limits?.maxEventLogSize ?? 1000,
104
+ maxPendingIntents: limits?.maxPendingIntents ?? 500,
105
+ maxCheckpoints: limits?.maxCheckpoints ?? 50,
106
+ reconciliationOptions: {
107
+ allowBlindCarry: raw.settings?.allowBlindCarry,
108
+ allowPartialRestore: raw.settings?.allowPartialRestore,
109
+ },
110
+ validateOnUpdate: raw.settings?.validateOnUpdate ?? false,
111
+ currentView: raw.currentView ?? null,
112
+ currentData: raw.currentData ?? null,
113
+ priorView: raw.priorView ?? null,
114
+ issues: raw.issues ?? [],
115
+ diffs: raw.diffs ?? [],
116
+ resolutions: raw.resolutions ?? [],
117
+ eventLog: (raw.eventLog ?? []).slice(-(limits?.maxEventLogSize ?? 1000)),
118
+ pendingIntents: (raw.pendingIntents ?? []).slice(-(limits?.maxPendingIntents ?? 500)),
119
+ checkpoints: (raw.checkpoints ?? []).slice(-(limits?.maxCheckpoints ?? 50)),
120
+ snapshotListeners: new Set(),
121
+ issueListeners: new Set(),
122
+ pendingProposals: {},
123
+ actionRegistry: new Map(),
124
+ destroyed: false,
125
+ };
126
+ }
@@ -0,0 +1,65 @@
1
+ import type { ContinuitySnapshot, Interaction, PendingIntent, Checkpoint, ViewDefinition, DataSnapshot, DetachedValuePolicy, ProposedValue, ActionRegistration, ActionHandler } from '@continuum/contract';
2
+ import type { ReconciliationIssue, ReconciliationOptions, ReconciliationResolution, StateDiff } from '@continuum/runtime';
3
+ /**
4
+ * Internal mutable session backing state.
5
+ *
6
+ * This shape is not exported from the package barrel and is intended for
7
+ * internal orchestration modules only.
8
+ */
9
+ export interface SessionState {
10
+ sessionId: string;
11
+ clock: () => number;
12
+ maxEventLogSize: number;
13
+ maxPendingIntents: number;
14
+ maxCheckpoints: number;
15
+ reconciliationOptions?: Omit<ReconciliationOptions, 'clock'>;
16
+ validateOnUpdate: boolean;
17
+ detachedValuePolicy?: DetachedValuePolicy;
18
+ currentView: ViewDefinition | null;
19
+ currentData: DataSnapshot | null;
20
+ priorView: ViewDefinition | null;
21
+ issues: ReconciliationIssue[];
22
+ diffs: StateDiff[];
23
+ resolutions: ReconciliationResolution[];
24
+ eventLog: Interaction[];
25
+ pendingIntents: PendingIntent[];
26
+ checkpoints: Checkpoint[];
27
+ snapshotListeners: Set<(snapshot: ContinuitySnapshot) => void>;
28
+ issueListeners: Set<(issues: ReconciliationIssue[]) => void>;
29
+ pendingProposals: Record<string, ProposedValue>;
30
+ actionRegistry: Map<string, {
31
+ registration: ActionRegistration;
32
+ handler: ActionHandler;
33
+ }>;
34
+ destroyed: boolean;
35
+ }
36
+ /**
37
+ * Creates default empty internal session state.
38
+ *
39
+ * @param sessionId Generated session id.
40
+ * @param clock Clock source used for ids/timestamps.
41
+ * @returns Initialized internal state object.
42
+ */
43
+ export declare function createEmptySessionState(sessionId: string, clock: () => number): SessionState;
44
+ /**
45
+ * Resets active timeline data while preserving configuration and listeners.
46
+ *
47
+ * @param internal Mutable internal session state.
48
+ */
49
+ export declare function resetSessionState(internal: SessionState): void;
50
+ /**
51
+ * Replaces runtime portions of internal state from another state object.
52
+ *
53
+ * @param internal Destination state object.
54
+ * @param next Source state values to apply.
55
+ */
56
+ export declare function replaceInternalState(internal: SessionState, next: SessionState): void;
57
+ /**
58
+ * Generates a unique id using prefix, timestamp, and random UUID bytes.
59
+ *
60
+ * @param prefix Id namespace prefix.
61
+ * @param clock Clock source used for timestamp component.
62
+ * @returns Stable unique id string.
63
+ */
64
+ export declare function generateId(prefix: string, clock: () => number): string;
65
+ //# sourceMappingURL=session-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-state.d.ts","sourceRoot":"","sources":["../../../../../packages/session/src/lib/session/session-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,UAAU,EACV,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,aAAa,EACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACV,mBAAmB,EACnB,qBAAqB,EACrB,wBAAwB,EACxB,SAAS,EACV,MAAM,oBAAoB,CAAC;AAE5B;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;IAC7D,gBAAgB,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,WAAW,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,WAAW,EAAE,wBAAwB,EAAE,CAAC;IACxC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,iBAAiB,EAAE,GAAG,CAAC,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC,CAAC;IAC/D,cAAc,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,mBAAmB,EAAE,KAAK,IAAI,CAAC,CAAC;IAC7D,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAChD,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IAC1F,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,MAAM,GAAG,YAAY,CAwB5F;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAW9D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,YAAY,GACjB,IAAI,CAYN;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,MAAM,GAAG,MAAM,CAGtE"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Creates default empty internal session state.
3
+ *
4
+ * @param sessionId Generated session id.
5
+ * @param clock Clock source used for ids/timestamps.
6
+ * @returns Initialized internal state object.
7
+ */
8
+ export function createEmptySessionState(sessionId, clock) {
9
+ return {
10
+ sessionId,
11
+ clock,
12
+ maxEventLogSize: 1000,
13
+ maxPendingIntents: 500,
14
+ maxCheckpoints: 50,
15
+ reconciliationOptions: undefined,
16
+ validateOnUpdate: false,
17
+ currentView: null,
18
+ currentData: null,
19
+ priorView: null,
20
+ issues: [],
21
+ diffs: [],
22
+ resolutions: [],
23
+ eventLog: [],
24
+ pendingIntents: [],
25
+ checkpoints: [],
26
+ snapshotListeners: new Set(),
27
+ issueListeners: new Set(),
28
+ pendingProposals: {},
29
+ actionRegistry: new Map(),
30
+ destroyed: false,
31
+ };
32
+ }
33
+ /**
34
+ * Resets active timeline data while preserving configuration and listeners.
35
+ *
36
+ * @param internal Mutable internal session state.
37
+ */
38
+ export function resetSessionState(internal) {
39
+ internal.currentView = null;
40
+ internal.currentData = null;
41
+ internal.priorView = null;
42
+ internal.issues = [];
43
+ internal.diffs = [];
44
+ internal.resolutions = [];
45
+ internal.eventLog = [];
46
+ internal.pendingIntents = [];
47
+ internal.checkpoints = [];
48
+ internal.pendingProposals = {};
49
+ }
50
+ /**
51
+ * Replaces runtime portions of internal state from another state object.
52
+ *
53
+ * @param internal Destination state object.
54
+ * @param next Source state values to apply.
55
+ */
56
+ export function replaceInternalState(internal, next) {
57
+ internal.currentView = next.currentView;
58
+ internal.currentData = next.currentData;
59
+ internal.priorView = next.priorView;
60
+ internal.issues = next.issues;
61
+ internal.diffs = next.diffs;
62
+ internal.resolutions = next.resolutions;
63
+ internal.eventLog = next.eventLog;
64
+ internal.pendingIntents = next.pendingIntents;
65
+ internal.checkpoints = next.checkpoints;
66
+ internal.validateOnUpdate = next.validateOnUpdate;
67
+ internal.reconciliationOptions = next.reconciliationOptions;
68
+ }
69
+ /**
70
+ * Generates a unique id using prefix, timestamp, and random UUID bytes.
71
+ *
72
+ * @param prefix Id namespace prefix.
73
+ * @param clock Clock source used for timestamp component.
74
+ * @returns Stable unique id string.
75
+ */
76
+ export function generateId(prefix, clock) {
77
+ const randomPart = globalThis.crypto.randomUUID().replace(/-/g, '');
78
+ return `${prefix}_${clock()}_${randomPart}`;
79
+ }