@concavejs/runtime-cf-base 0.0.1-alpha.10

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 (57) hide show
  1. package/dist/adapters/cf-websocket-adapter.d.ts +38 -0
  2. package/dist/adapters/cf-websocket-adapter.js +83 -0
  3. package/dist/durable-objects/blobstore-rpc.d.ts +13 -0
  4. package/dist/durable-objects/blobstore-rpc.js +27 -0
  5. package/dist/durable-objects/concave-do-base.d.ts +169 -0
  6. package/dist/durable-objects/concave-do-base.js +466 -0
  7. package/dist/durable-objects/docstore-rpc.d.ts +46 -0
  8. package/dist/durable-objects/docstore-rpc.js +63 -0
  9. package/dist/durable-objects/scheduler-manager.d.ts +19 -0
  10. package/dist/durable-objects/scheduler-manager.js +53 -0
  11. package/dist/durable-objects/sync-notifier.d.ts +16 -0
  12. package/dist/durable-objects/sync-notifier.js +38 -0
  13. package/dist/env.d.ts +19 -0
  14. package/dist/env.js +6 -0
  15. package/dist/http/dx-http.d.ts +43 -0
  16. package/dist/http/dx-http.js +327 -0
  17. package/dist/http/http-api.d.ts +38 -0
  18. package/dist/http/http-api.js +399 -0
  19. package/dist/http/index.d.ts +7 -0
  20. package/dist/http/index.js +7 -0
  21. package/dist/index.d.ts +18 -0
  22. package/dist/index.js +27 -0
  23. package/dist/internal.d.ts +4 -0
  24. package/dist/internal.js +4 -0
  25. package/dist/routing/instance.d.ts +25 -0
  26. package/dist/routing/instance.js +101 -0
  27. package/dist/routing/sync-topology.d.ts +40 -0
  28. package/dist/routing/sync-topology.js +669 -0
  29. package/dist/rpc/blobstore-proxy.d.ts +11 -0
  30. package/dist/rpc/blobstore-proxy.js +28 -0
  31. package/dist/rpc/docstore-proxy.d.ts +11 -0
  32. package/dist/rpc/docstore-proxy.js +73 -0
  33. package/dist/rpc/index.d.ts +2 -0
  34. package/dist/rpc/index.js +2 -0
  35. package/dist/sync/cf-websocket-adapter.d.ts +15 -0
  36. package/dist/sync/cf-websocket-adapter.js +22 -0
  37. package/dist/sync/concave-do-udf-executor.d.ts +46 -0
  38. package/dist/sync/concave-do-udf-executor.js +75 -0
  39. package/dist/sync/index.d.ts +2 -0
  40. package/dist/sync/index.js +2 -0
  41. package/dist/udf/executor/do-client-executor.d.ts +14 -0
  42. package/dist/udf/executor/do-client-executor.js +58 -0
  43. package/dist/udf/executor/index.d.ts +8 -0
  44. package/dist/udf/executor/index.js +8 -0
  45. package/dist/udf/executor/inline-executor.d.ts +13 -0
  46. package/dist/udf/executor/inline-executor.js +25 -0
  47. package/dist/udf/executor/isolated-executor.d.ts +24 -0
  48. package/dist/udf/executor/isolated-executor.js +31 -0
  49. package/dist/udf/executor/shim-content.d.ts +1 -0
  50. package/dist/udf/executor/shim-content.js +3 -0
  51. package/dist/worker/create-concave-worker.d.ts +79 -0
  52. package/dist/worker/create-concave-worker.js +196 -0
  53. package/dist/worker/index.d.ts +6 -0
  54. package/dist/worker/index.js +6 -0
  55. package/dist/worker/udf-worker.d.ts +25 -0
  56. package/dist/worker/udf-worker.js +63 -0
  57. package/package.json +99 -0
