@abloatai/ablo 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/CHANGELOG.md +59 -3
  2. package/README.md +1 -1
  3. package/dist/BaseSyncedStore.js +8 -9
  4. package/dist/Database.d.ts +14 -1
  5. package/dist/Database.js +228 -28
  6. package/dist/Model.d.ts +17 -0
  7. package/dist/Model.js +32 -1
  8. package/dist/SyncClient.d.ts +10 -6
  9. package/dist/SyncClient.js +390 -75
  10. package/dist/adapters/inMemoryStorage.d.ts +1 -0
  11. package/dist/adapters/inMemoryStorage.js +29 -13
  12. package/dist/ai-sdk/coordinatedTool.d.ts +15 -2
  13. package/dist/auth/schemas.d.ts +6 -0
  14. package/dist/auth/schemas.js +7 -0
  15. package/dist/cli.cjs +821 -402
  16. package/dist/client/Ablo.d.ts +53 -24
  17. package/dist/client/Ablo.js +12 -199
  18. package/dist/client/claimHeartbeatLoop.d.ts +1 -1
  19. package/dist/client/claimHeartbeatLoop.js +1 -1
  20. package/dist/client/createInternalComponents.d.ts +4 -0
  21. package/dist/client/createInternalComponents.js +3 -1
  22. package/dist/client/durableWrites.d.ts +21 -0
  23. package/dist/client/durableWrites.js +46 -0
  24. package/dist/client/httpClient.d.ts +21 -45
  25. package/dist/client/httpClient.js +104 -26
  26. package/dist/client/httpTransport.d.ts +8 -0
  27. package/dist/client/{ApiClient.js → httpTransport.js} +361 -308
  28. package/dist/client/modelRegistration.js +11 -0
  29. package/dist/client/options.d.ts +54 -7
  30. package/dist/client/options.js +3 -2
  31. package/dist/client/resourceTypes.d.ts +9 -85
  32. package/dist/client/resourceTypes.js +1 -1
  33. package/dist/client/sessionMint.d.ts +5 -6
  34. package/dist/client/sessionMint.js +4 -5
  35. package/dist/client/validateAbloOptions.js +3 -3
  36. package/dist/client/wsMutationExecutor.d.ts +3 -2
  37. package/dist/client/wsMutationExecutor.js +3 -2
  38. package/dist/coordination/schema.d.ts +30 -0
  39. package/dist/coordination/schema.js +14 -0
  40. package/dist/core/StoreManager.d.ts +2 -0
  41. package/dist/core/StoreManager.js +12 -0
  42. package/dist/core/index.d.ts +1 -1
  43. package/dist/errorCodes.js +6 -2
  44. package/dist/errors.d.ts +4 -40
  45. package/dist/errors.js +5 -5
  46. package/dist/index.d.ts +20 -8
  47. package/dist/index.js +9 -5
  48. package/dist/interfaces/index.d.ts +5 -2
  49. package/dist/mutators/UndoManager.d.ts +2 -0
  50. package/dist/mutators/UndoManager.js +32 -0
  51. package/dist/mutators/defineMutators.d.ts +18 -0
  52. package/dist/mutators/defineMutators.js +4 -8
  53. package/dist/react/index.d.ts +1 -1
  54. package/dist/react/useAblo.d.ts +6 -4
  55. package/dist/react/useAblo.js +25 -3
  56. package/dist/schema/index.d.ts +2 -2
  57. package/dist/schema/index.js +1 -1
  58. package/dist/schema/schema.d.ts +31 -1
  59. package/dist/schema/select.d.ts +15 -0
  60. package/dist/schema/select.js +27 -3
  61. package/dist/source/connector.d.ts +3 -3
  62. package/dist/source/factory.d.ts +4 -6
  63. package/dist/source/factory.js +4 -7
  64. package/dist/source/index.d.ts +4 -4
  65. package/dist/source/index.js +2 -2
  66. package/dist/source/signing.d.ts +0 -3
  67. package/dist/source/types.d.ts +0 -26
  68. package/dist/stores/ObjectStore.d.ts +14 -1
  69. package/dist/stores/ObjectStore.js +33 -10
  70. package/dist/stores/ObjectStoreContract.d.ts +2 -0
  71. package/dist/surface.d.ts +1 -1
  72. package/dist/surface.js +3 -0
  73. package/dist/sync/BootstrapFetcher.d.ts +10 -0
  74. package/dist/sync/BootstrapFetcher.js +27 -4
  75. package/dist/sync/SyncWebSocket.d.ts +2 -1
  76. package/dist/sync/createClaimStream.d.ts +11 -2
  77. package/dist/sync/createClaimStream.js +4 -3
  78. package/dist/sync/persistedPrefix.d.ts +12 -0
  79. package/dist/sync/persistedPrefix.js +22 -0
  80. package/dist/testing/index.d.ts +2 -0
  81. package/dist/testing/index.js +1 -0
  82. package/dist/testing/mocks/FakeDatabase.d.ts +18 -0
  83. package/dist/testing/mocks/FakeDatabase.js +10 -0
  84. package/dist/testing/mocks/MockWebSocket.d.ts +2 -1
  85. package/dist/transactions/TransactionQueue.d.ts +66 -8
  86. package/dist/transactions/TransactionQueue.js +607 -89
  87. package/dist/transactions/commitEnvelope.d.ts +132 -0
  88. package/dist/transactions/commitEnvelope.js +139 -0
  89. package/dist/transactions/commitOutboxStore.d.ts +32 -0
  90. package/dist/transactions/commitOutboxStore.js +26 -0
  91. package/dist/transactions/commitPayload.d.ts +15 -0
  92. package/dist/transactions/commitPayload.js +6 -0
  93. package/dist/transactions/durableWriteStore.d.ts +123 -0
  94. package/dist/transactions/durableWriteStore.js +30 -0
  95. package/dist/transactions/httpCommitEnvelope.d.ts +44 -0
  96. package/dist/transactions/httpCommitEnvelope.js +181 -0
  97. package/dist/transactions/idempotencyKey.d.ts +10 -0
  98. package/dist/transactions/idempotencyKey.js +9 -0
  99. package/dist/transactions/replayValidation.d.ts +83 -0
  100. package/dist/transactions/replayValidation.js +46 -1
  101. package/dist/types/global.d.ts +10 -29
  102. package/dist/types/global.js +4 -7
  103. package/dist/types/streams.d.ts +2 -28
  104. package/dist/wire/bootstrapReason.d.ts +9 -0
  105. package/dist/wire/bootstrapReason.js +8 -0
  106. package/dist/wire/frames.d.ts +6 -21
  107. package/dist/wire/frames.js +4 -4
  108. package/dist/wire/index.d.ts +6 -3
  109. package/dist/wire/index.js +3 -2
  110. package/dist/wire/protocolVersion.d.ts +30 -17
  111. package/dist/wire/protocolVersion.js +34 -18
  112. package/docs/agents.md +8 -3
  113. package/docs/api.md +16 -14
  114. package/docs/client-behavior.md +6 -3
  115. package/docs/coordination.md +4 -5
  116. package/docs/data-sources.md +5 -8
  117. package/docs/guarantees.md +21 -0
  118. package/docs/integration-guide.md +1 -0
  119. package/docs/mcp.md +1 -1
  120. package/docs/migration.md +21 -1
  121. package/docs/quickstart.md +11 -0
  122. package/docs/react.md +0 -46
  123. package/examples/README.md +6 -5
  124. package/llms.txt +15 -15
  125. package/package.json +3 -3
  126. package/dist/client/ApiClient.d.ts +0 -177
