@concavejs/runtime-cf-base 0.0.1-alpha.7 → 0.0.1-alpha.9

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.
@@ -0,0 +1,13 @@
1
+ import type { BlobStore, StorageOptions, StorageMetadata } from "@concavejs/core/abstractions";
2
+ /**
3
+ * BlobStore RPC surface for service-binding isolation.
4
+ * Provides async delegation methods for blob storage operations.
5
+ */
6
+ export declare class BlobStoreRpc {
7
+ private readonly blobstore;
8
+ constructor(blobstore: BlobStore);
9
+ store(buffer: ArrayBuffer, options?: StorageOptions): Promise<StorageMetadata>;
10
+ get(storageId: string): Promise<ArrayBuffer | null>;
11
+ delete(storageId: string): Promise<void>;
12
+ getUrl(storageId: string): Promise<string | null>;
13
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * BlobStore RPC surface for service-binding isolation.
3
+ * Provides async delegation methods for blob storage operations.
4
+ */
5
+ export class BlobStoreRpc {
6
+ blobstore;
7
+ constructor(blobstore) {
8
+ this.blobstore = blobstore;
9
+ }
10
+ async store(buffer, options) {
11
+ return this.blobstore.store(buffer, options);
12
+ }
13
+ async get(storageId) {
14
+ const result = await this.blobstore.get(storageId);
15
+ if (result === null)
16
+ return null;
17
+ if (result instanceof Blob)
18
+ return result.arrayBuffer();
19
+ return result;
20
+ }
21
+ async delete(storageId) {
22
+ return this.blobstore.delete(storageId);
23
+ }
24
+ async getUrl(storageId) {
25
+ return this.blobstore.getUrl(storageId);
26
+ }
27
+ }
@@ -63,6 +63,10 @@ export declare class ConcaveDOBase extends DurableObject {
63
63
  env: any;
64
64
  protected scheduler: ScheduledFunctionExecutor;
65
65
  protected cronExecutor: CronExecutor;
66
+ private readonly docStoreRpc;
67
+ private readonly blobStoreRpc;
68
+ private readonly syncNotifier;
69
+ private schedulerManager;
66
70
  constructor(state: DurableObjectState, env: any, config: ConcaveDOConfig);
67
71
  /**
68
72
  * Initialize scheduler and cron executor
@@ -14,6 +14,10 @@ import { runAsClientCall, runAsServerCall } from "@concavejs/core/udf";
14
14
  import { ScheduledFunctionExecutor, CronExecutor } from "@concavejs/core";
15
15
  import { resolveAuthContext } from "@concavejs/core/http";
16
16
  import { AdminAuthError, identityFromToken, isAdminToken, isSystemToken, JWTValidationError, resolveAdminAuthConfigFromEnv, resolveJwtValidationConfigFromEnv, resolveSystemAuthConfigFromEnv, setAdminAuthConfig, setJwtValidationConfig, setSystemAuthConfig, SystemAuthError, } from "@concavejs/core/auth";
17
+ import { DocStoreRpc } from "./docstore-rpc";
18
+ import { BlobStoreRpc } from "./blobstore-rpc";
19
+ import { SyncNotifier } from "./sync-notifier";
20
+ import { SchedulerManager } from "./scheduler-manager";
17
21
  const VERSIONED_API_PREFIX = /^\/api\/\d+\.\d+(?:\.\d+)?(?=\/|$)/;
18
22
  function stripApiVersionPrefix(pathname) {
19
23
  return pathname.replace(VERSIONED_API_PREFIX, "/api");
@@ -86,6 +90,10 @@ export class ConcaveDOBase extends DurableObject {
86
90
  env;
87
91
  scheduler;
88
92
  cronExecutor;
93
+ docStoreRpc;
94
+ blobStoreRpc;
95
+ syncNotifier;
96
+ schedulerManager;
89
97
  constructor(state, env, config) {
90
98
  super(state, env);
91
99
  this.doState = state;
@@ -108,6 +116,9 @@ export class ConcaveDOBase extends DurableObject {
108
116
  this._docstore = config.createDocstore ? config.createDocstore(adapterContext) : new DODocStore(state);
109
117
  // Create BlobStore (allow override for testing or alternative implementations)
110
118
  this._blobstore = config.createBlobstore?.(adapterContext);
119
+ this.docStoreRpc = new DocStoreRpc(this._docstore);
120
+ this.blobStoreRpc = this._blobstore ? new BlobStoreRpc(this._blobstore) : null;
121
+ this.syncNotifier = new SyncNotifier(state, env);
111
122
  // Create UDF executor from resolved runtime services
112
123
  this.udfExecutor = config.createUdfExecutor({
113
124
  ...adapterContext,
@@ -152,6 +163,7 @@ export class ConcaveDOBase extends DurableObject {
152
163
  notifyWrites,
153
164
  allocateTimestamp,
154
165
  });
166
+ this.schedulerManager = new SchedulerManager(this.scheduler, this.cronExecutor, this.doState);
155
167
  }
156
168
  /**
157
169
  * Discover and sync cron specs during DO initialization.
@@ -223,7 +235,9 @@ export class ConcaveDOBase extends DurableObject {
223
235
  const exec = () => this.execute(path, convexArgs, type, auth, componentPath, requestId, parsedSnapshotTimestamp);
224
236
  const result = caller === "server" ? await runAsServerCall(exec, path) : await runAsClientCall(exec);
225
237
  if (type === "mutation" || type === "action") {
226
- this.doState.waitUntil(this.reschedule());
238
+ this.doState.waitUntil(this.reschedule().catch((error) => {
239
+ console.error("[ConcaveDO] Failed to reschedule alarm", error?.message ?? error);
240
+ }));
227
241
  }
228
242
  const writtenTables = writtenTablesFromRanges(result.writtenRanges) ?? [];
229
243
  const responseBody = {
@@ -240,8 +254,9 @@ export class ConcaveDOBase extends DurableObject {
240
254
  }
241
255
  catch (e) {
242
256
  console.error(e);
243
- return new Response("Internal Server Error", {
244
- headers: this.corsHeaders(request),
257
+ const errorMessage = e?.message ?? "Internal Server Error";
258
+ return new Response(JSON.stringify({ error: errorMessage }), {
259
+ headers: { "Content-Type": "application/json", ...this.corsHeaders(request) },
245
260
  status: 500,
246
261
  });
247
262
  }
@@ -288,170 +303,103 @@ export class ConcaveDOBase extends DurableObject {
288
303
  * Handle scheduled function alarms
289
304
  */
290
305
  async alarm() {
291
- try {
292
- const scheduledResult = await this.scheduler.runDueJobs();
293
- const cronResult = await this.cronExecutor.runDueJobs();
294
- const nextTimes = [scheduledResult.nextScheduledTime, cronResult.nextScheduledTime].filter((t) => t !== null);
295
- if (nextTimes.length === 0) {
296
- await this.doState.storage.deleteAlarm();
297
- }
298
- else {
299
- await this.doState.storage.setAlarm(Math.min(...nextTimes));
300
- }
301
- }
302
- catch (error) {
303
- console.error("[ConcaveDO] Alarm handler failed:", error?.message ?? error);
304
- // Re-check for pending jobs so we don't lose scheduled work
305
- try {
306
- await this.reschedule();
307
- }
308
- catch (rescheduleError) {
309
- console.error("[ConcaveDO] Reschedule after alarm failure also failed:", rescheduleError?.message);
310
- }
311
- }
306
+ return this.schedulerManager.handleAlarm();
312
307
  }
313
308
  /**
314
309
  * Reschedule alarms
315
310
  */
316
311
  async reschedule() {
317
- const scheduledTime = await this.scheduler.getNextScheduledTime();
318
- const cronTime = await this.cronExecutor.getNextScheduledTime();
319
- const nextTimes = [scheduledTime, cronTime].filter((t) => t !== null);
320
- if (nextTimes.length === 0) {
321
- await this.doState.storage.deleteAlarm();
322
- }
323
- else {
324
- await this.doState.storage.setAlarm(Math.min(...nextTimes));
325
- }
312
+ return this.schedulerManager.reschedule();
326
313
  }
327
314
  /**
328
315
  * Sync cron specs
329
316
  */
330
317
  async syncCronSpecs(cronSpecs) {
331
- await this.cronExecutor.syncCronSpecs(cronSpecs);
332
- await this.reschedule();
318
+ return this.schedulerManager.syncCronSpecs(cronSpecs);
333
319
  }
334
320
  // =============================================================================
335
321
  // DocStore RPC Methods - Direct delegation to _docstore
336
322
  // =============================================================================
337
323
  async setupSchema(options) {
338
- return this._docstore.setupSchema(options);
324
+ return this.docStoreRpc.setupSchema(options);
339
325
  }
340
326
  async write(documents, indexes, conflictStrategy) {
341
- return this._docstore.write(documents, indexes, conflictStrategy);
327
+ return this.docStoreRpc.write(documents, indexes, conflictStrategy);
342
328
  }
343
329
  async get(id, readTimestamp) {
344
- return this._docstore.get(id, readTimestamp);
330
+ return this.docStoreRpc.get(id, readTimestamp);
345
331
  }
346
332
  async scan(table, readTimestamp) {
347
- return this._docstore.scan(table, readTimestamp);
333
+ return this.docStoreRpc.scan(table, readTimestamp);
348
334
  }
349
335
  async scanPaginated(table, cursor, limit, order, readTimestamp) {
350
- return this._docstore.scanPaginated(table, cursor, limit, order, readTimestamp);
336
+ return this.docStoreRpc.scanPaginated(table, cursor, limit, order, readTimestamp);
351
337
  }
352
338
  /**
353
339
  * Generators return arrays over RPC (cannot stream async generators)
354
340
  */
355
341
  async index_scan(indexId, tabletId, readTimestamp, interval, order) {
356
- const results = [];
357
- for await (const item of this._docstore.index_scan(indexId, tabletId, readTimestamp, interval, order)) {
358
- results.push(item);
359
- }
360
- return results;
342
+ return this.docStoreRpc.index_scan(indexId, tabletId, readTimestamp, interval, order);
361
343
  }
362
344
  /**
363
345
  * Generators return arrays over RPC (cannot stream async generators)
364
346
  */
365
347
  async load_documents(range, order) {
366
- const results = [];
367
- for await (const item of this._docstore.load_documents(range, order)) {
368
- results.push(item);
369
- }
370
- return results;
348
+ return this.docStoreRpc.load_documents(range, order);
371
349
  }
372
350
  async count(table) {
373
- return this._docstore.count(table);
351
+ return this.docStoreRpc.count(table);
374
352
  }
375
353
  async search(indexId, searchQuery, filters, options) {
376
- return this._docstore.search(indexId, searchQuery, filters, options);
354
+ return this.docStoreRpc.search(indexId, searchQuery, filters, options);
377
355
  }
378
356
  async vectorSearch(indexId, vector, limit, filters) {
379
- return this._docstore.vectorSearch(indexId, vector, limit, filters);
357
+ return this.docStoreRpc.vectorSearch(indexId, vector, limit, filters);
380
358
  }
381
359
  async getGlobal(key) {
382
- return this._docstore.getGlobal(key);
360
+ return this.docStoreRpc.getGlobal(key);
383
361
  }
384
362
  async writeGlobal(key, value) {
385
- return this._docstore.writeGlobal(key, value);
363
+ return this.docStoreRpc.writeGlobal(key, value);
386
364
  }
387
365
  async previous_revisions(queries) {
388
- const result = await this._docstore.previous_revisions(queries);
389
- return Array.from(result.entries());
366
+ return this.docStoreRpc.previous_revisions(queries);
390
367
  }
391
368
  async previous_revisions_of_documents(queries) {
392
- const result = await this._docstore.previous_revisions_of_documents(queries);
393
- return Array.from(result.entries());
369
+ return this.docStoreRpc.previous_revisions_of_documents(queries);
394
370
  }
395
371
  // =============================================================================
396
372
  // Blobstore RPC Methods - Prefixed to avoid collision with other methods
397
373
  // =============================================================================
398
374
  async blobstoreStore(buffer, options) {
399
- if (!this._blobstore) {
375
+ if (!this.blobStoreRpc) {
400
376
  throw new Error("Blobstore not configured");
401
377
  }
402
- return this._blobstore.store(buffer, options);
378
+ return this.blobStoreRpc.store(buffer, options);
403
379
  }
404
380
  async blobstoreGet(storageId) {
405
- if (!this._blobstore) {
381
+ if (!this.blobStoreRpc) {
406
382
  throw new Error("Blobstore not configured");
407
383
  }
408
- const result = await this._blobstore.get(storageId);
409
- if (result === null)
410
- return null;
411
- if (result instanceof Blob)
412
- return result.arrayBuffer();
413
- return result;
384
+ return this.blobStoreRpc.get(storageId);
414
385
  }
415
386
  async blobstoreDelete(storageId) {
416
- if (!this._blobstore) {
387
+ if (!this.blobStoreRpc) {
417
388
  throw new Error("Blobstore not configured");
418
389
  }
419
- return this._blobstore.delete(storageId);
390
+ return this.blobStoreRpc.delete(storageId);
420
391
  }
421
392
  async blobstoreGetUrl(storageId) {
422
- if (!this._blobstore) {
393
+ if (!this.blobStoreRpc) {
423
394
  throw new Error("Blobstore not configured");
424
395
  }
425
- return this._blobstore.getUrl(storageId);
396
+ return this.blobStoreRpc.getUrl(storageId);
426
397
  }
427
398
  /**
428
399
  * Notify SyncDO of writes for subscription invalidation
429
400
  */
430
401
  async notifySyncDo(writtenRanges, writtenTables, commitTimestamp) {
431
- if (!writtenRanges?.length && !writtenTables?.length) {
432
- return;
433
- }
434
- try {
435
- const instanceName = this.doState.id.name ?? "singleton";
436
- const syncNamespace = this.env?.SYNC_DO;
437
- if (!syncNamespace) {
438
- return;
439
- }
440
- const syncId = syncNamespace.idFromName(instanceName);
441
- const syncStub = syncNamespace.get(syncId);
442
- await syncStub.fetch("http://do/notify", {
443
- method: "POST",
444
- headers: { "Content-Type": "application/json" },
445
- body: JSON.stringify({
446
- writtenRanges,
447
- writtenTables,
448
- commitTimestamp: commitTimestamp ? commitTimestamp.toString() : undefined,
449
- }),
450
- });
451
- }
452
- catch (error) {
453
- console.warn("Failed to notify SyncDO", error?.message ?? error);
454
- }
402
+ return this.syncNotifier.notify(writtenRanges, writtenTables, commitTimestamp);
455
403
  }
456
404
  /**
457
405
  * Get CORS headers for responses
@@ -0,0 +1,46 @@
1
+ import type { DocStore, DocumentLogEntry, DatabaseIndexUpdate, Interval, Order, IndexKeyBytes, LatestDocument, TimestampRange, InternalDocumentId, GlobalKey, DocumentPrevTsQuery, SearchIndexDefinition, VectorIndexDefinition } from "@concavejs/core/docstore";
2
+ import type { JSONValue } from "convex/values";
3
+ /**
4
+ * DocStore RPC surface for service-binding isolation.
5
+ * Provides async delegation methods that flatten generators into arrays
6
+ * for RPC transport (async generators cannot be streamed over service bindings).
7
+ */
8
+ export declare class DocStoreRpc {
9
+ private readonly docstore;
10
+ constructor(docstore: DocStore);
11
+ setupSchema(options?: {
12
+ searchIndexes?: SearchIndexDefinition[];
13
+ vectorIndexes?: VectorIndexDefinition[];
14
+ }): Promise<void>;
15
+ write(documents: DocumentLogEntry[], indexes: Set<{
16
+ ts: bigint;
17
+ update: DatabaseIndexUpdate;
18
+ }>, conflictStrategy: "Error" | "Overwrite"): Promise<void>;
19
+ get(id: InternalDocumentId, readTimestamp?: bigint): Promise<LatestDocument | null>;
20
+ scan(table: string, readTimestamp?: bigint): Promise<LatestDocument[]>;
21
+ scanPaginated(table: string, cursor: string | null, limit: number, order: Order, readTimestamp?: bigint): Promise<{
22
+ documents: LatestDocument[];
23
+ nextCursor: string | null;
24
+ hasMore: boolean;
25
+ }>;
26
+ index_scan(indexId: string, tabletId: string, readTimestamp: bigint, interval: Interval, order: Order): Promise<[IndexKeyBytes, LatestDocument][]>;
27
+ load_documents(range: TimestampRange, order: Order): Promise<DocumentLogEntry[]>;
28
+ count(table: string): Promise<number>;
29
+ search(indexId: string, searchQuery: string, filters: Map<string, unknown>, options?: {
30
+ limit?: number;
31
+ }): Promise<{
32
+ doc: LatestDocument;
33
+ score: number;
34
+ }[]>;
35
+ vectorSearch(indexId: string, vector: number[], limit: number, filters: Map<string, string>): Promise<{
36
+ doc: LatestDocument;
37
+ score: number;
38
+ }[]>;
39
+ getGlobal(key: GlobalKey): Promise<JSONValue | null>;
40
+ writeGlobal(key: GlobalKey, value: JSONValue): Promise<void>;
41
+ previous_revisions(queries: Set<{
42
+ id: InternalDocumentId;
43
+ ts: bigint;
44
+ }>): Promise<[string, DocumentLogEntry][]>;
45
+ previous_revisions_of_documents(queries: Set<DocumentPrevTsQuery>): Promise<[string, DocumentLogEntry][]>;
46
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * DocStore RPC surface for service-binding isolation.
3
+ * Provides async delegation methods that flatten generators into arrays
4
+ * for RPC transport (async generators cannot be streamed over service bindings).
5
+ */
6
+ export class DocStoreRpc {
7
+ docstore;
8
+ constructor(docstore) {
9
+ this.docstore = docstore;
10
+ }
11
+ async setupSchema(options) {
12
+ return this.docstore.setupSchema(options);
13
+ }
14
+ async write(documents, indexes, conflictStrategy) {
15
+ return this.docstore.write(documents, indexes, conflictStrategy);
16
+ }
17
+ async get(id, readTimestamp) {
18
+ return this.docstore.get(id, readTimestamp);
19
+ }
20
+ async scan(table, readTimestamp) {
21
+ return this.docstore.scan(table, readTimestamp);
22
+ }
23
+ async scanPaginated(table, cursor, limit, order, readTimestamp) {
24
+ return this.docstore.scanPaginated(table, cursor, limit, order, readTimestamp);
25
+ }
26
+ async index_scan(indexId, tabletId, readTimestamp, interval, order) {
27
+ const results = [];
28
+ for await (const item of this.docstore.index_scan(indexId, tabletId, readTimestamp, interval, order)) {
29
+ results.push(item);
30
+ }
31
+ return results;
32
+ }
33
+ async load_documents(range, order) {
34
+ const results = [];
35
+ for await (const item of this.docstore.load_documents(range, order)) {
36
+ results.push(item);
37
+ }
38
+ return results;
39
+ }
40
+ async count(table) {
41
+ return this.docstore.count(table);
42
+ }
43
+ async search(indexId, searchQuery, filters, options) {
44
+ return this.docstore.search(indexId, searchQuery, filters, options);
45
+ }
46
+ async vectorSearch(indexId, vector, limit, filters) {
47
+ return this.docstore.vectorSearch(indexId, vector, limit, filters);
48
+ }
49
+ async getGlobal(key) {
50
+ return this.docstore.getGlobal(key);
51
+ }
52
+ async writeGlobal(key, value) {
53
+ return this.docstore.writeGlobal(key, value);
54
+ }
55
+ async previous_revisions(queries) {
56
+ const result = await this.docstore.previous_revisions(queries);
57
+ return Array.from(result.entries());
58
+ }
59
+ async previous_revisions_of_documents(queries) {
60
+ const result = await this.docstore.previous_revisions_of_documents(queries);
61
+ return Array.from(result.entries());
62
+ }
63
+ }
@@ -0,0 +1,19 @@
1
+ import type { ScheduledFunctionExecutor, CronExecutor } from "@concavejs/core";
2
+ /**
3
+ * Manages scheduled function and cron job execution.
4
+ * Extracted from ConcaveDOBase for single-responsibility.
5
+ */
6
+ export declare class SchedulerManager {
7
+ private readonly scheduler;
8
+ private readonly cronExecutor;
9
+ private readonly doState;
10
+ constructor(scheduler: ScheduledFunctionExecutor, cronExecutor: CronExecutor, doState: {
11
+ storage: {
12
+ setAlarm(time: number): Promise<void>;
13
+ deleteAlarm(): Promise<void>;
14
+ };
15
+ });
16
+ handleAlarm(): Promise<void>;
17
+ reschedule(): Promise<void>;
18
+ syncCronSpecs(cronSpecs: Record<string, any>): Promise<void>;
19
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Manages scheduled function and cron job execution.
3
+ * Extracted from ConcaveDOBase for single-responsibility.
4
+ */
5
+ export class SchedulerManager {
6
+ scheduler;
7
+ cronExecutor;
8
+ doState;
9
+ constructor(scheduler, cronExecutor, doState) {
10
+ this.scheduler = scheduler;
11
+ this.cronExecutor = cronExecutor;
12
+ this.doState = doState;
13
+ }
14
+ async handleAlarm() {
15
+ try {
16
+ const scheduledResult = await this.scheduler.runDueJobs();
17
+ const cronResult = await this.cronExecutor.runDueJobs();
18
+ const nextTimes = [scheduledResult.nextScheduledTime, cronResult.nextScheduledTime].filter((t) => t !== null);
19
+ if (nextTimes.length === 0) {
20
+ await this.doState.storage.deleteAlarm();
21
+ }
22
+ else {
23
+ await this.doState.storage.setAlarm(Math.min(...nextTimes));
24
+ }
25
+ }
26
+ catch (error) {
27
+ console.error("[ConcaveDO] Alarm handler failed:", error?.message ?? error);
28
+ try {
29
+ await this.reschedule();
30
+ }
31
+ catch (rescheduleError) {
32
+ console.error("[ConcaveDO] Reschedule after alarm failure also failed:", rescheduleError?.message);
33
+ }
34
+ }
35
+ }
36
+ async reschedule() {
37
+ const scheduledTime = await this.scheduler.getNextScheduledTime();
38
+ const cronTime = await this.cronExecutor.getNextScheduledTime();
39
+ const nextTimes = [scheduledTime, cronTime].filter((t) => t !== null);
40
+ if (nextTimes.length === 0) {
41
+ await this.doState.storage.deleteAlarm();
42
+ }
43
+ else {
44
+ const alarmTime = Math.min(...nextTimes);
45
+ await this.doState.storage.setAlarm(alarmTime);
46
+ }
47
+ }
48
+ async syncCronSpecs(cronSpecs) {
49
+ await this.cronExecutor.syncCronSpecs(cronSpecs);
50
+ await this.reschedule();
51
+ }
52
+ }
@@ -0,0 +1,16 @@
1
+ import type { SerializedKeyRange } from "@concavejs/core/queryengine";
2
+ /**
3
+ * Notifies the SyncDO of writes for subscription invalidation.
4
+ * Extracted from ConcaveDOBase for single-responsibility.
5
+ */
6
+ export declare class SyncNotifier {
7
+ private readonly doState;
8
+ private readonly env;
9
+ constructor(doState: {
10
+ id: {
11
+ name: string | null;
12
+ toString(): string;
13
+ };
14
+ }, env: any);
15
+ notify(writtenRanges?: SerializedKeyRange[], writtenTables?: string[], commitTimestamp?: bigint): Promise<void>;
16
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Notifies the SyncDO of writes for subscription invalidation.
3
+ * Extracted from ConcaveDOBase for single-responsibility.
4
+ */
5
+ export class SyncNotifier {
6
+ doState;
7
+ env;
8
+ constructor(doState, env) {
9
+ this.doState = doState;
10
+ this.env = env;
11
+ }
12
+ async notify(writtenRanges, writtenTables, commitTimestamp) {
13
+ if (!writtenRanges?.length && !writtenTables?.length) {
14
+ return;
15
+ }
16
+ try {
17
+ const instanceName = this.doState.id.name ?? "singleton";
18
+ const syncNamespace = this.env?.SYNC_DO;
19
+ if (!syncNamespace) {
20
+ return;
21
+ }
22
+ const syncId = syncNamespace.idFromName(instanceName);
23
+ const syncStub = syncNamespace.get(syncId);
24
+ await syncStub.fetch("http://do/notify", {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify({
28
+ writtenRanges,
29
+ writtenTables,
30
+ commitTimestamp: commitTimestamp ? commitTimestamp.toString() : undefined,
31
+ }),
32
+ });
33
+ }
34
+ catch (error) {
35
+ console.warn("Failed to notify SyncDO", error?.message ?? error);
36
+ }
37
+ }
38
+ }
@@ -1,7 +1,38 @@
1
+ import type { SerializedKeyRange } from "@concavejs/core/queryengine";
1
2
  /** Minimal Env shape needed by the HTTP API handler */
2
3
  interface Env {
3
4
  CONCAVE_DO: DurableObjectNamespace;
4
5
  SYNC_DO: DurableObjectNamespace;
6
+ SYNC_NOTIFY_QUEUE?: QueueLike;
5
7
  }
6
- export declare function handleHttpApiRequest(request: Request, env: Env, ctx: ExecutionContext, instance?: string): Promise<Response>;
8
+ type QueueLike = {
9
+ send(message: unknown): Promise<void>;
10
+ };
11
+ export interface HttpApiRoutingTargets {
12
+ /** Physical ConcaveDO name for execution traffic. Defaults to the logical `instance`. */
13
+ concaveDoName?: string;
14
+ /** Alias for non-DO runtimes to describe the write execution target identifier. */
15
+ concaveTargetName?: string;
16
+ /**
17
+ * Physical SyncDO names to notify after writes.
18
+ * Defaults to `[instance]`.
19
+ */
20
+ syncDoNames?: string[];
21
+ /** Alias for non-DO runtimes to describe sync invalidation targets. */
22
+ syncTargetNames?: string[];
23
+ /**
24
+ * Optional custom write dispatcher.
25
+ * When set, it is responsible for fanning out invalidations (queue, stream, etc.).
26
+ */
27
+ dispatchSyncWrites?: (payload: SyncWriteDispatchPayload) => Promise<void>;
28
+ }
29
+ export interface SyncWriteDispatchPayload {
30
+ logicalInstance: string;
31
+ projectId?: string;
32
+ syncTargets: string[];
33
+ writtenRanges?: SerializedKeyRange[];
34
+ writtenTables?: string[];
35
+ commitTimestamp?: string;
36
+ }
37
+ export declare function handleHttpApiRequest(request: Request, env: Env, ctx: ExecutionContext, instance?: string, routingTargets?: HttpApiRoutingTargets): Promise<Response>;
7
38
  export {};