@dxos/functions-runtime-cloudflare 0.8.4-main.1068cf700f
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/LICENSE +8 -0
- package/README.md +47 -0
- package/dist/lib/browser/index.mjs +1137 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/node-esm/index.mjs +1139 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/types/src/functions-client.d.ts +33 -0
- package/dist/types/src/functions-client.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +6 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/internal/adapter.d.ts +12 -0
- package/dist/types/src/internal/adapter.d.ts.map +1 -0
- package/dist/types/src/internal/data-service-impl.d.ts +26 -0
- package/dist/types/src/internal/data-service-impl.d.ts.map +1 -0
- package/dist/types/src/internal/index.d.ts +2 -0
- package/dist/types/src/internal/index.d.ts.map +1 -0
- package/dist/types/src/internal/query-service-impl.d.ts +19 -0
- package/dist/types/src/internal/query-service-impl.d.ts.map +1 -0
- package/dist/types/src/internal/queue-service-impl.d.ts +10 -0
- package/dist/types/src/internal/queue-service-impl.d.ts.map +1 -0
- package/dist/types/src/internal/service-container.d.ts +25 -0
- package/dist/types/src/internal/service-container.d.ts.map +1 -0
- package/dist/types/src/internal/utils.d.ts +2 -0
- package/dist/types/src/internal/utils.d.ts.map +1 -0
- package/dist/types/src/logger.d.ts +2 -0
- package/dist/types/src/logger.d.ts.map +1 -0
- package/dist/types/src/queues-api.d.ts +22 -0
- package/dist/types/src/queues-api.d.ts.map +1 -0
- package/dist/types/src/space-proxy.d.ts +26 -0
- package/dist/types/src/space-proxy.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +31 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/wrap-handler-for-cloudflare.d.ts +6 -0
- package/dist/types/src/wrap-handler-for-cloudflare.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +52 -0
- package/src/functions-client.ts +88 -0
- package/src/index.ts +9 -0
- package/src/internal/adapter.ts +48 -0
- package/src/internal/data-service-impl.ts +142 -0
- package/src/internal/index.ts +5 -0
- package/src/internal/query-service-impl.ts +107 -0
- package/src/internal/queue-service-impl.ts +62 -0
- package/src/internal/service-container.ts +71 -0
- package/src/internal/utils.ts +5 -0
- package/src/logger.ts +42 -0
- package/src/queues-api.ts +38 -0
- package/src/space-proxy.ts +66 -0
- package/src/types.ts +40 -0
- package/src/wrap-handler-for-cloudflare.ts +132 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Resource } from '@dxos/context';
|
|
6
|
+
import { type Database } from '@dxos/echo';
|
|
7
|
+
import { type CoreDatabase, type EchoClient, type EchoDatabaseImpl } from '@dxos/echo-db';
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { PublicKey, type SpaceId } from '@dxos/keys';
|
|
10
|
+
|
|
11
|
+
import type { ServiceContainer } from './internal';
|
|
12
|
+
import { type QueuesAPI, QueuesAPIImpl } from './queues-api';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated
|
|
16
|
+
*/
|
|
17
|
+
export class SpaceProxy extends Resource {
|
|
18
|
+
private _db?: EchoDatabaseImpl = undefined;
|
|
19
|
+
private _queuesApi: QueuesAPIImpl;
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly _serviceContainer: ServiceContainer,
|
|
23
|
+
private readonly _echoClient: EchoClient,
|
|
24
|
+
private readonly _id: SpaceId,
|
|
25
|
+
) {
|
|
26
|
+
super();
|
|
27
|
+
this._queuesApi = new QueuesAPIImpl(this._serviceContainer, this._id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get id(): SpaceId {
|
|
31
|
+
return this._id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get db(): Database.Database {
|
|
35
|
+
invariant(this._db);
|
|
36
|
+
return this._db;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated Use db API.
|
|
41
|
+
*/
|
|
42
|
+
get crud(): CoreDatabase {
|
|
43
|
+
invariant(this._db);
|
|
44
|
+
return this._db.coreDatabase;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get queues(): QueuesAPI {
|
|
48
|
+
return this._queuesApi;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected override async _open() {
|
|
52
|
+
const meta = await this._serviceContainer.getSpaceMeta(this._id);
|
|
53
|
+
if (!meta) {
|
|
54
|
+
throw new Error(`Space not found: ${this._id}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this._db = this._echoClient.constructDatabase({
|
|
58
|
+
spaceId: this._id,
|
|
59
|
+
spaceKey: PublicKey.from(meta.spaceKey),
|
|
60
|
+
reactiveSchemaQuery: false,
|
|
61
|
+
owningObject: this,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await this._db.coreDatabase.open({ rootUrl: meta.rootDocumentId });
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { JsonSchemaType } from '@dxos/echo/internal';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Is used for to route the request to the metadata handler instead of the main handler.
|
|
9
|
+
*/
|
|
10
|
+
export const FUNCTION_ROUTE_HEADER = 'X-DXOS-Function-Route';
|
|
11
|
+
|
|
12
|
+
export enum FunctionRouteValue {
|
|
13
|
+
Meta = 'meta',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type FunctionMetadata = {
|
|
17
|
+
/**
|
|
18
|
+
* FQN.
|
|
19
|
+
*/
|
|
20
|
+
key: string;
|
|
21
|
+
/**
|
|
22
|
+
* Human-readable name.
|
|
23
|
+
*/
|
|
24
|
+
name?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Description.
|
|
28
|
+
*/
|
|
29
|
+
description?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Input schema.
|
|
33
|
+
*/
|
|
34
|
+
inputSchema?: JsonSchemaType;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Output schema.
|
|
38
|
+
*/
|
|
39
|
+
outputSchema?: JsonSchemaType;
|
|
40
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import type { JsonSchemaType } from '@dxos/echo/internal';
|
|
6
|
+
import { invariant } from '@dxos/invariant';
|
|
7
|
+
import { SpaceId } from '@dxos/keys';
|
|
8
|
+
import { log } from '@dxos/log';
|
|
9
|
+
import { EdgeResponse } from '@dxos/protocols';
|
|
10
|
+
import type { EdgeFunctionEnv, FunctionProtocol } from '@dxos/protocols';
|
|
11
|
+
|
|
12
|
+
import { ServiceContainer } from './internal';
|
|
13
|
+
import { FUNCTION_ROUTE_HEADER, type FunctionMetadata, FunctionRouteValue } from './types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Wraps a user function in a Cloudflare-compatible handler.
|
|
17
|
+
*/
|
|
18
|
+
export const wrapHandlerForCloudflare = (func: FunctionProtocol.Func): ExportedHandlerFetchHandler<any> => {
|
|
19
|
+
return async (request: Request, env: EdgeFunctionEnv.Env): Promise<Response> => {
|
|
20
|
+
// TODO(dmaretskyi): Should theÓ scope name reflect the function name?
|
|
21
|
+
// TODO(mykola): Wrap in withCleanAutomergeWasmState;
|
|
22
|
+
// TODO(mykola): Wrap in withNewExecutionContext;
|
|
23
|
+
// Meta route is used to get the input schema of the function by the functions service.
|
|
24
|
+
if (request.headers.get(FUNCTION_ROUTE_HEADER) === FunctionRouteValue.Meta) {
|
|
25
|
+
return handleFunctionMetaCall(func, request);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const spaceId = new URL(request.url).searchParams.get('spaceId');
|
|
30
|
+
if (spaceId) {
|
|
31
|
+
if (!SpaceId.isValid(spaceId)) {
|
|
32
|
+
return new Response('Invalid spaceId', { status: 400 });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const serviceContainer = new ServiceContainer({}, env.DATA_SERVICE, env.QUEUE_SERVICE, env.FUNCTIONS_AI_SERVICE);
|
|
37
|
+
const context = await createFunctionContext({
|
|
38
|
+
serviceContainer,
|
|
39
|
+
contextSpaceId: spaceId as SpaceId | undefined,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return EdgeResponse.success(await invokeFunction(func, context, request));
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
log.error('error invoking function', { error, stack: error.stack });
|
|
45
|
+
return EdgeResponse.failure({
|
|
46
|
+
message: error?.message ?? 'Internal error',
|
|
47
|
+
error,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const invokeFunction = async (func: FunctionProtocol.Func, context: FunctionProtocol.Context, request: Request) => {
|
|
54
|
+
// TODO(dmaretskyi): For some reason requests get wrapped like this.
|
|
55
|
+
const { data } = await decodeRequest(request);
|
|
56
|
+
|
|
57
|
+
return func.handler({
|
|
58
|
+
context,
|
|
59
|
+
data,
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const decodeRequest = async (request: Request) => {
|
|
64
|
+
const {
|
|
65
|
+
data: { bodyText, ...rest },
|
|
66
|
+
trigger,
|
|
67
|
+
} = (await request.json()) as any;
|
|
68
|
+
|
|
69
|
+
if (!bodyText) {
|
|
70
|
+
return { data: rest, trigger };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Webhook passed body as bodyText. Use it as function input if a well-formatted JSON
|
|
74
|
+
// TODO: better trigger input mapping
|
|
75
|
+
try {
|
|
76
|
+
const data = JSON.parse(bodyText);
|
|
77
|
+
return { data, trigger: { ...trigger, ...rest } };
|
|
78
|
+
} catch (err) {
|
|
79
|
+
log.catch(err);
|
|
80
|
+
return { data: { bodyText, ...rest } };
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleFunctionMetaCall = (functionDefinition: FunctionProtocol.Func, request: Request): Response => {
|
|
85
|
+
const response: FunctionMetadata = {
|
|
86
|
+
key: functionDefinition.meta.key,
|
|
87
|
+
name: functionDefinition.meta.name,
|
|
88
|
+
description: functionDefinition.meta.description,
|
|
89
|
+
inputSchema: functionDefinition.meta.inputSchema as JsonSchemaType | undefined,
|
|
90
|
+
outputSchema: functionDefinition.meta.outputSchema as JsonSchemaType | undefined,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return new Response(JSON.stringify(response), {
|
|
94
|
+
headers: {
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const createFunctionContext = async ({
|
|
101
|
+
serviceContainer,
|
|
102
|
+
contextSpaceId,
|
|
103
|
+
}: {
|
|
104
|
+
serviceContainer: ServiceContainer;
|
|
105
|
+
contextSpaceId: SpaceId | undefined;
|
|
106
|
+
}): Promise<FunctionProtocol.Context> => {
|
|
107
|
+
const { dataService, queryService, queueService, functionsAiService } = await serviceContainer.createServices();
|
|
108
|
+
|
|
109
|
+
let spaceKey: string | undefined;
|
|
110
|
+
let rootUrl: string | undefined;
|
|
111
|
+
if (contextSpaceId) {
|
|
112
|
+
const meta = await serviceContainer.getSpaceMeta(contextSpaceId);
|
|
113
|
+
if (!meta) {
|
|
114
|
+
throw new Error(`Space not found: ${contextSpaceId}`);
|
|
115
|
+
}
|
|
116
|
+
spaceKey = meta.spaceKey;
|
|
117
|
+
invariant(!meta.rootDocumentId.startsWith('automerge:'));
|
|
118
|
+
rootUrl = `automerge:${meta.rootDocumentId}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
services: {
|
|
123
|
+
dataService,
|
|
124
|
+
queryService,
|
|
125
|
+
queueService,
|
|
126
|
+
functionsAiService,
|
|
127
|
+
},
|
|
128
|
+
spaceId: contextSpaceId,
|
|
129
|
+
spaceKey,
|
|
130
|
+
spaceRootUrl: rootUrl,
|
|
131
|
+
} as any; // TODO(dmaretskyi): Link and fix before merging
|
|
132
|
+
};
|