@concavejs/core 0.0.1-alpha.5 → 0.0.1-alpha.6
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.
- package/dist/http/api-router.d.ts +1 -1
- package/dist/http/api-router.js +2 -11
- package/dist/http/http-handler.js +37 -3
- package/dist/interfaces/execution-context.d.ts +2 -1
- package/dist/kernel/blob-store-gateway.js +7 -7
- package/dist/kernel/context-storage.js +6 -0
- package/dist/kernel/syscalls/action-syscalls.js +4 -6
- package/dist/kernel/syscalls/database-syscalls.js +1 -1
- package/dist/kernel/syscalls/js-router.d.ts +2 -2
- package/dist/kernel/syscalls/kernel-syscalls.d.ts +1 -1
- package/dist/kernel/syscalls/query-syscalls.js +2 -2
- package/dist/kernel/syscalls/router.d.ts +3 -2
- package/dist/kernel/syscalls/utils.d.ts +3 -3
- package/dist/kernel/syscalls/utils.js +3 -2
- package/dist/queryengine/developer-id.d.ts +8 -0
- package/dist/queryengine/developer-id.js +23 -2
- package/dist/router/router.d.ts +8 -3
- package/dist/runtime/runtime-context.d.ts +1 -1
- package/dist/transactor/occ-transaction.js +3 -3
- package/dist/udf/executor/interface.d.ts +5 -2
- package/dist/udf/runtime/udf-setup.d.ts +3 -3
- package/dist/udf/runtime/udf-setup.js +7 -5
- package/dist/utils/long.d.ts +1 -1
- package/package.json +1 -1
|
@@ -24,7 +24,7 @@ export interface StorageStoreResult {
|
|
|
24
24
|
}
|
|
25
25
|
export interface StorageGetResult {
|
|
26
26
|
blob: Blob | null;
|
|
27
|
-
headers?:
|
|
27
|
+
headers?: Record<string, string> | [string, string][] | Headers;
|
|
28
28
|
}
|
|
29
29
|
export interface StorageAdapter {
|
|
30
30
|
store(blob: Blob, request: Request): Promise<StorageStoreResult>;
|
package/dist/http/api-router.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { AdminAuthError, assertAdminToken, assertSystemToken, identityFromToken, isAdminToken, isSystemToken, JWTValidationError, SystemAuthError, } from "../auth";
|
|
2
|
-
import {
|
|
3
|
-
import { isValidDocumentId } from "../id-codec";
|
|
2
|
+
import { writtenTablesFromRanges } from "../utils";
|
|
4
3
|
import { InternalFunctionAccessError } from "../errors";
|
|
5
4
|
export function computeCorsHeaders(request) {
|
|
6
5
|
const origin = request.headers.get("Origin");
|
|
@@ -154,18 +153,10 @@ export async function handleCoreHttpApiRequest(request, options) {
|
|
|
154
153
|
}
|
|
155
154
|
if (request.method === "GET" && routeSegments.length === 2) {
|
|
156
155
|
try {
|
|
157
|
-
|
|
158
|
-
const originalId = storageId;
|
|
159
|
-
// If the ID doesn't have a table prefix and isn't a valid Base32 document ID,
|
|
160
|
-
// it's likely a raw hex storage ID. Prepend the default _storage table prefix.
|
|
161
|
-
if (!storageId.includes(":") && !isValidDocumentId(storageId)) {
|
|
162
|
-
const tableHex = stringToHex("_storage");
|
|
163
|
-
storageId = `${tableHex}:${storageId}`;
|
|
164
|
-
}
|
|
156
|
+
const storageId = decodeURIComponent(routeSegments[1]);
|
|
165
157
|
const result = await options.storage.get(storageId, request);
|
|
166
158
|
const blob = result.blob;
|
|
167
159
|
if (!blob) {
|
|
168
|
-
console.error(`[CoreHttpApi] Storage object not found. ID: ${storageId} (original: ${originalId})`);
|
|
169
160
|
return {
|
|
170
161
|
handled: true,
|
|
171
162
|
response: apply(new Response("Object not found", { status: 404 })),
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Delegates Convex-style HTTP API routes to shared core logic while
|
|
5
5
|
* wiring runtime execution adapters and docstore.
|
|
6
6
|
*/
|
|
7
|
+
import { loadConvexModule } from "../udf";
|
|
7
8
|
import { createClientAdapter } from "../udf/execution-adapter";
|
|
8
9
|
import { UdfKernel } from "../queryengine";
|
|
9
10
|
import { applyCors, computeCorsHeaders, handleCoreHttpApiRequest, resolveAuthContext } from "./index";
|
|
@@ -161,10 +162,43 @@ export class HttpHandler {
|
|
|
161
162
|
return apply(Response.json({ error: "Invalid function path" }, { status: 400 }));
|
|
162
163
|
}
|
|
163
164
|
const isFunctionHandle = body.path.startsWith("function://");
|
|
165
|
+
// Normalize slash-separated paths to colon format (e.g. "messages/list" → "messages:list")
|
|
166
|
+
if (!isFunctionHandle && !body.path.includes(":") && body.path.includes("/")) {
|
|
167
|
+
const lastSlash = body.path.lastIndexOf("/");
|
|
168
|
+
body.path = body.path.substring(0, lastSlash) + ":" + body.path.substring(lastSlash + 1);
|
|
169
|
+
}
|
|
164
170
|
if (!isFunctionHandle && !body.path.includes(":")) {
|
|
165
171
|
return apply(Response.json({ error: "Invalid function path" }, { status: 400 }));
|
|
166
172
|
}
|
|
167
|
-
|
|
173
|
+
let udfType;
|
|
174
|
+
if (body.type === "query" || body.type === "mutation" || body.type === "action") {
|
|
175
|
+
udfType = body.type;
|
|
176
|
+
}
|
|
177
|
+
else if (body.type === undefined || body.type === null) {
|
|
178
|
+
// Auto-detect type by loading the module and inspecting the export
|
|
179
|
+
try {
|
|
180
|
+
const modulePath = body.path.split(":")[0];
|
|
181
|
+
const functionName = body.path.split(":")[1] ?? "default";
|
|
182
|
+
const module = await loadConvexModule(modulePath, { hint: "udf" });
|
|
183
|
+
const func = module[functionName];
|
|
184
|
+
if (!func) {
|
|
185
|
+
return apply(Response.json({ error: `Function ${body.path} not found` }, { status: 404 }));
|
|
186
|
+
}
|
|
187
|
+
if (func.isQuery)
|
|
188
|
+
udfType = "query";
|
|
189
|
+
else if (func.isMutation)
|
|
190
|
+
udfType = "mutation";
|
|
191
|
+
else if (func.isAction)
|
|
192
|
+
udfType = "action";
|
|
193
|
+
else {
|
|
194
|
+
return apply(Response.json({ error: `Function ${body.path} is not a valid query, mutation, or action` }, { status: 400 }));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
return apply(Response.json({ error: `Could not auto-detect function type: ${error.message}` }, { status: 400 }));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
168
202
|
return apply(Response.json({ error: "Invalid function type" }, { status: 400 }));
|
|
169
203
|
}
|
|
170
204
|
try {
|
|
@@ -216,10 +250,10 @@ export class HttpHandler {
|
|
|
216
250
|
return apply(Response.json({ error: "Invalid args" }, { status: 400 }));
|
|
217
251
|
}
|
|
218
252
|
const normalizedArgs = rawArgs ?? {};
|
|
219
|
-
const result = await this.adapter.executeUdf(body.path, normalizedArgs,
|
|
253
|
+
const result = await this.adapter.executeUdf(body.path, normalizedArgs, udfType, authForExecution, body.componentPath);
|
|
220
254
|
const writtenTables = writtenTablesFromRanges(result.writtenRanges);
|
|
221
255
|
// Notify subscriptions for mutations/actions with writes
|
|
222
|
-
if ((
|
|
256
|
+
if ((udfType === "mutation" || udfType === "action") && result.writtenRanges?.length) {
|
|
223
257
|
await notifyWrites(result.writtenRanges, writtenTables, result.commitTimestamp);
|
|
224
258
|
}
|
|
225
259
|
return apply(Response.json({
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* - Distributed Mode: May read from cache or remote, writes go via coordinator
|
|
8
8
|
*/
|
|
9
9
|
import type { Transactor, TransactionHandle } from "./transactor";
|
|
10
|
+
import type { UserIdentityAttributes } from "convex/server";
|
|
10
11
|
export interface AuthContext {
|
|
11
12
|
type: "Admin" | "User" | "None";
|
|
12
|
-
userIdentity?:
|
|
13
|
+
userIdentity?: UserIdentityAttributes;
|
|
13
14
|
}
|
|
14
15
|
export interface ExecutionContext {
|
|
15
16
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { encodeDocumentId, internalIdToHex } from "../id-codec";
|
|
2
|
-
import {
|
|
2
|
+
import { parseStorageId } from "../queryengine/developer-id";
|
|
3
3
|
import { serializeDeveloperId, stringToHex } from "../utils/utils";
|
|
4
4
|
export class BlobStoreGateway {
|
|
5
5
|
context;
|
|
@@ -51,19 +51,19 @@ export class BlobStoreGateway {
|
|
|
51
51
|
if (!this.storage) {
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
|
-
const docId =
|
|
54
|
+
const docId = parseStorageId(storageId);
|
|
55
55
|
if (!docId) {
|
|
56
|
-
console.
|
|
56
|
+
console.debug(`[BlobStoreGateway] Failed to parse storage ID: ${storageId}`);
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
const docValue = await this.queryRuntime.getVisibleDocumentById(storageId, docId);
|
|
60
60
|
if (!docValue) {
|
|
61
|
-
console.
|
|
61
|
+
console.debug(`[BlobStoreGateway] Document not found for storage ID: ${storageId} (table: ${docId.table}, internalId: ${docId.internalId}, ts: ${this.context.snapshotTimestamp})`);
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
64
|
const storedBlob = await this.storage.get(docId.internalId);
|
|
65
65
|
if (!storedBlob) {
|
|
66
|
-
console.
|
|
66
|
+
console.debug(`[BlobStoreGateway] Blob not found in storage: ${docId.internalId}`);
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
69
69
|
return storedBlob instanceof Blob ? storedBlob : new Blob([storedBlob]);
|
|
@@ -72,7 +72,7 @@ export class BlobStoreGateway {
|
|
|
72
72
|
if (!this.storage) {
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
|
-
const docId =
|
|
75
|
+
const docId = parseStorageId(storageId);
|
|
76
76
|
if (!docId) {
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
@@ -85,7 +85,7 @@ export class BlobStoreGateway {
|
|
|
85
85
|
}
|
|
86
86
|
async delete(storageId) {
|
|
87
87
|
const storage = this.requireStorage();
|
|
88
|
-
const docId =
|
|
88
|
+
const docId = parseStorageId(storageId);
|
|
89
89
|
if (!docId) {
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
@@ -30,18 +30,24 @@ export class ContextStorage {
|
|
|
30
30
|
}
|
|
31
31
|
return this.stack.length > 0 ? this.stack[this.stack.length - 1] : undefined;
|
|
32
32
|
}
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
34
|
run(value, callback) {
|
|
34
35
|
if (this.als) {
|
|
36
|
+
// ALS.run handles both sync and async callbacks correctly
|
|
35
37
|
return this.als.run(value, callback);
|
|
36
38
|
}
|
|
37
39
|
this.stack.push(value);
|
|
38
40
|
const result = callback();
|
|
39
41
|
if (result && typeof result.then === "function") {
|
|
42
|
+
// For async callbacks, clean up the stack when the promise settles.
|
|
43
|
+
// The cast is safe: the callback returned a Promise<R> which is assignable to R
|
|
44
|
+
// when the caller expects a Promise return (which is always the case for async callbacks).
|
|
40
45
|
return result.finally(() => {
|
|
41
46
|
this.stack.pop();
|
|
42
47
|
});
|
|
43
48
|
}
|
|
44
49
|
this.stack.pop();
|
|
50
|
+
// At this point we've ruled out the Promise branch above, so result is R
|
|
45
51
|
return result;
|
|
46
52
|
}
|
|
47
53
|
}
|
|
@@ -34,19 +34,17 @@ export class ActionSyscalls {
|
|
|
34
34
|
async handleRunUdf(args) {
|
|
35
35
|
const target = resolveFunctionTarget(args, this.context.componentPath);
|
|
36
36
|
const udfArguments = normalizeUdfArgsPayload(args.args);
|
|
37
|
-
const type = args.udfType ?? args.type ?? "mutation";
|
|
37
|
+
const type = (args.udfType ?? args.type ?? "mutation");
|
|
38
38
|
return await this.invocationManager.execute(target.udfPath, udfArguments, type, target.componentPath);
|
|
39
39
|
}
|
|
40
|
-
handleCreateFunctionHandle(args) {
|
|
40
|
+
async handleCreateFunctionHandle(args) {
|
|
41
41
|
const target = resolveFunctionTarget(args, this.context.componentPath);
|
|
42
42
|
return formatFunctionHandle(target.componentPath ?? "", target.udfPath);
|
|
43
43
|
}
|
|
44
44
|
async handleVectorSearch(args) {
|
|
45
|
-
|
|
46
|
-
return this.queryRuntime.runVectorSearchAction(vectorQuery);
|
|
45
|
+
return this.queryRuntime.runVectorSearchAction(args.query);
|
|
47
46
|
}
|
|
48
47
|
async handleSearchAction(args) {
|
|
49
|
-
|
|
50
|
-
return this.queryRuntime.runSearchAction(searchQuery);
|
|
48
|
+
return this.queryRuntime.runSearchAction(args.query);
|
|
51
49
|
}
|
|
52
50
|
}
|
|
@@ -181,7 +181,7 @@ export class DatabaseSyscalls {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
else {
|
|
184
|
-
for (const [key, val] of Object.entries(value ?? {})) {
|
|
184
|
+
for (const [key, val] of Object.entries((value ?? {}))) {
|
|
185
185
|
if (val === "$undefined" || val === undefined) {
|
|
186
186
|
delete newValue[key];
|
|
187
187
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
type JsSyscallHandler = (args: Record<string,
|
|
1
|
+
type JsSyscallHandler = (args: Record<string, unknown>) => Promise<unknown>;
|
|
2
2
|
export declare class JsSyscallRouter {
|
|
3
3
|
private readonly handlers;
|
|
4
4
|
register(op: string, handler: JsSyscallHandler): void;
|
|
5
5
|
has(op: string): boolean;
|
|
6
|
-
dispatch(op: string, args: Record<string,
|
|
6
|
+
dispatch(op: string, args: Record<string, unknown>): Promise<unknown>;
|
|
7
7
|
}
|
|
8
8
|
export {};
|
|
@@ -5,5 +5,5 @@ export declare class KernelSyscalls {
|
|
|
5
5
|
constructor(resources: KernelResources);
|
|
6
6
|
syscall(op: string, jsonArgs: string): string;
|
|
7
7
|
asyncSyscall(op: string, jsonArgs: string): Promise<string>;
|
|
8
|
-
jsSyscall(op: string, args: Record<string,
|
|
8
|
+
jsSyscall(op: string, args: Record<string, unknown>): Promise<unknown>;
|
|
9
9
|
}
|
|
@@ -19,13 +19,13 @@ export class QuerySyscalls {
|
|
|
19
19
|
return { queryId };
|
|
20
20
|
}
|
|
21
21
|
handleQueryCleanup(args) {
|
|
22
|
-
const
|
|
22
|
+
const queryId = args.queryId;
|
|
23
23
|
delete this.pendingQueries[queryId];
|
|
24
24
|
delete this.queryResults[queryId];
|
|
25
25
|
return {};
|
|
26
26
|
}
|
|
27
27
|
async handleQueryStreamNext(args) {
|
|
28
|
-
const
|
|
28
|
+
const queryId = args.queryId;
|
|
29
29
|
if (this.pendingQueries[queryId]) {
|
|
30
30
|
const query = this.pendingQueries[queryId];
|
|
31
31
|
delete this.pendingQueries[queryId];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
type
|
|
2
|
-
type
|
|
1
|
+
type SyscallArgs = Record<string, unknown>;
|
|
2
|
+
type SyncSyscallHandler = (args: SyscallArgs) => unknown;
|
|
3
|
+
type AsyncSyscallHandler = (args: SyscallArgs) => Promise<unknown>;
|
|
3
4
|
export declare class SyscallRouter {
|
|
4
5
|
private readonly syncSyscalls;
|
|
5
6
|
private readonly asyncSyscalls;
|
|
@@ -5,11 +5,11 @@ export interface FunctionTarget {
|
|
|
5
5
|
componentPath?: string;
|
|
6
6
|
}
|
|
7
7
|
export declare function resolveTableName(docId: InternalDocumentId, tableRegistry: TableRegistry): Promise<string>;
|
|
8
|
-
export declare function normalizeUdfArgsPayload(udfArgs:
|
|
9
|
-
export declare function resolveFunctionTarget(callArgs:
|
|
8
|
+
export declare function normalizeUdfArgsPayload(udfArgs: unknown): unknown;
|
|
9
|
+
export declare function resolveFunctionTarget(callArgs: Record<string, unknown>, currentComponentPath: string): FunctionTarget;
|
|
10
10
|
export declare function parseFunctionHandleTarget(handle: string): FunctionTarget;
|
|
11
11
|
export declare function parseReferenceAddress(reference: string, currentComponentPath: string): FunctionTarget;
|
|
12
12
|
export declare function joinComponentScope(base: string, child: string): string;
|
|
13
13
|
export declare function normalizeComponentPathValue(path?: string | null): string | undefined;
|
|
14
14
|
export declare function formatFunctionHandle(componentPath: string | undefined, udfPath: string): string;
|
|
15
|
-
export declare function evaluatePatchValue(value:
|
|
15
|
+
export declare function evaluatePatchValue(value: unknown): unknown;
|
|
@@ -37,8 +37,9 @@ export function resolveFunctionTarget(callArgs, currentComponentPath) {
|
|
|
37
37
|
return parseFunctionHandleTarget(callArgs.functionHandle);
|
|
38
38
|
}
|
|
39
39
|
if (callArgs && typeof callArgs.functionHandle === "object" && callArgs.functionHandle !== null) {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const handle = callArgs.functionHandle;
|
|
41
|
+
if (typeof handle.handle === "string") {
|
|
42
|
+
return parseFunctionHandleTarget(handle.handle);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
if (callArgs && typeof callArgs.reference === "string") {
|
|
@@ -14,6 +14,14 @@ import type { InternalDocumentId } from "../docstore/interface";
|
|
|
14
14
|
* @returns The parsed InternalDocumentId or null if invalid
|
|
15
15
|
*/
|
|
16
16
|
export declare function parseDeveloperId(developerId: string): InternalDocumentId | null;
|
|
17
|
+
/**
|
|
18
|
+
* Parse a storage ID, which may be a full developer ID or a raw internal ID.
|
|
19
|
+
* If it's a raw internal ID (32-char hex), it defaults to the _storage table.
|
|
20
|
+
*
|
|
21
|
+
* @param storageId - The storage ID string
|
|
22
|
+
* @returns The parsed InternalDocumentId or null if invalid
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseStorageId(storageId: string): InternalDocumentId | null;
|
|
17
25
|
/**
|
|
18
26
|
* Check if an InternalDocumentId uses the component format (has tableNumber).
|
|
19
27
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { deserializeDeveloperId } from "../utils/utils";
|
|
2
|
-
import { isValidDocumentId, decodeDocumentId, internalIdToHex } from "../id-codec";
|
|
1
|
+
import { deserializeDeveloperId, stringToHex } from "../utils/utils";
|
|
2
|
+
import { isValidDocumentId, decodeDocumentId, internalIdToHex, INTERNAL_ID_LENGTH } from "../id-codec";
|
|
3
3
|
/**
|
|
4
4
|
* Parse a developer-facing ID string into an InternalDocumentId.
|
|
5
5
|
*
|
|
@@ -38,6 +38,27 @@ export function parseDeveloperId(developerId) {
|
|
|
38
38
|
}
|
|
39
39
|
return { table: parts.table, internalId: parts.internalId };
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse a storage ID, which may be a full developer ID or a raw internal ID.
|
|
43
|
+
* If it's a raw internal ID (32-char hex), it defaults to the _storage table.
|
|
44
|
+
*
|
|
45
|
+
* @param storageId - The storage ID string
|
|
46
|
+
* @returns The parsed InternalDocumentId or null if invalid
|
|
47
|
+
*/
|
|
48
|
+
export function parseStorageId(storageId) {
|
|
49
|
+
const parsed = parseDeveloperId(storageId);
|
|
50
|
+
if (parsed) {
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
// If it's a 32-character hex string, it's likely a raw internal ID for storage
|
|
54
|
+
if (storageId.length === INTERNAL_ID_LENGTH * 2 && /^[0-9a-fA-F]+$/.test(storageId)) {
|
|
55
|
+
return {
|
|
56
|
+
table: stringToHex("_storage"),
|
|
57
|
+
internalId: storageId.toLowerCase(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
41
62
|
/**
|
|
42
63
|
* Check if an InternalDocumentId uses the component format (has tableNumber).
|
|
43
64
|
*/
|
package/dist/router/router.d.ts
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
* Runtime adapter for Concave CLI
|
|
3
3
|
* This is a minimal runtime library used by the generated entry module
|
|
4
4
|
*/
|
|
5
|
+
/** Minimal execution context compatible with Cloudflare Workers, Bun, and Node.js */
|
|
6
|
+
export interface RuntimeExecutionContext {
|
|
7
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
8
|
+
passThroughOnException?(): void;
|
|
9
|
+
}
|
|
5
10
|
export type ConcaveContext = {
|
|
6
11
|
env: any;
|
|
7
12
|
request: Request;
|
|
8
|
-
ctx:
|
|
13
|
+
ctx: RuntimeExecutionContext;
|
|
9
14
|
};
|
|
10
15
|
export type UdfFunction = (ctx: ConcaveContext, args: unknown) => Promise<unknown>;
|
|
11
16
|
export interface ConcaveRouter {
|
|
12
|
-
fetch: (request: Request, env: any, ctx:
|
|
17
|
+
fetch: (request: Request, env: any, ctx: RuntimeExecutionContext) => Promise<Response>;
|
|
13
18
|
}
|
|
14
19
|
export interface RouterOptions {
|
|
15
20
|
/**
|
|
@@ -17,7 +22,7 @@ export interface RouterOptions {
|
|
|
17
22
|
* If provided, this handler is called first and can choose to handle the request
|
|
18
23
|
* or delegate to the default router by returning null
|
|
19
24
|
*/
|
|
20
|
-
customFetch?: (request: Request, env: any, ctx:
|
|
25
|
+
customFetch?: (request: Request, env: any, ctx: RuntimeExecutionContext, registry: Record<string, UdfFunction>) => Promise<Response | null>;
|
|
21
26
|
/**
|
|
22
27
|
* Base path for routes (default: "")
|
|
23
28
|
* Example: "/api" would make routes accessible at /api/convex/...
|
|
@@ -9,7 +9,7 @@ export interface RuntimeContextOptions {
|
|
|
9
9
|
blobstore?: BlobStore;
|
|
10
10
|
/** Vector storage provider (reserved for future use) */
|
|
11
11
|
vecstore?: unknown;
|
|
12
|
-
logger?: Pick<typeof console, "info" | "warn" | "error">;
|
|
12
|
+
logger?: Pick<typeof console, "debug" | "info" | "warn" | "error">;
|
|
13
13
|
moduleLoader?: ModuleLoader | ModuleLoader[];
|
|
14
14
|
moduleLoaders?: ModuleLoader[];
|
|
15
15
|
configureModuleLoaders?: (registry: ModuleRegistry) => void;
|
|
@@ -50,9 +50,9 @@ export class OccMutationTransaction {
|
|
|
50
50
|
}
|
|
51
51
|
getStorageTransaction() {
|
|
52
52
|
const maybeDocStore = this.docstore;
|
|
53
|
-
const
|
|
54
|
-
if (typeof transaction === "function") {
|
|
55
|
-
return { transaction };
|
|
53
|
+
const storage = maybeDocStore.state?.storage;
|
|
54
|
+
if (storage && typeof storage.transaction === "function") {
|
|
55
|
+
return { transaction: (fn) => storage.transaction(fn) };
|
|
56
56
|
}
|
|
57
57
|
return undefined;
|
|
58
58
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { UdfResult } from "../runtime/udf-setup";
|
|
2
|
+
import type { AuthContext } from "../../sync/protocol-handler";
|
|
3
|
+
import type { UserIdentityAttributes } from "convex/server";
|
|
4
|
+
export type { UdfResult };
|
|
2
5
|
export interface UdfExec {
|
|
3
|
-
execute(path: string, args: Record<string,
|
|
4
|
-
executeHttp(request: Request, auth?:
|
|
6
|
+
execute(path: string, args: Record<string, unknown>, type: "query" | "mutation" | "action", auth?: AuthContext | UserIdentityAttributes, componentPath?: string, requestId?: string): Promise<UdfResult>;
|
|
7
|
+
executeHttp(request: Request, auth?: AuthContext | UserIdentityAttributes, requestId?: string): Promise<Response>;
|
|
5
8
|
}
|
|
@@ -16,11 +16,11 @@ export interface UdfResult {
|
|
|
16
16
|
export interface UdfRuntimeOps {
|
|
17
17
|
mathRandom: typeof globalThis.Math.random | null;
|
|
18
18
|
dateNow: typeof globalThis.Date.now | null;
|
|
19
|
-
cryptoRandomUUID:
|
|
19
|
+
cryptoRandomUUID: (() => string) | null;
|
|
20
20
|
cryptoGetRandomValues: typeof globalThis.crypto.getRandomValues | null;
|
|
21
21
|
fetch: typeof globalThis.fetch | null;
|
|
22
|
-
setInterval: typeof globalThis.setInterval | null;
|
|
23
|
-
setTimeout: typeof globalThis.setTimeout | null;
|
|
22
|
+
setInterval: ((callback: (...args: any[]) => void, delay?: number, ...args: any[]) => ReturnType<typeof globalThis.setInterval>) | null;
|
|
23
|
+
setTimeout: ((callback: (...args: any[]) => void, delay?: number, ...args: any[]) => ReturnType<typeof globalThis.setTimeout>) | null;
|
|
24
24
|
console: typeof console | null;
|
|
25
25
|
convex: ConvexInterface;
|
|
26
26
|
}
|
|
@@ -68,9 +68,9 @@ function patchGlobals() {
|
|
|
68
68
|
globalThis.Date.now = udfDateNow;
|
|
69
69
|
globalThis.fetch = udfFetch;
|
|
70
70
|
globalThis.Math.random = udfMathRandom;
|
|
71
|
-
globalThis.setInterval = udfSetInterval;
|
|
72
|
-
globalThis.setTimeout = udfSetTimeout;
|
|
73
71
|
const globalAny = globalThis;
|
|
72
|
+
globalAny.setInterval = udfSetInterval;
|
|
73
|
+
globalAny.setTimeout = udfSetTimeout;
|
|
74
74
|
globalAny.crypto.randomUUID = udfCryptoRandomUUID;
|
|
75
75
|
globalAny.crypto.getRandomValues = udfCryptoGetRandomValues;
|
|
76
76
|
globalAny.console = udfConsole;
|
|
@@ -274,9 +274,11 @@ export function runUdfMutation(docstore, fn, auth, storage, requestId, udfExecut
|
|
|
274
274
|
// execute within the same transaction scope
|
|
275
275
|
const existingIdGenerator = idGeneratorContext.getStore();
|
|
276
276
|
const idGenerator = existingIdGenerator ?? createDeterministicIdGenerator(seed);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
// ContextStorage.run returns R (UdfResult here) but in practice the async
|
|
278
|
+
// callback produces a Promise<UdfResult>. We need to attach a .catch for
|
|
279
|
+
// savepoint rollback, so cast to the runtime-accurate Promise type.
|
|
280
|
+
const nestedResult = transactionContext.run(parentTransaction, () => idGeneratorContext.run(idGenerator, () => runUdfAndGetLogs(docstore, fn, ops, auth, "mutation", storage, seed, parentTransaction, udfExecutor, componentPath)));
|
|
281
|
+
return nestedResult.catch((error) => {
|
|
280
282
|
parentTransaction.restoreSavepoint(savepoint);
|
|
281
283
|
throw error;
|
|
282
284
|
});
|
package/dist/utils/long.d.ts
CHANGED