@dxos/edge-compute 0.0.0 → 0.11.0
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/lib/bundler.mjs +195 -0
- package/dist/lib/bundler.mjs.map +1 -0
- package/dist/lib/chunk-http-plugin-esbuild.mjs +52 -0
- package/dist/lib/chunk-http-plugin-esbuild.mjs.map +1 -0
- package/dist/lib/index.mjs +444 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/native.mjs +148 -0
- package/dist/lib/native.mjs.map +1 -0
- package/dist/lib/testing.mjs +40 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/EdgeProcessManager.d.ts +10 -4
- package/dist/types/src/EdgeProcessManager.d.ts.map +1 -1
- package/dist/types/src/EdgeProcessManager.test.d.ts +2 -0
- package/dist/types/src/EdgeProcessManager.test.d.ts.map +1 -0
- package/dist/types/src/EdgeTriggerManager.d.ts +11 -3
- package/dist/types/src/EdgeTriggerManager.d.ts.map +1 -1
- package/dist/types/src/native/bundler.d.ts.map +1 -1
- package/dist/types/src/testing/services.d.ts +7 -4
- package/dist/types/src/testing/services.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -22
- package/src/EdgeProcessManager.test.ts +59 -0
- package/src/EdgeProcessManager.ts +65 -16
- package/src/EdgeTriggerManager.ts +105 -31
- package/src/native/bundler.ts +8 -1
- package/src/testing/services.ts +22 -16
- package/dist/lib/neutral/bundler/index.mjs +0 -213
- package/dist/lib/neutral/bundler/index.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-RN6KVGN4.mjs +0 -63
- package/dist/lib/neutral/chunk-RN6KVGN4.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -343
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/native/index.mjs +0 -186
- package/dist/lib/neutral/native/index.mjs.map +0 -7
- package/dist/lib/neutral/testing/index.mjs +0 -41
- package/dist/lib/neutral/testing/index.mjs.map +0 -7
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { invariant } from "@dxos/invariant";
|
|
2
|
+
import { safeParseJson } from "@dxos/util";
|
|
3
|
+
import * as Duration from "effect/Duration";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Function$ from "effect/Function";
|
|
6
|
+
import * as Schedule from "effect/Schedule";
|
|
7
|
+
import * as Array from "effect/Array";
|
|
8
|
+
import * as Order from "effect/Order";
|
|
9
|
+
import { createEdgeIdentity } from "@dxos/client/edge";
|
|
10
|
+
import { FunctionError, Operation } from "@dxos/compute";
|
|
11
|
+
import { FUNCTIONS_META_KEY, FunctionServiceError, RemoteOperationInvoker, RemoteProcessManager, RemoteTriggerManager, setUserFunctionIdInMetadata } from "@dxos/compute-runtime";
|
|
12
|
+
import { Obj, Ref } from "@dxos/echo";
|
|
13
|
+
import { EdgeHttpClient } from "@dxos/edge-client";
|
|
14
|
+
import { log } from "@dxos/log";
|
|
15
|
+
import * as Layer from "effect/Layer";
|
|
16
|
+
import { ClientService } from "@dxos/client";
|
|
17
|
+
import { Atom, Registry } from "@effect-atom/atom";
|
|
18
|
+
import { Context } from "@dxos/context";
|
|
19
|
+
import { EID, SpaceId } from "@dxos/keys";
|
|
20
|
+
import * as Exit from "effect/Exit";
|
|
21
|
+
//#region \0rolldown/runtime.js
|
|
22
|
+
var __defProp = Object.defineProperty;
|
|
23
|
+
var __exportAll = (all, no_symbols) => {
|
|
24
|
+
let target = {};
|
|
25
|
+
for (var name in all) __defProp(target, name, {
|
|
26
|
+
get: all[name],
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
30
|
+
return target;
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/edge-client.ts
|
|
34
|
+
var __dxlog_file$3 = "/__w/dxos/dxos/packages/core/compute/edge-compute/src/edge-client.ts";
|
|
35
|
+
/** @deprecated Migrate to `client.edge`. */
|
|
36
|
+
var createEdgeClient = (client) => {
|
|
37
|
+
const edgeUrl = client.config.values.runtime?.services?.edge?.url;
|
|
38
|
+
invariant(edgeUrl, "Edge is not configured.", {
|
|
39
|
+
"~LogMeta": "~LogMeta",
|
|
40
|
+
F: __dxlog_file$3,
|
|
41
|
+
L: 38,
|
|
42
|
+
S: void 0,
|
|
43
|
+
A: ["edgeUrl", "'Edge is not configured.'"]
|
|
44
|
+
});
|
|
45
|
+
const edgeClient = new EdgeHttpClient(edgeUrl);
|
|
46
|
+
const edgeIdentity = createEdgeIdentity(client);
|
|
47
|
+
edgeClient.setIdentity(edgeIdentity);
|
|
48
|
+
return edgeClient;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* @deprecated Use {@link FunctionsServiceClient} instead.
|
|
52
|
+
*/
|
|
53
|
+
var uploadWorkerFunction = async (ctx, { client, version, name, functionId, ownerUri, entryPoint, assets }) => {
|
|
54
|
+
log("uploading function", {
|
|
55
|
+
functionId,
|
|
56
|
+
name,
|
|
57
|
+
version,
|
|
58
|
+
ownerUri
|
|
59
|
+
}, {
|
|
60
|
+
"~LogMeta": "~LogMeta",
|
|
61
|
+
F: __dxlog_file$3,
|
|
62
|
+
L: 52,
|
|
63
|
+
S: void 0
|
|
64
|
+
});
|
|
65
|
+
const response = await createEdgeClient(client).uploadFunction(ctx, { functionId }, {
|
|
66
|
+
name,
|
|
67
|
+
version,
|
|
68
|
+
ownerUri,
|
|
69
|
+
entryPoint,
|
|
70
|
+
assets
|
|
71
|
+
});
|
|
72
|
+
log("uploaded", {
|
|
73
|
+
identityKey: client.halo.identity.get()?.identityKey,
|
|
74
|
+
did: client.halo.identity.get()?.did,
|
|
75
|
+
functionId,
|
|
76
|
+
name,
|
|
77
|
+
version,
|
|
78
|
+
response
|
|
79
|
+
}, {
|
|
80
|
+
"~LogMeta": "~LogMeta",
|
|
81
|
+
F: __dxlog_file$3,
|
|
82
|
+
L: 61,
|
|
83
|
+
S: void 0
|
|
84
|
+
});
|
|
85
|
+
return response;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* @deprecated Use {@link FunctionsServiceClient} instead.
|
|
89
|
+
*/
|
|
90
|
+
var getDeployedFunctions = async (ctx, client, dedupe = false) => {
|
|
91
|
+
const functions = (await createEdgeClient(client).listFunctions(ctx)).uploadedFunctions.flatMap((record) => {
|
|
92
|
+
const latest = record.latestVersion ?? {};
|
|
93
|
+
const versionMeta = safeParseJson(latest.versionMetaJSON);
|
|
94
|
+
if (!versionMeta) return [];
|
|
95
|
+
const fn = Obj.make(Operation.PersistentOperation, {
|
|
96
|
+
[Obj.Meta]: {
|
|
97
|
+
key: versionMeta.key,
|
|
98
|
+
version: latest?.version ?? "0.0.0"
|
|
99
|
+
},
|
|
100
|
+
name: versionMeta.name ?? versionMeta.key ?? record.id,
|
|
101
|
+
updated: record?.updated !== void 0 ? new Date(record.updated).toISOString() : void 0,
|
|
102
|
+
description: versionMeta.description,
|
|
103
|
+
inputSchema: versionMeta.inputSchema,
|
|
104
|
+
outputSchema: versionMeta.outputSchema
|
|
105
|
+
});
|
|
106
|
+
Obj.update(fn, (fn) => setUserFunctionIdInMetadata(Obj.getMeta(fn), record.id));
|
|
107
|
+
return [fn];
|
|
108
|
+
});
|
|
109
|
+
if (dedupe) return Function$.pipe(functions, Array.filter((_) => Obj.getMeta(_).key !== void 0), Array.sort(Order.reverse(Order.mapInput(Order.string, (_) => _.updated ?? ""))), Array.dedupeWith((self, that) => Obj.getMeta(self).key === Obj.getMeta(that).key), Array.sort(Order.mapInput(Order.string, (_) => Obj.getMeta(_).key ?? "")));
|
|
110
|
+
else return functions;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* @deprecated Use {@link FunctionsServiceClient} instead.
|
|
114
|
+
*/
|
|
115
|
+
var invokeFunction = async (ctx, edgeClient, fn, input, { spaceId, cpuTimeLimit, subrequestsLimit } = {}) => {
|
|
116
|
+
const functionId = Obj.getMeta(fn).keys.find((key) => key.source === FUNCTIONS_META_KEY)?.id;
|
|
117
|
+
if (!functionId) throw new Error("No identifier for the function at the EDGE service");
|
|
118
|
+
const cleanedId = functionId.replace(/^\//, "");
|
|
119
|
+
return await edgeClient.invokeFunction(ctx, {
|
|
120
|
+
functionId: cleanedId,
|
|
121
|
+
spaceId,
|
|
122
|
+
cpuTimeLimit,
|
|
123
|
+
subrequestsLimit
|
|
124
|
+
}, input);
|
|
125
|
+
};
|
|
126
|
+
var incrementSemverPatch = (version) => {
|
|
127
|
+
const [major, minor, patch] = version.split(".");
|
|
128
|
+
const patchNum = Number(patch);
|
|
129
|
+
invariant(!Number.isNaN(patchNum), `Unexpected function version format: ${version}`, {
|
|
130
|
+
"~LogMeta": "~LogMeta",
|
|
131
|
+
F: __dxlog_file$3,
|
|
132
|
+
L: 149,
|
|
133
|
+
S: void 0,
|
|
134
|
+
A: ["!Number.isNaN(patchNum)", "`Unexpected function version format: ${version}`"]
|
|
135
|
+
});
|
|
136
|
+
return [
|
|
137
|
+
major,
|
|
138
|
+
minor,
|
|
139
|
+
String(patchNum + 1)
|
|
140
|
+
].join(".");
|
|
141
|
+
};
|
|
142
|
+
var publicKeyToDid = (key) => {
|
|
143
|
+
return `did:key:${key.toHex()}`;
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/EdgeOperationInvoker.ts
|
|
147
|
+
var EdgeOperationInvoker_exports = /* @__PURE__ */ __exportAll({
|
|
148
|
+
fromClient: () => fromClient$2,
|
|
149
|
+
fromEdgeClient: () => fromEdgeClient$2,
|
|
150
|
+
layer: () => layer$2
|
|
151
|
+
});
|
|
152
|
+
var make$2 = (getEdgeClient, spaceId) => ({ invoke: (ctx, deployedId, input) => Effect.gen(function* () {
|
|
153
|
+
const cleanedId = deployedId.replace(/^\//, "");
|
|
154
|
+
return yield* Effect.promise(() => getEdgeClient().invokeFunction(ctx, {
|
|
155
|
+
functionId: cleanedId,
|
|
156
|
+
spaceId
|
|
157
|
+
}, input)).pipe(Effect.mapError(FunctionError.wrap()), Effect.orDie);
|
|
158
|
+
}) });
|
|
159
|
+
/**
|
|
160
|
+
* For tests: provide a pre-built edge client.
|
|
161
|
+
*/
|
|
162
|
+
var fromEdgeClient$2 = (edgeClient, spaceId) => Layer.succeed(RemoteOperationInvoker.Service, make$2(() => edgeClient, spaceId));
|
|
163
|
+
/**
|
|
164
|
+
* Build from a `Client`, deferring edge-client creation until first invoke
|
|
165
|
+
* (identity may be absent at boot).
|
|
166
|
+
*/
|
|
167
|
+
var fromClient$2 = (client, spaceId) => {
|
|
168
|
+
let cached;
|
|
169
|
+
return Layer.succeed(RemoteOperationInvoker.Service, make$2(() => cached ??= createEdgeClient(client), spaceId));
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Build from the ambient `ClientService`.
|
|
173
|
+
*/
|
|
174
|
+
var layer$2 = (spaceId) => Layer.effect(RemoteOperationInvoker.Service, Effect.gen(function* () {
|
|
175
|
+
const client = yield* ClientService;
|
|
176
|
+
let cached;
|
|
177
|
+
return make$2(() => cached ??= createEdgeClient(client), spaceId);
|
|
178
|
+
}));
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/EdgeProcessManager.ts
|
|
181
|
+
var EdgeProcessManager_exports = /* @__PURE__ */ __exportAll({
|
|
182
|
+
fromClient: () => fromClient$1,
|
|
183
|
+
fromEdgeClient: () => fromEdgeClient$1,
|
|
184
|
+
layer: () => layer$1
|
|
185
|
+
});
|
|
186
|
+
var __dxlog_file$2 = "/__w/dxos/dxos/packages/core/compute/edge-compute/src/EdgeProcessManager.ts";
|
|
187
|
+
/**
|
|
188
|
+
* EDGE implementation of {@link RemoteProcessManager.Service}.
|
|
189
|
+
*
|
|
190
|
+
* The read view (`processTree`) is still empty — the EDGE worker exposes no
|
|
191
|
+
* process-tree endpoint yet (see Decision D3). What this adds is the `cancel`
|
|
192
|
+
* control: it force-cancels the current run of an edge trigger (its in-flight
|
|
193
|
+
* execution and `runAgain` continuation chain) via
|
|
194
|
+
* {@link EdgeHttpClient.cancelTriggerRun}, addressed by `trigger` in `space`.
|
|
195
|
+
* A manager built without a client (the {@link layer} stub) omits `cancel`.
|
|
196
|
+
*/
|
|
197
|
+
var makeManager = (registry, getEdgeClient) => {
|
|
198
|
+
const processTreeAtom = Atom.make([]);
|
|
199
|
+
registry.mount(processTreeAtom);
|
|
200
|
+
return {
|
|
201
|
+
processTree: Effect.sync(() => registry.get(processTreeAtom)),
|
|
202
|
+
processTreeAtom,
|
|
203
|
+
...getEdgeClient ? { cancel: ({ space, trigger }) => Effect.gen(function* () {
|
|
204
|
+
if (!SpaceId.isValid(space)) {
|
|
205
|
+
log.warn("remote trigger cancel skipped: invalid space id", { space }, {
|
|
206
|
+
"~LogMeta": "~LogMeta",
|
|
207
|
+
F: __dxlog_file$2,
|
|
208
|
+
L: 48,
|
|
209
|
+
S: this
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
yield* Effect.tryPromise(() => getEdgeClient().cancelTriggerRun(Context.default(void 0, {
|
|
214
|
+
"~LogMeta": "~LogMeta",
|
|
215
|
+
F: __dxlog_file$2,
|
|
216
|
+
L: 52
|
|
217
|
+
}), space, trigger)).pipe(Effect.asVoid, Effect.catchAll((error) => Effect.sync(() => log.warn("remote trigger cancel failed", { error }, {
|
|
218
|
+
"~LogMeta": "~LogMeta",
|
|
219
|
+
F: __dxlog_file$2,
|
|
220
|
+
L: 57,
|
|
221
|
+
S: this
|
|
222
|
+
}))));
|
|
223
|
+
}) } : {}
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
var make$1 = (getEdgeClient) => Layer.effect(RemoteProcessManager.Service, Effect.gen(function* () {
|
|
227
|
+
return makeManager(yield* Registry.AtomRegistry, getEdgeClient);
|
|
228
|
+
}));
|
|
229
|
+
/**
|
|
230
|
+
* For tests: provide a pre-built edge client.
|
|
231
|
+
*/
|
|
232
|
+
var fromEdgeClient$1 = (edgeClient) => make$1(() => edgeClient);
|
|
233
|
+
/**
|
|
234
|
+
* Build from a `Client`, deferring edge-client creation until the first cancel
|
|
235
|
+
* (identity / edge config may be absent at boot).
|
|
236
|
+
*/
|
|
237
|
+
var fromClient$1 = (client) => {
|
|
238
|
+
let cached;
|
|
239
|
+
return make$1(() => cached ??= createEdgeClient(client));
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* EDGE process manager with no client — empty process tree and no cancel control.
|
|
243
|
+
* Used where edge is not configured.
|
|
244
|
+
*/
|
|
245
|
+
var layer$1 = make$1();
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/EdgeTriggerManager.ts
|
|
248
|
+
var EdgeTriggerManager_exports = /* @__PURE__ */ __exportAll({
|
|
249
|
+
fromClient: () => fromClient,
|
|
250
|
+
fromEdgeClient: () => fromEdgeClient,
|
|
251
|
+
layer: () => layer
|
|
252
|
+
});
|
|
253
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/compute/edge-compute/src/EdgeTriggerManager.ts";
|
|
254
|
+
/** How often the remote trigger view is refreshed from the EDGE dispatcher. */
|
|
255
|
+
var POLL_INTERVAL = Duration.seconds(15);
|
|
256
|
+
/**
|
|
257
|
+
* EDGE implementation of {@link RemoteTriggerManager.Service}.
|
|
258
|
+
*
|
|
259
|
+
* The `triggers` atom is populated by polling
|
|
260
|
+
* {@link EdgeHttpClient.getSpaceTriggers} and mapping each edge trigger's
|
|
261
|
+
* runtime status into a {@link Trigger.State} (marked `environment: 'edge'`).
|
|
262
|
+
* The referenced `Trigger` objects are replicated into the local database, so
|
|
263
|
+
* the trigger ref is a space-relative echo ref synthesized from the id. The
|
|
264
|
+
* aggregate {@link TriggerMonitor} dedupes these against the database-derived
|
|
265
|
+
* view (edge entries here supersede the bare database ones).
|
|
266
|
+
*
|
|
267
|
+
* `invokeTrigger` force-runs the trigger's cron on the EDGE dispatcher via
|
|
268
|
+
* {@link EdgeHttpClient.forceRunCronTrigger}.
|
|
269
|
+
*/
|
|
270
|
+
var toState = (status) => ({
|
|
271
|
+
trigger: Ref.fromURI(EID.make({ entityId: status.triggerId })),
|
|
272
|
+
environment: "edge",
|
|
273
|
+
nextExecution: status.nextExecutionTimestamp !== void 0 ? new Date(status.nextExecutionTimestamp) : void 0,
|
|
274
|
+
cooldownUntil: status.cooldownUntilTimestamp !== void 0 ? new Date(status.cooldownUntilTimestamp) : void 0,
|
|
275
|
+
lastResult: toExit(status.lastResult)
|
|
276
|
+
});
|
|
277
|
+
var toExit = (result) => {
|
|
278
|
+
if (!result) return null;
|
|
279
|
+
return result.status === "success" ? Exit.void : Exit.die(new Error(result.error?.message ?? "Edge trigger invocation failed"));
|
|
280
|
+
};
|
|
281
|
+
var make = (getEdgeClient, spaceId, registry) => Effect.gen(function* () {
|
|
282
|
+
const triggers = Atom.make([]);
|
|
283
|
+
registry.mount(triggers);
|
|
284
|
+
const refresh = Effect.tryPromise(() => getEdgeClient().getSpaceTriggers(Context.default(void 0, {
|
|
285
|
+
"~LogMeta": "~LogMeta",
|
|
286
|
+
F: __dxlog_file$1,
|
|
287
|
+
L: 74
|
|
288
|
+
}), spaceId)).pipe(Effect.tap((response) => Effect.sync(() => registry.update(triggers, () => response.triggers.map(toState)))), Effect.catchAll(() => Effect.void));
|
|
289
|
+
yield* refresh.pipe(Effect.repeat(Schedule.spaced(POLL_INTERVAL)), Effect.forkScoped);
|
|
290
|
+
return {
|
|
291
|
+
triggers,
|
|
292
|
+
invokeTrigger: (options) => Effect.promise(() => getEdgeClient().forceRunCronTrigger(Context.default(void 0, {
|
|
293
|
+
"~LogMeta": "~LogMeta",
|
|
294
|
+
F: __dxlog_file$1,
|
|
295
|
+
L: 89
|
|
296
|
+
}), spaceId, options.trigger.id)).pipe(Effect.asVoid, Effect.orDie, Effect.tap(() => refresh))
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
/**
|
|
300
|
+
* For tests: provide a pre-built edge client.
|
|
301
|
+
*/
|
|
302
|
+
var fromEdgeClient = (edgeClient, spaceId) => Layer.scoped(RemoteTriggerManager.Service, Effect.gen(function* () {
|
|
303
|
+
return yield* make(() => edgeClient, spaceId, yield* Registry.AtomRegistry);
|
|
304
|
+
}));
|
|
305
|
+
/**
|
|
306
|
+
* Build from a `Client`, deferring edge-client creation until first use
|
|
307
|
+
* (identity may be absent at boot).
|
|
308
|
+
*/
|
|
309
|
+
var fromClient = (client, spaceId) => Layer.scoped(RemoteTriggerManager.Service, Effect.gen(function* () {
|
|
310
|
+
const registry = yield* Registry.AtomRegistry;
|
|
311
|
+
let cached;
|
|
312
|
+
return yield* make(() => cached ??= createEdgeClient(client), spaceId, registry);
|
|
313
|
+
}));
|
|
314
|
+
/**
|
|
315
|
+
* Build from the ambient `ClientService`.
|
|
316
|
+
*/
|
|
317
|
+
var layer = (spaceId) => Layer.scoped(RemoteTriggerManager.Service, Effect.gen(function* () {
|
|
318
|
+
const client = yield* ClientService;
|
|
319
|
+
const registry = yield* Registry.AtomRegistry;
|
|
320
|
+
let cached;
|
|
321
|
+
return yield* make(() => cached ??= createEdgeClient(client), spaceId, registry);
|
|
322
|
+
}));
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/FunctionsServiceClient.ts
|
|
325
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/core/compute/edge-compute/src/FunctionsServiceClient.ts";
|
|
326
|
+
var FunctionsServiceClient = class FunctionsServiceClient {
|
|
327
|
+
static fromClient(client) {
|
|
328
|
+
return new FunctionsServiceClient(createEdgeClient(client));
|
|
329
|
+
}
|
|
330
|
+
#edgeClient;
|
|
331
|
+
constructor(edgeClient) {
|
|
332
|
+
this.#edgeClient = edgeClient;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Deploys a function to the EDGE service.
|
|
336
|
+
*/
|
|
337
|
+
async deploy(ctx, request) {
|
|
338
|
+
try {
|
|
339
|
+
invariant(Object.keys(request.assets).every((path) => !path.startsWith("/")), "Asset paths must be relative", {
|
|
340
|
+
"~LogMeta": "~LogMeta",
|
|
341
|
+
F: __dxlog_file,
|
|
342
|
+
L: 92,
|
|
343
|
+
S: this,
|
|
344
|
+
A: ["Object.keys(request.assets).every((path) => !path.startsWith('/'))", "'Asset paths must be relative'"]
|
|
345
|
+
});
|
|
346
|
+
const response = await this.#edgeClient.uploadFunction(ctx, { functionId: request.functionId }, {
|
|
347
|
+
name: request.name,
|
|
348
|
+
version: request.version,
|
|
349
|
+
ownerUri: request.ownerUri,
|
|
350
|
+
entryPoint: request.entryPoint,
|
|
351
|
+
assets: request.assets,
|
|
352
|
+
runtime: request.runtime
|
|
353
|
+
}, {
|
|
354
|
+
retry: { count: 3 },
|
|
355
|
+
auth: true
|
|
356
|
+
});
|
|
357
|
+
log.verbose("deploy result", { ...response }, {
|
|
358
|
+
"~LogMeta": "~LogMeta",
|
|
359
|
+
F: __dxlog_file,
|
|
360
|
+
L: 109,
|
|
361
|
+
S: this
|
|
362
|
+
});
|
|
363
|
+
return Obj.make(Operation.PersistentOperation, {
|
|
364
|
+
[Obj.Meta]: {
|
|
365
|
+
keys: [{
|
|
366
|
+
source: FUNCTIONS_META_KEY,
|
|
367
|
+
id: response.functionId
|
|
368
|
+
}],
|
|
369
|
+
key: response.meta.key,
|
|
370
|
+
version: response.version
|
|
371
|
+
},
|
|
372
|
+
name: response.meta.name ?? "Unnamed function",
|
|
373
|
+
description: response.meta.description,
|
|
374
|
+
inputSchema: response.meta.inputSchema,
|
|
375
|
+
outputSchema: response.meta.outputSchema
|
|
376
|
+
});
|
|
377
|
+
} catch (error) {
|
|
378
|
+
throw FunctionServiceError.wrap({
|
|
379
|
+
message: "Failed to deploy function",
|
|
380
|
+
ifTypeDiffers: true
|
|
381
|
+
})(error);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Queries the EDGE service for deployed functions.
|
|
386
|
+
*/
|
|
387
|
+
async query(ctx) {
|
|
388
|
+
try {
|
|
389
|
+
return (await this.#edgeClient.listFunctions(ctx)).uploadedFunctions.flatMap((record) => {
|
|
390
|
+
const latest = record.latestVersion ?? {};
|
|
391
|
+
const versionMeta = safeParseJson(latest.versionMetaJSON);
|
|
392
|
+
if (!versionMeta) return [];
|
|
393
|
+
return [Obj.make(Operation.PersistentOperation, {
|
|
394
|
+
[Obj.Meta]: {
|
|
395
|
+
keys: [{
|
|
396
|
+
source: FUNCTIONS_META_KEY,
|
|
397
|
+
id: record.id
|
|
398
|
+
}],
|
|
399
|
+
key: versionMeta.key,
|
|
400
|
+
version: latest?.version ?? "0.0.0"
|
|
401
|
+
},
|
|
402
|
+
name: versionMeta.name ?? versionMeta.key ?? record.id,
|
|
403
|
+
updated: record?.updated !== void 0 ? new Date(record.updated).toISOString() : void 0,
|
|
404
|
+
description: versionMeta.description,
|
|
405
|
+
inputSchema: versionMeta.inputSchema,
|
|
406
|
+
outputSchema: versionMeta.outputSchema
|
|
407
|
+
})];
|
|
408
|
+
});
|
|
409
|
+
} catch (error) {
|
|
410
|
+
throw FunctionServiceError.wrap({
|
|
411
|
+
message: "Failed to query functions",
|
|
412
|
+
ifTypeDiffers: true
|
|
413
|
+
})(error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async invoke(ctx, func, input, options) {
|
|
417
|
+
const functionId = Obj.getMeta(func).keys.find((key) => key.source === FUNCTIONS_META_KEY)?.id;
|
|
418
|
+
if (!functionId) throw new FunctionServiceError({ message: "No identifier for the function at the EDGE service" });
|
|
419
|
+
const cleanedId = functionId.replace(/^\//, "");
|
|
420
|
+
try {
|
|
421
|
+
return await this.#edgeClient.invokeFunction(ctx, {
|
|
422
|
+
functionId: cleanedId,
|
|
423
|
+
spaceId: options?.spaceId,
|
|
424
|
+
cpuTimeLimit: options?.cpuTimeLimit,
|
|
425
|
+
subrequestsLimit: options?.subrequestsLimit
|
|
426
|
+
}, input);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
throw FunctionError.wrap({
|
|
429
|
+
message: "Failed to invoke function",
|
|
430
|
+
ifTypeDiffers: true
|
|
431
|
+
})(error);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async forceRunCronTrigger(ctx, spaceId, triggerId) {
|
|
435
|
+
return await this.#edgeClient.forceRunCronTrigger(ctx, spaceId, triggerId);
|
|
436
|
+
}
|
|
437
|
+
async getTriggersDispatcherStatus(ctx, spaceId) {
|
|
438
|
+
return this.#edgeClient.getTriggersDispatcherStatus(ctx, spaceId);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
//#endregion
|
|
442
|
+
export { EdgeOperationInvoker_exports as EdgeOperationInvoker, EdgeProcessManager_exports as EdgeProcessManager, EdgeTriggerManager_exports as EdgeTriggerManager, FunctionsServiceClient, createEdgeClient, getDeployedFunctions, incrementSemverPatch, invokeFunction, publicKeyToDid, uploadWorkerFunction };
|
|
443
|
+
|
|
444
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/edge-client.ts","../../src/EdgeOperationInvoker.ts","../../src/EdgeProcessManager.ts","../../src/EdgeTriggerManager.ts","../../src/FunctionsServiceClient.ts"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport * as Array from 'effect/Array';\nimport * as Function$ from 'effect/Function';\nimport * as Order from 'effect/Order';\nimport { type DID } from 'iso-did/types';\n\nimport { type Client } from '@dxos/client';\nimport { createEdgeIdentity } from '@dxos/client/edge';\nimport { Operation } from '@dxos/compute';\nimport { FUNCTIONS_META_KEY, setUserFunctionIdInMetadata } from '@dxos/compute-runtime';\nimport { Context } from '@dxos/context';\nimport { Obj } from '@dxos/echo';\nimport { EdgeHttpClient } from '@dxos/edge-client';\nimport { invariant } from '@dxos/invariant';\nimport { type PublicKey, type SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { type UploadFunctionResponseBody } from '@dxos/protocols';\nimport { safeParseJson } from '@dxos/util';\n\n/** @deprecated Use {@link FunctionsServiceClient} instead. */\nexport type UploadWorkerArgs = {\n client: Client;\n version: string;\n name?: string;\n functionId?: string;\n /** Owner identity DID (`did:halo:…`). */\n ownerUri: string;\n entryPoint: string;\n assets: Record<string, Uint8Array>;\n};\n\n/** @deprecated Migrate to `client.edge`. */\nexport const createEdgeClient = (client: Client): EdgeHttpClient => {\n const edgeUrl = client.config.values.runtime?.services?.edge?.url;\n invariant(edgeUrl, 'Edge is not configured.');\n const edgeClient = new EdgeHttpClient(edgeUrl);\n const edgeIdentity = createEdgeIdentity(client);\n edgeClient.setIdentity(edgeIdentity);\n return edgeClient;\n};\n\n/**\n * @deprecated Use {@link FunctionsServiceClient} instead.\n */\nexport const uploadWorkerFunction = async (\n ctx: Context,\n { client, version, name, functionId, ownerUri, entryPoint, assets }: UploadWorkerArgs,\n): Promise<UploadFunctionResponseBody> => {\n log('uploading function', { functionId, name, version, ownerUri });\n const edgeClient = createEdgeClient(client);\n const response = await edgeClient.uploadFunction(\n ctx,\n { functionId },\n { name, version, ownerUri, entryPoint, assets },\n );\n\n // TODO(burdon): Edge service log.\n log('uploaded', {\n identityKey: client.halo.identity.get()?.identityKey,\n did: client.halo.identity.get()?.did,\n functionId,\n name,\n version,\n response,\n });\n\n return response;\n};\n\n/**\n * @deprecated Use {@link FunctionsServiceClient} instead.\n */\nexport const getDeployedFunctions = async (\n ctx: Context,\n client: Client,\n dedupe = false,\n): Promise<Operation.PersistentOperation[]> => {\n const edgeClient = createEdgeClient(client);\n const result = await edgeClient.listFunctions(ctx);\n const functions: Operation.PersistentOperation[] = result.uploadedFunctions.flatMap((record: any) => {\n // Record shape is determined by EDGE API. We defensively parse.\n const latest = record.latestVersion ?? {};\n const versionMeta = safeParseJson<any>(latest.versionMetaJSON);\n if (!versionMeta) {\n return [];\n }\n const fn = Obj.make(Operation.PersistentOperation, {\n [Obj.Meta]: {\n key: versionMeta.key,\n version: latest?.version ?? '0.0.0',\n },\n name: versionMeta.name ?? versionMeta.key ?? record.id,\n updated: record?.updated !== undefined ? new Date(record.updated).toISOString() : undefined,\n description: versionMeta.description,\n inputSchema: versionMeta.inputSchema,\n outputSchema: versionMeta.outputSchema,\n });\n\n Obj.update(fn, (fn) => setUserFunctionIdInMetadata(Obj.getMeta(fn), record.id));\n return [fn];\n });\n\n if (dedupe) {\n return Function$.pipe(\n functions,\n Array.filter((_) => Obj.getMeta(_).key !== undefined),\n Array.sort(Order.reverse(Order.mapInput(Order.string, (_: Operation.PersistentOperation) => _.updated ?? ''))),\n Array.dedupeWith((self, that) => Obj.getMeta(self).key === Obj.getMeta(that).key),\n Array.sort(Order.mapInput(Order.string, (_: Operation.PersistentOperation) => Obj.getMeta(_).key ?? '')),\n );\n } else {\n return functions;\n }\n};\n\n/**\n * @deprecated Use {@link FunctionsServiceClient} instead.\n */\nexport const invokeFunction = async (\n ctx: Context,\n edgeClient: EdgeHttpClient,\n fn: Operation.PersistentOperation,\n input: unknown,\n {\n spaceId,\n cpuTimeLimit,\n subrequestsLimit,\n }: { spaceId?: SpaceId; cpuTimeLimit?: number; subrequestsLimit?: number } = {},\n) => {\n const functionId = Obj.getMeta(fn).keys.find((key) => key.source === FUNCTIONS_META_KEY)?.id;\n if (!functionId) {\n throw new Error('No identifier for the function at the EDGE service');\n }\n // COMPAT: Previously functionId was a URL `/<guid>`. Now it's just the `<guid>`.\n const cleanedId = functionId.replace(/^\\//, '');\n return await edgeClient.invokeFunction(\n ctx,\n { functionId: cleanedId, spaceId, cpuTimeLimit, subrequestsLimit },\n input,\n );\n};\n\nexport const incrementSemverPatch = (version: string): string => {\n const [major, minor, patch] = version.split('.');\n const patchNum = Number(patch);\n invariant(!Number.isNaN(patchNum), `Unexpected function version format: ${version}`);\n return [major, minor, String(patchNum + 1)].join('.');\n};\n\n// TODO(burdon): Factor out.\nexport const publicKeyToDid = (key: PublicKey): DID => {\n return `did:key:${key.toHex()}`;\n};\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nimport { type Client, ClientService } from '@dxos/client';\nimport { FunctionError } from '@dxos/compute';\nimport { RemoteOperationInvoker } from '@dxos/compute-runtime';\nimport { type Context as DxosContext } from '@dxos/context';\nimport { type SpaceId } from '@dxos/keys';\n\nimport { createEdgeClient } from './edge-client';\n\ntype EdgeClient = ReturnType<typeof createEdgeClient>;\n\nconst make = (getEdgeClient: () => EdgeClient, spaceId?: SpaceId): RemoteOperationInvoker.Invoker => ({\n invoke: <I, O>(ctx: DxosContext, deployedId: string, input: I): Effect.Effect<O> =>\n Effect.gen(function* () {\n const cleanedId = deployedId.replace(/^\\//, '');\n return yield* Effect.promise(() =>\n getEdgeClient().invokeFunction(ctx, { functionId: cleanedId, spaceId }, input),\n ).pipe(Effect.mapError(FunctionError.wrap()), Effect.orDie);\n }),\n});\n\n/**\n * For tests: provide a pre-built edge client.\n */\nexport const fromEdgeClient = (\n edgeClient: EdgeClient,\n spaceId?: SpaceId,\n): Layer.Layer<RemoteOperationInvoker.Service> =>\n Layer.succeed(\n RemoteOperationInvoker.Service,\n make(() => edgeClient, spaceId),\n );\n\n/**\n * Build from a `Client`, deferring edge-client creation until first invoke\n * (identity may be absent at boot).\n */\nexport const fromClient = (client: Client, spaceId?: SpaceId): Layer.Layer<RemoteOperationInvoker.Service> => {\n let cached: EdgeClient | undefined;\n return Layer.succeed(\n RemoteOperationInvoker.Service,\n make(() => (cached ??= createEdgeClient(client)), spaceId),\n );\n};\n\n/**\n * Build from the ambient `ClientService`.\n */\nexport const layer = (spaceId?: SpaceId): Layer.Layer<RemoteOperationInvoker.Service, never, ClientService> =>\n Layer.effect(\n RemoteOperationInvoker.Service,\n Effect.gen(function* () {\n const client = yield* ClientService;\n let cached: EdgeClient | undefined;\n return make(() => (cached ??= createEdgeClient(client)), spaceId);\n }),\n );\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport { Atom, Registry } from '@effect-atom/atom';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\n\nimport { type Client } from '@dxos/client';\nimport { type Process } from '@dxos/compute';\nimport { RemoteProcessManager } from '@dxos/compute-runtime';\nimport { Context as DxosContext } from '@dxos/context';\nimport { type EdgeHttpClient } from '@dxos/edge-client';\nimport { SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\n\nimport { createEdgeClient } from './edge-client';\n\n/**\n * EDGE implementation of {@link RemoteProcessManager.Service}.\n *\n * The read view (`processTree`) is still empty — the EDGE worker exposes no\n * process-tree endpoint yet (see Decision D3). What this adds is the `cancel`\n * control: it force-cancels the current run of an edge trigger (its in-flight\n * execution and `runAgain` continuation chain) via\n * {@link EdgeHttpClient.cancelTriggerRun}, addressed by `trigger` in `space`.\n * A manager built without a client (the {@link layer} stub) omits `cancel`.\n */\nconst makeManager = (\n registry: Registry.Registry,\n getEdgeClient?: () => EdgeHttpClient,\n): RemoteProcessManager.Manager => {\n // TODO(edge): Populate from an EDGE process-tree endpoint once available.\n const processTreeAtom = Atom.make<readonly Process.Info[]>([]);\n registry.mount(processTreeAtom);\n return {\n processTree: Effect.sync(() => registry.get(processTreeAtom)),\n processTreeAtom,\n ...(getEdgeClient\n ? {\n cancel: ({ space, trigger }: RemoteProcessManager.CancelTarget) =>\n Effect.gen(function* () {\n // `space` arrives as an untyped string from trace meta; skip rather than throw when it is\n // not a valid space id (best-effort — the meter has already cleared locally).\n if (!SpaceId.isValid(space)) {\n log.warn('remote trigger cancel skipped: invalid space id', { space });\n return;\n }\n yield* Effect.tryPromise(() =>\n getEdgeClient().cancelTriggerRun(DxosContext.default(), space, trigger),\n ).pipe(\n Effect.asVoid,\n // A missing/unreachable endpoint (e.g. an older edge deploy) must not surface as a defect\n // from this fire-and-forget cancel — log and move on.\n Effect.catchAll((error) => Effect.sync(() => log.warn('remote trigger cancel failed', { error }))),\n );\n }),\n }\n : {}),\n } satisfies RemoteProcessManager.Manager;\n};\n\nconst make = (\n getEdgeClient?: () => EdgeHttpClient,\n): Layer.Layer<RemoteProcessManager.Service, never, Registry.AtomRegistry> =>\n Layer.effect(\n RemoteProcessManager.Service,\n Effect.gen(function* () {\n const registry = yield* Registry.AtomRegistry;\n return makeManager(registry, getEdgeClient);\n }),\n );\n\n/**\n * For tests: provide a pre-built edge client.\n */\nexport const fromEdgeClient = (\n edgeClient: EdgeHttpClient,\n): Layer.Layer<RemoteProcessManager.Service, never, Registry.AtomRegistry> => make(() => edgeClient);\n\n/**\n * Build from a `Client`, deferring edge-client creation until the first cancel\n * (identity / edge config may be absent at boot).\n */\nexport const fromClient = (client: Client): Layer.Layer<RemoteProcessManager.Service, never, Registry.AtomRegistry> => {\n let cached: EdgeHttpClient | undefined;\n return make(() => (cached ??= createEdgeClient(client)));\n};\n\n/**\n * EDGE process manager with no client — empty process tree and no cancel control.\n * Used where edge is not configured.\n */\nexport const layer: Layer.Layer<RemoteProcessManager.Service, never, Registry.AtomRegistry> = make();\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nimport { Atom, Registry } from '@effect-atom/atom';\nimport * as Duration from 'effect/Duration';\nimport * as Effect from 'effect/Effect';\nimport * as Exit from 'effect/Exit';\nimport * as Layer from 'effect/Layer';\nimport * as Schedule from 'effect/Schedule';\nimport type * as Scope from 'effect/Scope';\n\nimport { type Client, ClientService } from '@dxos/client';\nimport { type Trigger } from '@dxos/compute';\nimport { RemoteTriggerManager } from '@dxos/compute-runtime';\nimport { Context as DxosContext } from '@dxos/context';\nimport { Ref } from '@dxos/echo';\nimport { type EdgeTriggerStatus } from '@dxos/edge-client';\nimport { EID, type SpaceId } from '@dxos/keys';\n\nimport { createEdgeClient } from './edge-client';\n\ntype EdgeClient = ReturnType<typeof createEdgeClient>;\n\n/** How often the remote trigger view is refreshed from the EDGE dispatcher. */\nconst POLL_INTERVAL = Duration.seconds(15);\n\n/**\n * EDGE implementation of {@link RemoteTriggerManager.Service}.\n *\n * The `triggers` atom is populated by polling\n * {@link EdgeHttpClient.getSpaceTriggers} and mapping each edge trigger's\n * runtime status into a {@link Trigger.State} (marked `environment: 'edge'`).\n * The referenced `Trigger` objects are replicated into the local database, so\n * the trigger ref is a space-relative echo ref synthesized from the id. The\n * aggregate {@link TriggerMonitor} dedupes these against the database-derived\n * view (edge entries here supersede the bare database ones).\n *\n * `invokeTrigger` force-runs the trigger's cron on the EDGE dispatcher via\n * {@link EdgeHttpClient.forceRunCronTrigger}.\n */\nconst toState = (status: EdgeTriggerStatus): Trigger.State => ({\n // Space-relative echo ref (same shape as `Ref.make(trigger)` for an in-space object), resolvable\n // against the space database where the edge trigger is replicated.\n trigger: Ref.fromURI(EID.make({ entityId: status.triggerId })),\n environment: 'edge',\n nextExecution: status.nextExecutionTimestamp !== undefined ? new Date(status.nextExecutionTimestamp) : undefined,\n cooldownUntil: status.cooldownUntilTimestamp !== undefined ? new Date(status.cooldownUntilTimestamp) : undefined,\n lastResult: toExit(status.lastResult),\n});\n\nconst toExit = (result: EdgeTriggerStatus['lastResult']): Exit.Exit<unknown> | null => {\n if (!result) {\n return null;\n }\n // The original error channel is not reconstructed over the wire, so failures are surfaced as\n // defects carrying the serialized message.\n return result.status === 'success'\n ? Exit.void\n : Exit.die(new Error(result.error?.message ?? 'Edge trigger invocation failed'));\n};\n\nconst make = (\n getEdgeClient: () => EdgeClient,\n spaceId: SpaceId,\n registry: Registry.Registry,\n): Effect.Effect<RemoteTriggerManager.Manager, never, Scope.Scope> =>\n Effect.gen(function* () {\n const triggers = Atom.make<readonly Trigger.State[]>([]);\n registry.mount(triggers);\n\n const refresh = Effect.tryPromise(() => getEdgeClient().getSpaceTriggers(DxosContext.default(), spaceId)).pipe(\n Effect.tap((response) => Effect.sync(() => registry.update(triggers, () => response.triggers.map(toState)))),\n // The endpoint may be unimplemented or unreachable; a failed poll must not tear down the layer.\n Effect.catchAll(() => Effect.void),\n );\n\n // Poll the EDGE dispatcher on an interval; the fiber is scoped to the layer's lifetime.\n yield* refresh.pipe(Effect.repeat(Schedule.spaced(POLL_INTERVAL)), Effect.forkScoped);\n\n return {\n triggers,\n invokeTrigger: (options: Trigger.InvokeOptions) =>\n // Manual invocation of a remote trigger maps onto force-running its cron on the EDGE\n // dispatcher; refresh the view promptly afterwards.\n Effect.promise(() =>\n getEdgeClient().forceRunCronTrigger(DxosContext.default(), spaceId, options.trigger.id),\n ).pipe(\n Effect.asVoid,\n Effect.orDie,\n Effect.tap(() => refresh),\n ),\n } satisfies RemoteTriggerManager.Manager;\n });\n\n/**\n * For tests: provide a pre-built edge client.\n */\nexport const fromEdgeClient = (\n edgeClient: EdgeClient,\n spaceId: SpaceId,\n): Layer.Layer<RemoteTriggerManager.Service, never, Registry.AtomRegistry> =>\n Layer.scoped(\n RemoteTriggerManager.Service,\n Effect.gen(function* () {\n const registry = yield* Registry.AtomRegistry;\n return yield* make(() => edgeClient, spaceId, registry);\n }),\n );\n\n/**\n * Build from a `Client`, deferring edge-client creation until first use\n * (identity may be absent at boot).\n */\nexport const fromClient = (\n client: Client,\n spaceId: SpaceId,\n): Layer.Layer<RemoteTriggerManager.Service, never, Registry.AtomRegistry> =>\n Layer.scoped(\n RemoteTriggerManager.Service,\n Effect.gen(function* () {\n const registry = yield* Registry.AtomRegistry;\n let cached: EdgeClient | undefined;\n return yield* make(() => (cached ??= createEdgeClient(client)), spaceId, registry);\n }),\n );\n\n/**\n * Build from the ambient `ClientService`.\n */\nexport const layer = (\n spaceId: SpaceId,\n): Layer.Layer<RemoteTriggerManager.Service, never, Registry.AtomRegistry | ClientService> =>\n Layer.scoped(\n RemoteTriggerManager.Service,\n Effect.gen(function* () {\n const client = yield* ClientService;\n const registry = yield* Registry.AtomRegistry;\n let cached: EdgeClient | undefined;\n return yield* make(() => (cached ??= createEdgeClient(client)), spaceId, registry);\n }),\n );\n","//\n// Copyright 2025 DXOS.org\n//\n\nimport { type Client } from '@dxos/client';\nimport { FunctionError, Operation } from '@dxos/compute';\nimport { FUNCTIONS_META_KEY } from '@dxos/compute-runtime';\nimport { FunctionServiceError } from '@dxos/compute-runtime';\nimport { type Context } from '@dxos/context';\nimport { Obj } from '@dxos/echo';\nimport { type EdgeHttpClient, type TriggersDispatcherStatus } from '@dxos/edge-client';\nimport { invariant } from '@dxos/invariant';\nimport { type EntityId, type SpaceId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { type FunctionRuntimeKind, type SerializedError } from '@dxos/protocols';\nimport { safeParseJson } from '@dxos/util';\n\nimport { createEdgeClient } from './edge-client';\n\n// TODO(wittjosiah): Copied from @dxos/functions-simulator-cloudflare.\nexport type InvokeResult =\n | {\n _kind: 'success';\n /**\n * The output of the function.\n */\n result: unknown;\n }\n | {\n _kind: 'error';\n error: SerializedError;\n };\n\nexport type FunctionDeployOptions = {\n name?: string;\n version: string;\n\n /** Service-side function id in case this function is a re-upload of a previous version. */\n functionId?: string;\n\n /** Owner identity DID (`did:halo:…`). Must equal the authenticated presenter DID. */\n ownerUri: string;\n\n runtime?: FunctionRuntimeKind;\n\n /** Path of the entry point file in the assets table. */\n entryPoint: string;\n\n assets: {\n // TODO(dmaretskyi): Allow passing strings and setting mime-type.\n [path: string]: Uint8Array;\n };\n};\n\nexport type FunctionInvokeOptions = {\n /**\n * Space in which the function is invoked.\n * Binds the Database.Service injected into the function to this space.\n * Without this, the function will not have access to any database.\n */\n spaceId?: SpaceId;\n\n /**\n * CPU time limit in milliseconds.\n * See Cloudflare docs for details.\n */\n cpuTimeLimit?: number;\n\n /**\n * Maximum number of subrequests.\n * See Cloudflare docs for details.\n */\n subrequestsLimit?: number;\n};\n\nexport class FunctionsServiceClient {\n static fromClient(client: Client) {\n return new FunctionsServiceClient(createEdgeClient(client));\n }\n\n #edgeClient: EdgeHttpClient;\n\n constructor(edgeClient: EdgeHttpClient) {\n this.#edgeClient = edgeClient;\n }\n\n /**\n * Deploys a function to the EDGE service.\n */\n async deploy(ctx: Context, request: FunctionDeployOptions): Promise<Operation.PersistentOperation> {\n try {\n invariant(\n Object.keys(request.assets).every((path) => !path.startsWith('/')),\n 'Asset paths must be relative',\n );\n const response = await this.#edgeClient.uploadFunction(\n ctx,\n { functionId: request.functionId },\n {\n name: request.name,\n version: request.version,\n ownerUri: request.ownerUri,\n entryPoint: request.entryPoint,\n assets: request.assets,\n runtime: request.runtime,\n },\n { retry: { count: 3 }, auth: true },\n );\n log.verbose('deploy result', { ...response });\n\n return Obj.make(Operation.PersistentOperation, {\n [Obj.Meta]: {\n keys: [{ source: FUNCTIONS_META_KEY, id: response.functionId }],\n key: response.meta.key,\n version: response.version,\n },\n name: response.meta.name ?? 'Unnamed function',\n description: response.meta.description,\n inputSchema: response.meta.inputSchema,\n outputSchema: response.meta.outputSchema,\n });\n } catch (error) {\n throw FunctionServiceError.wrap({ message: 'Failed to deploy function', ifTypeDiffers: true })(error);\n }\n }\n\n /**\n * Queries the EDGE service for deployed functions.\n */\n // TODO(dmaretskyi): Add query filters.\n async query(ctx: Context): Promise<Operation.PersistentOperation[]> {\n try {\n const response = await this.#edgeClient.listFunctions(ctx);\n return response.uploadedFunctions.flatMap((record: any) => {\n const latest = record.latestVersion ?? {};\n const versionMeta = safeParseJson<any>(latest.versionMetaJSON);\n if (!versionMeta) {\n return [];\n }\n const fn = Obj.make(Operation.PersistentOperation, {\n [Obj.Meta]: {\n keys: [{ source: FUNCTIONS_META_KEY, id: record.id }],\n key: versionMeta.key,\n version: latest?.version ?? '0.0.0',\n },\n name: versionMeta.name ?? versionMeta.key ?? record.id,\n updated: record?.updated !== undefined ? new Date(record.updated).toISOString() : undefined,\n description: versionMeta.description,\n inputSchema: versionMeta.inputSchema,\n outputSchema: versionMeta.outputSchema,\n });\n return [fn];\n });\n } catch (error) {\n throw FunctionServiceError.wrap({ message: 'Failed to query functions', ifTypeDiffers: true })(error);\n }\n }\n\n async invoke(ctx: Context, func: Operation.PersistentOperation, input: unknown, options?: FunctionInvokeOptions) {\n const functionId = Obj.getMeta(func).keys.find((key) => key.source === FUNCTIONS_META_KEY)?.id;\n if (!functionId) {\n throw new FunctionServiceError({ message: 'No identifier for the function at the EDGE service' });\n }\n // COMPAT: Previously functionId was a URL `/<guid>`. Now it's just the `<guid>`.\n const cleanedId = functionId.replace(/^\\//, '');\n try {\n return await this.#edgeClient.invokeFunction(\n ctx,\n {\n functionId: cleanedId,\n spaceId: options?.spaceId,\n cpuTimeLimit: options?.cpuTimeLimit,\n subrequestsLimit: options?.subrequestsLimit,\n },\n input,\n );\n } catch (error) {\n throw FunctionError.wrap({ message: 'Failed to invoke function', ifTypeDiffers: true })(error);\n }\n }\n\n async forceRunCronTrigger(ctx: Context, spaceId: SpaceId, triggerId: EntityId): Promise<InvokeResult> {\n return (await this.#edgeClient.forceRunCronTrigger(ctx, spaceId, triggerId)) as InvokeResult;\n }\n\n async getTriggersDispatcherStatus(ctx: Context, spaceId: SpaceId): Promise<TriggersDispatcherStatus> {\n return this.#edgeClient.getTriggersDispatcherStatus(ctx, spaceId);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { t as httpPlugin } from "./chunk-http-plugin-esbuild.mjs";
|
|
2
|
+
import { Unit, trim } from "@dxos/util";
|
|
3
|
+
import * as Function from "effect/Function";
|
|
4
|
+
import { BaseError } from "@dxos/errors";
|
|
5
|
+
import * as Array from "effect/Array";
|
|
6
|
+
import * as Order from "effect/Order";
|
|
7
|
+
import { PublicKey } from "@dxos/keys";
|
|
8
|
+
import * as Record from "effect/Record";
|
|
9
|
+
import { build } from "esbuild";
|
|
10
|
+
import { statSync } from "@dxos/node-std/fs";
|
|
11
|
+
import * as fs from "@dxos/node-std/fs/promises";
|
|
12
|
+
import { writeFile } from "@dxos/node-std/fs/promises";
|
|
13
|
+
import { basename, dirname, join, relative, resolve } from "@dxos/node-std/path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
//#region src/native/bundler.ts
|
|
16
|
+
/**
|
|
17
|
+
* Bundles a function.
|
|
18
|
+
* Pulls in node_modules and wasm files.
|
|
19
|
+
*/
|
|
20
|
+
var bundleFunction = async (options) => {
|
|
21
|
+
const outdir = `/tmp/dxos-functions-bundle-${(/* @__PURE__ */ new Date()).toISOString()}-${PublicKey.random().toHex()}`;
|
|
22
|
+
const gitDir = findGitDir(dirname(options.entryPoint));
|
|
23
|
+
const result = await build({
|
|
24
|
+
entryPoints: { index: "dxos:entrypoint" },
|
|
25
|
+
bundle: true,
|
|
26
|
+
format: "esm",
|
|
27
|
+
platform: "browser",
|
|
28
|
+
conditions: [
|
|
29
|
+
"workerd",
|
|
30
|
+
"worker",
|
|
31
|
+
"browser"
|
|
32
|
+
],
|
|
33
|
+
outdir,
|
|
34
|
+
metafile: true,
|
|
35
|
+
treeShaking: true,
|
|
36
|
+
sourcemap: true,
|
|
37
|
+
splitting: true,
|
|
38
|
+
logOverride: { "ignored-bare-import": "silent" },
|
|
39
|
+
loader: { ".wasm": "copy" },
|
|
40
|
+
external: [
|
|
41
|
+
"node:*",
|
|
42
|
+
"cloudflare:workers",
|
|
43
|
+
"functions-service:user-script"
|
|
44
|
+
],
|
|
45
|
+
plugins: [
|
|
46
|
+
httpPlugin,
|
|
47
|
+
{
|
|
48
|
+
name: "entrypoint",
|
|
49
|
+
setup: (build) => {
|
|
50
|
+
build.onResolve({ filter: /^dxos:entrypoint$/ }, () => ({
|
|
51
|
+
path: "dxos:entrypoint",
|
|
52
|
+
namespace: "dxos:entrypoint"
|
|
53
|
+
}));
|
|
54
|
+
build.onLoad({
|
|
55
|
+
filter: /^dxos:entrypoint$/,
|
|
56
|
+
namespace: "dxos:entrypoint"
|
|
57
|
+
}, () => ({
|
|
58
|
+
contents: trim`
|
|
59
|
+
export default {
|
|
60
|
+
fetch: async (...args) => {
|
|
61
|
+
const { wrapFunctionHandler } = await import('@dxos/compute-runtime');
|
|
62
|
+
const { wrapHandlerForCloudflare, setupFunctionsLogger } = await import('@dxos/functions-runtime-cloudflare');
|
|
63
|
+
const { default: handler } = await import('${options.entryPoint}');
|
|
64
|
+
|
|
65
|
+
setupFunctionsLogger();
|
|
66
|
+
|
|
67
|
+
//
|
|
68
|
+
// Wrapper to make the function cloudflare-compatible.
|
|
69
|
+
//
|
|
70
|
+
return wrapHandlerForCloudflare(wrapFunctionHandler(handler))(...args);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
`,
|
|
74
|
+
resolveDir: dirname(fileURLToPath(import.meta.url))
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "metafile",
|
|
80
|
+
setup: (build) => {
|
|
81
|
+
build.onEnd(async (result) => {
|
|
82
|
+
await writeFile(join(build.initialOptions.outdir, "metafile.json"), JSON.stringify(result.metafile, null, 2));
|
|
83
|
+
await writeFile(join(build.initialOptions.outdir, "manifest.json"), JSON.stringify({ files: Object.keys(result.metafile.outputs).map((path) => ({ name: relative(build.initialOptions.outdir, path) })) }, null, 2));
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
});
|
|
89
|
+
if (result.errors.length > 0) throw new BundleCreationError(result.errors);
|
|
90
|
+
const assetPaths = Object.keys(result.metafile.outputs).map((path) => relative(outdir, path));
|
|
91
|
+
const assets = Function.pipe(await Promise.all(assetPaths.map((path) => fs.readFile(join(outdir, path)))), Array.zipWith(assetPaths, (content, path) => [path, content]), Record.fromEntries);
|
|
92
|
+
if (options.verbose) {
|
|
93
|
+
console.log("Function compiled");
|
|
94
|
+
console.log("Metafile path:", `${outdir}/metafile.json`);
|
|
95
|
+
console.log("Assets:\n");
|
|
96
|
+
console.log(Object.entries(result.metafile.outputs).sort((a, b) => b[1].bytes - a[1].bytes).map(([path, desc]) => `${formatBytes(desc.bytes).padEnd(10)} - ${relative(outdir, path)} ${basename(path) === "index.js" ? " (entry point)" : ""}`).join("\n"));
|
|
97
|
+
const filesInOutput = Function.pipe(result.metafile.outputs, Record.values, Array.flatMap((_) => Record.toEntries(_.inputs)));
|
|
98
|
+
const moduleInOutput = Function.pipe(result.metafile.inputs, Record.values, Array.flatMap((_) => _.imports), Array.filter((_) => !!_.original?.match(/^@[a-zA-Z]+/)), Array.filter((_) => !!filesInOutput.find(([name]) => name === _.path)), Array.map((_) => _.original), Array.dedupe, Array.sort(Order.string));
|
|
99
|
+
console.log(`Modules in output:\n${moduleInOutput.map((_) => ` - ${_}`).join("\n")}`);
|
|
100
|
+
}
|
|
101
|
+
for (const path of Object.keys(assets)) if (path.endsWith(".map")) {
|
|
102
|
+
const content = assets[path];
|
|
103
|
+
const processedMap = postprocessSourceMap(JSON.parse(new TextDecoder().decode(content)), outdir, gitDir);
|
|
104
|
+
assets[path] = Buffer.from(JSON.stringify(processedMap));
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
entryPoint: "index.js",
|
|
108
|
+
assets
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
var BundleCreationError = class extends BaseError.extend("BundleCreationError", "Bundle creation failed") {
|
|
112
|
+
constructor(errors) {
|
|
113
|
+
super({ context: { errors } });
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var formatBytes = (bytes) => {
|
|
117
|
+
if (bytes < Unit.Kilobyte(1).unit.quotient) return Unit.Byte(bytes).toString();
|
|
118
|
+
if (bytes < Unit.Megabyte(1).unit.quotient) return Unit.Kilobyte(bytes).toString();
|
|
119
|
+
if (bytes < Unit.Gigabyte(1).unit.quotient) return Unit.Megabyte(bytes).toString();
|
|
120
|
+
return Unit.Gigabyte(bytes).toString();
|
|
121
|
+
};
|
|
122
|
+
var findGitDir = (startPath) => {
|
|
123
|
+
let current = startPath;
|
|
124
|
+
while (current !== "/") {
|
|
125
|
+
const gitPath = join(current, ".git");
|
|
126
|
+
try {
|
|
127
|
+
if (statSync(gitPath).isDirectory()) return gitPath;
|
|
128
|
+
} catch {}
|
|
129
|
+
current = dirname(current);
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Adjust source paths to be relative to the repo root.
|
|
135
|
+
*/
|
|
136
|
+
var postprocessSourceMap = (map, outDir, gitDir) => {
|
|
137
|
+
if (!gitDir) return map;
|
|
138
|
+
if (map.sources && Array.isArray(map.sources)) map.sources = map.sources.map((source) => {
|
|
139
|
+
const resolved = resolve(outDir, source);
|
|
140
|
+
if (resolved.startsWith(dirname(gitDir))) return relative(dirname(gitDir), resolved);
|
|
141
|
+
return source;
|
|
142
|
+
});
|
|
143
|
+
return map;
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
export { bundleFunction };
|
|
147
|
+
|
|
148
|
+
//# sourceMappingURL=native.mjs.map
|