@@ -0,0 +1,399 @@
1
+ import { ConcaveStubExecutor } from "../udf/executor/do-client-executor";
2
+ import { createClientAdapter } from "@concavejs/core/udf/execution-adapter";
3
+ import { loadConvexModule } from "@concavejs/core/udf";
4
+ import { writtenTablesFromRanges } from "@concavejs/core/utils";
5
+ import { applyCors, computeCorsHeaders, handleCoreHttpApiRequest, resolveAuthContext } from "@concavejs/core/http";
6
+ import { AdminAuthError, JWTValidationError, createTenantAuthResolverFromEnv, AuthConfigService, } from "@concavejs/core/auth";
7
+ import { InternalFunctionAccessError } from "@concavejs/core/errors";
8
+ // Module-level cached auth.config.ts providers. The AuthConfigService
9
+ // lazy-loads and caches internally; we keep a single instance so warm
10
+ // worker invocations reuse the result.
11
+ let _cachedAuthConfigProviders = null;
12
+ let _authConfigLoadPromise = null;
13
+ async function getCachedAuthConfigProviders() {
14
+ if (_cachedAuthConfigProviders !== null) {
15
+ return _cachedAuthConfigProviders;
16
+ }
17
+ if (!_authConfigLoadPromise) {
18
+ _authConfigLoadPromise = new AuthConfigService()
19
+ .getProviders()
20
+ .then((providers) => {
21
+ _cachedAuthConfigProviders = providers;
22
+ return providers;
23
+ })
24
+ .catch(() => {
25
+ _cachedAuthConfigProviders = [];
26
+ return [];
27
+ });
28
+ }
29
+ return _authConfigLoadPromise;
30
+ }
31
+ const VERSIONED_API_PREFIX = /^\/api\/\d+\.\d+(?:\.\d+)?(?=\/|$)/;
32
+ function stripApiVersionPrefix(pathname) {
33
+ return pathname.replace(VERSIONED_API_PREFIX, "/api");
34
+ }
35
+ function isReservedApiPath(pathname) {
36
+ const normalizedPath = stripApiVersionPrefix(pathname);
37
+ if (normalizedPath === "/api/execute" ||
38
+ normalizedPath === "/api/sync" ||
39
+ normalizedPath === "/api/reset-test-state" ||
40
+ normalizedPath === "/api/query" ||
41
+ normalizedPath === "/api/query_ts" ||
42
+ normalizedPath === "/api/query_at_ts" ||
43
+ normalizedPath === "/api/mutation" ||
44
+ normalizedPath === "/api/action") {
45
+ return true;
46
+ }
47
+ if (normalizedPath === "/api/storage" || normalizedPath.startsWith("/api/storage/")) {
48
+ return true;
49
+ }
50
+ if (normalizedPath === "/api/http" || normalizedPath.startsWith("/api/http/")) {
51
+ return true;
52
+ }
53
+ if (normalizedPath === "/api/run" || normalizedPath.startsWith("/api/run/")) {
54
+ return true;
55
+ }
56
+ return false;
57
+ }
58
+ function shouldForwardApiPath(pathname) {
59
+ if (!pathname.startsWith("/api/")) {
60
+ return false;
61
+ }
62
+ return !isReservedApiPath(pathname);
63
+ }
64
+ function arrayBufferToBase64(buffer) {
65
+ const bytes = new Uint8Array(buffer);
66
+ const chunkSize = 0x8000;
67
+ let binary = "";
68
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) {
69
+ const chunk = bytes.subarray(offset, Math.min(offset + chunkSize, bytes.length));
70
+ binary += String.fromCharCode(...chunk);
71
+ }
72
+ return btoa(binary);
73
+ }
74
+ function base64ToArrayBuffer(base64) {
75
+ const binary = atob(base64);
76
+ const bytes = new Uint8Array(binary.length);
77
+ for (let i = 0; i < binary.length; i++) {
78
+ bytes[i] = binary.charCodeAt(i);
79
+ }
80
+ return bytes.buffer;
81
+ }
82
+ /**
83
+ * Create a storage adapter that routes through the ConcaveDO's storage syscall handler.
84
+ * This ensures storage operations are properly isolated within the DO.
85
+ */
86
+ function createStorageAdapter(concaveDO) {
87
+ return {
88
+ store: async (blob) => {
89
+ const buffer = await blob.arrayBuffer();
90
+ const base64 = arrayBufferToBase64(buffer);
91
+ const response = await concaveDO.fetch("http://do/storage", {
92
+ method: "POST",
93
+ headers: { "Content-Type": "application/json" },
94
+ body: JSON.stringify({
95
+ method: "store",
96
+ args: [{ __arrayBuffer: base64 }, { contentType: blob.type }],
97
+ }),
98
+ });
99
+ if (!response.ok) {
100
+ const error = await response.json();
101
+ throw new Error(error?.error?.message ?? "Storage store failed");
102
+ }
103
+ const result = await response.json();
104
+ return {
105
+ storageId: result.result.storageId,
106
+ writtenRanges: [],
107
+ writtenTables: ["_storage"],
108
+ };
109
+ },
110
+ get: async (storageId) => {
111
+ const response = await concaveDO.fetch("http://do/storage", {
112
+ method: "POST",
113
+ headers: { "Content-Type": "application/json" },
114
+ body: JSON.stringify({
115
+ method: "get",
116
+ args: [storageId],
117
+ }),
118
+ });
119
+ if (!response.ok) {
120
+ const error = await response.json();
121
+ throw new Error(error?.error?.message ?? "Storage get failed");
122
+ }
123
+ const result = await response.json();
124
+ if (!result.result || !result.result.__arrayBuffer) {
125
+ return { blob: null };
126
+ }
127
+ const buffer = base64ToArrayBuffer(result.result.__arrayBuffer);
128
+ return { blob: new Blob([buffer]) };
129
+ },
130
+ };
131
+ }
132
+ function createNotifyWrites(env, syncDoNames, ctx, logicalInstance, projectId, dispatchSyncWrites) {
133
+ return async (writtenRanges, writtenTables, commitTimestamp) => {
134
+ if (!writtenRanges?.length && !writtenTables?.length) {
135
+ return;
136
+ }
137
+ const payload = {
138
+ logicalInstance,
139
+ projectId,
140
+ syncTargets: syncDoNames,
141
+ writtenRanges,
142
+ writtenTables: writtenTables ?? writtenTablesFromRanges(writtenRanges),
143
+ commitTimestamp: commitTimestamp ? commitTimestamp.toString() : undefined,
144
+ };
145
+ if (dispatchSyncWrites) {
146
+ ctx.waitUntil(dispatchSyncWrites(payload).catch((error) => {
147
+ console.warn("[notifyWrites] Custom sync write dispatch failed", error);
148
+ }));
149
+ return;
150
+ }
151
+ if (isQueueLike(env.SYNC_NOTIFY_QUEUE)) {
152
+ ctx.waitUntil(env.SYNC_NOTIFY_QUEUE.send(payload).catch((error) => {
153
+ console.warn("[notifyWrites] Queue-based sync write dispatch failed", error);
154
+ }));
155
+ return;
156
+ }
157
+ const doPayload = {
158
+ writtenRanges,
159
+ writtenTables: writtenTables ?? writtenTablesFromRanges(writtenRanges),
160
+ commitTimestamp: commitTimestamp ? commitTimestamp.toString() : undefined,
161
+ };
162
+ for (const syncDoName of syncDoNames) {
163
+ const syncDoId = env.SYNC_DO.idFromName(syncDoName);
164
+ const syncDo = env.SYNC_DO.get(syncDoId);
165
+ const headers = {
166
+ "Content-Type": "application/json",
167
+ "X-Concave-Instance": logicalInstance,
168
+ };
169
+ if (projectId) {
170
+ headers["X-Concave-Project-Id"] = projectId;
171
+ }
172
+ ctx.waitUntil(syncDo
173
+ .fetch("http://do/notify", {
174
+ method: "POST",
175
+ headers,
176
+ body: JSON.stringify(doPayload),
177
+ })
178
+ .catch((error) => {
179
+ console.warn("[notifyWrites] Direct SyncDO notify failed", error);
180
+ }));
181
+ }
182
+ };
183
+ }
184
+ function isQueueLike(value) {
185
+ if (!value || typeof value !== "object") {
186
+ return false;
187
+ }
188
+ const candidate = value;
189
+ return typeof candidate.send === "function";
190
+ }
191
+ export async function handleHttpApiRequest(request, env, ctx, instance = "singleton", routingTargets) {
192
+ const corsHeaders = computeCorsHeaders(request);
193
+ const apply = (response) => applyCors(response, corsHeaders);
194
+ const url = new URL(request.url);
195
+ const pathParts = url.pathname.slice(1).split("/");
196
+ if (pathParts[0] !== "api") {
197
+ return apply(new Response("Not found", { status: 404 }));
198
+ }
199
+ const logicalInstance = instance;
200
+ const concaveDoName = normalizeDoName(routingTargets?.concaveDoName ?? routingTargets?.concaveTargetName, logicalInstance);
201
+ const syncDoNames = normalizeSyncDoNames(routingTargets?.syncDoNames ?? routingTargets?.syncTargetNames, logicalInstance);
202
+ const projectId = request.headers.get("X-Concave-Project-Id") ?? undefined;
203
+ const authResolver = createTenantAuthResolverFromEnv(env, projectId);
204
+ // Wire auth.config.ts JWT providers into the resolver (non-blocking best-effort)
205
+ try {
206
+ const jwtProviders = await getCachedAuthConfigProviders();
207
+ if (jwtProviders.length > 0 && "updateJwtProviders" in authResolver) {
208
+ authResolver.updateJwtProviders(jwtProviders);
209
+ }
210
+ }
211
+ catch {
212
+ // auth.config.ts not available — proceed with env-only config
213
+ }
214
+ console.log(`[handleHttpApiRequest] logicalInstance=${logicalInstance} concaveDo=${concaveDoName} syncTargets=${syncDoNames.join(",")}`);
215
+ const concaveId = env.CONCAVE_DO.idFromName(concaveDoName);
216
+ const concaveRaw = env.CONCAVE_DO.get(concaveId);
217
+ const concave = wrapConcaveStub(concaveRaw, logicalInstance, projectId);
218
+ const executor = new ConcaveStubExecutor(concave);
219
+ const adapter = createClientAdapter(executor);
220
+ const notifyWrites = createNotifyWrites(env, syncDoNames, ctx, logicalInstance, projectId, routingTargets?.dispatchSyncWrites);
221
+ // Route storage operations through the DO's storage syscall handler
222
+ const storageAdapter = createStorageAdapter(concave);
223
+ const authHeader = request.headers.get("Authorization");
224
+ const headerToken = authHeader?.replace(/^Bearer\s+/i, "").trim() || undefined;
225
+ const headerIdentity = undefined;
226
+ // Note: Internal function access control is now handled by core executor (fail-closed)
227
+ const coreResult = await handleCoreHttpApiRequest(request, {
228
+ executeFunction: async ({ type, path, args, auth, componentPath, snapshotTimestamp }) => adapter.executeUdf(path, args, type, auth, componentPath, undefined, snapshotTimestamp),
229
+ notifyWrites,
230
+ storage: storageAdapter,
231
+ corsHeaders,
232
+ authResolver,
233
+ getSnapshotTimestamp: async () => {
234
+ try {
235
+ const response = await concave.fetch("http://do/query_ts", {
236
+ method: "POST",
237
+ });
238
+ if (!response.ok) {
239
+ throw new Error(`query_ts failed with status ${response.status}`);
240
+ }
241
+ const body = (await response.json());
242
+ if (typeof body.ts !== "string" || !/^\d+$/.test(body.ts)) {
243
+ throw new Error("Invalid query_ts response");
244
+ }
245
+ return BigInt(body.ts);
246
+ }
247
+ catch {
248
+ return BigInt(Date.now());
249
+ }
250
+ },
251
+ });
252
+ if (coreResult?.handled) {
253
+ return coreResult.response;
254
+ }
255
+ // Handle /api/http/*
256
+ if (pathParts.length >= 2 && pathParts[1] === "http") {
257
+ const forwardUrl = new URL(request.url);
258
+ const forwardedRequest = new Request(forwardUrl.toString(), request);
259
+ const response = await concave.fetch(forwardedRequest);
260
+ return apply(response);
261
+ }
262
+ // Handle /api/run/{functionIdentifier}
263
+ if (pathParts.length > 2 && pathParts[1] === "run") {
264
+ const functionIdentifier = pathParts.slice(2).join("/");
265
+ const udfPath = functionIdentifier.replace(/\//g, ":");
266
+ let bodyArgs = {};
267
+ if (request.headers.get("Content-Type")?.includes("application/json") && request.body) {
268
+ try {
269
+ const body = await request.clone().json();
270
+ if (body.args && typeof body.args === "object") {
271
+ bodyArgs = body.args;
272
+ }
273
+ }
274
+ catch (_error) {
275
+ // Ignore parse errors
276
+ }
277
+ }
278
+ const queryArgs = {};
279
+ for (const [key, value] of url.searchParams.entries()) {
280
+ try {
281
+ queryArgs[key] = JSON.parse(value);
282
+ }
283
+ catch {
284
+ queryArgs[key] = value;
285
+ }
286
+ }
287
+ const mergedArgs = { ...queryArgs, ...bodyArgs };
288
+ let authForExecution;
289
+ try {
290
+ authForExecution = await resolveAuthContext(undefined, headerToken, headerIdentity, authResolver);
291
+ }
292
+ catch (error) {
293
+ if (error instanceof JWTValidationError || error instanceof AdminAuthError) {
294
+ return apply(Response.json({ error: "Unauthorized" }, { status: 401 }));
295
+ }
296
+ throw error;
297
+ }
298
+ try {
299
+ const modulePath = udfPath.split(":")[0];
300
+ const functionName = udfPath.split(":")[1] ?? "default";
301
+ const module = await loadConvexModule(modulePath, { hint: "udf" });
302
+ const func = module[functionName];
303
+ if (!func) {
304
+ return apply(new Response(`Function ${udfPath} not found`, { status: 404 }));
305
+ }
306
+ // Note: Internal function access control is now handled by core executor (fail-closed)
307
+ let udfType = null;
308
+ if (func.isQuery)
309
+ udfType = "query";
310
+ else if (func.isMutation)
311
+ udfType = "mutation";
312
+ else if (func.isAction)
313
+ udfType = "action";
314
+ if (!udfType) {
315
+ console.error(`Function ${udfPath} is not a valid query, mutation, or action.`);
316
+ return apply(new Response(`Function ${udfPath} is not a valid query, mutation, or action.`, {
317
+ status: 400,
318
+ }));
319
+ }
320
+ const result = await adapter.executeUdf(udfPath, mergedArgs, udfType, authForExecution);
321
+ if ((udfType === "mutation" || udfType === "action") && result.writtenRanges?.length) {
322
+ await notifyWrites(result.writtenRanges, writtenTablesFromRanges(result.writtenRanges));
323
+ }
324
+ return apply(Response.json({
325
+ status: "success",
326
+ value: result.result,
327
+ logLines: [],
328
+ }));
329
+ }
330
+ catch (error) {
331
+ // Handle internal function access errors with 403
332
+ if (error instanceof InternalFunctionAccessError) {
333
+ return apply(Response.json({
334
+ status: "error",
335
+ errorMessage: error.message,
336
+ }, { status: 403 }));
337
+ }
338
+ if (typeof error?.message === "string") {
339
+ const message = error.message.toLowerCase();
340
+ if (message.includes("module not found") ||
341
+ message.includes("failed to load convex module") ||
342
+ message.includes("unable to resolve module")) {
343
+ return apply(new Response(`Module for function ${udfPath} not found.`, {
344
+ status: 404,
345
+ }));
346
+ }
347
+ }
348
+ if (error instanceof Response) {
349
+ return apply(error);
350
+ }
351
+ return apply(new Response(`Error determining function type: ${error?.message ?? String(error)}`, {
352
+ status: 500,
353
+ }));
354
+ }
355
+ }
356
+ if (shouldForwardApiPath(url.pathname)) {
357
+ const forwardUrl = new URL(request.url);
358
+ forwardUrl.pathname = stripApiVersionPrefix(forwardUrl.pathname);
359
+ const forwardedRequest = new Request(forwardUrl.toString(), request);
360
+ const response = await concave.fetch(forwardedRequest);
361
+ return apply(response);
362
+ }
363
+ return apply(new Response("Not found", { status: 404 }));
364
+ }
365
+ function wrapConcaveStub(stub, logicalInstance, projectId) {
366
+ return {
367
+ fetch: async (input, init) => {
368
+ const headers = new Headers(init?.headers);
369
+ headers.set("X-Concave-Instance", logicalInstance);
370
+ if (projectId) {
371
+ headers.set("X-Concave-Project-Id", projectId);
372
+ }
373
+ return stub.fetch(input, {
374
+ ...init,
375
+ headers,
376
+ });
377
+ },
378
+ };
379
+ }
380
+ function normalizeDoName(target, fallback) {
381
+ const trimmed = target?.trim();
382
+ return trimmed && trimmed.length > 0 ? trimmed : fallback;
383
+ }
384
+ function normalizeSyncDoNames(targets, fallback) {
385
+ if (targets === undefined) {
386
+ return [fallback];
387
+ }
388
+ if (targets.length === 0) {
389
+ return [];
390
+ }
391
+ const deduped = new Set();
392
+ for (const target of targets) {
393
+ const trimmed = target.trim();
394
+ if (trimmed.length > 0) {
395
+ deduped.add(trimmed);
396
+ }
397
+ }
398
+ return Array.from(deduped);
399
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * HTTP Module
3
+ *
4
+ * HTTP API handlers and request processing
5
+ */
6
+ export * from "./http-api";
7
+ export * from "./dx-http";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * HTTP Module
3
+ *
4
+ * HTTP API handlers and request processing
5
+ */
6
+ export * from "./http-api";
7
+ export * from "./dx-http";
@@ -0,0 +1,18 @@
1
+ export * from "@concavejs/core";
2
+ export type { ConcaveEnv, ConcaveStorageEnv, ConcaveD1Env } from "./env";
3
+ export { UdfExecutorRpc } from "./worker/udf-worker";
4
+ export { createConcaveWorker, resolveNamespaceBinding, createScopedNamespace, type ConcaveWorkerOptions, type ConcaveWorkerBindings, type ConcaveWorker, type ConcaveWorkerRouteContext, type ConcaveWorkerRouteTargets, } from "./worker/create-concave-worker";
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";
7
+ export { UdfExecInline } from "./udf/executor/inline-executor";
8
+ export { UdfExecIsolated } from "./udf/executor/isolated-executor";
9
+ export { ConcaveDOBase, type ConcaveDOConfig, type ConcaveDOAdapterContext, type ConcaveDOExecutorContext, } from "./durable-objects/concave-do-base";
10
+ export { createDocStoreProxy, createBlobStoreProxy, createGatewayDocStoreProxy, createGatewayBlobStoreProxy, } from "./rpc";
11
+ export { CFWebSocketAdapter } from "./adapters/cf-websocket-adapter";
12
+ export { CFWebSocketAdapter as SyncWebSocketAdapter } from "./sync/cf-websocket-adapter";
13
+ export { ConcaveDOUdfExecutor } from "./sync/concave-do-udf-executor";
14
+ export { SHIM_SOURCE } from "./udf/executor/shim-content";
15
+ export { DODocStore } from "@concavejs/docstore-cf-do";
16
+ export { D1DocStore } from "@concavejs/docstore-cf-d1";
17
+ export { HyperdriveDocStore } from "@concavejs/docstore-cf-hyperdrive";
18
+ export { R2BlobStore } from "@concavejs/blobstore-cf-r2";
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ // Re-export core functionality
2
+ export * from "@concavejs/core";
3
+ // Export Cloudflare Workers utilities
4
+ export { UdfExecutorRpc } from "./worker/udf-worker";
5
+ export { createConcaveWorker, resolveNamespaceBinding, createScopedNamespace, } from "./worker/create-concave-worker";
6
+ // Export instance routing helpers
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";
9
+ // Export UDF executors
10
+ export { UdfExecInline } from "./udf/executor/inline-executor";
11
+ export { UdfExecIsolated } from "./udf/executor/isolated-executor";
12
+ // Export Durable Object base classes
13
+ export { ConcaveDOBase, } from "./durable-objects/concave-do-base";
14
+ // Export RPC proxy utilities
15
+ export { createDocStoreProxy, createBlobStoreProxy, createGatewayDocStoreProxy, createGatewayBlobStoreProxy, } from "./rpc";
16
+ // Export CF-specific adapters
17
+ export { CFWebSocketAdapter } from "./adapters/cf-websocket-adapter";
18
+ // Export sync protocol adapters
19
+ export { CFWebSocketAdapter as SyncWebSocketAdapter } from "./sync/cf-websocket-adapter";
20
+ export { ConcaveDOUdfExecutor } from "./sync/concave-do-udf-executor";
21
+ // Export embedded shim source
22
+ export { SHIM_SOURCE } from "./udf/executor/shim-content";
23
+ // Export CF-specific DocStore implementations
24
+ export { DODocStore } from "@concavejs/docstore-cf-do";
25
+ export { D1DocStore } from "@concavejs/docstore-cf-d1";
26
+ export { HyperdriveDocStore } from "@concavejs/docstore-cf-hyperdrive";
27
+ export { R2BlobStore } from "@concavejs/blobstore-cf-r2";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Internal exports re-exported from core
3
+ */
4
+ export * from "@concavejs/core/system/internal";
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Internal exports re-exported from core
3
+ */
4
+ export * from "@concavejs/core/system/internal";
@@ -0,0 +1,25 @@
1
+ export declare const DEFAULT_INSTANCE_KEY = "x-concave-instance";
2
+ export declare const DEFAULT_INSTANCE_VALUE = "singleton";
3
+ export declare const DEFAULT_INSTANCE_COOKIE_PATH = "/";
4
+ export type InstanceResolutionSource = "query" | "header" | "cookie" | "default";
5
+ export type InstanceResolution = {
6
+ value: string;
7
+ source: InstanceResolutionSource;
8
+ queryValue?: string;
9
+ headerValue?: string;
10
+ cookieValue?: string;
11
+ };
12
+ export type InstanceResolutionOptions = {
13
+ instanceKey?: string;
14
+ defaultInstance?: string;
15
+ };
16
+ export type InstanceCookieOptions = {
17
+ path?: string;
18
+ sameSite?: "Lax" | "Strict" | "None";
19
+ secure?: boolean;
20
+ httpOnly?: boolean;
21
+ };
22
+ export declare function resolveInstanceFromRequest(request: Request, options?: InstanceResolutionOptions): InstanceResolution;
23
+ export declare function maybeAttachInstanceCookie(response: Response, request: Request, resolution: InstanceResolution, options?: InstanceResolutionOptions & InstanceCookieOptions): Response;
24
+ export declare function readCookieValue(cookieHeader: string | null, name: string): string | undefined;
25
+ export declare function buildInstanceCookie(request: Request, name: string, value: string, options?: InstanceCookieOptions): string;
@@ -0,0 +1,101 @@
1
+ // Centralized instance routing rules for HTTP + WS entry points.
2
+ // Precedence: query param -> header -> cookie -> default.
3
+ export const DEFAULT_INSTANCE_KEY = "x-concave-instance";
4
+ export const DEFAULT_INSTANCE_VALUE = "singleton";
5
+ export const DEFAULT_INSTANCE_COOKIE_PATH = "/";
6
+ export function resolveInstanceFromRequest(request, options = {}) {
7
+ const instanceKey = options.instanceKey ?? DEFAULT_INSTANCE_KEY;
8
+ const defaultInstance = options.defaultInstance ?? DEFAULT_INSTANCE_VALUE;
9
+ const url = new URL(request.url);
10
+ const queryValue = url.searchParams.get(instanceKey) ?? undefined;
11
+ if (queryValue) {
12
+ return {
13
+ value: queryValue,
14
+ source: "query",
15
+ queryValue,
16
+ headerValue: request.headers.get(instanceKey) ?? undefined,
17
+ cookieValue: readCookieValue(request.headers.get("Cookie"), instanceKey),
18
+ };
19
+ }
20
+ const headerValue = request.headers.get(instanceKey) ?? undefined;
21
+ if (headerValue) {
22
+ return {
23
+ value: headerValue,
24
+ source: "header",
25
+ queryValue: undefined,
26
+ headerValue,
27
+ cookieValue: readCookieValue(request.headers.get("Cookie"), instanceKey),
28
+ };
29
+ }
30
+ const cookieValue = readCookieValue(request.headers.get("Cookie"), instanceKey);
31
+ if (cookieValue) {
32
+ return {
33
+ value: cookieValue,
34
+ source: "cookie",
35
+ queryValue: undefined,
36
+ headerValue: undefined,
37
+ cookieValue,
38
+ };
39
+ }
40
+ return {
41
+ value: defaultInstance,
42
+ source: "default",
43
+ };
44
+ }
45
+ export function maybeAttachInstanceCookie(response, request, resolution, options = {}) {
46
+ // Only persist explicit instance selections (query/header) into cookies.
47
+ if (resolution.source !== "query" && resolution.source !== "header") {
48
+ return response;
49
+ }
50
+ const instanceKey = options.instanceKey ?? DEFAULT_INSTANCE_KEY;
51
+ const existingCookie = readCookieValue(request.headers.get("Cookie"), instanceKey);
52
+ if (existingCookie === resolution.value) {
53
+ return response;
54
+ }
55
+ const headers = new Headers(response.headers);
56
+ const cookie = buildInstanceCookie(request, instanceKey, resolution.value, options);
57
+ headers.append("Set-Cookie", cookie);
58
+ const webSocket = response.webSocket;
59
+ return new Response(response.body, {
60
+ status: response.status,
61
+ statusText: response.statusText,
62
+ headers,
63
+ ...(webSocket ? { webSocket } : {}),
64
+ });
65
+ }
66
+ export function readCookieValue(cookieHeader, name) {
67
+ if (!cookieHeader) {
68
+ return undefined;
69
+ }
70
+ const target = name.toLowerCase();
71
+ const entries = cookieHeader.split(";").map((part) => part.trim());
72
+ for (const entry of entries) {
73
+ if (!entry) {
74
+ continue;
75
+ }
76
+ const equals = entry.indexOf("=");
77
+ if (equals === -1) {
78
+ continue;
79
+ }
80
+ const key = entry.slice(0, equals).trim().toLowerCase();
81
+ if (key !== target) {
82
+ continue;
83
+ }
84
+ return decodeURIComponent(entry.slice(equals + 1).trim());
85
+ }
86
+ return undefined;
87
+ }
88
+ export function buildInstanceCookie(request, name, value, options = {}) {
89
+ const path = options.path ?? DEFAULT_INSTANCE_COOKIE_PATH;
90
+ const sameSite = options.sameSite ?? "Lax";
91
+ const secure = options.secure ?? new URL(request.url).protocol === "https:";
92
+ const httpOnly = options.httpOnly ?? true;
93
+ const parts = [`${name}=${encodeURIComponent(value)}`, `Path=${path}`, `SameSite=${sameSite}`];
94
+ if (secure) {
95
+ parts.push("Secure");
96
+ }
97
+ if (httpOnly) {
98
+ parts.push("HttpOnly");
99
+ }
100
+ return parts.join("; ");
101
+ }
@@ -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[];