@abloatai/ablo 0.25.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.
Files changed (244) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +3 -3
  4. package/dist/BaseSyncedStore.d.ts +72 -195
  5. package/dist/BaseSyncedStore.js +240 -625
  6. package/dist/Database.d.ts +8 -9
  7. package/dist/Database.js +9 -14
  8. package/dist/LazyReferenceCollection.js +5 -0
  9. package/dist/Model.js +6 -8
  10. package/dist/ModelRegistry.d.ts +25 -6
  11. package/dist/ModelRegistry.js +12 -6
  12. package/dist/NetworkMonitor.js +1 -1
  13. package/dist/ObjectPool.js +9 -3
  14. package/dist/SyncClient.d.ts +15 -5
  15. package/dist/SyncClient.js +152 -66
  16. package/dist/SyncEngineContext.d.ts +1 -3
  17. package/dist/SyncEngineContext.js +1 -2
  18. package/dist/agent/Agent.d.ts +21 -8
  19. package/dist/agent/Agent.js +26 -13
  20. package/dist/ai-sdk/coordination-context.js +9 -3
  21. package/dist/ai-sdk/wrap.d.ts +1 -1
  22. package/dist/ai-sdk/wrap.js +1 -1
  23. package/dist/auth/credentialPolicy.d.ts +10 -2
  24. package/dist/auth/credentialSource.d.ts +1 -13
  25. package/dist/auth/credentialSource.js +6 -13
  26. package/dist/auth/index.js +3 -3
  27. package/dist/cli.cjs +328 -262
  28. package/dist/client/Ablo.d.ts +35 -764
  29. package/dist/client/Ablo.js +64 -688
  30. package/dist/client/ApiClient.d.ts +14 -1
  31. package/dist/client/ApiClient.js +63 -9
  32. package/dist/client/auth.d.ts +8 -17
  33. package/dist/client/auth.js +42 -6
  34. package/dist/client/consoleLogger.d.ts +36 -0
  35. package/dist/client/consoleLogger.js +45 -0
  36. package/dist/client/createInternalComponents.js +1 -1
  37. package/dist/client/createModelProxy.js +10 -6
  38. package/dist/client/credentialEndpoint.d.ts +63 -0
  39. package/dist/client/credentialEndpoint.js +87 -0
  40. package/dist/client/hostedEndpoints.d.ts +24 -0
  41. package/dist/client/hostedEndpoints.js +24 -0
  42. package/dist/client/httpClient.d.ts +1 -1
  43. package/dist/client/identity.js +2 -1
  44. package/dist/client/modelRegistration.d.ts +14 -0
  45. package/dist/client/modelRegistration.js +321 -0
  46. package/dist/client/options.d.ts +396 -0
  47. package/dist/client/options.js +10 -0
  48. package/dist/client/resourceTypes.d.ts +344 -0
  49. package/dist/client/resourceTypes.js +13 -0
  50. package/dist/client/schemaConfig.d.ts +56 -0
  51. package/dist/client/schemaConfig.js +188 -0
  52. package/dist/client/sessionMint.d.ts +1 -1
  53. package/dist/client/validateAbloOptions.js +1 -1
  54. package/dist/client/wsMutationExecutor.d.ts +33 -0
  55. package/dist/client/wsMutationExecutor.js +77 -0
  56. package/dist/context.js +0 -3
  57. package/dist/coordination/index.d.ts +8 -1
  58. package/dist/coordination/index.js +24 -1
  59. package/dist/coordination/trace.d.ts +0 -1
  60. package/dist/coordination/trace.js +0 -1
  61. package/dist/core/DatabaseManager.d.ts +0 -1
  62. package/dist/core/DatabaseManager.js +26 -24
  63. package/dist/core/QueryProcessor.js +7 -8
  64. package/dist/core/QueryView.d.ts +14 -2
  65. package/dist/core/QueryView.js +4 -5
  66. package/dist/core/StoreManager.d.ts +2 -2
  67. package/dist/core/StoreManager.js +5 -1
  68. package/dist/core/index.d.ts +1 -1
  69. package/dist/core/openIDBWithTimeout.js +15 -11
  70. package/dist/core/query-utils.js +1 -1
  71. package/dist/core/storeContract.d.ts +143 -0
  72. package/dist/core/storeContract.js +16 -0
  73. package/dist/errorCodes.d.ts +11 -2
  74. package/dist/errorCodes.js +155 -137
  75. package/dist/errors.d.ts +18 -7
  76. package/dist/errors.js +14 -1
  77. package/dist/index.js +5 -0
  78. package/dist/interfaces/index.d.ts +6 -20
  79. package/dist/keys/index.js +18 -9
  80. package/dist/mutators/UndoManager.js +6 -1
  81. package/dist/mutators/defineMutators.d.ts +1 -3
  82. package/dist/policy/types.d.ts +1 -1
  83. package/dist/query/client.d.ts +11 -0
  84. package/dist/query/client.js +81 -52
  85. package/dist/react/AbloProvider.d.ts +6 -10
  86. package/dist/react/AbloProvider.js +22 -15
  87. package/dist/react/context.d.ts +2 -118
  88. package/dist/react/useAblo.js +1 -1
  89. package/dist/react/useErrorListener.js +1 -1
  90. package/dist/react/useMutationFailureListener.js +1 -1
  91. package/dist/react/useMutators.js +7 -1
  92. package/dist/react/useReactive.js +0 -1
  93. package/dist/react/useUndoScope.js +2 -2
  94. package/dist/schema/ddlLock.d.ts +39 -0
  95. package/dist/schema/ddlLock.js +51 -0
  96. package/dist/schema/field.js +1 -1
  97. package/dist/schema/index.d.ts +4 -3
  98. package/dist/schema/index.js +15 -5
  99. package/dist/schema/model.d.ts +3 -3
  100. package/dist/schema/model.js +2 -2
  101. package/dist/schema/openapi.js +5 -1
  102. package/dist/schema/queries.d.ts +1 -1
  103. package/dist/schema/queries.js +1 -0
  104. package/dist/schema/residency.d.ts +35 -0
  105. package/dist/schema/residency.js +27 -0
  106. package/dist/schema/roles.d.ts +25 -0
  107. package/dist/schema/roles.js +37 -0
  108. package/dist/schema/schema.d.ts +3 -3
  109. package/dist/schema/schema.js +11 -7
  110. package/dist/schema/serialize.d.ts +8 -8
  111. package/dist/schema/sync-delta-row.d.ts +2 -2
  112. package/dist/schema/sync-delta-row.js +2 -2
  113. package/dist/server/storage-mode.d.ts +8 -1
  114. package/dist/server/storage-mode.js +14 -4
  115. package/dist/source/adapter.d.ts +1 -1
  116. package/dist/source/adapters/drizzle.js +4 -3
  117. package/dist/source/adapters/kysely.js +4 -3
  118. package/dist/source/adapters/memory.js +1 -1
  119. package/dist/source/adapters/prisma.js +5 -4
  120. package/dist/source/conformance.js +10 -8
  121. package/dist/source/connector.js +4 -2
  122. package/dist/source/factory.d.ts +106 -0
  123. package/dist/source/factory.js +273 -0
  124. package/dist/source/index.d.ts +11 -461
  125. package/dist/source/index.js +14 -416
  126. package/dist/source/next.d.ts +1 -1
  127. package/dist/source/next.js +1 -1
  128. package/dist/source/pushQueue.d.ts +1 -1
  129. package/dist/source/pushQueue.js +6 -3
  130. package/dist/source/signing.d.ts +59 -0
  131. package/dist/source/signing.js +142 -0
  132. package/dist/source/types.d.ts +334 -0
  133. package/dist/source/types.js +44 -0
  134. package/dist/stores/ObjectStore.d.ts +1 -1
  135. package/dist/stores/ObjectStore.js +23 -23
  136. package/dist/stores/SyncActionStore.d.ts +1 -1
  137. package/dist/stores/SyncActionStore.js +70 -35
  138. package/dist/surface.d.ts +1 -1
  139. package/dist/surface.js +1 -0
  140. package/dist/sync/AreaOfInterestManager.d.ts +2 -2
  141. package/dist/sync/AreaOfInterestManager.js +1 -1
  142. package/dist/sync/BootstrapHelper.d.ts +1 -3
  143. package/dist/sync/BootstrapHelper.js +29 -8
  144. package/dist/sync/ConnectionManager.d.ts +8 -0
  145. package/dist/sync/ConnectionManager.js +19 -4
  146. package/dist/sync/HydrationCoordinator.d.ts +12 -0
  147. package/dist/sync/HydrationCoordinator.js +25 -5
  148. package/dist/sync/NetworkProbe.d.ts +3 -2
  149. package/dist/sync/NetworkProbe.js +7 -6
  150. package/dist/sync/SyncWebSocket.d.ts +71 -120
  151. package/dist/sync/SyncWebSocket.js +324 -630
  152. package/dist/sync/awaitClaimGrant.js +27 -19
  153. package/dist/sync/bootstrapApply.d.ts +60 -0
  154. package/dist/sync/bootstrapApply.js +65 -0
  155. package/dist/sync/commitFrames.d.ts +43 -0
  156. package/dist/sync/commitFrames.js +94 -0
  157. package/dist/sync/createClaimStream.js +3 -2
  158. package/dist/sync/createSnapshot.d.ts +1 -3
  159. package/dist/sync/credentialLifecycle.d.ts +175 -0
  160. package/dist/sync/credentialLifecycle.js +329 -0
  161. package/dist/sync/deltaPipeline.d.ts +111 -0
  162. package/dist/sync/deltaPipeline.js +262 -0
  163. package/dist/sync/groupChange.d.ts +111 -0
  164. package/dist/sync/groupChange.js +246 -0
  165. package/dist/sync/heartbeat.d.ts +62 -0
  166. package/dist/sync/heartbeat.js +91 -0
  167. package/dist/sync/participants.d.ts +8 -8
  168. package/dist/sync/syncCursor.d.ts +44 -0
  169. package/dist/sync/syncCursor.js +59 -0
  170. package/dist/sync/syncPlan.d.ts +62 -0
  171. package/dist/sync/syncPlan.js +51 -0
  172. package/dist/sync/wsFrameHandlers.d.ts +117 -0
  173. package/dist/sync/wsFrameHandlers.js +378 -0
  174. package/dist/testing/fixtures/bootstrap.d.ts +9 -11
  175. package/dist/testing/fixtures/deltas.d.ts +1 -1
  176. package/dist/testing/fixtures/models.js +1 -0
  177. package/dist/testing/helpers/sync-engine-harness.d.ts +0 -3
  178. package/dist/testing/helpers/sync-engine-harness.js +2 -1
  179. package/dist/testing/index.d.ts +1 -1
  180. package/dist/testing/index.js +1 -1
  181. package/dist/testing/mocks/MockSyncContext.d.ts +1 -17
  182. package/dist/testing/mocks/MockSyncContext.js +1 -32
  183. package/dist/testing/mocks/MockSyncStore.d.ts +4 -4
  184. package/dist/testing/mocks/MockWebSocket.d.ts +2 -2
  185. package/dist/transactions/TransactionQueue.d.ts +20 -56
  186. package/dist/transactions/TransactionQueue.js +101 -487
  187. package/dist/transactions/TransactionStore.d.ts +18 -0
  188. package/dist/transactions/TransactionStore.js +51 -0
  189. package/dist/transactions/coalesceRules.d.ts +34 -0
  190. package/dist/transactions/coalesceRules.js +117 -0
  191. package/dist/transactions/commitPayload.d.ts +134 -0
  192. package/dist/transactions/commitPayload.js +152 -0
  193. package/dist/transactions/deltaConfirmation.d.ts +60 -0
  194. package/dist/transactions/deltaConfirmation.js +223 -0
  195. package/dist/transactions/optimistic.d.ts +24 -0
  196. package/dist/transactions/optimistic.js +45 -0
  197. package/dist/transactions/persistedReplay.d.ts +93 -0
  198. package/dist/transactions/persistedReplay.js +105 -0
  199. package/dist/types/index.d.ts +3 -3
  200. package/dist/types/index.js +0 -5
  201. package/dist/types/modelData.d.ts +12 -0
  202. package/dist/types/modelData.js +11 -0
  203. package/dist/types/participant.d.ts +21 -0
  204. package/dist/types/participant.js +12 -0
  205. package/dist/types/streams.d.ts +8 -14
  206. package/dist/utils/mobx-setup.d.ts +1 -1
  207. package/dist/utils/mobx-setup.js +2 -2
  208. package/dist/wire/errorEnvelope.d.ts +11 -2
  209. package/dist/wire/errorEnvelope.js +14 -2
  210. package/dist/wire/frames.d.ts +90 -0
  211. package/dist/wire/frames.js +55 -1
  212. package/dist/wire/index.d.ts +4 -1
  213. package/dist/wire/index.js +20 -1
  214. package/dist/wire/protocol.d.ts +45 -0
  215. package/dist/wire/protocol.js +45 -0
  216. package/dist/wire/protocolVersion.d.ts +56 -0
  217. package/dist/wire/protocolVersion.js +63 -0
  218. package/docs/api-keys.md +4 -3
  219. package/docs/examples/existing-python-backend.md +3 -3
  220. package/docs/identity.md +4 -4
  221. package/docs/integration-guide.md +1 -1
  222. package/docs/react.md +1 -1
  223. package/docs/sessions.md +5 -7
  224. package/package.json +15 -13
  225. package/dist/client/index.d.ts +0 -36
  226. package/dist/client/index.js +0 -33
  227. package/dist/config/index.d.ts +0 -10
  228. package/dist/config/index.js +0 -12
  229. package/dist/interfaces/headless.d.ts +0 -95
  230. package/dist/interfaces/headless.js +0 -41
  231. package/dist/query/index.d.ts +0 -6
  232. package/dist/query/index.js +0 -5
  233. package/dist/realtime/index.d.ts +0 -10
  234. package/dist/realtime/index.js +0 -9
  235. package/dist/schema/plane.d.ts +0 -23
  236. package/dist/schema/plane.js +0 -19
  237. package/dist/server/next.d.ts +0 -51
  238. package/dist/server/next.js +0 -47
  239. package/dist/sync/OfflineFlush.d.ts +0 -9
  240. package/dist/sync/OfflineFlush.js +0 -22
  241. package/dist/sync/OfflineTransactionStore.d.ts +0 -37
  242. package/dist/sync/OfflineTransactionStore.js +0 -263
  243. package/dist/transactions/index.d.ts +0 -16
  244. 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
+ }