@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.
@@ -1,5 +1,6 @@
1
1
  import type { DurableObjectNamespace, ExecutionContext } from "@cloudflare/workers-types";
2
2
  import { handleHttpApiRequest } from "../http/http-api";
3
+ import type { SyncWriteDispatchPayload } from "../http/http-api";
3
4
  import { type ModuleLoader, type ModuleRegistry } from "@concavejs/core/udf";
4
5
  export interface ConcaveWorkerBindings {
5
6
  getConcaveNamespace: (env: any, ctx: ExecutionContext) => DurableObjectNamespace | undefined;
@@ -25,6 +26,50 @@ export interface ConcaveWorkerOptions {
25
26
  configureModuleLoaders?: (registry: ModuleRegistry) => void;
26
27
  /** Pre-loaded dashboard HTML (avoids bundling dashboard-local) */
27
28
  dashboardHtml?: string;
29
+ /**
30
+ * Optional per-request routing overrides for selecting physical DO targets.
31
+ *
32
+ * `instance` remains the logical tenant/app instance used for auth/data scoping.
33
+ * Route targets can point at a pooled DO name for scaling.
34
+ */
35
+ resolveRouteTargets?: (context: ConcaveWorkerRouteContext) => ConcaveWorkerRouteTargets | Promise<ConcaveWorkerRouteTargets | undefined> | undefined;
36
+ }
37
+ export interface ConcaveWorkerRouteContext<Env = any> {
38
+ request: Request;
39
+ env: Env;
40
+ ctx: ExecutionContext;
41
+ /** Logical instance selected via query/header/cookie routing. */
42
+ instance: string;
43
+ }
44
+ export interface ConcaveWorkerRouteTargets {
45
+ /** Physical ConcaveDO name to route /api and /udf requests to. */
46
+ concaveDoName?: string;
47
+ /** Physical SyncDO name to route /sync websocket connections to. */
48
+ syncDoName?: string;
49
+ /**
50
+ * Physical SyncDO names that should receive mutation write notifications.
51
+ * Defaults to `[syncDoName]`.
52
+ */
53
+ syncNotifyDoNames?: string[];
54
+ /**
55
+ * Structured target descriptor for runtime adapters.
56
+ * `targetName` maps to ConcaveDO in Cloudflare runtime.
57
+ */
58
+ concave?: {
59
+ targetName?: string;
60
+ };
61
+ /**
62
+ * Structured sync target descriptor for runtime adapters.
63
+ * `targetName` maps to SyncDO, and `notifyTargetNames` maps to fanout targets.
64
+ */
65
+ sync?: {
66
+ targetName?: string;
67
+ notifyTargetNames?: string[];
68
+ };
69
+ /**
70
+ * Optional write-dispatch override (queue/stream fanout) for API mutations.
71
+ */
72
+ dispatchSyncWrites?: (payload: SyncWriteDispatchPayload) => Promise<void>;
28
73
  }
29
74
  export declare function resolveNamespaceBinding(env: any, ctx: ExecutionContext, bindingName: string, exportName: string): DurableObjectNamespace | undefined;
30
75
  export declare function createScopedNamespace(ns: DurableObjectNamespace, prefix: string): DurableObjectNamespace;
@@ -22,7 +22,7 @@ export function createScopedNamespace(ns, prefix) {
22
22
  };
23
23
  }