@@ -1,32 +1,15 @@
1
- /**
2
- * Creates a stateless, typed HTTP client for server-side actors — agents,
3
- * workers, and serverless handlers. It talks to Ablo over plain request/response
4
- * HTTP, uses the bearer credential as its identity, and holds no WebSocket and no
5
- * connection state.
6
- *
7
- * This is the counterpart to the stateful {@link Ablo} client. The stateful
8
- * client is for interactive participants: it opens a WebSocket, learns its
9
- * identity (user id and organization id) during the connect-and-bootstrap step,
10
- * and routes writes through a queue that waits for that identity. A server-side
11
- * actor has no socket, so instead of reusing that machinery it uses this client,
12
- * where the credential itself carries identity and the server resolves it on
13
- * every request.
14
- *
15
- * Under the hood this wraps the schema-agnostic protocol client that
16
- * {@link createProtocolClient} returns in a typed proxy. The protocol client
17
- * commits over `POST /v1/commits` and reads over HTTP, authenticating with the
18
- * bearer token each time; its model access is string-keyed (`api.model('slides')`).
19
- * The proxy gives server code the same typed `client.<model>` surface the
20
- * stateful client offers, over stateless transport.
21
- */
22
- import { type AbloApiClientOptions } from './ApiClient.js';
23
- import type { CommitReceipt, CommitResource, HttpClaimApi, ModelRead, ModelReadOptions, CreateSessionParams, AbloSession } from './resourceTypes.js';
1
+ import type { AbloOptions } from './options.js';
2
+ import type { CommitResource, HttpClaimApi, ModelReadOptions, CreateSessionParams, AbloSession } from './resourceTypes.js';
24
3
  import type { ModelCreateParams, ModelDeleteParams, ServerReadOptions, ModelRetrieveParams, ModelUpdateParams } from './createModelProxy.js';
