@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.
@@ -60,7 +60,7 @@ function base64ToArrayBuffer(base64) {
60
60
  * Create a storage adapter that routes through the ConcaveDO's storage syscall handler.
61
61
  * This ensures storage operations are properly isolated within the DO.
62
62
  */
63
- function createStorageAdapter(concaveDO, _instance) {
63
+ function createStorageAdapter(concaveDO) {
64
64
  return {
65
65
  store: async (blob) => {
66
66
  const buffer = await blob.arrayBuffer();
@@ -106,26 +106,66 @@ function createStorageAdapter(concaveDO, _instance) {
106
106
  },
107
107
  };
108
108
  }
109
- function createNotifyWrites(env, instance, ctx) {
109
+ function createNotifyWrites(env, syncDoNames, ctx, logicalInstance, projectId, dispatchSyncWrites) {
110
110
  return async (writtenRanges, writtenTables, commitTimestamp) => {
111
111
  if (!writtenRanges?.length && !writtenTables?.length) {
112
112
  return;
113
113
  }
114
- const syncDoId = env.SYNC_DO.idFromName(instance);
115
- const syncDo = env.SYNC_DO.get(syncDoId);
116
114
  const payload = {
115
+ logicalInstance,
116
+ projectId,
117
+ syncTargets: syncDoNames,
117
118
  writtenRanges,
118
119
  writtenTables: writtenTables ?? writtenTablesFromRanges(writtenRanges),
119
120
  commitTimestamp: commitTimestamp ? commitTimestamp.toString() : undefined,
120
121
  };
121
- ctx.waitUntil(syncDo.fetch("http://do/notify", {
122
- method: "POST",
123
- headers: { "Content-Type": "application/json" },
124
- body: JSON.stringify(payload),
125
- }));
122
+ if (dispatchSyncWrites) {
123
+ ctx.waitUntil(dispatchSyncWrites(payload).catch((error) => {
124
+ console.warn("[notifyWrites] Custom sync write dispatch failed", error);
125
+ }));
126
+ return;
127
+ }
128
+ if (isQueueLike(env.SYNC_NOTIFY_QUEUE)) {
129
+ ctx.waitUntil(env.SYNC_NOTIFY_QUEUE.send(payload).catch((error) => {
130
+ console.warn("[notifyWrites] Queue-based sync write dispatch failed", error);
131
+ }));
132
+ return;
133
+ }
134
+ const doPayload = {
135
+ writtenRanges,
136
+ writtenTables: writtenTables ?? writtenTablesFromRanges(writtenRanges),
137
+ commitTimestamp: commitTimestamp ? commitTimestamp.toString() : undefined,
138
+ };
139
+ for (const syncDoName of syncDoNames) {
140
+ const syncDoId = env.SYNC_DO.idFromName(syncDoName);
141
+ const syncDo = env.SYNC_DO.get(syncDoId);
142
+ const headers = {
143
+ "Content-Type": "application/json",
144
+ "X-Concave-Instance": logicalInstance,
145
+ };
146
+ if (projectId) {
147
+ headers["X-Concave-Project-Id"] = projectId;
148
+ }
149
+ ctx.waitUntil(syncDo
150
+ .fetch("http://do/notify", {
151
+ method: "POST",
152
+ headers,
153
+ body: JSON.stringify(doPayload),
154
+ })
155
+ .catch((error) => {
156
+ console.warn("[notifyWrites] Direct SyncDO notify failed", error);
157
+ }));
158
+ }
126
159
  };
127
160
  }
128
- export async function handleHttpApiRequest(request, env, ctx, instance = "singleton") {
161
+ function isQueueLike(value) {
162
+ if (!value || typeof value !== "object") {
163
+ return false;
164
+ }
165
+ const candidate = value;
166
+ return typeof candidate.send === "function";
167
+ }
168
+ export async function handleHttpApiRequest(request, env, ctx, instance = "singleton", routingTargets) {
129
169
  const corsHeaders = computeCorsHeaders(request);
130
170
  const apply = (response) => applyCors(response, corsHeaders);
131
171
  const url = new URL(request.url);
@@ -133,14 +173,19 @@ export async function handleHttpApiRequest(request, env, ctx, instance = "single
133
173
  if (pathParts[0] !== "api") {
134
174
  return apply(new Response("Not found", { status: 404 }));
135
175
  }
136
- console.log(`[handleHttpApiRequest] instance=${instance}`);
137
- const concaveId = env.CONCAVE_DO.idFromName(instance);
138
- const concave = env.CONCAVE_DO.get(concaveId);
176
+ const logicalInstance = instance;
177
+ const concaveDoName = normalizeDoName(routingTargets?.concaveDoName ?? routingTargets?.concaveTargetName, logicalInstance);
178
+ const syncDoNames = normalizeSyncDoNames(routingTargets?.syncDoNames ?? routingTargets?.syncTargetNames, logicalInstance);
179
+ const projectId = request.headers.get("X-Concave-Project-Id") ?? undefined;
180
+ console.log(`[handleHttpApiRequest] logicalInstance=${logicalInstance} concaveDo=${concaveDoName} syncTargets=${syncDoNames.join(",")}`);
181
+ const concaveId = env.CONCAVE_DO.idFromName(concaveDoName);
182
+ const concaveRaw = env.CONCAVE_DO.get(concaveId);
183
+ const concave = wrapConcaveStub(concaveRaw, logicalInstance, projectId);
139
184
  const executor = new ConcaveStubExecutor(concave);
140
185
  const adapter = createClientAdapter(executor);
141
- const notifyWrites = createNotifyWrites(env, instance, ctx);
186
+ const notifyWrites = createNotifyWrites(env, syncDoNames, ctx, logicalInstance, projectId, routingTargets?.dispatchSyncWrites);
142
187
  // Route storage operations through the DO's storage syscall handler
143
- const storageAdapter = createStorageAdapter(concave, instance);
188
+ const storageAdapter = createStorageAdapter(concave);
144
189
  const authHeader = request.headers.get("Authorization");
145
190
  const headerToken = authHeader?.replace(/^Bearer\s+/i, "").trim() || undefined;
146
191
  let headerIdentity;
@@ -294,3 +339,38 @@ export async function handleHttpApiRequest(request, env, ctx, instance = "single
294
339
  }
295
340
  return apply(new Response("Not found", { status: 404 }));
296
341
  }
342
+ function wrapConcaveStub(stub, logicalInstance, projectId) {
343
+ return {
344
+ fetch: async (input, init) => {
345
+ const headers = new Headers(init?.headers);
346
+ headers.set("X-Concave-Instance", logicalInstance);
347
+ if (projectId) {
348
+ headers.set("X-Concave-Project-Id", projectId);
349
+ }
350
+ return stub.fetch(input, {
351
+ ...init,
352
+ headers,
353
+ });
354
+ },
355
+ };
356
+ }
357
+ function normalizeDoName(target, fallback) {
358
+ const trimmed = target?.trim();
359
+ return trimmed && trimmed.length > 0 ? trimmed : fallback;
360
+ }
361
+ function normalizeSyncDoNames(targets, fallback) {
362
+ if (targets === undefined) {
363
+ return [fallback];
364
+ }
365
+ if (targets.length === 0) {
366
+ return [];
367
+ }
368
+ const deduped = new Set();
369
+ for (const target of targets) {
370
+ const trimmed = target.trim();
371
+ if (trimmed.length > 0) {
372
+ deduped.add(trimmed);
373
+ }
374
+ }
375
+ return Array.from(deduped);
376
+ }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export * from "@concavejs/core";
2
2
  export type { ConcaveEnv, ConcaveStorageEnv, ConcaveD1Env } from "./env";
3
3
  export { UdfExecutorRpc } from "./worker/udf-worker";
4
- export { createConcaveWorker, resolveNamespaceBinding, createScopedNamespace, type ConcaveWorkerOptions, type ConcaveWorkerBindings, type ConcaveWorker, } from "./worker/create-concave-worker";
4
+ export { createConcaveWorker, resolveNamespaceBinding, createScopedNamespace, type ConcaveWorkerOptions, type ConcaveWorkerBindings, type ConcaveWorker, type ConcaveWorkerRouteContext, type ConcaveWorkerRouteTargets, } from "./worker/create-concave-worker";
5
5
  export { DEFAULT_INSTANCE_KEY, DEFAULT_INSTANCE_VALUE, DEFAULT_INSTANCE_COOKIE_PATH, resolveInstanceFromRequest, maybeAttachInstanceCookie, readCookieValue, buildInstanceCookie, type InstanceResolution, type InstanceResolutionOptions, type InstanceCookieOptions, } from "./routing/instance";
6
+ export { createCfSyncTopologyRuntime, buildPooledInstanceName as buildSyncPooledInstanceName, parseNodeIds as parseSyncNodeIds, parseNodeIdsByRegion as parseSyncNodeIdsByRegion, normalizeTargetList as normalizeSyncTargetList, type CfSyncTopologyConfig, type BuildShardMapFromReportsOptions as SyncBuildShardMapFromReportsOptions, type ResolveSyncFanoutDoNamesAsyncOptions, type CreateCfSyncTopologyRuntimeOptions, type CfSyncTopologyRuntime, } from "./routing/sync-topology";
6
7
  export { UdfExecInline } from "./udf/executor/inline-executor";
7
8
  export { UdfExecIsolated } from "./udf/executor/isolated-executor";
8
9
  export { ConcaveDOBase, type ConcaveDOConfig, type ConcaveDOAdapterContext, type ConcaveDOExecutorContext, } from "./durable-objects/concave-do-base";
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export { UdfExecutorRpc } from "./worker/udf-worker";
5
5
  export { createConcaveWorker, resolveNamespaceBinding, createScopedNamespace, } from "./worker/create-concave-worker";
6
6
  // Export instance routing helpers
7
7
  export { DEFAULT_INSTANCE_KEY, DEFAULT_INSTANCE_VALUE, DEFAULT_INSTANCE_COOKIE_PATH, resolveInstanceFromRequest, maybeAttachInstanceCookie, readCookieValue, buildInstanceCookie, } from "./routing/instance";
8
+ export { createCfSyncTopologyRuntime, buildPooledInstanceName as buildSyncPooledInstanceName, parseNodeIds as parseSyncNodeIds, parseNodeIdsByRegion as parseSyncNodeIdsByRegion, normalizeTargetList as normalizeSyncTargetList, } from "./routing/sync-topology";
8
9
  // Export UDF executors
9
10
  export { UdfExecInline } from "./udf/executor/inline-executor";
10
11
  export { UdfExecIsolated } from "./udf/executor/isolated-executor";
@@ -0,0 +1,40 @@
1
+ import type { ConcaveWorkerRouteContext, ConcaveWorkerRouteTargets } from "../worker/create-concave-worker";
2
+ import { type BuildShardMapFromReportsOptions as SharedBuildShardMapFromReportsOptions, type SyncShardMap, type SyncTopologyConfig } from "@concavejs/runtime-base";
3
+ export interface CfSyncTopologyConfig extends SyncTopologyConfig {
4
+ syncAffinityKey: string;
5
+ regionKey: string;
6
+ regionByColo: Record<string, string>;
7
+ useColoAsRegion: boolean;
8
+ }
9
+ export interface BuildShardMapFromReportsOptions extends Omit<SharedBuildShardMapFromReportsOptions, "config"> {
10
+ logicalInstance: string;
11
+ env: Record<string, unknown>;
12
+ }
13
+ export interface ResolveSyncFanoutDoNamesAsyncOptions {
14
+ request?: Request;
15
+ projectId?: string;
16
+ }
17
+ export interface CreateCfSyncTopologyRuntimeOptions {
18
+ logPrefix?: string;
19
+ projectHeaderName?: string;
20
+ coordinatorBindingName?: string;
21
+ shardMapKvBindingName?: string;
22
+ resolveProjectId?: (request: Request, env: Record<string, unknown>) => string | undefined;
23
+ buildCoordinatorDoName: (logicalInstance: string, projectId?: string) => string;
24
+ buildShardMapKvKey: (logicalInstance: string, projectId?: string) => string;
25
+ buildShardMapCacheKey?: (logicalInstance: string, projectId?: string) => string;
26
+ }
27
+ export interface CfSyncTopologyRuntime {
28
+ resolveRouteTargets(context: ConcaveWorkerRouteContext): Promise<ConcaveWorkerRouteTargets>;
29
+ resolveSyncFanoutDoNames(logicalInstance: string, env: Record<string, unknown>): string[];
30
+ resolveSyncFanoutDoNamesAsync(logicalInstance: string, env: Record<string, unknown>, options?: ResolveSyncFanoutDoNamesAsyncOptions): Promise<string[]>;
31
+ resolveStaticSyncShardMap(logicalInstance: string, env: Record<string, unknown>, nowMs?: number): SyncShardMap;
32
+ buildShardMapFromReports(options: BuildShardMapFromReportsOptions): SyncShardMap;
33
+ resolveTopologyConfig(env: Record<string, unknown>): CfSyncTopologyConfig;
34
+ resolveRequestRegion(request: Request, env: Record<string, unknown>): string;
35
+ }
36
+ export declare function createCfSyncTopologyRuntime(options: CreateCfSyncTopologyRuntimeOptions): CfSyncTopologyRuntime;
37
+ export declare function buildPooledInstanceName(logicalInstance: string, poolKind: "sync" | "udf", nodeId: string): string;
38
+ export declare function parseNodeIds(raw: unknown): string[];
39
+ export declare function parseNodeIdsByRegion(raw: unknown): Record<string, string[]>;
40
+ export declare function normalizeTargetList(targets: string[] | undefined): string[];