@abloatai/humans 0.57.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Ablo.d.ts +8 -18
- package/dist/client.d.ts +6 -19
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/local/Database.d.ts +1 -1
- package/dist/local/SyncClient.js +5 -0
- package/dist/local/client/createInternalComponents.d.ts +1 -1
- package/dist/local/client/createInternalComponents.js +1 -1
- package/dist/local/client/{createModelProxy.d.ts → createModelOperations.d.ts} +11 -10
- package/dist/local/client/{createModelProxy.js → createModelOperations.js} +240 -313
- package/dist/local/client/options.d.ts +2 -2
- package/dist/local/client/reactiveEngine.js +34 -45
- package/dist/local/client/resourceTypes.d.ts +3 -3
- package/dist/local/client/resourceTypes.js +1 -1
- package/dist/local/client/wsMutationExecutor.d.ts +4 -4
- package/dist/local/client/wsMutationExecutor.js +3 -5
- package/dist/local/coordination/ClaimLog.d.ts +3 -3
- package/dist/local/coordination/ClaimLog.js +2 -2
- package/dist/local/interfaces/index.d.ts +5 -7
- package/dist/local/mutators/mutateActions.d.ts +3 -2
- package/dist/local/query/types.d.ts +2 -2
- package/dist/local/stores/syncAction.d.ts +1 -1
- package/dist/local/stores/syncAction.js +1 -1
- package/dist/local/sync/ConnectionManager.d.ts +1 -1
- package/dist/local/sync/ConnectionManager.js +1 -1
- package/dist/local/sync/OnDemandLoader.d.ts +1 -1
- package/dist/local/sync/OnDemandLoader.js +1 -1
- package/dist/local/sync/SyncWebSocket.d.ts +3 -3
- package/dist/local/sync/SyncWebSocket.js +2 -2
- package/dist/local/sync/commitFrames.d.ts +1 -1
- package/dist/local/sync/commitFrames.js +1 -1
- package/dist/local/sync/contextOnChange.d.ts +8 -0
- package/dist/local/sync/contextOnChange.js +65 -0
- package/dist/local/sync/createClaimStream.d.ts +1 -1
- package/dist/local/sync/createClaimStream.js +47 -28
- package/dist/local/sync/credentialLifecycle.d.ts +1 -1
- package/dist/local/sync/credentialLifecycle.js +1 -1
- package/dist/local/sync/participants.d.ts +1 -1
- package/dist/local/sync/schemas.d.ts +2 -2
- package/dist/local/sync/schemas.js +1 -1
- package/dist/local/sync/wsFrameHandlers.d.ts +1 -1
- package/dist/local/sync/wsFrameHandlers.js +1 -1
- package/dist/local/transactions/databaseCommitOutbox.d.ts +2 -2
- package/dist/local/transactions/mutations/MutationQueue.d.ts +4 -19
- package/dist/local/transactions/mutations/MutationQueue.js +5 -70
- package/dist/local/transactions/mutations/batchProcessing.d.ts +2 -13
- package/dist/local/transactions/mutations/batchProcessing.js +0 -20
- package/dist/local/transactions/mutations/commitApi.d.ts +2 -3
- package/dist/local/transactions/mutations/commitApi.js +0 -4
- package/dist/local/transactions/mutations/commitLane.d.ts +3 -8
- package/dist/local/transactions/mutations/commitLane.js +0 -11
- package/dist/local/transactions/mutations/commitPayload.d.ts +2 -4
- package/dist/local/transactions/mutations/commitPayload.js +2 -6
- package/dist/local/transactions/mutations/commitTransport.d.ts +3 -4
- package/dist/local/transactions/mutations/commitTransport.js +2 -5
- package/dist/local/transactions/mutations/durableCommitRestore.d.ts +1 -1
- package/dist/local/transactions/mutations/durableCommitRestore.js +1 -4
- package/dist/local/transactions/mutations/durableWriteStore.d.ts +5 -5
- package/dist/local/transactions/mutations/durableWriteStore.js +3 -3
- package/dist/local/transactions/mutations/failurePolicy.d.ts +2 -2
- package/dist/local/transactions/mutations/mutationPersistence.d.ts +1 -1
- package/dist/local/transactions/mutations/pendingDrain.d.ts +2 -2
- package/dist/local/transactions/mutations/replayValidation.d.ts +0 -20
- package/dist/local/transactions/mutations/replayValidation.js +1 -3
- package/dist/local/transactions/persistedTransaction.d.ts +0 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/presenceStream.d.ts +1 -1
- package/dist/react/createAbloReact.d.ts +1 -1
- package/dist/react/useAblo.d.ts +2 -2
- package/dist/react/useAblo.js +1 -1
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +1 -2
- package/package.json +2 -2
- package/src/Ablo.ts +9 -27
- package/src/client.ts +6 -22
- package/src/core.ts +1 -1
- package/src/local/Database.ts +1 -1
- package/src/local/SyncClient.ts +5 -0
- package/src/local/client/createInternalComponents.ts +2 -2
- package/src/local/client/{createModelProxy.ts → createModelOperations.ts} +292 -367
- package/src/local/client/options.ts +2 -2
- package/src/local/client/reactiveEngine.ts +63 -55
- package/src/local/client/resourceTypes.ts +4 -4
- package/src/local/client/wsMutationExecutor.ts +3 -9
- package/src/local/coordination/ClaimLog.ts +3 -3
- package/src/local/interfaces/index.ts +5 -7
- package/src/local/mutators/mutateActions.ts +3 -2
- package/src/local/query/types.ts +2 -2
- package/src/local/stores/syncAction.ts +1 -1
- package/src/local/sync/ConnectionManager.ts +1 -1
- package/src/local/sync/OnDemandLoader.ts +1 -1
- package/src/local/sync/SyncWebSocket.ts +3 -3
- package/src/local/sync/commitFrames.ts +1 -2
- package/src/local/sync/contextOnChange.ts +92 -0
- package/src/local/sync/createClaimStream.ts +77 -39
- package/src/local/sync/credentialLifecycle.ts +1 -1
- package/src/local/sync/participants.ts +1 -1
- package/src/local/sync/schemas.ts +1 -1
- package/src/local/sync/wsFrameHandlers.ts +1 -1
- package/src/local/transactions/databaseCommitOutbox.ts +2 -2
- package/src/local/transactions/mutations/MutationQueue.ts +8 -95
- package/src/local/transactions/mutations/batchProcessing.ts +2 -38
- package/src/local/transactions/mutations/commitApi.ts +3 -7
- package/src/local/transactions/mutations/commitLane.ts +3 -17
- package/src/local/transactions/mutations/commitPayload.ts +3 -11
- package/src/local/transactions/mutations/commitTransport.ts +3 -7
- package/src/local/transactions/mutations/durableCommitRestore.ts +2 -5
- package/src/local/transactions/mutations/durableWriteStore.ts +5 -5
- package/src/local/transactions/mutations/failurePolicy.ts +2 -2
- package/src/local/transactions/mutations/mutationPersistence.ts +1 -1
- package/src/local/transactions/mutations/pendingDrain.ts +2 -2
- package/src/local/transactions/mutations/replayValidation.ts +1 -3
- package/src/local/transactions/persistedTransaction.ts +0 -3
- package/src/plugin.ts +2 -2
- package/src/presenceStream.ts +2 -2
- package/src/react/createAbloReact.ts +1 -1
- package/src/react/useAblo.ts +2 -2
- package/src/surface.ts +2 -3
- package/dist/local/sync/createSnapshot.d.ts +0 -29
- package/dist/local/sync/createSnapshot.js +0 -116
- package/src/local/sync/createSnapshot.ts +0 -160
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
2
|
-
import type { ReadDependency
|
|
2
|
+
import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
|
|
3
3
|
import type {
|
|
4
4
|
CommitOperationResult,
|
|
5
5
|
MutationCommitResult,
|
|
6
|
-
} from '@abloatai/transaction/
|
|
6
|
+
} from '@abloatai/transaction/commit';
|
|
7
7
|
import type {
|
|
8
8
|
DurableCommitEnvelope,
|
|
9
9
|
DurableCommitOperation,
|
|
10
|
-
} from '@abloatai/transaction/
|
|
10
|
+
} from '@abloatai/transaction/commit';
|
|
11
11
|
import type { SealDurableCommitInput } from './commitTransport.js';
|
|
12
12
|
import { transientRetryDelayMs } from './failureHandling.js';
|
|
13
13
|
|
|
@@ -21,10 +21,8 @@ export interface CommitTransaction {
|
|
|
21
21
|
input?: Record<string, unknown>;
|
|
22
22
|
transactionId?: string;
|
|
23
23
|
readAt?: number | null;
|
|
24
|
-
onStale?: OnStaleMode | null;
|
|
25
24
|
}[];
|
|
26
25
|
reads?: ReadDependency[] | null;
|
|
27
|
-
track?: TrackDependency[] | null;
|
|
28
26
|
status: 'pending' | 'executing' | 'awaiting_delta' | 'completed' | 'failed';
|
|
29
27
|
createdAt: number;
|
|
30
28
|
attempts: number;
|
|
@@ -50,7 +48,6 @@ export interface CommitLaneContext {
|
|
|
50
48
|
retryBackoff: { baseMs: number; capMs: number };
|
|
51
49
|
};
|
|
52
50
|
readonly commitLane: CommitTransaction[];
|
|
53
|
-
readonly commitNotifications: Map<string, StaleNotification[]>;
|
|
54
51
|
readonly commitMissingIds: Map<string, string[]>;
|
|
55
52
|
readonly commitProcessing: boolean;
|
|
56
53
|
readonly setCommitProcessing: (value: boolean) => void;
|
|
@@ -72,7 +69,6 @@ export interface CommitLaneContext {
|
|
|
72
69
|
|
|
73
70
|
export interface CommitReceiptContext {
|
|
74
71
|
readonly commitStore: Map<string, CommitTransaction>;
|
|
75
|
-
readonly commitNotifications: Map<string, StaleNotification[]>;
|
|
76
72
|
readonly commitMissingIds: Map<string, string[]>;
|
|
77
73
|
readonly replicationLagErrors: Map<string, Error>;
|
|
78
74
|
readonly on: (event: string, listener: (payload: object) => void) => void;
|
|
@@ -84,16 +80,9 @@ export function waitForCommitReceipt(
|
|
|
84
80
|
clientTxId: string,
|
|
85
81
|
): Promise<{
|
|
86
82
|
lastSyncId: number;
|
|
87
|
-
notifications?: StaleNotification[];
|
|
88
83
|
missingIds?: string[];
|
|
89
84
|
operationResults?: CommitOperationResult[];
|
|
90
85
|
}> {
|
|
91
|
-
const drainNotifications = (): StaleNotification[] | undefined => {
|
|
92
|
-
const notifications = ctx.commitNotifications.get(clientTxId);
|
|
93
|
-
if (!notifications) return undefined;
|
|
94
|
-
ctx.commitNotifications.delete(clientTxId);
|
|
95
|
-
return notifications.length > 0 ? notifications : undefined;
|
|
96
|
-
};
|
|
97
86
|
const drainMissingIds = (): string[] | undefined => {
|
|
98
87
|
const ids = ctx.commitMissingIds.get(clientTxId);
|
|
99
88
|
if (!ids) return undefined;
|
|
@@ -104,7 +93,6 @@ export function waitForCommitReceipt(
|
|
|
104
93
|
const missingIds = drainMissingIds();
|
|
105
94
|
return {
|
|
106
95
|
lastSyncId,
|
|
107
|
-
notifications: drainNotifications(),
|
|
108
96
|
...(transaction.operationResults
|
|
109
97
|
? { operationResults: transaction.operationResults }
|
|
110
98
|
: {}),
|
|
@@ -157,7 +145,6 @@ export async function processCommitLane(ctx: CommitLaneContext): Promise<void> {
|
|
|
157
145
|
sourceMutationIds: tx.sourceMutationIds,
|
|
158
146
|
commitOptions: {
|
|
159
147
|
...(tx.reads ? { reads: tx.reads } : {}),
|
|
160
|
-
...(tx.track ? { track: tx.track } : {}),
|
|
161
148
|
},
|
|
162
149
|
createdAt: tx.createdAt,
|
|
163
150
|
sealedAt: tx.sealedAt,
|
|
@@ -170,7 +157,6 @@ export async function processCommitLane(ctx: CommitLaneContext): Promise<void> {
|
|
|
170
157
|
tx.durableEnvelope = await ctx.persistDurableCommitAcceptance(durableEnvelope, result);
|
|
171
158
|
tx.lastSyncId = result.lastSyncId;
|
|
172
159
|
if (result.operationResults?.length) tx.operationResults = [...result.operationResults];
|
|
173
|
-
if (result.notifications?.length) ctx.commitNotifications.set(tx.id, result.notifications);
|
|
174
160
|
if (result.missingIds?.length) ctx.commitMissingIds.set(tx.id, result.missingIds);
|
|
175
161
|
ctx.commitLane.shift();
|
|
176
162
|
if (result.status === 'queued') {
|
|
@@ -13,9 +13,8 @@ import { globalRuntime } from '../../context.js';
|
|
|
13
13
|
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
14
14
|
import { MutationOperationType } from '@abloatai/transaction/types';
|
|
15
15
|
import { snapshotJsonValue } from '@abloatai/transaction/utils/json';
|
|
16
|
-
import type { OnStaleMode } from '@abloatai/transaction/coordination/schema';
|
|
17
16
|
import type { MutationOptions, WriteOptions } from '../../interfaces/index.js';
|
|
18
|
-
import type { CommitEnvelopeMember } from '@abloatai/transaction/
|
|
17
|
+
import type { CommitEnvelopeMember } from '@abloatai/transaction/commit';
|
|
19
18
|
|
|
20
19
|
export interface UserContext {
|
|
21
20
|
userId: string;
|
|
@@ -198,10 +197,7 @@ export const TX_TYPE_TO_MUTATION_OP: Record<QueuedMutation['type'], MutationOper
|
|
|
198
197
|
};
|
|
199
198
|
|
|
200
199
|
export function hasStaleWriteOptions(options?: WriteOptions): boolean {
|
|
201
|
-
return
|
|
202
|
-
options?.readAt !== undefined ||
|
|
203
|
-
options?.onStale !== undefined
|
|
204
|
-
);
|
|
200
|
+
return options?.readAt !== undefined;
|
|
205
201
|
}
|
|
206
202
|
|
|
207
203
|
/** Options whose identity/audit semantics forbid merging two caller writes. */
|
|
@@ -217,7 +213,6 @@ export function hasCommitCoalescingBarrier(options?: WriteOptions): boolean {
|
|
|
217
213
|
|
|
218
214
|
export interface WriteOperationFields {
|
|
219
215
|
readAt?: number | null;
|
|
220
|
-
onStale?: OnStaleMode | null;
|
|
221
216
|
fenceToken?: number | null;
|
|
222
217
|
claimId?: string;
|
|
223
218
|
options?: Pick<MutationOptions, 'idempotencyKey' | 'label'>;
|
|
@@ -225,7 +220,7 @@ export interface WriteOperationFields {
|
|
|
225
220
|
|
|
226
221
|
/**
|
|
227
222
|
* Copies a transaction's `writeOptions` onto the wire operation. The
|
|
228
|
-
* stale-context
|
|
223
|
+
* stale-context guard (`readAt`) sits at the operation's root,
|
|
229
224
|
* while `idempotencyKey` and `label` go in its `options` slot — the
|
|
230
225
|
* `mutation_log` cache key and audit tag. This is the one place caller-supplied
|
|
231
226
|
* write options cross onto the wire.
|
|
@@ -240,9 +235,6 @@ export function applyWriteOptions<T extends object>(
|
|
|
240
235
|
if (writeOptions.readAt !== undefined) {
|
|
241
236
|
operation.readAt = writeOptions.readAt;
|
|
242
237
|
}
|
|
243
|
-
if (writeOptions.onStale !== undefined) {
|
|
244
|
-
operation.onStale = writeOptions.onStale;
|
|
245
|
-
}
|
|
246
238
|
if (writeOptions.fenceToken !== undefined) {
|
|
247
239
|
operation.fenceToken = writeOptions.fenceToken;
|
|
248
240
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
2
2
|
import type { MutationExecutor } from '../../interfaces/index.js';
|
|
3
|
-
import type { ReadDependency
|
|
3
|
+
import type { ReadDependency } from '@abloatai/transaction/coordination/schema';
|
|
4
4
|
import {
|
|
5
5
|
AbloConnectionError,
|
|
6
6
|
AbloError,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
mutationCommitResultSchema,
|
|
11
11
|
type MutationCommitResult,
|
|
12
|
-
} from '@abloatai/transaction/
|
|
12
|
+
} from '@abloatai/transaction/commit';
|
|
13
13
|
import {
|
|
14
14
|
createDurableCommitEnvelope,
|
|
15
15
|
commitEnvelopeRecordId,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type CommitOutboxScope,
|
|
18
18
|
type DurableCommitEnvelope,
|
|
19
19
|
type DurableCommitOperation,
|
|
20
|
-
} from '@abloatai/transaction/
|
|
20
|
+
} from '@abloatai/transaction/commit';
|
|
21
21
|
import type { DurableWriteStore } from './durableWriteStore.js';
|
|
22
22
|
|
|
23
23
|
export interface CommitTransportContext {
|
|
@@ -39,7 +39,6 @@ export interface SealDurableCommitInput {
|
|
|
39
39
|
sourceMutationIds?: string[];
|
|
40
40
|
commitOptions?: {
|
|
41
41
|
reads?: readonly ReadDependency[] | null;
|
|
42
|
-
track?: readonly TrackDependency[] | null;
|
|
43
42
|
};
|
|
44
43
|
createdAt: number;
|
|
45
44
|
sealedAt: number;
|
|
@@ -63,9 +62,6 @@ export async function sealDurableCommit(
|
|
|
63
62
|
...(input.commitOptions?.reads !== undefined
|
|
64
63
|
? { reads: input.commitOptions.reads === null ? null : [...input.commitOptions.reads] }
|
|
65
64
|
: {}),
|
|
66
|
-
...(input.commitOptions?.track !== undefined
|
|
67
|
-
? { track: input.commitOptions.track === null ? null : [...input.commitOptions.track] }
|
|
68
|
-
: {}),
|
|
69
65
|
},
|
|
70
66
|
...(ctx.commitOutboxScope ? { scope: ctx.commitOutboxScope } : {}),
|
|
71
67
|
createdAt: input.createdAt,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AbloIdempotencyError, AbloValidationError } from '@abloatai/transaction/errors';
|
|
2
2
|
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
3
3
|
import type { CommitTransaction } from './commitLane.js';
|
|
4
|
-
import type { DurableCommitEnvelope, CommitOutboxScope } from '@abloatai/transaction/
|
|
4
|
+
import type { DurableCommitEnvelope, CommitOutboxScope } from '@abloatai/transaction/commit';
|
|
5
5
|
import type { DurableWriteStore } from './durableWriteStore.js';
|
|
6
|
-
import { durableCommitEnvelopeSchema } from '@abloatai/transaction/
|
|
6
|
+
import { durableCommitEnvelopeSchema } from '@abloatai/transaction/commit';
|
|
7
7
|
|
|
8
8
|
export interface DurableCommitRestoreContext {
|
|
9
9
|
readonly config: { enablePersistence: boolean };
|
|
@@ -95,9 +95,6 @@ export async function restoreDurableCommits(ctx: DurableCommitRestoreContext): P
|
|
|
95
95
|
...(envelope.commitOptions.reads
|
|
96
96
|
? { reads: [...envelope.commitOptions.reads] }
|
|
97
97
|
: {}),
|
|
98
|
-
...(envelope.commitOptions.track
|
|
99
|
-
? { track: [...envelope.commitOptions.track] }
|
|
100
|
-
: {}),
|
|
101
98
|
status: 'pending',
|
|
102
99
|
createdAt: envelope.createdAt,
|
|
103
100
|
sealedAt: envelope.sealedAt,
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
*
|
|
7
7
|
* The port and its config live in the core's `durableWrites` module (a behavior
|
|
8
8
|
* contract, not a persisted shape); the records that cross it are owned by
|
|
9
|
-
* `
|
|
9
|
+
* `commit/confirmation/pendingWrite`.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export {
|
|
13
13
|
durableWriteStoreSchema,
|
|
14
14
|
durableWritesConfigSchema,
|
|
15
|
-
} from '@abloatai/transaction/
|
|
15
|
+
} from '@abloatai/transaction/commit';
|
|
16
16
|
export type {
|
|
17
17
|
DurableWriteStore,
|
|
18
18
|
DurableWritesConfig,
|
|
19
|
-
} from '@abloatai/transaction/
|
|
20
|
-
export { pendingWriteSchema } from '@abloatai/transaction/
|
|
21
|
-
export type { PendingWrite } from '@abloatai/transaction/
|
|
19
|
+
} from '@abloatai/transaction/commit';
|
|
20
|
+
export { pendingWriteSchema } from '@abloatai/transaction/commit';
|
|
21
|
+
export type { PendingWrite } from '@abloatai/transaction/commit';
|
|
@@ -25,7 +25,7 @@ export function isDefinitiveRejection(error: Error): boolean {
|
|
|
25
25
|
return status !== undefined && status >= 400 && status < 500 && status !== 429;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export interface
|
|
28
|
+
export interface ConflictResolutionContext {
|
|
29
29
|
readonly config: Pick<MutationQueueConfig, 'conflictResolution'>;
|
|
30
30
|
readonly store: MutationStore;
|
|
31
31
|
readonly rollbackOptimistic: (transaction: QueuedMutation, reason: string) => Promise<void>;
|
|
@@ -34,7 +34,7 @@ export interface ConflictPolicyContext {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export async function handleConflict(
|
|
37
|
-
ctx:
|
|
37
|
+
ctx: ConflictResolutionContext,
|
|
38
38
|
transaction: QueuedMutation,
|
|
39
39
|
serverData: MutationInput,
|
|
40
40
|
): Promise<void> {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
2
2
|
import type { MutationPersistencePort } from '../../mutationPersistence.js';
|
|
3
3
|
import type { MutationQueueConfig } from './MutationQueue.js';
|
|
4
|
-
import type { CommitOutboxScope } from '@abloatai/transaction/
|
|
4
|
+
import type { CommitOutboxScope } from '@abloatai/transaction/commit';
|
|
5
5
|
import type { QueuedMutation } from './commitPayload.js';
|
|
6
6
|
import { MutationStore } from './MutationStore.js';
|
|
7
7
|
import { normalizeModelKey } from './commitPayload.js';
|
|
@@ -2,8 +2,8 @@ import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
|
2
2
|
import type { QueuedMutation } from './commitPayload.js';
|
|
3
3
|
import type { MutationStore } from './MutationStore.js';
|
|
4
4
|
import type { OptimisticUpdateEntry } from './localMutation.js';
|
|
5
|
-
import type { MutationCommitResult } from '@abloatai/transaction/
|
|
6
|
-
import type { DurableCommitEnvelope } from '@abloatai/transaction/
|
|
5
|
+
import type { MutationCommitResult } from '@abloatai/transaction/commit';
|
|
6
|
+
import type { DurableCommitEnvelope } from '@abloatai/transaction/commit';
|
|
7
7
|
import { applyWriteOptions, TX_TYPE_TO_MUTATION_OP } from './commitPayload.js';
|
|
8
8
|
|
|
9
9
|
export interface PendingDrainContext {
|
|
@@ -23,14 +23,12 @@ import type { RuntimeContext } from '../../RuntimeContext.js';
|
|
|
23
23
|
import {
|
|
24
24
|
commitEnvelopeMemberSchema,
|
|
25
25
|
commitOutboxScopeSchema,
|
|
26
|
-
} from '@abloatai/transaction/
|
|
27
|
-
import { onStaleModeSchema } from '@abloatai/transaction/coordination/schema';
|
|
26
|
+
} from '@abloatai/transaction/commit';
|
|
28
27
|
|
|
29
28
|
/** The subset of a write's options that is stored with each transaction or queued mutation. */
|
|
30
29
|
const persistedWriteOptionsSchema = z
|
|
31
30
|
.object({
|
|
32
31
|
readAt: z.number().nullable().optional(),
|
|
33
|
-
onStale: onStaleModeSchema.nullable().optional(),
|
|
34
32
|
idempotencyKey: z.string().optional(),
|
|
35
33
|
label: z.string().optional(),
|
|
36
34
|
// Aligned with the `WriteOptions` type: a claimed write persisted locally
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { OnStaleMode } from '@abloatai/transaction/coordination/schema';
|
|
2
|
-
|
|
3
1
|
type ModelData = Record<string, unknown>;
|
|
4
2
|
|
|
5
3
|
/** One mutation retained in the durable local transaction journal. */
|
|
@@ -10,7 +8,6 @@ interface PersistedMutation {
|
|
|
10
8
|
timestamp: string;
|
|
11
9
|
writeOptions?: {
|
|
12
10
|
readAt?: number | null;
|
|
13
|
-
onStale?: OnStaleMode | null;
|
|
14
11
|
};
|
|
15
12
|
}
|
|
16
13
|
|
package/src/plugin.ts
CHANGED
|
@@ -19,9 +19,9 @@ import type { ParticipantKind } from '@abloatai/transaction/types/participant';
|
|
|
19
19
|
import type { ErrorCodeSpec } from '@abloatai/transaction/errorCodes';
|
|
20
20
|
import type { Logger } from '@abloatai/transaction/logger';
|
|
21
21
|
import type { CoordinationObservability } from '@abloatai/transaction/observability';
|
|
22
|
-
import type { WsTransport } from '@abloatai/transaction/transport/
|
|
22
|
+
import type { WsTransport } from '@abloatai/transaction/transport/websocket';
|
|
23
23
|
import type { AuthCredentialSource } from '@abloatai/transaction/auth/credentialSource';
|
|
24
|
-
import type { SyncDeltaWireCore } from '@abloatai/transaction/
|
|
24
|
+
import type { SyncDeltaWireCore } from '@abloatai/transaction/observation';
|
|
25
25
|
import type { ModelData } from '@abloatai/transaction/types/modelData';
|
|
26
26
|
|
|
27
27
|
/**
|
package/src/presenceStream.ts
CHANGED
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* `leave`.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import type { WsTransport } from '@abloatai/transaction/transport/
|
|
27
|
-
import type { PresenceUpdate } from '@abloatai/transaction/transport/
|
|
26
|
+
import type { WsTransport } from '@abloatai/transaction/transport/websocket';
|
|
27
|
+
import type { PresenceUpdate } from '@abloatai/transaction/transport/websocket';
|
|
28
28
|
import type {
|
|
29
29
|
Activity,
|
|
30
30
|
Peer,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
type UseAbloModelResult,
|
|
37
37
|
} from './useAblo.js';
|
|
38
38
|
import type { AbloClient as Ablo } from '../client.js';
|
|
39
|
-
import type { ModelOperations } from '../local/client/
|
|
39
|
+
import type { ModelOperations } from '../local/client/createModelOperations.js';
|
|
40
40
|
import type { Schema, SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
41
41
|
|
|
42
42
|
/** What a binding returns: the provider and the hook, with `S` fixed. */
|
package/src/react/useAblo.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { ModelClaim } from '@abloatai/transaction/coordination';
|
|
|
7
7
|
import {
|
|
8
8
|
getModelClientMeta,
|
|
9
9
|
type ModelOperations,
|
|
10
|
-
} from '../local/client/
|
|
10
|
+
} from '../local/client/createModelOperations.js';
|
|
11
11
|
import { Model } from '../local/Model.js';
|
|
12
12
|
import type { SchemaRecord } from '@abloatai/transaction/schema/schema';
|
|
13
13
|
import type { ResolveSchema } from '@abloatai/transaction/types/global';
|
|
@@ -138,7 +138,7 @@ function snapshotValue<T>(value: T): T {
|
|
|
138
138
|
* // With the Register augmentation (recommended):
|
|
139
139
|
* const ablo = useAblo();
|
|
140
140
|
* if (!ablo) return <Loading />;
|
|
141
|
-
* const doc = await ablo.records.get({ id }); // async server read
|
|
141
|
+
* const doc = await ablo.records.get({ id }); // observational async server read
|
|
142
142
|
*
|
|
143
143
|
* // Reactive selector (a synchronous local snapshot). The selector's reads
|
|
144
144
|
* // are typed as snapshot rows — data fields + computeds, no relation
|
package/src/surface.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* written by hand, since prose cannot be type-checked.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { ModelOperations, LocalReadOptions } from './local/client/
|
|
17
|
+
import type { ModelOperations, LocalReadOptions } from './local/client/createModelOperations.js';
|
|
18
18
|
import type { AbloOptions } from './Ablo.js';
|
|
19
19
|
|
|
20
20
|
// ── compile-time exact-equality (no runtime, no casts) ─────────────────────
|
|
@@ -33,7 +33,7 @@ type Expect<T extends true> = T;
|
|
|
33
33
|
*/
|
|
34
34
|
export const PUBLIC_MODEL_VERBS = [
|
|
35
35
|
'get',
|
|
36
|
-
'
|
|
36
|
+
'read',
|
|
37
37
|
'list',
|
|
38
38
|
'listAll',
|
|
39
39
|
'local',
|
|
@@ -41,7 +41,6 @@ export const PUBLIC_MODEL_VERBS = [
|
|
|
41
41
|
'update',
|
|
42
42
|
'delete',
|
|
43
43
|
'claim',
|
|
44
|
-
'track',
|
|
45
44
|
'join',
|
|
46
45
|
'onChange',
|
|
47
46
|
] as const;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Captures a local {@link Snapshot} of a chosen set of entities, along with a
|
|
3
|
-
* watermark, so a caller can detect when that state has gone stale. This is
|
|
4
|
-
* what an LLM caller threads into a prompt: `stamp` flows into later writes as
|
|
5
|
-
* `readAt`, so the server rejects a mutation premised on data that has since
|
|
6
|
-
* changed; `signal` is an `AbortSignal` that fires as soon as any captured
|
|
7
|
-
* entity receives a delta, so a mid-generation invalidation can abort the token
|
|
8
|
-
* stream instead of producing output against stale context.
|
|
9
|
-
*
|
|
10
|
-
* It reads the current entity state from the in-memory pool, reads the engine's
|
|
11
|
-
* current `lastSyncId` as the watermark, and subscribes to delta frames on the
|
|
12
|
-
* existing sync connection — no second connection.
|
|
13
|
-
*/
|
|
14
|
-
import type { InstanceCache } from '../InstanceCache.js';
|
|
15
|
-
import type { Schema } from '@abloatai/transaction/schema/schema';
|
|
16
|
-
import type { SyncWebSocket } from './SyncWebSocket.js';
|
|
17
|
-
import type { Snapshot } from '@abloatai/transaction/types/streams';
|
|
18
|
-
export interface CreateSnapshotArgs<TSchema extends Schema = Schema, K extends keyof TSchema['models'] & string = keyof TSchema['models'] & string> {
|
|
19
|
-
pool: InstanceCache;
|
|
20
|
-
/** Live transport for delta subscriptions. May be null if the engine
|
|
21
|
-
* hasn't connected yet — the snapshot still resolves with current
|
|
22
|
-
* pool state, but `signal` won't fire until reconnect. */
|
|
23
|
-
transport: SyncWebSocket | null;
|
|
24
|
-
/** Returns the engine's current `lastSyncId`. Read at snapshot time
|
|
25
|
-
* to stamp the watermark; not re-read after. */
|
|
26
|
-
getLastSyncId: () => number;
|
|
27
|
-
entities: Readonly<Record<K, string | readonly string[]>>;
|
|
28
|
-
}
|
|
29
|
-
export declare function createSnapshot<TSchema extends Schema, K extends keyof TSchema['models'] & string>(args: CreateSnapshotArgs<TSchema, K>): Snapshot<TSchema, K>;
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Captures a local {@link Snapshot} of a chosen set of entities, along with a
|
|
3
|
-
* watermark, so a caller can detect when that state has gone stale. This is
|
|
4
|
-
* what an LLM caller threads into a prompt: `stamp` flows into later writes as
|
|
5
|
-
* `readAt`, so the server rejects a mutation premised on data that has since
|
|
6
|
-
* changed; `signal` is an `AbortSignal` that fires as soon as any captured
|
|
7
|
-
* entity receives a delta, so a mid-generation invalidation can abort the token
|
|
8
|
-
* stream instead of producing output against stale context.
|
|
9
|
-
*
|
|
10
|
-
* It reads the current entity state from the in-memory pool, reads the engine's
|
|
11
|
-
* current `lastSyncId` as the watermark, and subscribes to delta frames on the
|
|
12
|
-
* existing sync connection — no second connection.
|
|
13
|
-
*/
|
|
14
|
-
import { AbloValidationError } from '@abloatai/transaction/errors';
|
|
15
|
-
import { modelAsRow } from '../Model.js';
|
|
16
|
-
/**
|
|
17
|
-
* The snapshot result exposes `stamp`, `signal`, and `onChange` at its top
|
|
18
|
-
* level, alongside one bucket per model. If a schema declares a model with one
|
|
19
|
-
* of these names, the two would collide, so snapshot creation throws instead.
|
|
20
|
-
*/
|
|
21
|
-
const RESERVED_SNAPSHOT_KEYS = new Set([
|
|
22
|
-
'stamp',
|
|
23
|
-
'signal',
|
|
24
|
-
'onChange',
|
|
25
|
-
]);
|
|
26
|
-
export function createSnapshot(args) {
|
|
27
|
-
const { pool, transport, getLastSyncId, entities } = args;
|
|
28
|
-
// ── Validate keys ────────────────────────────────────────────────
|
|
29
|
-
for (const key of Object.keys(entities)) {
|
|
30
|
-
if (RESERVED_SNAPSHOT_KEYS.has(key)) {
|
|
31
|
-
throw new AbloValidationError(`engine.snapshot: model key "${key}" collides with a reserved ` +
|
|
32
|
-
`snapshot field (stamp / signal / onChange). Rename the model ` +
|
|
33
|
-
'in your schema.', { code: 'snapshot_reserved_key' });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
// ── Watermark ────────────────────────────────────────────────────
|
|
37
|
-
const stamp = getLastSyncId();
|
|
38
|
-
// ── Capture data + watched set ───────────────────────────────────
|
|
39
|
-
const watched = new Set(); // `${type}:${id}`
|
|
40
|
-
const data = {};
|
|
41
|
-
for (const [type, idOrIds] of Object.entries(entities)) {
|
|
42
|
-
const ids = Array.isArray(idOrIds)
|
|
43
|
-
? idOrIds
|
|
44
|
-
: [idOrIds];
|
|
45
|
-
const bucket = {};
|
|
46
|
-
for (const id of ids) {
|
|
47
|
-
const m = pool.get(id);
|
|
48
|
-
// Only include if the model actually has the requested type —
|
|
49
|
-
// pool keys models globally by id, so `pool.get(id)` could
|
|
50
|
-
// return a different model that happens to share the id (rare,
|
|
51
|
-
// but type guards keep the surface honest).
|
|
52
|
-
if (m &&
|
|
53
|
-
typeof m.getModelName === 'function' &&
|
|
54
|
-
m.getModelName() === type) {
|
|
55
|
-
bucket[id] = modelAsRow(m);
|
|
56
|
-
}
|
|
57
|
-
watched.add(`${type}:${id}`);
|
|
58
|
-
}
|
|
59
|
-
data[type] = bucket;
|
|
60
|
-
}
|
|
61
|
-
// ── Invalidation wiring ──────────────────────────────────────────
|
|
62
|
-
const listeners = new Set();
|
|
63
|
-
const controller = new AbortController();
|
|
64
|
-
const fireChange = (change) => {
|
|
65
|
-
if (!controller.signal.aborted) {
|
|
66
|
-
controller.abort(new Error('snapshot invalidated — underlying entity received a delta'));
|
|
67
|
-
}
|
|
68
|
-
for (const l of listeners) {
|
|
69
|
-
try {
|
|
70
|
-
l(change);
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
/* listener errors don't break siblings */
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
let unsubDelta = null;
|
|
78
|
-
if (transport) {
|
|
79
|
-
unsubDelta = transport.subscribe('delta', (delta) => {
|
|
80
|
-
const key = `${delta.modelName}:${delta.modelId}`;
|
|
81
|
-
if (!watched.has(key))
|
|
82
|
-
return;
|
|
83
|
-
// Every delta to a captured entity is reported as 'semantic' severity.
|
|
84
|
-
fireChange({
|
|
85
|
-
model: delta.modelName,
|
|
86
|
-
id: delta.modelId,
|
|
87
|
-
severity: 'semantic',
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
// ── Build the flat result ────────────────────────────────────────
|
|
92
|
-
const result = {
|
|
93
|
-
stamp,
|
|
94
|
-
signal: controller.signal,
|
|
95
|
-
onChange: (listener) => {
|
|
96
|
-
listeners.add(listener);
|
|
97
|
-
// The caller unsubscribes its own listener via the returned function.
|
|
98
|
-
// The underlying delta subscription lives for the snapshot's lifetime;
|
|
99
|
-
// there is no explicit dispose because a snapshot is short-lived (one
|
|
100
|
-
// LLM call's worth) and the subscription is cheap.
|
|
101
|
-
return () => {
|
|
102
|
-
listeners.delete(listener);
|
|
103
|
-
// Once the last listener is gone and the abort has fired, drop the
|
|
104
|
-
// delta subscription too — nothing is listening anymore.
|
|
105
|
-
if (listeners.size === 0 && controller.signal.aborted && unsubDelta) {
|
|
106
|
-
unsubDelta();
|
|
107
|
-
unsubDelta = null;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
for (const [modelName, bucket] of Object.entries(data)) {
|
|
113
|
-
result[modelName] = bucket;
|
|
114
|
-
}
|
|
115
|
-
return result;
|
|
116
|
-
}
|