@abloatai/ablo 0.24.0 → 0.26.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/AGENTS.md +5 -3
- package/CHANGELOG.md +33 -0
- package/README.md +45 -36
- package/dist/BaseSyncedStore.d.ts +72 -195
- package/dist/BaseSyncedStore.js +240 -625
- package/dist/Database.d.ts +8 -9
- package/dist/Database.js +9 -14
- package/dist/LazyReferenceCollection.js +5 -0
- package/dist/Model.js +6 -8
- package/dist/ModelRegistry.d.ts +25 -6
- package/dist/ModelRegistry.js +12 -6
- package/dist/NetworkMonitor.js +1 -1
- package/dist/ObjectPool.js +9 -3
- package/dist/SyncClient.d.ts +15 -5
- package/dist/SyncClient.js +152 -66
- package/dist/SyncEngineContext.d.ts +1 -3
- package/dist/SyncEngineContext.js +1 -2
- package/dist/agent/Agent.d.ts +21 -8
- package/dist/agent/Agent.js +26 -13
- package/dist/ai-sdk/coordination-context.js +9 -3
- package/dist/ai-sdk/wrap.d.ts +1 -1
- package/dist/ai-sdk/wrap.js +1 -1
- package/dist/auth/credentialPolicy.d.ts +10 -2
- package/dist/auth/credentialSource.d.ts +1 -13
- package/dist/auth/credentialSource.js +6 -13
- package/dist/auth/index.js +3 -3
- package/dist/cli.cjs +583 -287
- package/dist/client/Ablo.d.ts +35 -764
- package/dist/client/Ablo.js +64 -688
- package/dist/client/ApiClient.d.ts +14 -1
- package/dist/client/ApiClient.js +63 -9
- package/dist/client/auth.d.ts +8 -17
- package/dist/client/auth.js +42 -6
- package/dist/client/consoleLogger.d.ts +36 -0
- package/dist/client/consoleLogger.js +45 -0
- package/dist/client/createInternalComponents.js +1 -1
- package/dist/client/createModelProxy.js +10 -6
- package/dist/client/credentialEndpoint.d.ts +63 -0
- package/dist/client/credentialEndpoint.js +87 -0
- package/dist/client/hostedEndpoints.d.ts +24 -0
- package/dist/client/hostedEndpoints.js +24 -0
- package/dist/client/httpClient.d.ts +1 -1
- package/dist/client/identity.js +2 -1
- package/dist/client/modelRegistration.d.ts +14 -0
- package/dist/client/modelRegistration.js +321 -0
- package/dist/client/options.d.ts +396 -0
- package/dist/client/options.js +10 -0
- package/dist/client/resourceTypes.d.ts +344 -0
- package/dist/client/resourceTypes.js +13 -0
- package/dist/client/schemaConfig.d.ts +56 -0
- package/dist/client/schemaConfig.js +188 -0
- package/dist/client/sessionMint.d.ts +1 -1
- package/dist/client/validateAbloOptions.js +1 -1
- package/dist/client/wsMutationExecutor.d.ts +33 -0
- package/dist/client/wsMutationExecutor.js +77 -0
- package/dist/context.js +0 -3
- package/dist/coordination/index.d.ts +8 -1
- package/dist/coordination/index.js +24 -1
- package/dist/coordination/trace.d.ts +0 -1
- package/dist/coordination/trace.js +0 -1
- package/dist/core/DatabaseManager.d.ts +0 -1
- package/dist/core/DatabaseManager.js +26 -24
- package/dist/core/QueryProcessor.js +7 -8
- package/dist/core/QueryView.d.ts +14 -2
- package/dist/core/QueryView.js +4 -5
- package/dist/core/StoreManager.d.ts +2 -2
- package/dist/core/StoreManager.js +5 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/openIDBWithTimeout.js +15 -11
- package/dist/core/query-utils.js +1 -1
- package/dist/core/storeContract.d.ts +143 -0
- package/dist/core/storeContract.js +16 -0
- package/dist/errorCodes.d.ts +11 -2
- package/dist/errorCodes.js +155 -137
- package/dist/errors.d.ts +18 -7
- package/dist/errors.js +14 -1
- package/dist/index.js +5 -0
- package/dist/interfaces/index.d.ts +6 -20
- package/dist/keys/index.js +18 -9
- package/dist/mutators/UndoManager.js +6 -1
- package/dist/mutators/defineMutators.d.ts +1 -3
- package/dist/policy/types.d.ts +1 -1
- package/dist/query/client.d.ts +11 -0
- package/dist/query/client.js +81 -52
- package/dist/react/AbloProvider.d.ts +6 -10
- package/dist/react/AbloProvider.js +22 -15
- package/dist/react/context.d.ts +2 -118
- package/dist/react/useAblo.js +1 -1
- package/dist/react/useErrorListener.js +1 -1
- package/dist/react/useMutationFailureListener.js +1 -1
- package/dist/react/useMutators.js +7 -1
- package/dist/react/useReactive.js +0 -1
- package/dist/react/useUndoScope.js +2 -2
- package/dist/schema/ddlLock.d.ts +39 -0
- package/dist/schema/ddlLock.js +51 -0
- package/dist/schema/field.js +1 -1
- package/dist/schema/index.d.ts +4 -3
- package/dist/schema/index.js +15 -5
- package/dist/schema/model.d.ts +3 -3
- package/dist/schema/model.js +2 -2
- package/dist/schema/openapi.js +5 -1
- package/dist/schema/queries.d.ts +1 -1
- package/dist/schema/queries.js +1 -0
- package/dist/schema/residency.d.ts +35 -0
- package/dist/schema/residency.js +27 -0
- package/dist/schema/roles.d.ts +25 -0
- package/dist/schema/roles.js +37 -0
- package/dist/schema/schema.d.ts +3 -3
- package/dist/schema/schema.js +11 -7
- package/dist/schema/serialize.d.ts +8 -8
- package/dist/schema/sync-delta-row.d.ts +2 -2
- package/dist/schema/sync-delta-row.js +2 -2
- package/dist/server/storage-mode.d.ts +8 -1
- package/dist/server/storage-mode.js +14 -4
- package/dist/source/adapter.d.ts +1 -1
- package/dist/source/adapters/drizzle.js +4 -3
- package/dist/source/adapters/kysely.js +4 -3
- package/dist/source/adapters/memory.js +1 -1
- package/dist/source/adapters/prisma.js +5 -4
- package/dist/source/conformance.js +10 -8
- package/dist/source/connector.js +4 -2
- package/dist/source/factory.d.ts +106 -0
- package/dist/source/factory.js +273 -0
- package/dist/source/index.d.ts +11 -461
- package/dist/source/index.js +14 -416
- package/dist/source/next.d.ts +1 -1
- package/dist/source/next.js +1 -1
- package/dist/source/pushQueue.d.ts +1 -1
- package/dist/source/pushQueue.js +6 -3
- package/dist/source/signing.d.ts +59 -0
- package/dist/source/signing.js +142 -0
- package/dist/source/types.d.ts +334 -0
- package/dist/source/types.js +44 -0
- package/dist/stores/ObjectStore.d.ts +1 -1
- package/dist/stores/ObjectStore.js +23 -23
- package/dist/stores/SyncActionStore.d.ts +1 -1
- package/dist/stores/SyncActionStore.js +70 -35
- package/dist/surface.d.ts +1 -1
- package/dist/surface.js +1 -0
- package/dist/sync/AreaOfInterestManager.d.ts +2 -2
- package/dist/sync/AreaOfInterestManager.js +1 -1
- package/dist/sync/BootstrapHelper.d.ts +1 -3
- package/dist/sync/BootstrapHelper.js +29 -8
- package/dist/sync/ConnectionManager.d.ts +8 -0
- package/dist/sync/ConnectionManager.js +19 -4
- package/dist/sync/HydrationCoordinator.d.ts +12 -0
- package/dist/sync/HydrationCoordinator.js +25 -5
- package/dist/sync/NetworkProbe.d.ts +3 -2
- package/dist/sync/NetworkProbe.js +7 -6
- package/dist/sync/SyncWebSocket.d.ts +71 -120
- package/dist/sync/SyncWebSocket.js +324 -630
- package/dist/sync/awaitClaimGrant.js +27 -19
- package/dist/sync/bootstrapApply.d.ts +60 -0
- package/dist/sync/bootstrapApply.js +65 -0
- package/dist/sync/commitFrames.d.ts +43 -0
- package/dist/sync/commitFrames.js +94 -0
- package/dist/sync/createClaimStream.js +3 -2
- package/dist/sync/createSnapshot.d.ts +1 -3
- package/dist/sync/credentialLifecycle.d.ts +175 -0
- package/dist/sync/credentialLifecycle.js +329 -0
- package/dist/sync/deltaPipeline.d.ts +111 -0
- package/dist/sync/deltaPipeline.js +262 -0
- package/dist/sync/groupChange.d.ts +111 -0
- package/dist/sync/groupChange.js +246 -0
- package/dist/sync/heartbeat.d.ts +62 -0
- package/dist/sync/heartbeat.js +91 -0
- package/dist/sync/participants.d.ts +8 -8
- package/dist/sync/syncCursor.d.ts +44 -0
- package/dist/sync/syncCursor.js +59 -0
- package/dist/sync/syncPlan.d.ts +62 -0
- package/dist/sync/syncPlan.js +51 -0
- package/dist/sync/wsFrameHandlers.d.ts +117 -0
- package/dist/sync/wsFrameHandlers.js +378 -0
- package/dist/testing/fixtures/bootstrap.d.ts +9 -11
- package/dist/testing/fixtures/deltas.d.ts +1 -1
- package/dist/testing/fixtures/models.js +1 -0
- package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
- package/dist/testing/helpers/sync-engine-harness.js +2 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
- package/dist/testing/mocks/MockSyncContext.js +1 -32
- package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
- package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
- package/dist/transactions/TransactionQueue.d.ts +20 -56
- package/dist/transactions/TransactionQueue.js +101 -487
- package/dist/transactions/TransactionStore.d.ts +18 -0
- package/dist/transactions/TransactionStore.js +51 -0
- package/dist/transactions/coalesceRules.d.ts +34 -0
- package/dist/transactions/coalesceRules.js +117 -0
- package/dist/transactions/commitPayload.d.ts +134 -0
- package/dist/transactions/commitPayload.js +152 -0
- package/dist/transactions/deltaConfirmation.d.ts +60 -0
- package/dist/transactions/deltaConfirmation.js +223 -0
- package/dist/transactions/optimistic.d.ts +24 -0
- package/dist/transactions/optimistic.js +45 -0
- package/dist/transactions/persistedReplay.d.ts +93 -0
- package/dist/transactions/persistedReplay.js +105 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +0 -5
- package/dist/types/modelData.d.ts +12 -0
- package/dist/types/modelData.js +11 -0
- package/dist/types/participant.d.ts +21 -0
- package/dist/types/participant.js +12 -0
- package/dist/types/streams.d.ts +8 -14
- package/dist/utils/mobx-setup.d.ts +1 -1
- package/dist/utils/mobx-setup.js +2 -2
- package/dist/wire/errorEnvelope.d.ts +11 -2
- package/dist/wire/errorEnvelope.js +14 -2
- package/dist/wire/frames.d.ts +90 -0
- package/dist/wire/frames.js +55 -1
- package/dist/wire/index.d.ts +4 -1
- package/dist/wire/index.js +20 -1
- package/dist/wire/protocol.d.ts +45 -0
- package/dist/wire/protocol.js +45 -0
- package/dist/wire/protocolVersion.d.ts +56 -0
- package/dist/wire/protocolVersion.js +63 -0
- package/docs/api-keys.md +4 -3
- package/docs/data-sources.md +12 -5
- package/docs/examples/existing-python-backend.md +3 -3
- package/docs/identity.md +4 -4
- package/docs/integration-guide.md +1 -1
- package/docs/react.md +1 -1
- package/docs/sessions.md +5 -7
- package/llms.txt +4 -2
- package/package.json +15 -13
- package/dist/client/index.d.ts +0 -36
- package/dist/client/index.js +0 -33
- package/dist/config/index.d.ts +0 -10
- package/dist/config/index.js +0 -12
- package/dist/interfaces/headless.d.ts +0 -95
- package/dist/interfaces/headless.js +0 -41
- package/dist/query/index.d.ts +0 -6
- package/dist/query/index.js +0 -5
- package/dist/realtime/index.d.ts +0 -10
- package/dist/realtime/index.js +0 -9
- package/dist/schema/plane.d.ts +0 -23
- package/dist/schema/plane.js +0 -19
- package/dist/server/next.d.ts +0 -51
- package/dist/server/next.js +0 -47
- package/dist/sync/OfflineFlush.d.ts +0 -9
- package/dist/sync/OfflineFlush.js +0 -22
- package/dist/sync/OfflineTransactionStore.d.ts +0 -37
- package/dist/sync/OfflineTransactionStore.js +0 -263
- package/dist/transactions/index.d.ts +0 -16
- package/dist/transactions/index.js +0 -7
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `abloSource()` / `dataSource()` endpoint factory — the customer-owned
|
|
3
|
+
* Data Source handler. Takes the options (schema, apiKey, handlers or ORM
|
|
4
|
+
* adapter), verifies the signed request, enforces per-key scopes, and
|
|
5
|
+
* dispatches the four wire operations (`load`/`list`/`commit`/`events`) to
|
|
6
|
+
* the configured handlers.
|
|
7
|
+
*
|
|
8
|
+
* `AbloSourceOptions` lives here (not `types.ts`) because it references the
|
|
9
|
+
* ORM `DataSourceAdapter` — keeping it with the factory keeps `types.ts` a
|
|
10
|
+
* dependency-free leaf for `adapter.ts`/`contract.ts` to import from.
|
|
11
|
+
*/
|
|
12
|
+
import { changeSetSchema } from './contract.js';
|
|
13
|
+
import { SourceSignatureError, verifyAbloSourceRequest, } from './signing.js';
|
|
14
|
+
function json(data, status = 200) {
|
|
15
|
+
return new Response(JSON.stringify(data), {
|
|
16
|
+
status,
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Serve a request from an ORM `adapter`. Routes the four operations to the adapter
|
|
22
|
+
* interface (`read`/`commit`/`events`) and shapes the wire response. The adapter is the
|
|
23
|
+
* single point of dispatch — no per-model branching here.
|
|
24
|
+
*/
|
|
25
|
+
async function handleViaAdapter(adapter, body, scope) {
|
|
26
|
+
if (body.type === 'load') {
|
|
27
|
+
const rows = await adapter.read({
|
|
28
|
+
kind: 'load',
|
|
29
|
+
model: body.model,
|
|
30
|
+
id: body.id,
|
|
31
|
+
...(scope ? { scope } : {}),
|
|
32
|
+
});
|
|
33
|
+
return json({ row: rows[0] ?? null });
|
|
34
|
+
}
|
|
35
|
+
if (body.type === 'list') {
|
|
36
|
+
const rows = await adapter.read({
|
|
37
|
+
kind: 'list',
|
|
38
|
+
model: body.model,
|
|
39
|
+
...(body.query ? { query: body.query } : {}),
|
|
40
|
+
...(scope ? { scope } : {}),
|
|
41
|
+
});
|
|
42
|
+
return json({ rows });
|
|
43
|
+
}
|
|
44
|
+
if (body.type === 'commit') {
|
|
45
|
+
if (!body.clientTxId) {
|
|
46
|
+
return json({ error: 'source_commit_requires_client_tx_id', message: 'commit requires a clientTxId for idempotency' }, 400);
|
|
47
|
+
}
|
|
48
|
+
const parsed = changeSetSchema.safeParse({
|
|
49
|
+
operations: body.operations,
|
|
50
|
+
clientTxId: body.clientTxId,
|
|
51
|
+
});
|
|
52
|
+
if (!parsed.success) {
|
|
53
|
+
return json({ error: 'source_commit_invalid', message: parsed.error.message }, 400);
|
|
54
|
+
}
|
|
55
|
+
const result = await adapter.commit(parsed.data);
|
|
56
|
+
return json({ rows: result.rows });
|
|
57
|
+
}
|
|
58
|
+
if (body.type === 'events') {
|
|
59
|
+
const page = await adapter.events(body.cursor ?? null, body.limit ?? 100);
|
|
60
|
+
return json({
|
|
61
|
+
events: page.events.map((event) => ({
|
|
62
|
+
id: event.id,
|
|
63
|
+
model: event.model,
|
|
64
|
+
entityId: event.entityId,
|
|
65
|
+
type: event.type,
|
|
66
|
+
...(event.data !== undefined && event.data !== null ? { data: event.data } : {}),
|
|
67
|
+
...(event.organizationId ? { organizationId: event.organizationId } : {}),
|
|
68
|
+
...(event.clientTxId ? { clientTxId: event.clientTxId } : {}),
|
|
69
|
+
...(event.occurredAt !== undefined && event.occurredAt !== null
|
|
70
|
+
? { occurredAt: event.occurredAt }
|
|
71
|
+
: {}),
|
|
72
|
+
})),
|
|
73
|
+
...(page.nextCursor !== null ? { nextCursor: page.nextCursor } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return json({ error: 'unknown_source_request' }, 400);
|
|
77
|
+
}
|
|
78
|
+
async function readBody(request) {
|
|
79
|
+
if (typeof request.text === 'function') {
|
|
80
|
+
const rawBody = await request.text();
|
|
81
|
+
return { rawBody, body: JSON.parse(rawBody) };
|
|
82
|
+
}
|
|
83
|
+
const body = (await request.json());
|
|
84
|
+
return { rawBody: JSON.stringify(body), body };
|
|
85
|
+
}
|
|
86
|
+
async function resolveApiKey(apiKey, context) {
|
|
87
|
+
if (!apiKey)
|
|
88
|
+
return null;
|
|
89
|
+
return typeof apiKey === 'function' ? apiKey(context) : apiKey;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Map a wire request to its scope tag. Each request type corresponds
|
|
93
|
+
* to one scope, so the function is total and exhaustive — adding a
|
|
94
|
+
* new request type forces a new scope tag, which is the right design
|
|
95
|
+
* pressure for keeping the scope vocabulary in sync with the wire.
|
|
96
|
+
*/
|
|
97
|
+
function scopeFor(body) {
|
|
98
|
+
switch (body.type) {
|
|
99
|
+
case 'load':
|
|
100
|
+
return 'load';
|
|
101
|
+
case 'list':
|
|
102
|
+
return 'list';
|
|
103
|
+
case 'commit':
|
|
104
|
+
return 'commit';
|
|
105
|
+
case 'events':
|
|
106
|
+
return 'events';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function normalizeListResult(result) {
|
|
110
|
+
if (Array.isArray(result)) {
|
|
111
|
+
return { rows: result };
|
|
112
|
+
}
|
|
113
|
+
const page = result;
|
|
114
|
+
return page.nextCursor !== undefined
|
|
115
|
+
? { rows: page.rows, nextCursor: page.nextCursor }
|
|
116
|
+
: { rows: page.rows };
|
|
117
|
+
}
|
|
118
|
+
function getModelHandlers(options, model) {
|
|
119
|
+
const grouped = options.models?.[model];
|
|
120
|
+
if (grouped)
|
|
121
|
+
return grouped;
|
|
122
|
+
const direct = options[model];
|
|
123
|
+
return direct;
|
|
124
|
+
}
|
|
125
|
+
function sameModel(operations) {
|
|
126
|
+
const first = operations[0]?.model;
|
|
127
|
+
if (!first)
|
|
128
|
+
return null;
|
|
129
|
+
return operations.every((op) => op.model === first) ? first : null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Create a customer-owned data source endpoint.
|
|
133
|
+
*
|
|
134
|
+
* App code still talks to Ablo with `ablo.files.load/list/update`.
|
|
135
|
+
* This helper is only for customers who keep canonical rows in their own
|
|
136
|
+
* database and want Ablo Cloud to call a narrow, signed endpoint instead
|
|
137
|
+
* of receiving database credentials.
|
|
138
|
+
*/
|
|
139
|
+
export function abloSource(options) {
|
|
140
|
+
return async function handleAbloSource(request) {
|
|
141
|
+
if (request.method !== 'POST') {
|
|
142
|
+
return json({ error: 'method_not_allowed' }, 405);
|
|
143
|
+
}
|
|
144
|
+
let body;
|
|
145
|
+
let rawBody;
|
|
146
|
+
try {
|
|
147
|
+
const parsed = await readBody(request);
|
|
148
|
+
body = parsed.body;
|
|
149
|
+
rawBody = parsed.rawBody;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return json({ error: 'invalid_json' }, 400);
|
|
153
|
+
}
|
|
154
|
+
let signature = null;
|
|
155
|
+
try {
|
|
156
|
+
const apiKey = await resolveApiKey(options.apiKey, {
|
|
157
|
+
request,
|
|
158
|
+
body,
|
|
159
|
+
rawBody,
|
|
160
|
+
});
|
|
161
|
+
if (!apiKey) {
|
|
162
|
+
return json({
|
|
163
|
+
error: 'source_api_key_missing',
|
|
164
|
+
message: 'Data Source apiKey is required',
|
|
165
|
+
}, 401);
|
|
166
|
+
}
|
|
167
|
+
signature = await verifyAbloSourceRequest({
|
|
168
|
+
request,
|
|
169
|
+
body: rawBody,
|
|
170
|
+
apiKey,
|
|
171
|
+
toleranceMs: options.signatureToleranceMs,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
if (err instanceof SourceSignatureError) {
|
|
176
|
+
return json({ error: err.code, message: err.message }, 401);
|
|
177
|
+
}
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
const auth = options.authorize
|
|
181
|
+
? await options.authorize({ request, body, rawBody })
|
|
182
|
+
: undefined;
|
|
183
|
+
// Per-key permission scope check. When `resolveScopes` is set,
|
|
184
|
+
// the customer returns the operation set this key is allowed to
|
|
185
|
+
// invoke; we enforce before any model handler runs.
|
|
186
|
+
if (options.resolveScopes) {
|
|
187
|
+
const required = scopeFor(body);
|
|
188
|
+
const granted = await options.resolveScopes({ auth, request, body });
|
|
189
|
+
const grantedSet = granted instanceof Set ? granted : new Set(granted);
|
|
190
|
+
if (!grantedSet.has(required)) {
|
|
191
|
+
return json({
|
|
192
|
+
error: 'source_forbidden',
|
|
193
|
+
required,
|
|
194
|
+
granted: Array.from(grantedSet),
|
|
195
|
+
}, 403);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const context = {
|
|
199
|
+
auth,
|
|
200
|
+
request,
|
|
201
|
+
messageId: signature?.messageId,
|
|
202
|
+
signedAt: signature?.signedAt,
|
|
203
|
+
...(body.scope ? { scope: body.scope } : {}),
|
|
204
|
+
};
|
|
205
|
+
// Adapter path: when an ORM adapter is configured it serves every operation,
|
|
206
|
+
// consumed at this generic layer (rows are JSON on the wire), so no per-model
|
|
207
|
+
// handler lookup and no typed↔generic boundary.
|
|
208
|
+
if (options.adapter) {
|
|
209
|
+
return handleViaAdapter(options.adapter, body, context.scope);
|
|
210
|
+
}
|
|
211
|
+
if (body.type === 'load') {
|
|
212
|
+
const handlers = getModelHandlers(options, body.model);
|
|
213
|
+
if (!handlers?.load) {
|
|
214
|
+
return json({ error: 'source_load_not_configured', model: body.model }, 404);
|
|
215
|
+
}
|
|
216
|
+
const row = await handlers.load({ id: body.id, context });
|
|
217
|
+
return json({ row });
|
|
218
|
+
}
|
|
219
|
+
if (body.type === 'list') {
|
|
220
|
+
const handlers = getModelHandlers(options, body.model);
|
|
221
|
+
if (!handlers?.list) {
|
|
222
|
+
return json({ error: 'source_list_not_configured', model: body.model }, 404);
|
|
223
|
+
}
|
|
224
|
+
const result = await handlers.list({ query: body.query ?? {}, context });
|
|
225
|
+
const normalized = normalizeListResult(result);
|
|
226
|
+
return json(normalized);
|
|
227
|
+
}
|
|
228
|
+
if (body.type === 'commit') {
|
|
229
|
+
if (options.commit) {
|
|
230
|
+
const result = await options.commit({
|
|
231
|
+
operations: body.operations,
|
|
232
|
+
clientTxId: body.clientTxId,
|
|
233
|
+
context,
|
|
234
|
+
});
|
|
235
|
+
return json(result);
|
|
236
|
+
}
|
|
237
|
+
const model = body.model ?? sameModel(body.operations);
|
|
238
|
+
if (!model) {
|
|
239
|
+
return json({ error: 'source_commit_requires_single_model' }, 400);
|
|
240
|
+
}
|
|
241
|
+
const handlers = getModelHandlers(options, model);
|
|
242
|
+
if (!handlers?.commit) {
|
|
243
|
+
return json({ error: 'source_commit_not_configured', model }, 404);
|
|
244
|
+
}
|
|
245
|
+
const result = await handlers.commit({
|
|
246
|
+
operations: body.operations,
|
|
247
|
+
clientTxId: body.clientTxId,
|
|
248
|
+
context,
|
|
249
|
+
});
|
|
250
|
+
return json(result);
|
|
251
|
+
}
|
|
252
|
+
if (body.type === 'events') {
|
|
253
|
+
if (!options.events) {
|
|
254
|
+
return json({ error: 'source_events_not_configured' }, 404);
|
|
255
|
+
}
|
|
256
|
+
const result = await options.events({
|
|
257
|
+
cursor: body.cursor,
|
|
258
|
+
limit: body.limit,
|
|
259
|
+
context,
|
|
260
|
+
});
|
|
261
|
+
return json({
|
|
262
|
+
events: result.events,
|
|
263
|
+
...(result.nextCursor !== undefined
|
|
264
|
+
? { nextCursor: result.nextCursor }
|
|
265
|
+
: {}),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return json({ error: 'unknown_source_request' }, 400);
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
export function dataSource(options) {
|
|
272
|
+
return abloSource(options);
|
|
273
|
+
}
|