25
4
  import type { Schema, SchemaRecord, InferModel, InferCreate } from '../schema/schema.js';
26
5
  import type { ModelUpdater, ContentionOptions } from './functionalUpdate.js';
27
- export interface AbloHttpClientOptions<S extends SchemaRecord> extends Omit<AbloApiClientOptions, 'schema'> {
28
- /** The schema. Used only to type the model proxies; it is never sent over the wire or read at runtime. */
29
- readonly schema: Schema<S>;
6
+ export interface AbloHttpClientOptions<S extends SchemaRecord> extends AbloOptions<S> {
7
+ /**
8
+ * Per-request deadline. A black-holed HTTP request otherwise has no platform
9
+ * timeout and can stall a headless worker forever. Pass `0` to disable it.
10
+ * @default 30_000
11
+ */
12
+ readonly timeoutMs?: number;
30
13
  }
31
14
  /**
32
15
  * The per-model surface of the stateless HTTP client — everything reachable over
@@ -37,14 +20,14 @@ export interface AbloHttpClientOptions<S extends SchemaRecord> extends Omit<Ablo
37
20
  * need a persistent socket; those are absent from the type, so reaching for one
38
21
  * is a compile error rather than a runtime gap.
39
22
  *
40
- * The read shapes differ on purpose. `retrieve` returns a {@link ModelRead}
41
- * envelope of `{ data, stamp, claims }`, because a stateless client keeps no local
42
- * copy of the data: the watermark (`stamp`) and any active claims must travel
43
- * inline on the read so a caller can follow it with a stale-guarded write. `list`
44
- * returns a plain array.
23
+ * The typed model contract is transport-independent: `retrieve` returns one row,
24
+ * `list` returns rows, `create`/`update` return the resulting row, and `delete`
25
+ * returns nothing. The low-level protocol client keeps its read watermark and
26
+ * commit receipt envelopes internally; callers should not have to change data
27
+ * access syntax when they switch transport.
45
28
  */
46
29
  export interface HttpModelClient<T, C = T> {
47
- retrieve(params: ModelRetrieveParams & ModelReadOptions): Promise<ModelRead<T>>;
30
+ retrieve(params: ModelRetrieveParams & ModelReadOptions): Promise<T | undefined>;
48
31
  list(options?: ServerReadOptions<T>): Promise<T[]>;
49
32
  /**
50
33
  * Creates a row and returns the confirmed server row, including any
@@ -52,7 +35,7 @@ export interface HttpModelClient<T, C = T> {
52
35
  * id that already exists is idempotent: the existing row is returned unchanged.
53
36
  */
54
37
  create(params: ModelCreateParams<T, C>): Promise<T>;
55
- update(params: ModelUpdateParams<C>): Promise<CommitReceipt>;
38
+ update(params: ModelUpdateParams<T>): Promise<T>;
56
39
  /**
57
40
  * Updates a row with a function of its latest value — `update(id, current =>
58
41
  * next)`, the data-layer equivalent of a `setState(prev => next)` reducer. The
@@ -62,8 +45,8 @@ export interface HttpModelClient<T, C = T> {
62
45
  * lands or throws `AbloContentionError` once its retry budget is spent. Return
63
46
  * `null` or `undefined` from the updater to skip the write.
64
47
  */
65
- update(id: string, updater: ModelUpdater<T>, options?: ContentionOptions): Promise<CommitReceipt | undefined>;
66
- delete(params: ModelDeleteParams<T>): Promise<CommitReceipt>;
48
+ update(id: string, updater: ModelUpdater<T>, options?: ContentionOptions): Promise<T | undefined>;
49
+ delete(params: ModelDeleteParams<T>): Promise<void>;
67
50
  claim: HttpClaimApi<T>;
68
51
  }
69
52
  /**
@@ -79,6 +62,8 @@ export type AbloHttpClient<S extends SchemaRecord> = {
79
62
  } & {
80
63
  /** Runs one-time setup, such as registering a configured `databaseUrl` data source, before the client is used. It also runs lazily ahead of the first request, so calling it yourself is optional. */
81
64
  ready(): Promise<void>;
65
+ /** Replays every pending durable HTTP write in seal order and waits for settlement. */
66
+ waitForFlush(): Promise<void>;
82
67
  readonly commits: CommitResource;
83
68
  dispose(): Promise<void>;
84
69
  /** Resolves the bearer credential this client authenticates with, or `null` if none is set. */
@@ -88,17 +73,8 @@ export type AbloHttpClient<S extends SchemaRecord> = {
88
73
  * request, so it is available here even though the local-cache reads are not.
89
74
  * Pass `{ user }` to mint an end-user key (`ek_`) or `{ agent, can }` to mint a
90
75
  * scoped agent key (`rk_`). See {@link CreateSessionParams}.
91
- */
76
+ */
92
77
  readonly sessions: {
93
78
  create(params: CreateSessionParams<S>): Promise<AbloSession>;
94
79
  };
95
- /** Looks up a model client by name, for when the model name is only known at runtime. */
96
- model<T = Record<string, unknown>>(name: string): HttpModelClient<T>;
97
80
  };
98
- /**
99
- * Builds the stateless, typed HTTP client. Each `client.<model>` resolves to the
100
- * protocol client's model accessor, while `commits`, `dispose`, and the other
101
- * protocol members pass through unchanged. No socket is ever opened; the bearer
102
- * credential is the identity.
103
- */
104
- export declare function createAbloHttpClient<S extends SchemaRecord>(options: AbloHttpClientOptions<S>): AbloHttpClient<S>;
@@ -12,52 +12,130 @@
12
12
  * where the credential itself carries identity and the server resolves it on
13
13
  * every request.
14
14
  *
15
- * Under the hood this wraps the schema-agnostic protocol client that
16
- * {@link createProtocolClient} returns in a typed proxy. The protocol client
17
- * commits over `POST /v1/commits` and reads over HTTP, authenticating with the
18
- * bearer token each time; its model access is string-keyed (`api.model('slides')`).
19
- * The proxy gives server code the same typed `client.<model>` surface the
20
- * stateful client offers, over stateless transport.
15
+ * Under the hood this wraps one schema-agnostic protocol client in a typed
16
+ * proxy. That protocol layer keeps transport envelopes and string-keyed model
17
+ * routing private; application code gets the same `client.<model>` surface the
18
+ * stateful client offers.
21
19
  */
22
- import { createProtocolClient, } from './ApiClient.js';
20
+ import { createHttpTransport, } from './httpTransport.js';
21
+ import { AbloConnectionError, AbloValidationError } from '../errors.js';
23
22
  /**
24
- * Names on the underlying protocol client that pass straight through the proxy.
25
- * This set intentionally leaves out names that collide with common schema models
26
- * `tasks`, `claims`, `capabilities`, `agent` — so that `client.tasks` resolves to
27
- * the schema model named `tasks` rather than a protocol resource. Only lifecycle
28
- * methods and the genuinely protocol-level members belong here.
23
+ * Private transport members that pass straight through the facade. Everything
24
+ * else must be a declared schema model; there is no dynamic fallback namespace.
29
25
  */
30
26
  const PROTOCOL_MEMBERS = new Set([
31
27
  'ready',
32
28
  'waitForFlush',
33
29
  'dispose',
34
- 'purge',
35
30
  'commits',
36
- 'model',
37
31
  'getAuthToken',
38
32
  'sessions',
39
33
  ]);
34
+ /** Narrows a bare property name to a transport key so the facade can index it typed. */
35
+ function isProtocolMember(prop) {
36
+ return PROTOCOL_MEMBERS.has(prop);
37
+ }
38
+ /**
39
+ * Adapts one low-level protocol model to the public typed model contract. The
40
+ * protocol deliberately retains `{ data, stamp, claims }` reads and commit
41
+ * receipts because functional updates need them. This is the single boundary
42
+ * that unwraps those transport details for application code.
43
+ */
44
+ function createHttpModelClient(protocol) {
45
+ async function readRow(id) {
46
+ const read = await protocol.retrieve({ id });
47
+ return read.data;
48
+ }
49
+ async function requireUpdatedRow(id) {
50
+ const row = await readRow(id);
51
+ if (row === undefined) {
52
+ throw new AbloConnectionError(`update settled but ${id} could not be read back from the server.`, { code: 'commit_no_result' });
53
+ }
54
+ return row;
55
+ }
56
+ // The protocol resource is schema-agnostic and types claim handles as records.
57
+ // The typed facade carries the same handle with its row type attached; runtime
58
+ // mutation code only reads the handle identity. Normalize that one generic
59
+ // boundary here instead of leaking casts into every model method.
60
+ function mutationOptions(params) {
61
+ const { claim, ...rest } = params;
62
+ return {
63
+ ...rest,
64
+ ...(claim !== undefined ? { claim } : {}),
65
+ };
66
+ }
67
+ async function update(arg, updater, options) {
68
+ if (typeof arg === 'string') {
69
+ if (!updater) {
70
+ throw new AbloValidationError('Functional update requires an updater function.', { code: 'write_options_invalid' });
71
+ }
72
+ const receipt = await protocol.update(arg, updater, options);
73
+ return receipt === undefined ? undefined : requireUpdatedRow(arg);
74
+ }
75
+ await protocol.update({
76
+ ...mutationOptions(arg),
77
+ data: arg.data,
78
+ });
79
+ return requireUpdatedRow(arg.id);
80
+ }
81
+ return {
82
+ async retrieve(params) {
83
+ const read = await protocol.retrieve(params);
84
+ return read.data;
85
+ },
86
+ list: (options) => protocol.list(options),
87
+ async create(params) {
88
+ const row = await protocol.create({
89
+ ...mutationOptions(params),
90
+ data: params.data,
91
+ });
92
+ return row;
93
+ },
94
+ update,
95
+ async delete(params) {
96
+ await protocol.delete(mutationOptions(params));
97
+ },
98
+ claim: protocol.claim,
99
+ };
100
+ }
40
101
  /**
41
102
  * Builds the stateless, typed HTTP client. Each `client.<model>` resolves to the
42
103
  * protocol client's model accessor, while `commits`, `dispose`, and the other
43
104
  * protocol members pass through unchanged. No socket is ever opened; the bearer
44
105
  * credential is the identity.
45
106
  */
107
+ /** @internal Constructed only through the public `Ablo({ transport: 'http' })` factory. */
46
108
  export function createAbloHttpClient(options) {
47
- // The schema is type-level only; the protocol client is schema-agnostic.
48
- const { schema: _schema, ...rest } = options;
49
- const api = createProtocolClient({ ...rest, schema: null });
50
- const facade = new Proxy(api, {
51
- get(target, prop) {
109
+ const { schema, ...rest } = options;
110
+ const modelTypenames = Object.fromEntries(Object.entries(schema.models).map(([key, definition]) => [
111
+ key,
112
+ definition.typename ?? key,
113
+ ]));
114
+ const transport = createHttpTransport({
115
+ ...rest,
116
+ modelTypenames,
117
+ });
118
+ const schemaModels = new Set(Object.keys(schema.models));
119
+ const models = new Map();
120
+ const model = (name) => {
121
+ const cached = models.get(name);
122
+ if (cached)
123
+ return cached;
124
+ const created = createHttpModelClient(transport.model(name));
125
+ models.set(name, created);
126
+ return created;
127
+ };
128
+ const facade = new Proxy({}, {
129
+ get(_target, prop) {
52
130
  if (typeof prop !== 'string')
53
- return Reflect.get(target, prop);
131
+ return undefined;
54
132
  // Real protocol members pass through unchanged.
55
- if (PROTOCOL_MEMBERS.has(prop) && prop in target)
56
- return Reflect.get(target, prop);
57
- // Anything else is a typed model accessor → the string-keyed protocol model
58
- // (which implements retrieve/list/create/update/delete/claim every method
59
- // `HttpModelClient` declares).
60
- return api.model(prop);
133
+ if (isProtocolMember(prop)) {
134
+ return transport[prop];
135
+ }
136
+ // Only schema models become model accessors. A typo or retired top-level
137
+ // member resolves to undefined instead of manufacturing a plausible client.
138
+ return schemaModels.has(prop) ? model(prop) : undefined;
61
139
  },
62
140
  });
63
141
  // A single boundary cast. `AbloHttpClient<S>` declares only what the model
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Private HTTP protocol client behind `Ablo({ schema, transport: 'http' })`.
3
+ * It carries no object pool, local database, or WebSocket and maps Model,
4
+ * Claim, and Commit protocol shapes directly to server routes. The typed
5
+ * facade in `httpClient.ts` is the application boundary; this module owns
6
+ * transport envelopes, watermarks, replay, and route details.
7
+ */
8
+ export {};