24
24
  export function createConcaveWorker(options) {
25
- const { bindings, instanceKey = DEFAULT_INSTANCE_KEY, cors = true, apiPrefix = "/api", udfPrefix = "/udf", syncPathMatcher = DEFAULT_SYNC_PATH_MATCHER, handleHttpApiRequest: apiHandler = handleHttpApiRequest, moduleLoader, moduleLoaders, configureModuleLoaders, dashboardHtml: injectedDashboardHtml, } = options;
25
+ const { bindings, instanceKey = DEFAULT_INSTANCE_KEY, cors = true, apiPrefix = "/api", udfPrefix = "/udf", syncPathMatcher = DEFAULT_SYNC_PATH_MATCHER, handleHttpApiRequest: apiHandler = handleHttpApiRequest, moduleLoader, moduleLoaders, configureModuleLoaders, dashboardHtml: injectedDashboardHtml, resolveRouteTargets, } = options;
26
26
  let cachedDashboardHtml = injectedDashboardHtml;
27
27
  const explicitLoaders = collectModuleLoaders(moduleLoader, moduleLoaders);
28
28
  if (explicitLoaders.length > 0) {
@@ -49,6 +49,10 @@ export function createConcaveWorker(options) {
49
49
  });
50
50
  const instance = instanceResolution.value;
51
51
  const forwardUrl = new URL(url.toString());
52
+ const routeTargets = (await resolveRouteTargets?.({ request, env, ctx, instance })) ?? {};
53
+ const concaveDoName = normalizeRouteTargetName(routeTargets.concaveDoName ?? routeTargets.concave?.targetName, instance);
54
+ const syncDoName = normalizeRouteTargetName(routeTargets.syncDoName ?? routeTargets.sync?.targetName, instance);
55
+ const syncNotifyDoNames = normalizeSyncNotifyTargets(routeTargets.syncNotifyDoNames ?? routeTargets.sync?.notifyTargetNames, syncDoName);
52
56
  if (cors && request.method === "OPTIONS") {
53
57
  return new Response("OK", {
54
58
  headers: buildCorsHeaders(request),
@@ -63,10 +67,11 @@ export function createConcaveWorker(options) {
63
67
  console.error("[ConcaveWorker] Sync Durable Object namespace is not configured");
64
68
  throw new Error("Sync Durable Object namespace is not configured");
65
69
  }
66
- const id = namespace.idFromName(instance);
70
+ const id = namespace.idFromName(syncDoName);
67
71
  const stub = namespace.get(id);
68
- console.log(`[ConcaveWorker] Forwarding to SyncDO: ${instance} (${id.toString()})`);
72
+ console.log(`[ConcaveWorker] Forwarding to SyncDO: logical=${instance} physical=${syncDoName} (${id.toString()})`);
69
73
  const forwardedRequest = new Request(forwardUrl.toString(), request);
74
+ forwardedRequest.headers.set("X-Concave-Instance", instance);
70
75
  if (request.headers.get("Upgrade")) {
71
76
  forwardedRequest.headers.set("Upgrade", request.headers.get("Upgrade"));
72
77
  }
@@ -85,7 +90,12 @@ export function createConcaveWorker(options) {
85
90
  }
86
91
  if (pathname.startsWith(apiPrefix)) {
87
92
  const forwardedRequest = new Request(forwardUrl.toString(), request);
88
- const response = await apiHandler(forwardedRequest, env, ctx, instance);
93
+ forwardedRequest.headers.set("X-Concave-Instance", instance);
94
+ const response = await apiHandler(forwardedRequest, env, ctx, instance, {
95
+ concaveDoName,
96
+ syncDoNames: syncNotifyDoNames,
97
+ dispatchSyncWrites: routeTargets.dispatchSyncWrites,
98
+ });
89
99
  return maybeAttachInstanceCookie(response, request, instanceResolution, { instanceKey });
90
100
  }
91
101
  if (pathname.startsWith(udfPrefix)) {
@@ -93,9 +103,10 @@ export function createConcaveWorker(options) {
93
103
  if (!namespace) {
94
104
  throw new Error("Concave Durable Object namespace is not configured");
95
105
  }
96
- const id = namespace.idFromName(instance);
106
+ const id = namespace.idFromName(concaveDoName);
97
107
  const stub = namespace.get(id);
98
108
  const forwardedRequest = new Request(forwardUrl.toString(), request);
109
+ forwardedRequest.headers.set("X-Concave-Instance", instance);
99
110
  const response = await stub.fetch(forwardedRequest);
100
111
  return maybeAttachInstanceCookie(response, request, instanceResolution, { instanceKey });
101
112
  }
@@ -160,3 +171,23 @@ function collectModuleLoaders(primary, additional) {
160
171
  function isDashboardPath(pathname) {
161
172
  return pathname === "/_dashboard" || pathname.startsWith("/_dashboard/");
162
173
  }
174
+ function normalizeRouteTargetName(target, fallback) {
175
+ const trimmed = target?.trim();
176
+ return trimmed && trimmed.length > 0 ? trimmed : fallback;
177
+ }
178
+ function normalizeSyncNotifyTargets(targets, fallback) {
179
+ if (targets === undefined) {
180
+ return [fallback];
181
+ }
182
+ if (targets.length === 0) {
183
+ return [];
184
+ }
185
+ const deduped = new Set();
186
+ for (const target of targets) {
187
+ const trimmed = target.trim();
188
+ if (trimmed.length > 0) {
189
+ deduped.add(trimmed);
190
+ }
191
+ }
192
+ return Array.from(deduped);
193
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@concavejs/runtime-cf-base",
3
- "version": "0.0.1-alpha.7",
3
+ "version": "0.0.1-alpha.9",
4
4
  "license": "FSL-1.1-Apache-2.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -68,6 +68,10 @@
68
68
  "types": "./dist/worker/udf-worker.d.ts",
69
69
  "default": "./dist/worker/udf-worker.js"
70
70
  },
71
+ "./routing/sync-topology": {
72
+ "types": "./dist/routing/sync-topology.d.ts",
73
+ "default": "./dist/routing/sync-topology.js"
74
+ },
71
75
  "./sync": {
72
76
  "types": "./dist/sync/index.d.ts",
73
77
  "default": "./dist/sync/index.js"
@@ -81,12 +85,12 @@
81
85
  "test": "bun test --run --passWithNoTests || true"
82
86
  },
83
87
  "dependencies": {
84
- "@concavejs/core": "0.0.1-alpha.6",
85
- "@concavejs/runtime-base": "0.0.1-alpha.6",
86
- "@concavejs/docstore-cf-do": "0.0.1-alpha.6",
87
- "@concavejs/docstore-cf-d1": "0.0.1-alpha.6",
88
- "@concavejs/docstore-cf-hyperdrive": "0.0.1-alpha.6",
89
- "@concavejs/blobstore-cf-r2": "0.0.1-alpha.6",
88
+ "@concavejs/core": "0.0.1-alpha.8",
89
+ "@concavejs/runtime-base": "0.0.1-alpha.8",
90
+ "@concavejs/docstore-cf-do": "0.0.1-alpha.8",
91
+ "@concavejs/docstore-cf-d1": "0.0.1-alpha.8",
92
+ "@concavejs/docstore-cf-hyperdrive": "0.0.1-alpha.8",
93
+ "@concavejs/blobstore-cf-r2": "0.0.1-alpha.8",
90
94
  "convex": "^1.27.3"
91
95
  },
92
96
  "devDependencies": {