@concavejs/runtime-cf-base 0.0.1-alpha.4
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.
Potentially problematic release.
This version of @concavejs/runtime-cf-base might be problematic. Click here for more details.
- package/dist/adapters/cf-websocket-adapter.d.ts +38 -0
- package/dist/adapters/cf-websocket-adapter.js +83 -0
- package/dist/durable-objects/concave-do-base.d.ts +158 -0
- package/dist/durable-objects/concave-do-base.js +412 -0
- package/dist/http/dx-http.d.ts +28 -0
- package/dist/http/dx-http.js +306 -0
- package/dist/http/http-api.d.ts +1 -0
- package/dist/http/http-api.js +262 -0
- package/dist/http/index.d.ts +7 -0
- package/dist/http/index.js +7 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +25 -0
- package/dist/internal.d.ts +4 -0
- package/dist/internal.js +4 -0
- package/dist/routing/instance.d.ts +25 -0
- package/dist/routing/instance.js +101 -0
- package/dist/rpc/blobstore-proxy.d.ts +11 -0
- package/dist/rpc/blobstore-proxy.js +28 -0
- package/dist/rpc/docstore-proxy.d.ts +11 -0
- package/dist/rpc/docstore-proxy.js +72 -0
- package/dist/rpc/index.d.ts +2 -0
- package/dist/rpc/index.js +2 -0
- package/dist/sync/cf-websocket-adapter.d.ts +15 -0
- package/dist/sync/cf-websocket-adapter.js +22 -0
- package/dist/sync/concave-do-udf-executor.d.ts +37 -0
- package/dist/sync/concave-do-udf-executor.js +67 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +2 -0
- package/dist/udf/executor/do-client-executor.d.ts +14 -0
- package/dist/udf/executor/do-client-executor.js +42 -0
- package/dist/udf/executor/index.d.ts +9 -0
- package/dist/udf/executor/index.js +9 -0
- package/dist/udf/executor/inline-executor.d.ts +13 -0
- package/dist/udf/executor/inline-executor.js +25 -0
- package/dist/udf/executor/isolated-executor.d.ts +24 -0
- package/dist/udf/executor/isolated-executor.js +31 -0
- package/dist/udf/executor/shim-content.d.ts +1 -0
- package/dist/udf/executor/shim-content.js +3 -0
- package/dist/worker/create-concave-worker.d.ts +34 -0
- package/dist/worker/create-concave-worker.js +162 -0
- package/dist/worker/index.d.ts +6 -0
- package/dist/worker/index.js +6 -0
- package/dist/worker/udf-worker.d.ts +14 -0
- package/dist/worker/udf-worker.js +63 -0
- package/package.json +45 -0
|
@@ -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,11 @@
|
|
|
1
|
+
import type { BlobStore } from "@concavejs/core/abstractions";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a DO stub as a BlobStore.
|
|
4
|
+
* Blobstore methods are prefixed with "blobstore" on the DO to avoid collisions.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createBlobStoreProxy(stub: any): BlobStore;
|
|
7
|
+
/**
|
|
8
|
+
* Wraps a SyscallGateway service binding as a BlobStore.
|
|
9
|
+
* Prepends projectId and instance to each call for multi-tenant routing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createGatewayBlobStoreProxy(gateway: any, projectId: string, instance: string): BlobStore;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a DO stub as a BlobStore.
|
|
3
|
+
* Blobstore methods are prefixed with "blobstore" on the DO to avoid collisions.
|
|
4
|
+
*/
|
|
5
|
+
export function createBlobStoreProxy(stub) {
|
|
6
|
+
return createBlobStoreProxyInternal(stub, (...args) => args);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Wraps a SyscallGateway service binding as a BlobStore.
|
|
10
|
+
* Prepends projectId and instance to each call for multi-tenant routing.
|
|
11
|
+
*/
|
|
12
|
+
export function createGatewayBlobStoreProxy(gateway, projectId, instance) {
|
|
13
|
+
return createBlobStoreProxyInternal(gateway, (...args) => [projectId, instance, ...args]);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Internal helper that creates a BlobStore proxy with configurable argument transformation.
|
|
17
|
+
*/
|
|
18
|
+
function createBlobStoreProxyInternal(target, transformArgs) {
|
|
19
|
+
return {
|
|
20
|
+
async store(blob, options) {
|
|
21
|
+
const buffer = blob instanceof Blob ? await blob.arrayBuffer() : blob;
|
|
22
|
+
return target.blobstoreStore(...transformArgs(buffer, options));
|
|
23
|
+
},
|
|
24
|
+
get: (id) => target.blobstoreGet(...transformArgs(id)),
|
|
25
|
+
delete: (id) => target.blobstoreDelete(...transformArgs(id)),
|
|
26
|
+
getUrl: (id) => target.blobstoreGetUrl(...transformArgs(id)),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DocStore } from "@concavejs/core/docstore";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a DO stub as a DocStore.
|
|
4
|
+
* Generator methods are converted from arrays back to async generators.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createDocStoreProxy(stub: any): DocStore;
|
|
7
|
+
/**
|
|
8
|
+
* Wraps a SyscallGateway service binding as a DocStore.
|
|
9
|
+
* Prepends projectId and instance to each call for multi-tenant routing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createGatewayDocStoreProxy(gateway: any, projectId: string, instance: string): DocStore;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a DO stub as a DocStore.
|
|
3
|
+
* Generator methods are converted from arrays back to async generators.
|
|
4
|
+
*/
|
|
5
|
+
export function createDocStoreProxy(stub) {
|
|
6
|
+
return createDocStoreProxyInternal(stub, (...args) => args);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Wraps a SyscallGateway service binding as a DocStore.
|
|
10
|
+
* Prepends projectId and instance to each call for multi-tenant routing.
|
|
11
|
+
*/
|
|
12
|
+
export function createGatewayDocStoreProxy(gateway, projectId, instance) {
|
|
13
|
+
return createDocStoreProxyInternal(gateway, (...args) => [projectId, instance, ...args]);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Internal helper that creates a DocStore proxy with configurable argument transformation.
|
|
17
|
+
*/
|
|
18
|
+
function createDocStoreProxyInternal(target, transformArgs) {
|
|
19
|
+
return new Proxy({}, {
|
|
20
|
+
get(_, prop) {
|
|
21
|
+
// Convert array results back to async generators
|
|
22
|
+
if (prop === "index_scan") {
|
|
23
|
+
return async function* (...args) {
|
|
24
|
+
const results = await target.index_scan(...transformArgs(...args));
|
|
25
|
+
for (const item of results) {
|
|
26
|
+
yield item;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (prop === "load_documents") {
|
|
31
|
+
return async function* (...args) {
|
|
32
|
+
const results = await target.load_documents(...transformArgs(...args));
|
|
33
|
+
for (const item of results) {
|
|
34
|
+
yield item;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Convert array results back to Map for previous_revisions
|
|
39
|
+
if (prop === "previous_revisions") {
|
|
40
|
+
return async function (...args) {
|
|
41
|
+
const result = await target.previous_revisions(...transformArgs(...args));
|
|
42
|
+
return new Map(result);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (prop === "previous_revisions_of_documents") {
|
|
46
|
+
return async function (...args) {
|
|
47
|
+
const result = await target.previous_revisions_of_documents(...transformArgs(...args));
|
|
48
|
+
return new Map(result);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Provide a local timestampOracle (DO-side oracle cannot be accessed via RPC)
|
|
52
|
+
if (prop === "timestampOracle") {
|
|
53
|
+
return {
|
|
54
|
+
observeTimestamp: () => { },
|
|
55
|
+
allocateTimestamp: () => BigInt(Date.now()),
|
|
56
|
+
getCurrentTimestamp: () => BigInt(Date.now()),
|
|
57
|
+
beginSnapshot: () => BigInt(Date.now()),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// No-op close
|
|
61
|
+
if (prop === "close") {
|
|
62
|
+
return async () => { };
|
|
63
|
+
}
|
|
64
|
+
// Bind methods to target with transformed args
|
|
65
|
+
const method = target[prop];
|
|
66
|
+
if (typeof method === "function") {
|
|
67
|
+
return (...args) => method.call(target, ...transformArgs(...args));
|
|
68
|
+
}
|
|
69
|
+
return method;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare WebSocket adapter for the platform-agnostic sync protocol.
|
|
3
|
+
* This adapter wraps a Cloudflare Worker WebSocket to implement the SyncWebSocket interface.
|
|
4
|
+
*/
|
|
5
|
+
import type { SyncWebSocket } from "@concavejs/core/sync";
|
|
6
|
+
/**
|
|
7
|
+
* WebSocket wrapper to adapt CF WebSocket to platform-agnostic interface
|
|
8
|
+
*/
|
|
9
|
+
export declare class CFWebSocketAdapter implements SyncWebSocket {
|
|
10
|
+
private ws;
|
|
11
|
+
constructor(ws: WebSocket);
|
|
12
|
+
send(data: string): void;
|
|
13
|
+
close(code?: number, reason?: string): void;
|
|
14
|
+
get readyState(): number;
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare WebSocket adapter for the platform-agnostic sync protocol.
|
|
3
|
+
* This adapter wraps a Cloudflare Worker WebSocket to implement the SyncWebSocket interface.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* WebSocket wrapper to adapt CF WebSocket to platform-agnostic interface
|
|
7
|
+
*/
|
|
8
|
+
export class CFWebSocketAdapter {
|
|
9
|
+
ws;
|
|
10
|
+
constructor(ws) {
|
|
11
|
+
this.ws = ws;
|
|
12
|
+
}
|
|
13
|
+
send(data) {
|
|
14
|
+
this.ws.send(data);
|
|
15
|
+
}
|
|
16
|
+
close(code, reason) {
|
|
17
|
+
this.ws.close(code, reason);
|
|
18
|
+
}
|
|
19
|
+
get readyState() {
|
|
20
|
+
return this.ws.readyState;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UDF executor that delegates to ConcaveDO via stub.
|
|
3
|
+
* Shared between runtime-cf and runtime-cloud.
|
|
4
|
+
*/
|
|
5
|
+
import type { SyncUdfExecutor, AuthContext } from "@concavejs/core/sync";
|
|
6
|
+
import type { JSONValue } from "convex/values";
|
|
7
|
+
import type { SerializedKeyRange } from "@concavejs/core/queryengine";
|
|
8
|
+
type MinimalDOStub = {
|
|
9
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* UDF executor that delegates to ConcaveDO
|
|
13
|
+
*/
|
|
14
|
+
export declare class ConcaveDOUdfExecutor implements SyncUdfExecutor {
|
|
15
|
+
private readonly getConcaveStub;
|
|
16
|
+
private readonly projectId?;
|
|
17
|
+
constructor(getConcaveStub: () => MinimalDOStub, projectId?: string | undefined);
|
|
18
|
+
private createAdapter;
|
|
19
|
+
executeQuery(path: string, args: Record<string, any>, auth: AuthContext, componentPath?: string): Promise<{
|
|
20
|
+
result: JSONValue;
|
|
21
|
+
readRanges?: SerializedKeyRange[];
|
|
22
|
+
logLines?: string[];
|
|
23
|
+
}>;
|
|
24
|
+
executeMutation(path: string, args: Record<string, any>, auth: AuthContext, componentPath?: string): Promise<{
|
|
25
|
+
result: JSONValue;
|
|
26
|
+
writtenRanges?: SerializedKeyRange[];
|
|
27
|
+
writtenTables?: string[];
|
|
28
|
+
logLines?: string[];
|
|
29
|
+
}>;
|
|
30
|
+
executeAction(path: string, args: Record<string, any>, auth: AuthContext, componentPath?: string): Promise<{
|
|
31
|
+
result: JSONValue;
|
|
32
|
+
writtenRanges?: SerializedKeyRange[];
|
|
33
|
+
writtenTables?: string[];
|
|
34
|
+
logLines?: string[];
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UDF executor that delegates to ConcaveDO via stub.
|
|
3
|
+
* Shared between runtime-cf and runtime-cloud.
|
|
4
|
+
*/
|
|
5
|
+
import { createClientAdapter } from "@concavejs/core/udf/execution-adapter";
|
|
6
|
+
import { writtenTablesFromRanges } from "@concavejs/core/utils";
|
|
7
|
+
import { ConcaveStubExecutor } from "../udf/executor/do-client-executor";
|
|
8
|
+
/**
|
|
9
|
+
* UDF executor that delegates to ConcaveDO
|
|
10
|
+
*/
|
|
11
|
+
export class ConcaveDOUdfExecutor {
|
|
12
|
+
getConcaveStub;
|
|
13
|
+
projectId;
|
|
14
|
+
constructor(getConcaveStub, projectId) {
|
|
15
|
+
this.getConcaveStub = getConcaveStub;
|
|
16
|
+
this.projectId = projectId;
|
|
17
|
+
}
|
|
18
|
+
createAdapter() {
|
|
19
|
+
const stub = this.getConcaveStub();
|
|
20
|
+
// Create a wrapper stub that adds the projectId header to all requests
|
|
21
|
+
const wrappedStub = {
|
|
22
|
+
fetch: async (input, init) => {
|
|
23
|
+
const headers = new Headers(init?.headers);
|
|
24
|
+
if (this.projectId) {
|
|
25
|
+
headers.set("X-Concave-Project-Id", this.projectId);
|
|
26
|
+
}
|
|
27
|
+
return stub.fetch(input, {
|
|
28
|
+
...init,
|
|
29
|
+
headers,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
// Cast to any to satisfy ConcaveStubExecutor which expects full DurableObjectStub
|
|
34
|
+
// We only use the fetch method, so this is safe
|
|
35
|
+
const executor = new ConcaveStubExecutor(wrappedStub);
|
|
36
|
+
return createClientAdapter(executor);
|
|
37
|
+
}
|
|
38
|
+
async executeQuery(path, args, auth, componentPath) {
|
|
39
|
+
const adapter = this.createAdapter();
|
|
40
|
+
const result = await adapter.executeUdf(path, args, "query", auth, componentPath);
|
|
41
|
+
return {
|
|
42
|
+
result: result.result,
|
|
43
|
+
readRanges: result.readRanges,
|
|
44
|
+
logLines: result.logLines,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async executeMutation(path, args, auth, componentPath) {
|
|
48
|
+
const adapter = this.createAdapter();
|
|
49
|
+
const result = await adapter.executeUdf(path, args, "mutation", auth, componentPath);
|
|
50
|
+
return {
|
|
51
|
+
result: result.result,
|
|
52
|
+
writtenRanges: result.writtenRanges,
|
|
53
|
+
writtenTables: result.writtenRanges ? writtenTablesFromRanges(result.writtenRanges) : undefined,
|
|
54
|
+
logLines: result.logLines,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async executeAction(path, args, auth, componentPath) {
|
|
58
|
+
const adapter = this.createAdapter();
|
|
59
|
+
const result = await adapter.executeUdf(path, args, "action", auth, componentPath);
|
|
60
|
+
return {
|
|
61
|
+
result: result.result,
|
|
62
|
+
writtenRanges: result.writtenRanges,
|
|
63
|
+
writtenTables: result.writtenRanges ? writtenTablesFromRanges(result.writtenRanges) : undefined,
|
|
64
|
+
logLines: result.logLines,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { UserIdentityAttributes } from "convex/server";
|
|
2
|
+
import type { DurableObjectStub } from "@cloudflare/workers-types";
|
|
3
|
+
import type { UdfExec, UdfResult } from "@concavejs/core/udf";
|
|
4
|
+
import type { AuthContext } from "@concavejs/core/sync/protocol-handler";
|
|
5
|
+
/**
|
|
6
|
+
* Durable Object stub executor that bridges HTTP API requests to ConcaveDO.
|
|
7
|
+
* Converts Convex values to JSON-safe payloads and forwards auth/context metadata.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ConcaveStubExecutor implements UdfExec {
|
|
10
|
+
private readonly stub;
|
|
11
|
+
constructor(stub: DurableObjectStub);
|
|
12
|
+
execute(path: string, args: Record<string, any>, type: "query" | "mutation" | "action", auth?: AuthContext | UserIdentityAttributes, componentPath?: string, requestId?: string): Promise<UdfResult>;
|
|
13
|
+
executeHttp(request: Request): Promise<Response>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { convexToJson } from "convex/values";
|
|
2
|
+
/**
|
|
3
|
+
* Durable Object stub executor that bridges HTTP API requests to ConcaveDO.
|
|
4
|
+
* Converts Convex values to JSON-safe payloads and forwards auth/context metadata.
|
|
5
|
+
*/
|
|
6
|
+
export class ConcaveStubExecutor {
|
|
7
|
+
stub;
|
|
8
|
+
constructor(stub) {
|
|
9
|
+
this.stub = stub;
|
|
10
|
+
}
|
|
11
|
+
async execute(path, args, type, auth, componentPath, requestId) {
|
|
12
|
+
const payload = {
|
|
13
|
+
path,
|
|
14
|
+
args: convexToJson(args),
|
|
15
|
+
type,
|
|
16
|
+
auth,
|
|
17
|
+
componentPath,
|
|
18
|
+
caller: "client",
|
|
19
|
+
requestId,
|
|
20
|
+
};
|
|
21
|
+
const response = await this.stub.fetch("http://do/execute", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
|
+
body: JSON.stringify(payload),
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const errorText = await response.text();
|
|
28
|
+
throw new Error(`Function execution failed with status ${response.status}: ${errorText}`);
|
|
29
|
+
}
|
|
30
|
+
const result = (await response.json());
|
|
31
|
+
if (result.commitTimestamp) {
|
|
32
|
+
return {
|
|
33
|
+
...result,
|
|
34
|
+
commitTimestamp: BigInt(result.commitTimestamp),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
async executeHttp(request) {
|
|
40
|
+
return this.stub.fetch(request);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DocStore } from "@concavejs/core/docstore";
|
|
2
|
+
import type { BlobStore } from "@concavejs/core/abstractions";
|
|
3
|
+
import { InlineUdfExecutor } from "@concavejs/core/udf";
|
|
4
|
+
export declare class UdfExecInline extends InlineUdfExecutor {
|
|
5
|
+
/**
|
|
6
|
+
* Create an inline UDF executor.
|
|
7
|
+
*
|
|
8
|
+
* @param docstore - Document store for data storage
|
|
9
|
+
* @param blobstoreOrBucket - Either a BlobStore or an R2Bucket
|
|
10
|
+
* @param r2PublicUrl - Public URL for R2 (only used if blobstoreOrBucket is an R2Bucket)
|
|
11
|
+
*/
|
|
12
|
+
constructor(docstore: DocStore, blobstoreOrBucket?: BlobStore | R2Bucket, r2PublicUrl?: string);
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { InlineUdfExecutor } from "@concavejs/core/udf";
|
|
2
|
+
import { R2BlobStore } from "@concavejs/blobstore-cf-r2";
|
|
3
|
+
export class UdfExecInline extends InlineUdfExecutor {
|
|
4
|
+
/**
|
|
5
|
+
* Create an inline UDF executor.
|
|
6
|
+
*
|
|
7
|
+
* @param docstore - Document store for data storage
|
|
8
|
+
* @param blobstoreOrBucket - Either a BlobStore or an R2Bucket
|
|
9
|
+
* @param r2PublicUrl - Public URL for R2 (only used if blobstoreOrBucket is an R2Bucket)
|
|
10
|
+
*/
|
|
11
|
+
constructor(docstore, blobstoreOrBucket, r2PublicUrl) {
|
|
12
|
+
let blobstore;
|
|
13
|
+
if (blobstoreOrBucket) {
|
|
14
|
+
// Check if it's already a BlobStore (has store method)
|
|
15
|
+
if (typeof blobstoreOrBucket.store === "function") {
|
|
16
|
+
blobstore = blobstoreOrBucket;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
// It's an R2Bucket, wrap it
|
|
20
|
+
blobstore = new R2BlobStore(blobstoreOrBucket, r2PublicUrl);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
super({ docstore, blobstore, logger: console });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { UdfExec, UdfResult } from "@concavejs/core/udf";
|
|
2
|
+
import type { UdfExecutorRpc } from "../../worker/udf-worker";
|
|
3
|
+
export interface UdfExecIsolatedOptions {
|
|
4
|
+
/** The RPC stub for the UDF worker */
|
|
5
|
+
stub: UdfExecutorRpc;
|
|
6
|
+
/** Instance name for syscall routing (default: "singleton") */
|
|
7
|
+
instance?: string;
|
|
8
|
+
/** Project ID for syscall routing (default: "default") */
|
|
9
|
+
projectId?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Executes a UDF in an isolated worker via RPC.
|
|
13
|
+
* This class acts as an RPC client.
|
|
14
|
+
*
|
|
15
|
+
* Supports passing instance context for proper syscall routing through the DO.
|
|
16
|
+
*/
|
|
17
|
+
export declare class UdfExecIsolated implements UdfExec {
|
|
18
|
+
private rpc;
|
|
19
|
+
private instance;
|
|
20
|
+
private projectId;
|
|
21
|
+
constructor(stubOrOptions: UdfExecutorRpc | UdfExecIsolatedOptions);
|
|
22
|
+
execute(path: string, args: Record<string, any>, type: "query" | "mutation" | "action", auth?: any, componentPath?: string, requestId?: string): Promise<UdfResult>;
|
|
23
|
+
executeHttp(request: Request, auth?: any, requestId?: string): Promise<Response>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executes a UDF in an isolated worker via RPC.
|
|
3
|
+
* This class acts as an RPC client.
|
|
4
|
+
*
|
|
5
|
+
* Supports passing instance context for proper syscall routing through the DO.
|
|
6
|
+
*/
|
|
7
|
+
export class UdfExecIsolated {
|
|
8
|
+
rpc;
|
|
9
|
+
instance;
|
|
10
|
+
projectId;
|
|
11
|
+
constructor(stubOrOptions) {
|
|
12
|
+
if ("stub" in stubOrOptions) {
|
|
13
|
+
this.rpc = stubOrOptions.stub;
|
|
14
|
+
this.instance = stubOrOptions.instance ?? "singleton";
|
|
15
|
+
this.projectId = stubOrOptions.projectId ?? "default";
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// Backwards compatibility: just the stub
|
|
19
|
+
this.rpc = stubOrOptions;
|
|
20
|
+
this.instance = "singleton";
|
|
21
|
+
this.projectId = "default";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async execute(path, args, type, auth, componentPath, requestId) {
|
|
25
|
+
// Pass instance context for syscall routing
|
|
26
|
+
return this.rpc.execute(path, args, type, auth, componentPath, requestId, this.instance, this.projectId);
|
|
27
|
+
}
|
|
28
|
+
async executeHttp(request, auth, requestId) {
|
|
29
|
+
return this.rpc.executeHttp(request, auth, requestId, this.instance, this.projectId);
|
|
30
|
+
}
|
|
31
|
+
}
|