@abloatai/ablo 0.6.0 → 0.7.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/CHANGELOG.md +45 -0
- package/README.md +64 -35
- package/dist/BaseSyncedStore.d.ts +1 -1
- package/dist/BaseSyncedStore.js +1 -1
- package/dist/client/Ablo.d.ts +1 -0
- package/dist/client/Ablo.js +1 -0
- package/dist/client/createModelProxy.d.ts +26 -3
- package/dist/client/createModelProxy.js +4 -1
- package/dist/client/validateAbloOptions.js +2 -2
- package/dist/coordination/index.d.ts +6 -0
- package/dist/coordination/index.js +6 -0
- package/dist/coordination/schema.d.ts +329 -0
- package/dist/coordination/schema.js +209 -0
- package/dist/core/QueryView.d.ts +4 -1
- package/dist/core/QueryView.js +1 -1
- package/dist/core/query-utils.d.ts +7 -10
- package/dist/core/query-utils.js +2 -3
- package/dist/errorCodes.d.ts +264 -0
- package/dist/errorCodes.js +251 -0
- package/dist/errors.d.ts +51 -6
- package/dist/errors.js +56 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/policy/index.d.ts +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/policy/types.d.ts +31 -0
- package/dist/policy/types.js +15 -0
- package/dist/react/AbloProvider.d.ts +12 -0
- package/dist/react/AbloProvider.js +11 -3
- package/dist/schema/ddl.d.ts +62 -0
- package/dist/schema/ddl.js +317 -0
- package/dist/schema/diff.d.ts +6 -0
- package/dist/schema/diff.js +21 -3
- package/dist/schema/field.d.ts +16 -19
- package/dist/schema/field.js +30 -17
- package/dist/schema/index.d.ts +7 -4
- package/dist/schema/index.js +9 -3
- package/dist/schema/model.d.ts +87 -25
- package/dist/schema/model.js +33 -3
- package/dist/schema/relation.d.ts +17 -0
- package/dist/schema/roles.d.ts +148 -0
- package/dist/schema/roles.js +149 -0
- package/dist/schema/schema.d.ts +2 -112
- package/dist/schema/schema.js +50 -62
- package/dist/schema/select.d.ts +25 -0
- package/dist/schema/select.js +55 -0
- package/dist/schema/serialize.d.ts +13 -9
- package/dist/schema/serialize.js +14 -10
- package/dist/schema/sugar.d.ts +20 -3
- package/dist/schema/sugar.js +5 -1
- package/dist/schema/tenancy.d.ts +66 -0
- package/dist/schema/tenancy.js +58 -0
- package/dist/sync/HydrationCoordinator.d.ts +2 -0
- package/dist/sync/HydrationCoordinator.js +23 -17
- package/dist/sync/createIntentStream.d.ts +2 -1
- package/dist/sync/createIntentStream.js +46 -1
- package/dist/sync/participants.js +5 -14
- package/dist/types/streams.d.ts +53 -33
- package/docs/api-keys.md +44 -0
- package/docs/api.md +11 -22
- package/docs/cli.md +212 -0
- package/docs/client-behavior.md +1 -1
- package/docs/coordination.md +61 -12
- package/docs/data-sources.md +2 -2
- package/docs/examples/existing-python-backend.md +3 -3
- package/docs/examples/scoped-agent.md +78 -0
- package/docs/guarantees.md +5 -2
- package/docs/identity.md +139 -68
- package/docs/index.md +6 -0
- package/docs/integration-guide.md +31 -35
- package/docs/interaction-model.md +3 -0
- package/docs/react.md +3 -3
- package/docs/roadmap.md +14 -2
- package/package.json +8 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Coordination wire schema — the ONE canonical source for the three layers
|
|
4
|
+
* that keep humans and agents from clobbering each other on a shared row.
|
|
5
|
+
* See `packages/sync-engine/docs/coordination.md` ("The model — three layers,
|
|
6
|
+
* one decision") for the conceptual model. The layers, outer-to-inner:
|
|
7
|
+
*
|
|
8
|
+
* 1. PRESENCE (observation) — who is working where; NEVER enforces.
|
|
9
|
+
* 2. PESSIMISTIC (claims/leases) — `intent_begin`/`intent_abandon`;
|
|
10
|
+
* mutual exclusion between participants.
|
|
11
|
+
* 3. OPTIMISTIC (stale-context) — `readAt` + `onStale` write-guard;
|
|
12
|
+
* last-writer-wins lost-update detection.
|
|
13
|
+
*
|
|
14
|
+
* Both the SDK (`types/streams.ts`) and the sync-server (`hub/types.ts`,
|
|
15
|
+
* `presence/*`) derive their TypeScript types from THESE schemas via
|
|
16
|
+
* `z.infer`, instead of re-declaring overlapping shapes. That collapses the
|
|
17
|
+
* field drift this surface accreted — e.g. the SDK's intent view dropping
|
|
18
|
+
* `status`/`error`, `onStale` declared 5×, `IntentStatus` declared 2× — into
|
|
19
|
+
* a single definition that the wire ingest can also validate at runtime.
|
|
20
|
+
*/
|
|
21
|
+
/** A line/column span within a text-bearing field (slide body, doc, cell). */
|
|
22
|
+
export declare const targetRangeSchema: z.ZodObject<{
|
|
23
|
+
startLine: z.ZodNumber;
|
|
24
|
+
endLine: z.ZodNumber;
|
|
25
|
+
startColumn: z.ZodOptional<z.ZodNumber>;
|
|
26
|
+
endColumn: z.ZodOptional<z.ZodNumber>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
export type TargetRange = z.infer<typeof targetRangeSchema>;
|
|
29
|
+
export declare const participantKindSchema: z.ZodEnum<{
|
|
30
|
+
user: "user";
|
|
31
|
+
agent: "agent";
|
|
32
|
+
system: "system";
|
|
33
|
+
}>;
|
|
34
|
+
export type ParticipantKind = z.infer<typeof participantKindSchema>;
|
|
35
|
+
/**
|
|
36
|
+
* What a claim / intent / activity points at. The common locator shared by
|
|
37
|
+
* all three layers — an entity, optionally narrowed to a path, range, or
|
|
38
|
+
* field, with opaque app metadata.
|
|
39
|
+
*/
|
|
40
|
+
export declare const targetRefSchema: z.ZodObject<{
|
|
41
|
+
entityType: z.ZodString;
|
|
42
|
+
entityId: z.ZodString;
|
|
43
|
+
path: z.ZodOptional<z.ZodString>;
|
|
44
|
+
range: z.ZodOptional<z.ZodObject<{
|
|
45
|
+
startLine: z.ZodNumber;
|
|
46
|
+
endLine: z.ZodNumber;
|
|
47
|
+
startColumn: z.ZodOptional<z.ZodNumber>;
|
|
48
|
+
endColumn: z.ZodOptional<z.ZodNumber>;
|
|
49
|
+
}, z.core.$strip>>;
|
|
50
|
+
field: z.ZodOptional<z.ZodString>;
|
|
51
|
+
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export type TargetRef = z.infer<typeof targetRefSchema>;
|
|
54
|
+
/**
|
|
55
|
+
* Mode applied when a write's snapshot watermark (`readAt`) is older than the
|
|
56
|
+
* target row's latest delta. `'reject'` is the default whenever `readAt` is
|
|
57
|
+
* present. `'flag'` and `'merge'` are reserved — the wire accepts them, the
|
|
58
|
+
* server does not yet enforce them.
|
|
59
|
+
*/
|
|
60
|
+
export declare const onStaleModeSchema: z.ZodEnum<{
|
|
61
|
+
reject: "reject";
|
|
62
|
+
force: "force";
|
|
63
|
+
flag: "flag";
|
|
64
|
+
merge: "merge";
|
|
65
|
+
}>;
|
|
66
|
+
export type OnStaleMode = z.infer<typeof onStaleModeSchema>;
|
|
67
|
+
/**
|
|
68
|
+
* The optimistic guard carried on a commit operation. `readAt` is the
|
|
69
|
+
* snapshot watermark from `context.capture` (null/absent ⇒ unguarded write).
|
|
70
|
+
* `bypass` is the explicit, recorded override of a *foreign* pessimistic
|
|
71
|
+
* claim — see the claim layer below.
|
|
72
|
+
*/
|
|
73
|
+
export declare const writeGuardSchema: z.ZodObject<{
|
|
74
|
+
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
75
|
+
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
76
|
+
reject: "reject";
|
|
77
|
+
force: "force";
|
|
78
|
+
flag: "flag";
|
|
79
|
+
merge: "merge";
|
|
80
|
+
}>>>;
|
|
81
|
+
bypass: z.ZodOptional<z.ZodBoolean>;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
export type WriteGuard = z.infer<typeof writeGuardSchema>;
|
|
84
|
+
/**
|
|
85
|
+
* Lifecycle of an intent — the Stripe `PaymentIntent.status` shape. Absent on
|
|
86
|
+
* the wire ⇒ `'active'` (additive back-compat). The server stamps `'active'`
|
|
87
|
+
* on `intent_begin` and emits a single terminal frame (`committed` /
|
|
88
|
+
* `canceled` / `expired`) as the claim ends, so contenders learn *how* it
|
|
89
|
+
* resolved, not merely that it vanished.
|
|
90
|
+
*/
|
|
91
|
+
export declare const intentStatusSchema: z.ZodEnum<{
|
|
92
|
+
active: "active";
|
|
93
|
+
committed: "committed";
|
|
94
|
+
expired: "expired";
|
|
95
|
+
canceled: "canceled";
|
|
96
|
+
}>;
|
|
97
|
+
export type IntentStatus = z.infer<typeof intentStatusSchema>;
|
|
98
|
+
/** Why a claim ended in a non-success terminal state. */
|
|
99
|
+
export declare const intentErrorSchema: z.ZodObject<{
|
|
100
|
+
code: z.ZodString;
|
|
101
|
+
message: z.ZodOptional<z.ZodString>;
|
|
102
|
+
heldBy: z.ZodOptional<z.ZodString>;
|
|
103
|
+
heldByIntentId: z.ZodOptional<z.ZodString>;
|
|
104
|
+
heldByExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
105
|
+
}, z.core.$strip>;
|
|
106
|
+
export type IntentError = z.infer<typeof intentErrorSchema>;
|
|
107
|
+
/**
|
|
108
|
+
* A declared pending-mutation intent — the unit broadcast in presence
|
|
109
|
+
* `activeIntents`. Clients supply the descriptive `targetRef` fields, an
|
|
110
|
+
* `action`, and a chosen `intentId`; the SERVER stamps `declaredAt` /
|
|
111
|
+
* `expiresAt` and may set `status` / `error`.
|
|
112
|
+
*
|
|
113
|
+
* `status` and `error` are OPTIONAL: this single shape serves both the
|
|
114
|
+
* server (which sets them) and the SDK view (which historically omitted
|
|
115
|
+
* them). The superset is structurally assignable wherever the leaner view
|
|
116
|
+
* was used, so the two prior copies collapse into this one without breaking
|
|
117
|
+
* SDK consumers.
|
|
118
|
+
*/
|
|
119
|
+
export declare const intentClaimSchema: z.ZodObject<{
|
|
120
|
+
entityType: z.ZodString;
|
|
121
|
+
entityId: z.ZodString;
|
|
122
|
+
path: z.ZodOptional<z.ZodString>;
|
|
123
|
+
range: z.ZodOptional<z.ZodObject<{
|
|
124
|
+
startLine: z.ZodNumber;
|
|
125
|
+
endLine: z.ZodNumber;
|
|
126
|
+
startColumn: z.ZodOptional<z.ZodNumber>;
|
|
127
|
+
endColumn: z.ZodOptional<z.ZodNumber>;
|
|
128
|
+
}, z.core.$strip>>;
|
|
129
|
+
field: z.ZodOptional<z.ZodString>;
|
|
130
|
+
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
131
|
+
intentId: z.ZodString;
|
|
132
|
+
action: z.ZodString;
|
|
133
|
+
declaredAt: z.ZodNumber;
|
|
134
|
+
expiresAt: z.ZodNumber;
|
|
135
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
136
|
+
active: "active";
|
|
137
|
+
committed: "committed";
|
|
138
|
+
expired: "expired";
|
|
139
|
+
canceled: "canceled";
|
|
140
|
+
}>>;
|
|
141
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
142
|
+
code: z.ZodString;
|
|
143
|
+
message: z.ZodOptional<z.ZodString>;
|
|
144
|
+
heldBy: z.ZodOptional<z.ZodString>;
|
|
145
|
+
heldByIntentId: z.ZodOptional<z.ZodString>;
|
|
146
|
+
heldByExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
147
|
+
}, z.core.$strip>>;
|
|
148
|
+
}, z.core.$strip>;
|
|
149
|
+
export type IntentClaim = z.infer<typeof intentClaimSchema>;
|
|
150
|
+
/**
|
|
151
|
+
* `intent_begin` payload (client → server). The descriptive target + action,
|
|
152
|
+
* plus an optional duration hint and the opt-in fair-queue flag. The server
|
|
153
|
+
* stamps the lifecycle/timestamp fields, so they are NOT part of the inbound
|
|
154
|
+
* shape — this is exactly what the WS ingest validates.
|
|
155
|
+
*/
|
|
156
|
+
export declare const intentBeginPayloadSchema: z.ZodObject<{
|
|
157
|
+
entityType: z.ZodString;
|
|
158
|
+
entityId: z.ZodString;
|
|
159
|
+
path: z.ZodOptional<z.ZodString>;
|
|
160
|
+
range: z.ZodOptional<z.ZodObject<{
|
|
161
|
+
startLine: z.ZodNumber;
|
|
162
|
+
endLine: z.ZodNumber;
|
|
163
|
+
startColumn: z.ZodOptional<z.ZodNumber>;
|
|
164
|
+
endColumn: z.ZodOptional<z.ZodNumber>;
|
|
165
|
+
}, z.core.$strip>>;
|
|
166
|
+
field: z.ZodOptional<z.ZodString>;
|
|
167
|
+
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
168
|
+
intentId: z.ZodString;
|
|
169
|
+
action: z.ZodString;
|
|
170
|
+
estimatedMs: z.ZodOptional<z.ZodNumber>;
|
|
171
|
+
queue: z.ZodOptional<z.ZodBoolean>;
|
|
172
|
+
}, z.core.$strip>;
|
|
173
|
+
export type IntentBeginPayload = z.infer<typeof intentBeginPayloadSchema>;
|
|
174
|
+
/**
|
|
175
|
+
* `intent_abandon` payload (client → server). `entityType`/`entityId` are
|
|
176
|
+
* carried so the server can DEQUEUE a still-*waiting* (not held) intent from
|
|
177
|
+
* the FIFO line — the held-intent path needs only `intentId`. (The previous
|
|
178
|
+
* wire type omitted these two even though the handler reads them; the schema
|
|
179
|
+
* documents what the code actually uses.)
|
|
180
|
+
*/
|
|
181
|
+
export declare const intentAbandonPayloadSchema: z.ZodObject<{
|
|
182
|
+
intentId: z.ZodString;
|
|
183
|
+
entityType: z.ZodOptional<z.ZodString>;
|
|
184
|
+
entityId: z.ZodOptional<z.ZodString>;
|
|
185
|
+
}, z.core.$strip>;
|
|
186
|
+
export type IntentAbandonPayload = z.infer<typeof intentAbandonPayloadSchema>;
|
|
187
|
+
/**
|
|
188
|
+
* `intent_reorder` payload (client → server). A privileged participant (e.g. a
|
|
189
|
+
* supervisor over its sub-agents) re-ranks the FIFO wait queue for an entity:
|
|
190
|
+
* `order` lists waiters by `heldBy`+`intentId` in the desired priority. Waiters
|
|
191
|
+
* not listed keep their relative order behind the listed ones. The server gates
|
|
192
|
+
* who may call this; an unauthorized sender is dropped. Unlike `intent_abandon`
|
|
193
|
+
* (acts on the caller's own entry), reorder acts on OTHER participants' queue
|
|
194
|
+
* positions — hence the authorization gate.
|
|
195
|
+
*/
|
|
196
|
+
export declare const intentReorderPayloadSchema: z.ZodObject<{
|
|
197
|
+
entityType: z.ZodString;
|
|
198
|
+
entityId: z.ZodString;
|
|
199
|
+
order: z.ZodArray<z.ZodObject<{
|
|
200
|
+
heldBy: z.ZodString;
|
|
201
|
+
intentId: z.ZodString;
|
|
202
|
+
}, z.core.$strip>>;
|
|
203
|
+
}, z.core.$strip>;
|
|
204
|
+
export type IntentReorderPayload = z.infer<typeof intentReorderPayloadSchema>;
|
|
205
|
+
export declare const commitOperationTypeSchema: z.ZodEnum<{
|
|
206
|
+
CREATE: "CREATE";
|
|
207
|
+
UPDATE: "UPDATE";
|
|
208
|
+
DELETE: "DELETE";
|
|
209
|
+
ARCHIVE: "ARCHIVE";
|
|
210
|
+
UNARCHIVE: "UNARCHIVE";
|
|
211
|
+
}>;
|
|
212
|
+
export type CommitOperationType = z.infer<typeof commitOperationTypeSchema>;
|
|
213
|
+
/**
|
|
214
|
+
* A single mutation in a commit batch, as it arrives on the wire. Extends the
|
|
215
|
+
* optimistic `writeGuard` (`readAt`/`onStale`/`bypass`) — the structural link
|
|
216
|
+
* that makes "every write is stale-guarded" legible in the type, not just in
|
|
217
|
+
* prose.
|
|
218
|
+
*/
|
|
219
|
+
export declare const commitOperationSchema: z.ZodObject<{
|
|
220
|
+
readAt: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
221
|
+
onStale: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
222
|
+
reject: "reject";
|
|
223
|
+
force: "force";
|
|
224
|
+
flag: "flag";
|
|
225
|
+
merge: "merge";
|
|
226
|
+
}>>>;
|
|
227
|
+
bypass: z.ZodOptional<z.ZodBoolean>;
|
|
228
|
+
type: z.ZodEnum<{
|
|
229
|
+
CREATE: "CREATE";
|
|
230
|
+
UPDATE: "UPDATE";
|
|
231
|
+
DELETE: "DELETE";
|
|
232
|
+
ARCHIVE: "ARCHIVE";
|
|
233
|
+
UNARCHIVE: "UNARCHIVE";
|
|
234
|
+
}>;
|
|
235
|
+
model: z.ZodString;
|
|
236
|
+
id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
237
|
+
input: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
238
|
+
transactionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
239
|
+
}, z.core.$strip>;
|
|
240
|
+
export type CommitOperation = z.infer<typeof commitOperationSchema>;
|
|
241
|
+
export declare const presenceKindSchema: z.ZodEnum<{
|
|
242
|
+
enter: "enter";
|
|
243
|
+
update: "update";
|
|
244
|
+
leave: "leave";
|
|
245
|
+
}>;
|
|
246
|
+
export type PresenceKind = z.infer<typeof presenceKindSchema>;
|
|
247
|
+
/** What a participant is actively working on (agents fill this in). */
|
|
248
|
+
export declare const presenceActivitySchema: z.ZodObject<{
|
|
249
|
+
entityType: z.ZodString;
|
|
250
|
+
entityId: z.ZodString;
|
|
251
|
+
path: z.ZodOptional<z.ZodString>;
|
|
252
|
+
range: z.ZodOptional<z.ZodObject<{
|
|
253
|
+
startLine: z.ZodNumber;
|
|
254
|
+
endLine: z.ZodNumber;
|
|
255
|
+
startColumn: z.ZodOptional<z.ZodNumber>;
|
|
256
|
+
endColumn: z.ZodOptional<z.ZodNumber>;
|
|
257
|
+
}, z.core.$strip>>;
|
|
258
|
+
field: z.ZodOptional<z.ZodString>;
|
|
259
|
+
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
260
|
+
action: z.ZodString;
|
|
261
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
262
|
+
}, z.core.$strip>;
|
|
263
|
+
export type PresenceActivity = z.infer<typeof presenceActivitySchema>;
|
|
264
|
+
/**
|
|
265
|
+
* Full `presence_update` frame as the server broadcasts it. The activity +
|
|
266
|
+
* `activeIntents` are the observation surface for the other two layers —
|
|
267
|
+
* rendered, never acted on as enforcement.
|
|
268
|
+
*/
|
|
269
|
+
export declare const presenceUpdateFrameSchema: z.ZodObject<{
|
|
270
|
+
kind: z.ZodEnum<{
|
|
271
|
+
enter: "enter";
|
|
272
|
+
update: "update";
|
|
273
|
+
leave: "leave";
|
|
274
|
+
}>;
|
|
275
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
276
|
+
syncGroups: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
277
|
+
timestamp: z.ZodOptional<z.ZodNumber>;
|
|
278
|
+
status: z.ZodString;
|
|
279
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
280
|
+
customStatus: z.ZodOptional<z.ZodString>;
|
|
281
|
+
activity: z.ZodOptional<z.ZodObject<{
|
|
282
|
+
entityType: z.ZodString;
|
|
283
|
+
entityId: z.ZodString;
|
|
284
|
+
path: z.ZodOptional<z.ZodString>;
|
|
285
|
+
range: z.ZodOptional<z.ZodObject<{
|
|
286
|
+
startLine: z.ZodNumber;
|
|
287
|
+
endLine: z.ZodNumber;
|
|
288
|
+
startColumn: z.ZodOptional<z.ZodNumber>;
|
|
289
|
+
endColumn: z.ZodOptional<z.ZodNumber>;
|
|
290
|
+
}, z.core.$strip>>;
|
|
291
|
+
field: z.ZodOptional<z.ZodString>;
|
|
292
|
+
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
293
|
+
action: z.ZodString;
|
|
294
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
295
|
+
}, z.core.$strip>>;
|
|
296
|
+
isAgent: z.ZodOptional<z.ZodBoolean>;
|
|
297
|
+
activeIntents: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
298
|
+
entityType: z.ZodString;
|
|
299
|
+
entityId: z.ZodString;
|
|
300
|
+
path: z.ZodOptional<z.ZodString>;
|
|
301
|
+
range: z.ZodOptional<z.ZodObject<{
|
|
302
|
+
startLine: z.ZodNumber;
|
|
303
|
+
endLine: z.ZodNumber;
|
|
304
|
+
startColumn: z.ZodOptional<z.ZodNumber>;
|
|
305
|
+
endColumn: z.ZodOptional<z.ZodNumber>;
|
|
306
|
+
}, z.core.$strip>>;
|
|
307
|
+
field: z.ZodOptional<z.ZodString>;
|
|
308
|
+
meta: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
309
|
+
intentId: z.ZodString;
|
|
310
|
+
action: z.ZodString;
|
|
311
|
+
declaredAt: z.ZodNumber;
|
|
312
|
+
expiresAt: z.ZodNumber;
|
|
313
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
314
|
+
active: "active";
|
|
315
|
+
committed: "committed";
|
|
316
|
+
expired: "expired";
|
|
317
|
+
canceled: "canceled";
|
|
318
|
+
}>>;
|
|
319
|
+
error: z.ZodOptional<z.ZodObject<{
|
|
320
|
+
code: z.ZodString;
|
|
321
|
+
message: z.ZodOptional<z.ZodString>;
|
|
322
|
+
heldBy: z.ZodOptional<z.ZodString>;
|
|
323
|
+
heldByIntentId: z.ZodOptional<z.ZodString>;
|
|
324
|
+
heldByExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
325
|
+
}, z.core.$strip>>;
|
|
326
|
+
}, z.core.$strip>>>;
|
|
327
|
+
delegatedFrom: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
328
|
+
}, z.core.$strip>;
|
|
329
|
+
export type PresenceUpdateFrame = z.infer<typeof presenceUpdateFrameSchema>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Coordination wire schema — the ONE canonical source for the three layers
|
|
4
|
+
* that keep humans and agents from clobbering each other on a shared row.
|
|
5
|
+
* See `packages/sync-engine/docs/coordination.md` ("The model — three layers,
|
|
6
|
+
* one decision") for the conceptual model. The layers, outer-to-inner:
|
|
7
|
+
*
|
|
8
|
+
* 1. PRESENCE (observation) — who is working where; NEVER enforces.
|
|
9
|
+
* 2. PESSIMISTIC (claims/leases) — `intent_begin`/`intent_abandon`;
|
|
10
|
+
* mutual exclusion between participants.
|
|
11
|
+
* 3. OPTIMISTIC (stale-context) — `readAt` + `onStale` write-guard;
|
|
12
|
+
* last-writer-wins lost-update detection.
|
|
13
|
+
*
|
|
14
|
+
* Both the SDK (`types/streams.ts`) and the sync-server (`hub/types.ts`,
|
|
15
|
+
* `presence/*`) derive their TypeScript types from THESE schemas via
|
|
16
|
+
* `z.infer`, instead of re-declaring overlapping shapes. That collapses the
|
|
17
|
+
* field drift this surface accreted — e.g. the SDK's intent view dropping
|
|
18
|
+
* `status`/`error`, `onStale` declared 5×, `IntentStatus` declared 2× — into
|
|
19
|
+
* a single definition that the wire ingest can also validate at runtime.
|
|
20
|
+
*/
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// Shared primitives
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
24
|
+
/** A line/column span within a text-bearing field (slide body, doc, cell). */
|
|
25
|
+
export const targetRangeSchema = z.object({
|
|
26
|
+
startLine: z.number(),
|
|
27
|
+
endLine: z.number(),
|
|
28
|
+
startColumn: z.number().optional(),
|
|
29
|
+
endColumn: z.number().optional(),
|
|
30
|
+
});
|
|
31
|
+
export const participantKindSchema = z.enum(['user', 'agent', 'system']);
|
|
32
|
+
/**
|
|
33
|
+
* What a claim / intent / activity points at. The common locator shared by
|
|
34
|
+
* all three layers — an entity, optionally narrowed to a path, range, or
|
|
35
|
+
* field, with opaque app metadata.
|
|
36
|
+
*/
|
|
37
|
+
export const targetRefSchema = z.object({
|
|
38
|
+
entityType: z.string(),
|
|
39
|
+
entityId: z.string(),
|
|
40
|
+
path: z.string().optional(),
|
|
41
|
+
range: targetRangeSchema.optional(),
|
|
42
|
+
field: z.string().optional(),
|
|
43
|
+
meta: z.record(z.string(), z.unknown()).optional(),
|
|
44
|
+
});
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// Layer 3 — OPTIMISTIC stale-context (the write-guard)
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
48
|
+
/**
|
|
49
|
+
* Mode applied when a write's snapshot watermark (`readAt`) is older than the
|
|
50
|
+
* target row's latest delta. `'reject'` is the default whenever `readAt` is
|
|
51
|
+
* present. `'flag'` and `'merge'` are reserved — the wire accepts them, the
|
|
52
|
+
* server does not yet enforce them.
|
|
53
|
+
*/
|
|
54
|
+
export const onStaleModeSchema = z.enum(['reject', 'force', 'flag', 'merge']);
|
|
55
|
+
/**
|
|
56
|
+
* The optimistic guard carried on a commit operation. `readAt` is the
|
|
57
|
+
* snapshot watermark from `context.capture` (null/absent ⇒ unguarded write).
|
|
58
|
+
* `bypass` is the explicit, recorded override of a *foreign* pessimistic
|
|
59
|
+
* claim — see the claim layer below.
|
|
60
|
+
*/
|
|
61
|
+
export const writeGuardSchema = z.object({
|
|
62
|
+
readAt: z.number().nullish(),
|
|
63
|
+
onStale: onStaleModeSchema.nullish(),
|
|
64
|
+
bypass: z.boolean().optional(),
|
|
65
|
+
});
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
67
|
+
// Layer 2 — PESSIMISTIC claim / intent-lease
|
|
68
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Lifecycle of an intent — the Stripe `PaymentIntent.status` shape. Absent on
|
|
71
|
+
* the wire ⇒ `'active'` (additive back-compat). The server stamps `'active'`
|
|
72
|
+
* on `intent_begin` and emits a single terminal frame (`committed` /
|
|
73
|
+
* `canceled` / `expired`) as the claim ends, so contenders learn *how* it
|
|
74
|
+
* resolved, not merely that it vanished.
|
|
75
|
+
*/
|
|
76
|
+
export const intentStatusSchema = z.enum([
|
|
77
|
+
'active',
|
|
78
|
+
'committed',
|
|
79
|
+
'expired',
|
|
80
|
+
'canceled',
|
|
81
|
+
]);
|
|
82
|
+
/** Why a claim ended in a non-success terminal state. */
|
|
83
|
+
export const intentErrorSchema = z.object({
|
|
84
|
+
code: z.string(),
|
|
85
|
+
message: z.string().optional(),
|
|
86
|
+
/** Participant already holding the target (conflict rejections). */
|
|
87
|
+
heldBy: z.string().optional(),
|
|
88
|
+
heldByIntentId: z.string().optional(),
|
|
89
|
+
heldByExpiresAt: z.number().optional(),
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* A declared pending-mutation intent — the unit broadcast in presence
|
|
93
|
+
* `activeIntents`. Clients supply the descriptive `targetRef` fields, an
|
|
94
|
+
* `action`, and a chosen `intentId`; the SERVER stamps `declaredAt` /
|
|
95
|
+
* `expiresAt` and may set `status` / `error`.
|
|
96
|
+
*
|
|
97
|
+
* `status` and `error` are OPTIONAL: this single shape serves both the
|
|
98
|
+
* server (which sets them) and the SDK view (which historically omitted
|
|
99
|
+
* them). The superset is structurally assignable wherever the leaner view
|
|
100
|
+
* was used, so the two prior copies collapse into this one without breaking
|
|
101
|
+
* SDK consumers.
|
|
102
|
+
*/
|
|
103
|
+
export const intentClaimSchema = targetRefSchema.extend({
|
|
104
|
+
intentId: z.string(),
|
|
105
|
+
/** Verb the agent expects: 'update' | 'create' | 'editing' | 'reviewing' … */
|
|
106
|
+
action: z.string(),
|
|
107
|
+
/** Server-stamped declaration time (epoch ms). */
|
|
108
|
+
declaredAt: z.number(),
|
|
109
|
+
/** Server-computed TTL deadline (epoch ms). Readers treat as advisory. */
|
|
110
|
+
expiresAt: z.number(),
|
|
111
|
+
status: intentStatusSchema.optional(),
|
|
112
|
+
error: intentErrorSchema.optional(),
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* `intent_begin` payload (client → server). The descriptive target + action,
|
|
116
|
+
* plus an optional duration hint and the opt-in fair-queue flag. The server
|
|
117
|
+
* stamps the lifecycle/timestamp fields, so they are NOT part of the inbound
|
|
118
|
+
* shape — this is exactly what the WS ingest validates.
|
|
119
|
+
*/
|
|
120
|
+
export const intentBeginPayloadSchema = targetRefSchema.extend({
|
|
121
|
+
intentId: z.string(),
|
|
122
|
+
action: z.string(),
|
|
123
|
+
/** Hint for `expiresAt`; the server caps it. */
|
|
124
|
+
estimatedMs: z.number().optional(),
|
|
125
|
+
/**
|
|
126
|
+
* Opt into the fair wait queue: when the target is already held, the server
|
|
127
|
+
* enqueues this claim (FIFO) and replies `intent_queued` → later
|
|
128
|
+
* `intent_granted`, instead of `intent_rejected`. Clients that set this MUST
|
|
129
|
+
* handle the grant.
|
|
130
|
+
*/
|
|
131
|
+
queue: z.boolean().optional(),
|
|
132
|
+
});
|
|
133
|
+
/**
|
|
134
|
+
* `intent_abandon` payload (client → server). `entityType`/`entityId` are
|
|
135
|
+
* carried so the server can DEQUEUE a still-*waiting* (not held) intent from
|
|
136
|
+
* the FIFO line — the held-intent path needs only `intentId`. (The previous
|
|
137
|
+
* wire type omitted these two even though the handler reads them; the schema
|
|
138
|
+
* documents what the code actually uses.)
|
|
139
|
+
*/
|
|
140
|
+
export const intentAbandonPayloadSchema = z.object({
|
|
141
|
+
intentId: z.string(),
|
|
142
|
+
entityType: z.string().optional(),
|
|
143
|
+
entityId: z.string().optional(),
|
|
144
|
+
});
|
|
145
|
+
/**
|
|
146
|
+
* `intent_reorder` payload (client → server). A privileged participant (e.g. a
|
|
147
|
+
* supervisor over its sub-agents) re-ranks the FIFO wait queue for an entity:
|
|
148
|
+
* `order` lists waiters by `heldBy`+`intentId` in the desired priority. Waiters
|
|
149
|
+
* not listed keep their relative order behind the listed ones. The server gates
|
|
150
|
+
* who may call this; an unauthorized sender is dropped. Unlike `intent_abandon`
|
|
151
|
+
* (acts on the caller's own entry), reorder acts on OTHER participants' queue
|
|
152
|
+
* positions — hence the authorization gate.
|
|
153
|
+
*/
|
|
154
|
+
export const intentReorderPayloadSchema = z.object({
|
|
155
|
+
entityType: z.string(),
|
|
156
|
+
entityId: z.string(),
|
|
157
|
+
order: z.array(z.object({ heldBy: z.string(), intentId: z.string() })),
|
|
158
|
+
});
|
|
159
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
160
|
+
// Commit operation — carries the optimistic write-guard (Layer 3)
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
162
|
+
export const commitOperationTypeSchema = z.enum([
|
|
163
|
+
'CREATE',
|
|
164
|
+
'UPDATE',
|
|
165
|
+
'DELETE',
|
|
166
|
+
'ARCHIVE',
|
|
167
|
+
'UNARCHIVE',
|
|
168
|
+
]);
|
|
169
|
+
/**
|
|
170
|
+
* A single mutation in a commit batch, as it arrives on the wire. Extends the
|
|
171
|
+
* optimistic `writeGuard` (`readAt`/`onStale`/`bypass`) — the structural link
|
|
172
|
+
* that makes "every write is stale-guarded" legible in the type, not just in
|
|
173
|
+
* prose.
|
|
174
|
+
*/
|
|
175
|
+
export const commitOperationSchema = writeGuardSchema.extend({
|
|
176
|
+
type: commitOperationTypeSchema,
|
|
177
|
+
model: z.string(),
|
|
178
|
+
id: z.string().nullish(),
|
|
179
|
+
input: z.record(z.string(), z.unknown()).nullish(),
|
|
180
|
+
/** Per-op client tx id, echoed on the broadcast delta. */
|
|
181
|
+
transactionId: z.string().nullish(),
|
|
182
|
+
});
|
|
183
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
184
|
+
// Layer 1 — PRESENCE (observation only; never enforces)
|
|
185
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
186
|
+
export const presenceKindSchema = z.enum(['enter', 'update', 'leave']);
|
|
187
|
+
/** What a participant is actively working on (agents fill this in). */
|
|
188
|
+
export const presenceActivitySchema = targetRefSchema.extend({
|
|
189
|
+
action: z.string(),
|
|
190
|
+
detail: z.string().optional(),
|
|
191
|
+
});
|
|
192
|
+
/**
|
|
193
|
+
* Full `presence_update` frame as the server broadcasts it. The activity +
|
|
194
|
+
* `activeIntents` are the observation surface for the other two layers —
|
|
195
|
+
* rendered, never acted on as enforcement.
|
|
196
|
+
*/
|
|
197
|
+
export const presenceUpdateFrameSchema = z.object({
|
|
198
|
+
kind: presenceKindSchema,
|
|
199
|
+
userId: z.string().optional(),
|
|
200
|
+
syncGroups: z.array(z.string()).optional(),
|
|
201
|
+
timestamp: z.number().optional(),
|
|
202
|
+
status: z.string(),
|
|
203
|
+
timezone: z.string().optional(),
|
|
204
|
+
customStatus: z.string().optional(),
|
|
205
|
+
activity: presenceActivitySchema.optional(),
|
|
206
|
+
isAgent: z.boolean().optional(),
|
|
207
|
+
activeIntents: z.array(intentClaimSchema).optional(),
|
|
208
|
+
delegatedFrom: z.string().nullish(),
|
|
209
|
+
});
|
package/dist/core/QueryView.d.ts
CHANGED
|
@@ -20,7 +20,10 @@ export interface QueryViewOptions<T> {
|
|
|
20
20
|
order?: 'asc' | 'desc';
|
|
21
21
|
limit?: number;
|
|
22
22
|
offset?: number;
|
|
23
|
-
|
|
23
|
+
/** Lifecycle filter — `live` (default), `archived`, or `all`. Named `state`
|
|
24
|
+
* (GitHub's open/closed/all precedent) so it doesn't collide with the
|
|
25
|
+
* sync-group `scope`. */
|
|
26
|
+
state?: ModelScope;
|
|
24
27
|
}
|
|
25
28
|
export declare class QueryView<T extends Record<string, unknown>> implements IncrementalView {
|
|
26
29
|
/** The full (unlimited) internal result set, kept sorted. */
|
package/dist/core/QueryView.js
CHANGED
|
@@ -50,7 +50,7 @@ export class QueryView {
|
|
|
50
50
|
this.sortDir = options.order === 'desc' ? -1 : 1;
|
|
51
51
|
this.limitN = options.limit;
|
|
52
52
|
this.offsetN = options.offset ?? 0;
|
|
53
|
-
this.scope = options.
|
|
53
|
+
this.scope = options.state ?? ModelScope.live;
|
|
54
54
|
// Check for FK-index optimization: single-field where with an indexed FK
|
|
55
55
|
this.fkField = null;
|
|
56
56
|
this.fkValue = null;
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* query-utils — Pure query helpers
|
|
3
|
-
*
|
|
4
|
-
* binary insertion logic.
|
|
2
|
+
* query-utils — Pure query helpers for the MobX `QueryView`. One source of
|
|
3
|
+
* truth for sort, filter, and binary insertion logic.
|
|
5
4
|
*
|
|
6
5
|
* No MobX, no ObjectPool, no Model — just arrays and values.
|
|
7
6
|
*/
|
|
8
7
|
/**
|
|
9
|
-
* The incremental-update contract
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* registries would have to widen via `unknown as View<Record<...>>`
|
|
15
|
-
* at every register/unregister/notify call.
|
|
8
|
+
* The incremental-update contract a view satisfies. `ViewRegistry` stores views
|
|
9
|
+
* as this base type so it can dispatch to many views with different `T`
|
|
10
|
+
* parameters from one Set — `View<T>` is invariant in T, so without this shared
|
|
11
|
+
* base the registry would have to widen via `unknown as View<Record<...>>` at
|
|
12
|
+
* every register/unregister/notify call.
|
|
16
13
|
*/
|
|
17
14
|
export interface IncrementalView {
|
|
18
15
|
handleAdded(entity: Record<string, unknown>): void;
|
package/dist/core/query-utils.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* query-utils — Pure query helpers
|
|
3
|
-
*
|
|
4
|
-
* binary insertion logic.
|
|
2
|
+
* query-utils — Pure query helpers for the MobX `QueryView`. One source of
|
|
3
|
+
* truth for sort, filter, and binary insertion logic.
|
|
5
4
|
*
|
|
6
5
|
* No MobX, no ObjectPool, no Model — just arrays and values.
|
|
7
6
|
*/
|