@abloatai/ablo 0.35.0 → 0.36.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 +713 -629
- package/README.md +56 -519
- package/bin/ablo.cjs +39 -0
- package/dist/BaseSyncedStore.d.ts +24 -4
- package/dist/BaseSyncedStore.js +53 -37
- package/dist/Database.d.ts +8 -20
- package/dist/Database.js +61 -59
- package/dist/InstanceCache.d.ts +6 -2
- package/dist/InstanceCache.js +18 -16
- package/dist/Model.d.ts +17 -7
- package/dist/Model.js +17 -7
- package/dist/ModelRegistry.d.ts +4 -0
- package/dist/ModelRegistry.js +17 -15
- package/dist/NetworkMonitor.d.ts +3 -1
- package/dist/NetworkMonitor.js +7 -5
- package/dist/SyncClient.d.ts +5 -15
- package/dist/SyncClient.js +60 -57
- package/dist/client/Ablo.js +30 -19
- package/dist/client/createInternalComponents.d.ts +4 -0
- package/dist/client/createInternalComponents.js +8 -2
- package/dist/client/createModelProxy.d.ts +21 -1
- package/dist/client/createModelProxy.js +123 -57
- package/dist/client/humans.d.ts +30 -9
- package/dist/client/humans.js +45 -19
- package/dist/client/reactiveEngine.d.ts +16 -11
- package/dist/client/reactiveEngine.js +113 -335
- package/dist/client/storeCluster.d.ts +47 -0
- package/dist/client/storeCluster.js +118 -0
- package/dist/client/storeLifecycle.d.ts +61 -0
- package/dist/client/storeLifecycle.js +231 -0
- package/dist/context.d.ts +13 -0
- package/dist/context.js +23 -0
- package/dist/core/index.d.ts +1 -1
- package/dist/docs/catalog.js +6 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/query/client.d.ts +3 -0
- package/dist/query/client.js +6 -5
- package/dist/react/AbloProvider.d.ts +13 -1
- package/dist/react/AbloProvider.js +5 -2
- package/dist/react/context.d.ts +2 -2
- package/dist/react/createAbloReact.d.ts +56 -0
- package/dist/react/createAbloReact.js +51 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/useAblo.d.ts +9 -2
- package/dist/react/useAblo.js +25 -7
- package/dist/schema/coordination.js +5 -1
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/select.js +3 -0
- package/dist/schema/serialize.js +3 -0
- package/dist/source/adapter.d.ts +7 -5
- package/dist/source/adapter.js +7 -5
- package/dist/source/index.d.ts +1 -1
- package/dist/source/index.js +1 -1
- package/dist/{core/storeContract.d.ts → storeContract.d.ts} +5 -5
- package/dist/{core → stores}/DatabaseManager.d.ts +3 -1
- package/dist/{core → stores}/DatabaseManager.js +13 -12
- package/dist/{core → stores}/StoreManager.d.ts +5 -3
- package/dist/{core → stores}/StoreManager.js +24 -22
- package/dist/stores/SyncActionStore.d.ts +3 -1
- package/dist/stores/SyncActionStore.js +9 -7
- package/dist/sync/BootstrapFetcher.d.ts +4 -0
- package/dist/sync/BootstrapFetcher.js +28 -26
- package/dist/sync/OnDemandLoader.d.ts +3 -0
- package/dist/sync/OnDemandLoader.js +1 -0
- package/dist/sync/bootstrapApply.d.ts +3 -0
- package/dist/sync/bootstrapApply.js +2 -2
- package/dist/sync/deltaPipeline.d.ts +13 -12
- package/dist/sync/deltaPipeline.js +21 -4
- package/dist/sync/groupChange.d.ts +3 -0
- package/dist/sync/groupChange.js +16 -14
- package/dist/sync/participants.d.ts +19 -2
- package/dist/sync/participants.js +3 -1
- package/dist/sync/schemas.d.ts +2 -1
- package/dist/sync/schemas.js +3 -3
- package/dist/syncLog/contract.d.ts +20 -0
- package/dist/syncLog/contract.js +19 -0
- package/dist/syncLog/index.d.ts +1 -0
- package/dist/syncLog/index.js +1 -0
- package/dist/transaction/auth/capability.d.ts +35 -0
- package/dist/transaction/auth/capability.js +25 -0
- package/dist/transaction/coordination/awaitClaimGrant.d.ts +7 -0
- package/dist/transaction/coordination/awaitClaimGrant.js +12 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.d.ts +34 -0
- package/dist/transaction/coordination/claimHeartbeatLoop.js +20 -0
- package/dist/transaction/coordination/index.d.ts +4 -4
- package/dist/transaction/coordination/index.js +4 -3
- package/dist/transaction/coordination/locator.d.ts +23 -2
- package/dist/transaction/coordination/locator.js +22 -2
- package/dist/transaction/coordination/schema.d.ts +125 -62
- package/dist/transaction/coordination/schema.js +228 -64
- package/dist/transaction/coordination/targetConflict.js +32 -28
- package/dist/transaction/errorCodes.d.ts +2 -2
- package/dist/transaction/errorCodes.js +13 -9
- package/dist/transaction/plugin.d.ts +95 -2
- package/dist/transaction/plugin.js +21 -2
- package/dist/transaction/resources/httpResources.d.ts +57 -2
- package/dist/transaction/resources/modelOperations.d.ts +148 -40
- package/dist/transaction/resources/where.d.ts +16 -0
- package/dist/transaction/resources/where.js +45 -0
- package/dist/transaction/schema/field.d.ts +12 -18
- package/dist/transaction/schema/fieldRef.d.ts +38 -0
- package/dist/transaction/schema/fieldRef.js +11 -0
- package/dist/transaction/schema/openapi.d.ts +16 -15
- package/dist/transaction/schema/openapi.js +186 -25
- package/dist/transaction/schema/relation.d.ts +7 -2
- package/dist/transaction/schema/schema.d.ts +27 -0
- package/dist/transaction/schema/schema.js +20 -0
- package/dist/transaction/transactions/settlement/commitEnvelope.d.ts +1 -1
- package/dist/transaction/transactions/settlement/pendingWrite.d.ts +1 -1
- package/dist/transaction/transport/httpClient.d.ts +9 -1
- package/dist/transaction/transport/httpClient.js +1 -0
- package/dist/transaction/transport/httpTransport.js +156 -44
- package/dist/transaction/transport/wsTransport.d.ts +2 -4
- package/dist/transaction/transport/wsTransport.js +16 -10
- package/dist/transaction/types/streams.d.ts +10 -0
- package/dist/transaction/utils/duration.d.ts +25 -0
- package/dist/transaction/utils/duration.js +32 -0
- package/dist/transaction/wire/accountResponses.d.ts +69 -0
- package/dist/transaction/wire/accountResponses.js +36 -1
- package/dist/transaction/wire/auth.d.ts +9 -2
- package/dist/transaction/wire/auth.js +7 -1
- package/dist/transaction/wire/claims.d.ts +164 -97
- package/dist/transaction/wire/claims.js +126 -28
- package/dist/transaction/wire/commit.d.ts +1 -1
- package/dist/transaction/wire/feedEvent.d.ts +27 -0
- package/dist/transaction/wire/feedEvent.js +27 -1
- package/dist/transaction/wire/frames.d.ts +2 -2
- package/dist/transaction/wire/inboundFrames.d.ts +12 -2
- package/dist/transaction/wire/index.d.ts +10 -6
- package/dist/transaction/wire/index.js +12 -3
- package/dist/transaction/wire/modelMutations.d.ts +31 -0
- package/dist/transaction/wire/modelMutations.js +52 -0
- package/dist/transaction/wire/modelShape.d.ts +78 -0
- package/dist/transaction/wire/modelShape.js +74 -0
- package/dist/transactions/mutations/MutationQueue.d.ts +6 -0
- package/dist/transactions/mutations/MutationQueue.js +55 -45
- package/dist/transactions/mutations/commitPayload.d.ts +3 -2
- package/dist/transactions/mutations/commitPayload.js +5 -5
- package/dist/transactions/mutations/deltaConfirmation.d.ts +4 -0
- package/dist/transactions/mutations/deltaConfirmation.js +10 -8
- package/dist/transactions/mutations/replayValidation.d.ts +2 -1
- package/dist/transactions/mutations/replayValidation.js +3 -2
- package/dist/{core → views}/QueryView.d.ts +1 -1
- package/dist/{core → views}/QueryView.js +1 -1
- package/dist/{core → views}/ViewRegistry.d.ts +1 -1
- package/dist/{core/queryUtils.d.ts → views/incrementalView.d.ts} +6 -6
- package/dist/{core/queryUtils.js → views/incrementalView.js} +6 -6
- package/docs/agents.md +1 -1
- package/docs/api-keys.md +6 -6
- package/docs/api.md +5 -43
- package/docs/audit.md +4 -3
- package/docs/cli.md +11 -11
- package/docs/client-behavior.md +4 -4
- package/docs/concurrency-convention.md +28 -42
- package/docs/coordination.md +235 -83
- package/docs/data-sources.md +4 -4
- package/docs/debugging.md +34 -12
- package/docs/deployment.md +8 -8
- package/docs/examples/scoped-agent.md +3 -3
- package/docs/groups.md +57 -3
- package/docs/guarantees.md +37 -10
- package/docs/how-it-works.md +29 -5
- package/docs/idempotency.md +6 -6
- package/docs/identity.md +22 -23
- package/docs/index.md +8 -8
- package/docs/integration-guide.md +14 -3
- package/docs/mcp.md +7 -7
- package/docs/migration.md +34 -15
- package/docs/projects.md +1 -1
- package/docs/react.md +19 -8
- package/docs/sessions.md +1 -1
- package/docs/webhooks.md +9 -9
- package/llms.txt +4 -4
- package/package.json +13 -20
- package/dist/cli.cjs +0 -288600
- package/dist/testing/fixtures/bootstrap.d.ts +0 -49
- package/dist/testing/fixtures/bootstrap.js +0 -59
- package/dist/testing/fixtures/deltas.d.ts +0 -83
- package/dist/testing/fixtures/deltas.js +0 -136
- package/dist/testing/fixtures/httpResponses.d.ts +0 -70
- package/dist/testing/fixtures/httpResponses.js +0 -90
- package/dist/testing/fixtures/models.d.ts +0 -83
- package/dist/testing/fixtures/models.js +0 -272
- package/dist/testing/helpers/reactWrapper.d.ts +0 -69
- package/dist/testing/helpers/reactWrapper.js +0 -67
- package/dist/testing/helpers/syncEngineHarness.d.ts +0 -54
- package/dist/testing/helpers/syncEngineHarness.js +0 -73
- package/dist/testing/helpers/wait.d.ts +0 -30
- package/dist/testing/helpers/wait.js +0 -49
- package/dist/testing/index.d.ts +0 -23
- package/dist/testing/index.js +0 -33
- package/dist/testing/mocks/FakeDatabase.d.ts +0 -18
- package/dist/testing/mocks/FakeDatabase.js +0 -10
- package/dist/testing/mocks/MockMutationExecutor.d.ts +0 -87
- package/dist/testing/mocks/MockMutationExecutor.js +0 -186
- package/dist/testing/mocks/MockNetworkMonitor.d.ts +0 -20
- package/dist/testing/mocks/MockNetworkMonitor.js +0 -46
- package/dist/testing/mocks/MockSyncContext.d.ts +0 -51
- package/dist/testing/mocks/MockSyncContext.js +0 -72
- package/dist/testing/mocks/MockSyncStore.d.ts +0 -88
- package/dist/testing/mocks/MockSyncStore.js +0 -171
- package/dist/testing/mocks/MockWebSocket.d.ts +0 -71
- package/dist/testing/mocks/MockWebSocket.js +0 -118
- package/docs/interaction-model.md +0 -99
- /package/dist/{core → query}/QueryProcessor.d.ts +0 -0
- /package/dist/{core → query}/QueryProcessor.js +0 -0
- /package/dist/{core/storeContract.js → storeContract.js} +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.d.ts +0 -0
- /package/dist/{core → stores}/openIDBWithTimeout.js +0 -0
- /package/dist/{source → transaction}/footprint.d.ts +0 -0
- /package/dist/{source → transaction}/footprint.js +0 -0
- /package/dist/{core → views}/ViewRegistry.js +0 -0
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { ModelScope } from '../types/index.js';
|
|
13
13
|
import type { ResolveClaimMeta } from '../types/global.js';
|
|
14
|
-
import type {
|
|
15
|
-
import type {
|
|
14
|
+
import type { ClaimPart, StaleNotification, TrackDependency } from '../coordination/schema.js';
|
|
15
|
+
import type { ClaimHeartbeatPlan } from '../coordination/claimHeartbeatLoop.js';
|
|
16
16
|
import type { Duration } from '../utils/duration.js';
|
|
17
17
|
import type { Claim, ClaimHeartbeat, ClaimHeartbeatOptions, HeldClaim, HeldLease, TargetRange } from '../types/streams.js';
|
|
18
18
|
import type { MutationOptions } from './mutationOptions.js';
|
|
@@ -99,24 +99,97 @@ export interface ServerReadOptions<T> {
|
|
|
99
99
|
/** Options for the single-row async server read `retrieve({ id })`. A subset of
|
|
100
100
|
* {@link ServerReadOptions} — `where`/`limit`/`orderBy` are fixed by the id. */
|
|
101
101
|
export type ServerRetrieveOptions = Pick<ServerReadOptions<unknown>, 'type' | 'expand'>;
|
|
102
|
+
/**
|
|
103
|
+
* A claimable part of a row: one of the model's own fields, an app-defined name
|
|
104
|
+
* marked with {@link part}, or any other string.
|
|
105
|
+
*
|
|
106
|
+
* The third arm is the one that matters, and it is not a TypeScript
|
|
107
|
+
* limitation. Function parameters are contravariant, so a claim accepting
|
|
108
|
+
* `'title' | 'status'` is not assignable to one accepting `string` — and the
|
|
109
|
+
* legacy `useAblo<R>()` path erases a concrete schema to `SchemaRecord`
|
|
110
|
+
* (`AbloProvider.tsx`: `engine as Ablo<SchemaRecord>`) and restores it, which
|
|
111
|
+
* is exactly that assignment. While that path exists this union must stay open,
|
|
112
|
+
* and `fields: ['titel']` therefore compiles, is granted, excludes nobody, and
|
|
113
|
+
* leaves the write of `title` unguarded — the conflict rule compares names as
|
|
114
|
+
* opaque strings, so an invented one matches no other claim and nothing reports
|
|
115
|
+
* it.
|
|
116
|
+
*
|
|
117
|
+
* The replacement already exists: `createAbloReact(schema)` creates its context
|
|
118
|
+
* after the schema is known, so nothing is erased and nothing is restored — the
|
|
119
|
+
* same shape as `createTRPCReact<AppRouter>()`. What remains is retiring the
|
|
120
|
+
* generic provider in favour of it; the callers are enumerated in
|
|
121
|
+
* docs/plans/typed-react-binding.md. When they have moved, this arm goes, and
|
|
122
|
+
* `fields: ['titel']` stops compiling with no change at any call site.
|
|
123
|
+
*/
|
|
124
|
+
export type ClaimField<T> = Extract<keyof T, string> | ClaimPart | (string & {});
|
|
125
|
+
/**
|
|
126
|
+
* The options on a claim, in four axes — each answers one question, and no
|
|
127
|
+
* member sits in two:
|
|
128
|
+
*
|
|
129
|
+
* - **what you claim** — `field` / `fields` / `path` / `range`, the target
|
|
130
|
+
* narrowed below the row;
|
|
131
|
+
* - **what others see** — `description` / `meta`, the presence half;
|
|
132
|
+
* - **how you wait** — `queue` / `maxQueueDepth` / `waitTimeoutMs` /
|
|
133
|
+
* `signal`, admission to the line;
|
|
134
|
+
* - **how long you hold** — `ttl` / `heartbeat`, the lease.
|
|
135
|
+
*/
|
|
102
136
|
export interface ClaimTargetOptions<T = Record<string, unknown>> {
|
|
103
|
-
/** Peer-visible description of the work being performed — the sentence a
|
|
104
|
-
* contending participant reads to decide whether to wait, work elsewhere, or
|
|
105
|
-
* move on. Defaults to `'editing'`. The same field on every claim surface. */
|
|
106
|
-
description?: string;
|
|
107
|
-
/** Field-level target, for fine-grained claimed-state badges. */
|
|
108
|
-
field?: string;
|
|
109
137
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
138
|
+
* @deprecated Say it as a set: `fields: ['title']`. Removed in 0.37.0.
|
|
139
|
+
*
|
|
140
|
+
* One member for a set of one, beside another for a set of any size, is two
|
|
141
|
+
* ways to say one thing — and the singular is the one that misleads. A caller
|
|
142
|
+
* needing two parts reached for the member named `field` and packed
|
|
143
|
+
* `'b_3,b_7'` into it, which compares as a single unrelated name, so both
|
|
144
|
+
* writers were granted the same part and one update was lost. That spelling
|
|
145
|
+
* is refused now, and this member is going with it.
|
|
146
|
+
*
|
|
147
|
+
* The wire keeps reading `field` — it is frozen, and an older client emits it
|
|
148
|
+
* — so this is a change of what you write, not of what is understood.
|
|
149
|
+
*/
|
|
150
|
+
field?: ClaimField<T>;
|
|
151
|
+
/**
|
|
152
|
+
* Narrow the claim to named parts of the row — one or several.
|
|
153
|
+
*
|
|
154
|
+
* Exclusion follows the target: claims on the same row conflict only where
|
|
155
|
+
* their sets intersect, so a holder on `['title']` and a holder on
|
|
156
|
+
* `['status']` proceed concurrently, while a whole-row claim (no target)
|
|
157
|
+
* conflicts with both. The per-field claimed-state badge is a consequence of
|
|
158
|
+
* the narrower lease, not its purpose.
|
|
159
|
+
*/
|
|
160
|
+
fields?: readonly ClaimField<T>[];
|
|
161
|
+
/**
|
|
162
|
+
* @deprecated Claim the field the position lives in — `fields: ['content']`.
|
|
163
|
+
* Removed in 0.37.0.
|
|
164
|
+
*
|
|
165
|
+
* A claim may not be finer than the smallest thing the write path can
|
|
166
|
+
* address, and today that is a field: nothing writes part of a value. Two
|
|
167
|
+
* holders of `/content/3` and `/content/7` therefore contend, because either
|
|
168
|
+
* one committing takes the whole `content` field — so naming the position
|
|
169
|
+
* bought exclusion it could not deliver, and this member read as a peer of
|
|
170
|
+
* `fields` while being nothing of the kind.
|
|
171
|
+
*
|
|
172
|
+
* For a UI that draws a rail per block, put the block id in `meta`: that is
|
|
173
|
+
* the open bag for describing your work to peers, it renders exactly as well,
|
|
174
|
+
* and it promises nothing about exclusion. The wire keeps parsing `path`, so
|
|
175
|
+
* an older client is unaffected.
|
|
114
176
|
*/
|
|
115
|
-
fields?: readonly string[];
|
|
116
|
-
/** Optional path for document/file-like targets. */
|
|
117
177
|
path?: string;
|
|
118
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* @deprecated Claim the field the span lives in — `fields: ['content']`.
|
|
180
|
+
* Removed in 0.37.0.
|
|
181
|
+
*
|
|
182
|
+
* Same rule as {@link path}: a span of a value is not separately writable, so
|
|
183
|
+
* two disjoint spans of one field contend. Unlike `path` this one has a
|
|
184
|
+
* future — a mergeable field makes an operation the write unit, at which
|
|
185
|
+
* point a span inside it is addressable and this becomes enforceable. It
|
|
186
|
+
* returns then, for code rather than prose, and it returns working.
|
|
187
|
+
*/
|
|
119
188
|
range?: TargetRange;
|
|
189
|
+
/** Peer-visible description of the work being performed — the sentence a
|
|
190
|
+
* contending participant reads to decide whether to wait, work elsewhere, or
|
|
191
|
+
* move on. Defaults to `'editing'`. The same field on every claim surface. */
|
|
192
|
+
description?: string;
|
|
120
193
|
/**
|
|
121
194
|
* App-defined structured metadata, carried verbatim to every participant
|
|
122
195
|
* that observes the claim. Declare its shape once, on `Register`'s
|
|
@@ -124,8 +197,6 @@ export interface ClaimTargetOptions<T = Record<string, unknown>> {
|
|
|
124
197
|
* find — the write side and the read side are the same declaration.
|
|
125
198
|
*/
|
|
126
199
|
meta?: ResolveClaimMeta;
|
|
127
|
-
/** Crash-cleanup TTL — the claim auto-releases if the holder dies. */
|
|
128
|
-
ttl?: Duration;
|
|
129
200
|
/**
|
|
130
201
|
* Behavior under contention. `true` (the default) queues behind the current
|
|
131
202
|
* holder and resolves once the row is yours. `false` is fail-fast: if another
|
|
@@ -133,10 +204,6 @@ export interface ClaimTargetOptions<T = Record<string, unknown>> {
|
|
|
133
204
|
* {@link AbloClaimedError} instead of waiting. Use `false` to deduplicate
|
|
134
205
|
* distributed work ("if someone else has this job, skip it"), where waiting
|
|
135
206
|
* would mean double-processing.
|
|
136
|
-
*
|
|
137
|
-
* The high-level typed claim defaults this on because it serializes writers;
|
|
138
|
-
* the low-level lease and the HTTP client default it off, since they resolve
|
|
139
|
-
* immediately and cannot transparently wait for a grant.
|
|
140
207
|
*/
|
|
141
208
|
queue?: boolean;
|
|
142
209
|
/**
|
|
@@ -146,31 +213,36 @@ export interface ClaimTargetOptions<T = Record<string, unknown>> {
|
|
|
146
213
|
* Omit to wait however deep the queue is.
|
|
147
214
|
*/
|
|
148
215
|
maxQueueDepth?: number;
|
|
216
|
+
/**
|
|
217
|
+
* Cap on how long a queued claim waits for its grant before rejecting with
|
|
218
|
+
* {@link AbloClaimedError} (`grant_timeout`). Omit to wait as long as the
|
|
219
|
+
* line takes. Same meaning on both transports; on the stateless HTTP client
|
|
220
|
+
* a timed-out wait also leaves the line, so the slot is not left to expire.
|
|
221
|
+
*/
|
|
222
|
+
waitTimeoutMs?: number;
|
|
223
|
+
/**
|
|
224
|
+
* Abort a pending wait from outside — the same signal that cancels
|
|
225
|
+
* everything else in the program, so a cancelled agent task or an unmounted
|
|
226
|
+
* component takes its queued claim with it. Rejects with
|
|
227
|
+
* {@link AbloClaimedError} (`claim_wait_aborted`); over HTTP the abort also
|
|
228
|
+
* leaves the line. Ignored once the grant has arrived — a held lease is
|
|
229
|
+
* never torn down by a late abort; release it instead.
|
|
230
|
+
*/
|
|
231
|
+
signal?: AbortSignal;
|
|
232
|
+
/** Crash-cleanup TTL — the claim auto-releases if the holder dies. */
|
|
233
|
+
ttl?: Duration;
|
|
149
234
|
/**
|
|
150
235
|
* Keep the lease alive for the duration of real work by beating on a
|
|
151
236
|
* cadence — the pattern for background workers whose task outlives the
|
|
152
237
|
* crash-cleanup TTL. `true` beats every third of the TTL (so two beats can
|
|
153
238
|
* fail before the lease is at risk, and a crashed worker's lease still
|
|
154
239
|
* lapses within one beat window); a duration such as `'2m'` sets the
|
|
155
|
-
* cadence explicitly
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
|
|
159
|
-
heartbeat?: true | Duration;
|
|
160
|
-
/**
|
|
161
|
-
* Called once if the auto-heartbeat learns the lease is no longer yours
|
|
162
|
-
* (expired and possibly granted onward). The loop has already stopped;
|
|
163
|
-
* abandon the work or re-claim. Any write attempted under the old lease is
|
|
164
|
-
* independently rejected by its `readAt` guard.
|
|
165
|
-
*/
|
|
166
|
-
onHeartbeatLost?: (error: AbloClaimedError) => void;
|
|
167
|
-
/**
|
|
168
|
-
* Called after every successful beat (manual or auto) with the server's
|
|
169
|
-
* answer — chiefly `queueDepth`, the number of participants waiting in
|
|
170
|
-
* line behind this lease. A worker that can checkpoint may read pressure
|
|
171
|
-
* here and release early when others wait.
|
|
240
|
+
* cadence explicitly; the structured {@link ClaimHeartbeatPlan} carries the
|
|
241
|
+
* cadence and both callbacks in one place —
|
|
242
|
+
* `heartbeat: { every: '2m', onBeat, onLost }`. The loop stops on release.
|
|
243
|
+
* You can also beat manually with `held.heartbeat()`.
|
|
172
244
|
*/
|
|
173
|
-
|
|
245
|
+
heartbeat?: true | Duration | ClaimHeartbeatPlan;
|
|
174
246
|
}
|
|
175
247
|
/** Options for `claim({ id, ... })`. */
|
|
176
248
|
export interface ClaimParams<T = Record<string, unknown>> extends ClaimTargetOptions<T> {
|
|
@@ -178,7 +250,10 @@ export interface ClaimParams<T = Record<string, unknown>> extends ClaimTargetOpt
|
|
|
178
250
|
}
|
|
179
251
|
export interface ClaimLookupParams<T = Record<string, unknown>> {
|
|
180
252
|
readonly id: string;
|
|
181
|
-
|
|
253
|
+
/** @deprecated Say it as a set: `fields: ['title']`. Removed in 0.37.0. */
|
|
254
|
+
readonly field?: ClaimField<T>;
|
|
255
|
+
/** Read the claim state of named parts of the row rather than the row. */
|
|
256
|
+
readonly fields?: readonly ClaimField<T>[];
|
|
182
257
|
}
|
|
183
258
|
export interface ClaimReorderParams<T = Record<string, unknown>> extends ClaimLookupParams<T> {
|
|
184
259
|
readonly order: readonly Claim[];
|
|
@@ -251,6 +326,24 @@ export interface ClaimReadApi<T = Record<string, unknown>> {
|
|
|
251
326
|
* carrying more than one meta shape occasionally needs.
|
|
252
327
|
*/
|
|
253
328
|
state<M = ResolveClaimMeta>(params: ClaimLookupParams<T>): Claim<Record<string, unknown>, M> | null;
|
|
329
|
+
/**
|
|
330
|
+
* Every holder of a row, not just one.
|
|
331
|
+
*
|
|
332
|
+
* A claim that names a narrower target — a `path`, a `range`, a `field` or
|
|
333
|
+
* `fields` — excludes only what it overlaps, so several participants hold
|
|
334
|
+
* disjoint parts of one row at the same time and
|
|
335
|
+
* {@link ClaimReadApi.state} answers with one of them. This is the read
|
|
336
|
+
* behind a per-region UI: a rail for each claimed block, a chip for each
|
|
337
|
+
* participant. Synchronous and reactive off the same snapshot as `state`,
|
|
338
|
+
* so a render reads it inline.
|
|
339
|
+
*
|
|
340
|
+
* Own claim first when this client holds one, then peers. Takes the same
|
|
341
|
+
* `meta` parameter as {@link ClaimReadApi.state}.
|
|
342
|
+
*/
|
|
343
|
+
list<M = ResolveClaimMeta>(params: ClaimLookupParams<T>): {
|
|
344
|
+
readonly object: 'list';
|
|
345
|
+
readonly data: readonly Claim<Record<string, unknown>, M>[];
|
|
346
|
+
};
|
|
254
347
|
/**
|
|
255
348
|
* FIFO wait line behind the current holder. Advanced: useful for operator
|
|
256
349
|
* UIs and schedulers. Takes the same `meta` parameter as
|
|
@@ -274,6 +367,17 @@ export interface ClaimReadApi<T = Record<string, unknown>> {
|
|
|
274
367
|
*/
|
|
275
368
|
export type AwaitedClaimMethod<F> = F extends (...args: infer A) => infer R ? R extends Promise<unknown> ? (...args: A) => R : (...args: A) => Promise<R> : F;
|
|
276
369
|
export interface ClaimApi<T> extends ClaimReadApi<T> {
|
|
370
|
+
/**
|
|
371
|
+
* The try-claim: `queue: false` treats a held target as an expected outcome,
|
|
372
|
+
* not an error — it resolves `null`, so claim-or-skip dedup reads
|
|
373
|
+
* `if (!claim) return` with no try/catch. Who holds it, and why, stays
|
|
374
|
+
* readable through `claim.state({ id })`. (A write to a row someone else
|
|
375
|
+
* holds still rejects with `entity_claimed` — a failed write is an error;
|
|
376
|
+
* a declined try is not.)
|
|
377
|
+
*/
|
|
378
|
+
(params: ClaimParams<T> & {
|
|
379
|
+
queue: false;
|
|
380
|
+
}): Promise<HeldClaim<T> | null>;
|
|
277
381
|
/**
|
|
278
382
|
* Takes a claim and returns an explicit held-work handle — a {@link HeldClaim}.
|
|
279
383
|
* `data`, `release`, `revoke`, and the async disposer are always present (this
|
|
@@ -281,6 +385,10 @@ export interface ClaimApi<T> extends ClaimReadApi<T> {
|
|
|
281
385
|
* directly and `await using` works without a guard.
|
|
282
386
|
*/
|
|
283
387
|
(params: ClaimParams<T>): Promise<HeldClaim<T>>;
|
|
388
|
+
/** The row-free try-claim — `null` when the key is already held. */
|
|
389
|
+
(id: string, opts: ClaimOptions<T> & {
|
|
390
|
+
queue: false;
|
|
391
|
+
}): Promise<HeldLease | null>;
|
|
284
392
|
/**
|
|
285
393
|
* Takes a claim by id alone, for a row that lives only in the customer's own
|
|
286
394
|
* database — Ablo has never seen it, so there is nothing to re-read. Returns a
|
|
@@ -55,6 +55,22 @@ export declare const WHERE_SCALAR_OPS: ReadonlySet<WhereOp>;
|
|
|
55
55
|
export declare const WHERE_ARRAY_OPS: ReadonlySet<WhereOp>;
|
|
56
56
|
export declare const WHERE_NULL_OPS: ReadonlySet<WhereOp>;
|
|
57
57
|
export declare const WHERE_LIKE_OPS: ReadonlySet<WhereOp>;
|
|
58
|
+
/**
|
|
59
|
+
* Does this operator accept this operand? The rule the sets above imply, stated
|
|
60
|
+
* once and thrown once.
|
|
61
|
+
*
|
|
62
|
+
* The sets were collapsed here because two copies of the split let one plane
|
|
63
|
+
* accept a request the other refused. The check that consumes them stayed
|
|
64
|
+
* duplicated — the SQL compiler and the log-plane evaluator each carried their
|
|
65
|
+
* own arity tests and their own wording — which leaves the same gap one step
|
|
66
|
+
* further along: relax `IN` in one evaluator and a request succeeds against a
|
|
67
|
+
* hosted plane and fails against a connected one, with no test in a position to
|
|
68
|
+
* notice.
|
|
69
|
+
*
|
|
70
|
+
* Returns the classification the caller needs next, so the check and the branch
|
|
71
|
+
* are the same statement rather than two that can disagree.
|
|
72
|
+
*/
|
|
73
|
+
export declare function classifyWhereOperand(op: WhereOp, value: unknown): 'null' | 'array' | 'scalar';
|
|
58
74
|
/**
|
|
59
75
|
* A single condition. Two supported shapes:
|
|
60
76
|
*
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* so they live with the settlement core rather than the reactive consumer.
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod';
|
|
16
|
+
import { AbloValidationError } from '../errors.js';
|
|
16
17
|
/**
|
|
17
18
|
* The comparison operators a {@link WhereClause} may use: equality and
|
|
18
19
|
* inequality, ordering, set membership (`IN` / `NOT IN`), null checks
|
|
@@ -68,3 +69,47 @@ export const WHERE_LIKE_OPS = new Set([
|
|
|
68
69
|
'ILIKE',
|
|
69
70
|
'NOT ILIKE',
|
|
70
71
|
]);
|
|
72
|
+
/**
|
|
73
|
+
* Does this operator accept this operand? The rule the sets above imply, stated
|
|
74
|
+
* once and thrown once.
|
|
75
|
+
*
|
|
76
|
+
* The sets were collapsed here because two copies of the split let one plane
|
|
77
|
+
* accept a request the other refused. The check that consumes them stayed
|
|
78
|
+
* duplicated — the SQL compiler and the log-plane evaluator each carried their
|
|
79
|
+
* own arity tests and their own wording — which leaves the same gap one step
|
|
80
|
+
* further along: relax `IN` in one evaluator and a request succeeds against a
|
|
81
|
+
* hosted plane and fails against a connected one, with no test in a position to
|
|
82
|
+
* notice.
|
|
83
|
+
*
|
|
84
|
+
* Returns the classification the caller needs next, so the check and the branch
|
|
85
|
+
* are the same statement rather than two that can disagree.
|
|
86
|
+
*/
|
|
87
|
+
export function classifyWhereOperand(op, value) {
|
|
88
|
+
if (WHERE_NULL_OPS.has(op)) {
|
|
89
|
+
if (value !== null) {
|
|
90
|
+
throw new AbloValidationError(`${op} only supports null RHS`, {
|
|
91
|
+
code: 'query_unsupported_operator',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return 'null';
|
|
95
|
+
}
|
|
96
|
+
if (WHERE_ARRAY_OPS.has(op)) {
|
|
97
|
+
if (!Array.isArray(value)) {
|
|
98
|
+
throw new AbloValidationError(`${op} requires an array RHS`, {
|
|
99
|
+
code: 'query_unsupported_operator',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return 'array';
|
|
103
|
+
}
|
|
104
|
+
if (WHERE_SCALAR_OPS.has(op)) {
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
throw new AbloValidationError(`${op} requires a scalar RHS`, {
|
|
107
|
+
code: 'query_unsupported_operator',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return 'scalar';
|
|
111
|
+
}
|
|
112
|
+
throw new AbloValidationError(`unsupported operator: ${op}`, {
|
|
113
|
+
code: 'query_unsupported_operator',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -24,24 +24,18 @@
|
|
|
24
24
|
* });
|
|
25
25
|
*/
|
|
26
26
|
import { z } from 'zod';
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* the column from the field name using the active casing convention.
|
|
40
|
-
*/
|
|
41
|
-
column?: string;
|
|
42
|
-
/** For enums: the allowed values. */
|
|
43
|
-
enumValues?: readonly string[];
|
|
44
|
-
}
|
|
27
|
+
/**
|
|
28
|
+
* The sync-engine metadata describing one field, available at runtime through a
|
|
29
|
+
* model's `fields` map. The {@link field} builders attach it, and the migration
|
|
30
|
+
* planner, type generator, and OpenAPI generator all read it.
|
|
31
|
+
*
|
|
32
|
+
* Declared in `wire/modelShape.ts` and inferred here, not restated: this record
|
|
33
|
+
* crosses the wire twice — serialized into the pushed artifact, and reported
|
|
34
|
+
* back by `GET /api/schema` — so a second declaration would be a copy that
|
|
35
|
+
* drifts in whichever direction nobody is looking.
|
|
36
|
+
*/
|
|
37
|
+
export type { FieldMeta } from '../wire/modelShape.js';
|
|
38
|
+
import type { FieldMeta } from '../wire/modelShape.js';
|
|
45
39
|
/**
|
|
46
40
|
* Extract FieldMeta from a Zod schema. Returns null if no sync-engine
|
|
47
41
|
* metadata is attached (e.g., raw `z.string()` usage).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reference to one field of one model — the field as a value rather than as
|
|
3
|
+
* a quoted name.
|
|
4
|
+
*
|
|
5
|
+
* A model is declared as Zod schemas keyed by name, so a field's name lives on
|
|
6
|
+
* the object key and never on the value: `model({ status: z.enum([...]) })`
|
|
7
|
+
* gives you nothing to point at, and every surface that needs to name a field
|
|
8
|
+
* has had to quote it. `fields: ['titel']` then compiles, is granted, excludes
|
|
9
|
+
* nobody, and leaves the write of `title` unguarded — the conflict rule
|
|
10
|
+
* compares names as opaque strings, so an invented one matches no other claim
|
|
11
|
+
* and nothing reports it.
|
|
12
|
+
*
|
|
13
|
+
* {@link Schema.fields} carries one of these per field, stamped from the key
|
|
14
|
+
* `defineSchema` is already holding. Referencing a field that does not exist
|
|
15
|
+
* stops compiling, and renaming one is a compile error at every use rather than
|
|
16
|
+
* a claim that quietly stops matching.
|
|
17
|
+
*
|
|
18
|
+
* An object rather than a branded string, for the reason `ClaimPart` is one: a
|
|
19
|
+
* brand makes a concrete schema's claim params mutually unassignable with the
|
|
20
|
+
* erased `SchemaRecord` view, and the react context boundary erases and
|
|
21
|
+
* restores exactly that way. Objects stay pairwise comparable across it, which
|
|
22
|
+
* a union of literal keys does not — function parameters are contravariant, so
|
|
23
|
+
* a claim accepting `'title' | 'status'` is not assignable to one accepting
|
|
24
|
+
* `string`.
|
|
25
|
+
*
|
|
26
|
+
* `model` rides along so a reference carries where it came from. Claiming
|
|
27
|
+
* `users.email` through `ablo.tasks` is a mistake nothing can currently see.
|
|
28
|
+
*/
|
|
29
|
+
export interface FieldRef {
|
|
30
|
+
/** The schema key of the model this field belongs to. */
|
|
31
|
+
readonly model: string;
|
|
32
|
+
/** The field's own name — the key it was declared under. */
|
|
33
|
+
readonly field: string;
|
|
34
|
+
}
|
|
35
|
+
/** Whether a value is a {@link FieldRef}. */
|
|
36
|
+
export declare function isFieldRef(value: unknown): value is FieldRef;
|
|
37
|
+
/** Build the reference for one declared field. */
|
|
38
|
+
export declare function fieldRef(model: string, field: string): FieldRef;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Whether a value is a {@link FieldRef}. */
|
|
2
|
+
export function isFieldRef(value) {
|
|
3
|
+
return (typeof value === 'object' &&
|
|
4
|
+
value !== null &&
|
|
5
|
+
typeof value.field === 'string' &&
|
|
6
|
+
typeof value.model === 'string');
|
|
7
|
+
}
|
|
8
|
+
/** Build the reference for one declared field. */
|
|
9
|
+
export function fieldRef(model, field) {
|
|
10
|
+
return { model, field };
|
|
11
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Two OpenAPI 3.1 documents describing the same API, for two different readers.
|
|
3
3
|
*
|
|
4
|
-
* The server registers ONE parameterised route family — `/api/v1/models/:model
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Bearer scheme, your API key.
|
|
4
|
+
* The server registers ONE parameterised route family — `/api/v1/models/:model`
|
|
5
|
+
* and what hangs below it, plus the coordination, credential and commit
|
|
6
|
+
* resources. The count does not move when a tenant's schema does.
|
|
7
|
+
* Authentication is a single Bearer scheme, your API key.
|
|
8
8
|
*
|
|
9
|
-
* {@link abloOpenApi} publishes
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* {@link abloOpenApi} publishes that family. It takes no schema, so it cannot
|
|
10
|
+
* grow with one: identical for every caller, stable across every push, and the
|
|
11
|
+
* document a Python or Go client is generated from once. `spec-covers-routes`
|
|
12
|
+
* holds it to the served surface in both directions, so this is the list of
|
|
13
|
+
* routes rather than a description of it.
|
|
12
14
|
*
|
|
13
|
-
* {@link schemaToOpenApi} expands the
|
|
15
|
+
* {@link schemaToOpenApi} expands the model half into one set per model, with
|
|
14
16
|
* payloads typed from each model's introspectable {@link FieldMeta}. Five paths
|
|
15
17
|
* per model, regenerated on every push — worth it when you want generated types
|
|
16
18
|
* for one schema, and the wrong thing to hand an agent.
|
|
@@ -30,15 +32,14 @@ export interface SchemaToOpenApiOptions {
|
|
|
30
32
|
}
|
|
31
33
|
type Json = Record<string, unknown>;
|
|
32
34
|
/**
|
|
33
|
-
* The protocol reference: the
|
|
34
|
-
* plus `/v1/commits`.
|
|
35
|
+
* The protocol reference: the route templates the server actually serves.
|
|
35
36
|
*
|
|
36
37
|
* This takes no schema, and that is the point. The server registers one
|
|
37
|
-
* parameterised route family (`/api/v1/models/:model/...`), so the
|
|
38
|
-
*
|
|
39
|
-
* a schema cannot grow with one. It is publishable once, identical
|
|
40
|
-
* caller, and stable across every schema push: the document a Python
|
|
41
|
-
* client is generated from, and the surface an agent is handed.
|
|
38
|
+
* parameterised route family (`/api/v1/models/:model/...`), so the surface is
|
|
39
|
+
* the same size no matter how many models a tenant defines — and a spec that
|
|
40
|
+
* cannot see a schema cannot grow with one. It is publishable once, identical
|
|
41
|
+
* for every caller, and stable across every schema push: the document a Python
|
|
42
|
+
* or Go client is generated from, and the surface an agent is handed.
|
|
42
43
|
*
|
|
43
44
|
* Payload shapes are generic here by design. A caller that wants them typed
|
|
44
45
|
* either reads the schema at runtime or generates the per-tenant expansion with
|