@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/edge-compute",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "EDGE compute: function deploy, scripts, and Edge implementations of Remote* runtime interfaces.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -16,22 +16,22 @@
|
|
|
16
16
|
".": {
|
|
17
17
|
"source": "./src/index.ts",
|
|
18
18
|
"types": "./dist/types/src/index.d.ts",
|
|
19
|
-
"
|
|
19
|
+
"import": "./dist/lib/index.mjs"
|
|
20
20
|
},
|
|
21
21
|
"./testing": {
|
|
22
22
|
"source": "./src/testing/index.ts",
|
|
23
23
|
"types": "./dist/types/src/testing/index.d.ts",
|
|
24
|
-
"
|
|
24
|
+
"import": "./dist/lib/testing.mjs"
|
|
25
25
|
},
|
|
26
26
|
"./bundler": {
|
|
27
27
|
"source": "./src/bundler/index.ts",
|
|
28
28
|
"types": "./dist/types/src/bundler/index.d.ts",
|
|
29
|
-
"
|
|
29
|
+
"import": "./dist/lib/bundler.mjs"
|
|
30
30
|
},
|
|
31
31
|
"./native": {
|
|
32
32
|
"source": "./src/native/index.ts",
|
|
33
33
|
"types": "./dist/types/src/native/index.d.ts",
|
|
34
|
-
"
|
|
34
|
+
"import": "./dist/lib/native.mjs"
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -60,23 +60,23 @@
|
|
|
60
60
|
"esbuild": "0.28.1",
|
|
61
61
|
"esbuild-wasm": "^0.28.0",
|
|
62
62
|
"iso-did": "^1.6.0",
|
|
63
|
-
"@dxos/client": "0.
|
|
64
|
-
"@dxos/compute
|
|
65
|
-
"@dxos/
|
|
66
|
-
"@dxos/context": "0.
|
|
67
|
-
"@dxos/
|
|
68
|
-
"@dxos/
|
|
69
|
-
"@dxos/echo-client": "0.
|
|
70
|
-
"@dxos/edge-client": "0.
|
|
71
|
-
"@dxos/effect": "0.
|
|
72
|
-
"@dxos/errors": "0.
|
|
73
|
-
"@dxos/functions-runtime-cloudflare": "0.
|
|
74
|
-
"@dxos/invariant": "0.
|
|
75
|
-
"@dxos/keys": "0.
|
|
76
|
-
"@dxos/
|
|
77
|
-
"@dxos/
|
|
78
|
-
"@dxos/protocols": "0.
|
|
79
|
-
"@dxos/
|
|
63
|
+
"@dxos/client": "0.11.0",
|
|
64
|
+
"@dxos/compute": "0.11.0",
|
|
65
|
+
"@dxos/compute-runtime": "0.11.0",
|
|
66
|
+
"@dxos/context": "0.11.0",
|
|
67
|
+
"@dxos/crypto": "0.11.0",
|
|
68
|
+
"@dxos/echo": "0.11.0",
|
|
69
|
+
"@dxos/echo-client": "0.11.0",
|
|
70
|
+
"@dxos/edge-client": "0.11.0",
|
|
71
|
+
"@dxos/effect": "0.11.0",
|
|
72
|
+
"@dxos/errors": "0.11.0",
|
|
73
|
+
"@dxos/functions-runtime-cloudflare": "0.11.0",
|
|
74
|
+
"@dxos/invariant": "0.11.0",
|
|
75
|
+
"@dxos/keys": "0.11.0",
|
|
76
|
+
"@dxos/log": "0.11.0",
|
|
77
|
+
"@dxos/node-std": "0.11.0",
|
|
78
|
+
"@dxos/protocols": "0.11.0",
|
|
79
|
+
"@dxos/util": "0.11.0"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@effect/vitest": "0.29.0",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Registry } from '@effect-atom/atom';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
8
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import { RemoteProcessManager } from '@dxos/compute-runtime';
|
|
11
|
+
import { EdgeHttpClient } from '@dxos/edge-client';
|
|
12
|
+
import { EffectEx } from '@dxos/effect';
|
|
13
|
+
import { invariant } from '@dxos/invariant';
|
|
14
|
+
import { SpaceId } from '@dxos/keys';
|
|
15
|
+
|
|
16
|
+
import * as EdgeProcessManager from './EdgeProcessManager';
|
|
17
|
+
|
|
18
|
+
describe('EdgeProcessManager', () => {
|
|
19
|
+
test('cancel force-cancels the trigger run on the edge client', async () => {
|
|
20
|
+
const edgeClient = new EdgeHttpClient('https://edge.example.com');
|
|
21
|
+
const cancelSpy = vi.spyOn(edgeClient, 'cancelTriggerRun').mockResolvedValue(undefined);
|
|
22
|
+
const space = SpaceId.random();
|
|
23
|
+
const trigger = 'TRIGGER1';
|
|
24
|
+
|
|
25
|
+
const program = Effect.gen(function* () {
|
|
26
|
+
const manager = yield* RemoteProcessManager.Service;
|
|
27
|
+
invariant(manager.cancel, 'edge process manager exposes cancel');
|
|
28
|
+
yield* manager.cancel({ space, trigger });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await EffectEx.runPromise(
|
|
32
|
+
program.pipe(
|
|
33
|
+
Effect.provide(EdgeProcessManager.fromEdgeClient(edgeClient)),
|
|
34
|
+
Effect.provide(Layer.succeed(Registry.AtomRegistry, Registry.make())),
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
expect(cancelSpy).toHaveBeenCalledTimes(1);
|
|
39
|
+
expect(cancelSpy.mock.calls[0]?.[1]).toBe(space);
|
|
40
|
+
expect(cancelSpy.mock.calls[0]?.[2]).toBe(trigger);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('layer without a client omits cancel and yields an empty tree', async () => {
|
|
44
|
+
const program = Effect.gen(function* () {
|
|
45
|
+
const manager = yield* RemoteProcessManager.Service;
|
|
46
|
+
return { hasCancel: manager.cancel !== undefined, tree: yield* manager.processTree };
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const result = await EffectEx.runPromise(
|
|
50
|
+
program.pipe(
|
|
51
|
+
Effect.provide(EdgeProcessManager.layer),
|
|
52
|
+
Effect.provide(Layer.succeed(Registry.AtomRegistry, Registry.make())),
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(result.hasCancel).toBe(false);
|
|
57
|
+
expect(result.tree).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -11,37 +11,86 @@ import * as Layer from 'effect/Layer';
|
|
|
11
11
|
import { type Client } from '@dxos/client';
|
|
12
12
|
import { type Process } from '@dxos/compute';
|
|
13
13
|
import { RemoteProcessManager } from '@dxos/compute-runtime';
|
|
14
|
+
import { Context as DxosContext } from '@dxos/context';
|
|
15
|
+
import { type EdgeHttpClient } from '@dxos/edge-client';
|
|
16
|
+
import { SpaceId } from '@dxos/keys';
|
|
17
|
+
import { log } from '@dxos/log';
|
|
18
|
+
|
|
19
|
+
import { createEdgeClient } from './edge-client';
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
22
|
* EDGE implementation of {@link RemoteProcessManager.Service}.
|
|
17
23
|
*
|
|
18
|
-
* The
|
|
19
|
-
*
|
|
20
|
-
*
|
|
24
|
+
* The read view (`processTree`) is still empty — the EDGE worker exposes no
|
|
25
|
+
* process-tree endpoint yet (see Decision D3). What this adds is the `cancel`
|
|
26
|
+
* control: it force-cancels the current run of an edge trigger (its in-flight
|
|
27
|
+
* execution and `runAgain` continuation chain) via
|
|
28
|
+
* {@link EdgeHttpClient.cancelTriggerRun}, addressed by `trigger` in `space`.
|
|
29
|
+
* A manager built without a client (the {@link layer} stub) omits `cancel`.
|
|
21
30
|
*/
|
|
22
|
-
const
|
|
31
|
+
const makeManager = (
|
|
32
|
+
registry: Registry.Registry,
|
|
33
|
+
getEdgeClient?: () => EdgeHttpClient,
|
|
34
|
+
): RemoteProcessManager.Manager => {
|
|
35
|
+
// TODO(edge): Populate from an EDGE process-tree endpoint once available.
|
|
36
|
+
const processTreeAtom = Atom.make<readonly Process.Info[]>([]);
|
|
37
|
+
registry.mount(processTreeAtom);
|
|
38
|
+
return {
|
|
39
|
+
processTree: Effect.sync(() => registry.get(processTreeAtom)),
|
|
40
|
+
processTreeAtom,
|
|
41
|
+
...(getEdgeClient
|
|
42
|
+
? {
|
|
43
|
+
cancel: ({ space, trigger }: RemoteProcessManager.CancelTarget) =>
|
|
44
|
+
Effect.gen(function* () {
|
|
45
|
+
// `space` arrives as an untyped string from trace meta; skip rather than throw when it is
|
|
46
|
+
// not a valid space id (best-effort — the meter has already cleared locally).
|
|
47
|
+
if (!SpaceId.isValid(space)) {
|
|
48
|
+
log.warn('remote trigger cancel skipped: invalid space id', { space });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
yield* Effect.tryPromise(() =>
|
|
52
|
+
getEdgeClient().cancelTriggerRun(DxosContext.default(), space, trigger),
|
|
53
|
+
).pipe(
|
|
54
|
+
Effect.asVoid,
|
|
55
|
+
// A missing/unreachable endpoint (e.g. an older edge deploy) must not surface as a defect
|
|
56
|
+
// from this fire-and-forget cancel — log and move on.
|
|
57
|
+
Effect.catchAll((error) => Effect.sync(() => log.warn('remote trigger cancel failed', { error }))),
|
|
58
|
+
);
|
|
59
|
+
}),
|
|
60
|
+
}
|
|
61
|
+
: {}),
|
|
62
|
+
} satisfies RemoteProcessManager.Manager;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const make = (
|
|
66
|
+
getEdgeClient?: () => EdgeHttpClient,
|
|
67
|
+
): Layer.Layer<RemoteProcessManager.Service, never, Registry.AtomRegistry> =>
|
|
23
68
|
Layer.effect(
|
|
24
69
|
RemoteProcessManager.Service,
|
|
25
70
|
Effect.gen(function* () {
|
|
26
71
|
const registry = yield* Registry.AtomRegistry;
|
|
27
|
-
|
|
28
|
-
const processTreeAtom = Atom.make<readonly Process.Info[]>([]);
|
|
29
|
-
registry.mount(processTreeAtom);
|
|
30
|
-
return {
|
|
31
|
-
processTree: Effect.sync(() => registry.get(processTreeAtom)),
|
|
32
|
-
processTreeAtom,
|
|
33
|
-
} satisfies RemoteProcessManager.Manager;
|
|
72
|
+
return makeManager(registry, getEdgeClient);
|
|
34
73
|
}),
|
|
35
74
|
);
|
|
36
75
|
|
|
37
76
|
/**
|
|
38
|
-
*
|
|
39
|
-
|
|
77
|
+
* For tests: provide a pre-built edge client.
|
|
78
|
+
*/
|
|
79
|
+
export const fromEdgeClient = (
|
|
80
|
+
edgeClient: EdgeHttpClient,
|
|
81
|
+
): Layer.Layer<RemoteProcessManager.Service, never, Registry.AtomRegistry> => make(() => edgeClient);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build from a `Client`, deferring edge-client creation until the first cancel
|
|
85
|
+
* (identity / edge config may be absent at boot).
|
|
40
86
|
*/
|
|
41
|
-
export const fromClient = (
|
|
42
|
-
|
|
87
|
+
export const fromClient = (client: Client): Layer.Layer<RemoteProcessManager.Service, never, Registry.AtomRegistry> => {
|
|
88
|
+
let cached: EdgeHttpClient | undefined;
|
|
89
|
+
return make(() => (cached ??= createEdgeClient(client)));
|
|
90
|
+
};
|
|
43
91
|
|
|
44
92
|
/**
|
|
45
|
-
*
|
|
93
|
+
* EDGE process manager with no client — empty process tree and no cancel control.
|
|
94
|
+
* Used where edge is not configured.
|
|
46
95
|
*/
|
|
47
96
|
export const layer: Layer.Layer<RemoteProcessManager.Service, never, Registry.AtomRegistry> = make();
|
|
@@ -5,66 +5,140 @@
|
|
|
5
5
|
// @import-as-namespace
|
|
6
6
|
|
|
7
7
|
import { Atom, Registry } from '@effect-atom/atom';
|
|
8
|
+
import * as Duration from 'effect/Duration';
|
|
8
9
|
import * as Effect from 'effect/Effect';
|
|
10
|
+
import * as Exit from 'effect/Exit';
|
|
9
11
|
import * as Layer from 'effect/Layer';
|
|
12
|
+
import * as Schedule from 'effect/Schedule';
|
|
13
|
+
import type * as Scope from 'effect/Scope';
|
|
10
14
|
|
|
11
15
|
import { type Client, ClientService } from '@dxos/client';
|
|
12
16
|
import { type Trigger } from '@dxos/compute';
|
|
13
17
|
import { RemoteTriggerManager } from '@dxos/compute-runtime';
|
|
14
|
-
import {
|
|
18
|
+
import { Context as DxosContext } from '@dxos/context';
|
|
19
|
+
import { Ref } from '@dxos/echo';
|
|
20
|
+
import { type EdgeTriggerStatus } from '@dxos/edge-client';
|
|
21
|
+
import { EID, type SpaceId } from '@dxos/keys';
|
|
22
|
+
|
|
23
|
+
import { createEdgeClient } from './edge-client';
|
|
24
|
+
|
|
25
|
+
type EdgeClient = ReturnType<typeof createEdgeClient>;
|
|
26
|
+
|
|
27
|
+
/** How often the remote trigger view is refreshed from the EDGE dispatcher. */
|
|
28
|
+
const POLL_INTERVAL = Duration.seconds(15);
|
|
15
29
|
|
|
16
30
|
/**
|
|
17
31
|
* EDGE implementation of {@link RemoteTriggerManager.Service}.
|
|
18
32
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `environment: 'edge'`)
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* {@link
|
|
33
|
+
* The `triggers` atom is populated by polling
|
|
34
|
+
* {@link EdgeHttpClient.getSpaceTriggers} and mapping each edge trigger's
|
|
35
|
+
* runtime status into a {@link Trigger.State} (marked `environment: 'edge'`).
|
|
36
|
+
* The referenced `Trigger` objects are replicated into the local database, so
|
|
37
|
+
* the trigger ref is a space-relative echo ref synthesized from the id. The
|
|
38
|
+
* aggregate {@link TriggerMonitor} dedupes these against the database-derived
|
|
39
|
+
* view (edge entries here supersede the bare database ones).
|
|
25
40
|
*
|
|
26
|
-
* `invokeTrigger`
|
|
27
|
-
*
|
|
41
|
+
* `invokeTrigger` force-runs the trigger's cron on the EDGE dispatcher via
|
|
42
|
+
* {@link EdgeHttpClient.forceRunCronTrigger}.
|
|
43
|
+
*/
|
|
44
|
+
const toState = (status: EdgeTriggerStatus): Trigger.State => ({
|
|
45
|
+
// Space-relative echo ref (same shape as `Ref.make(trigger)` for an in-space object), resolvable
|
|
46
|
+
// against the space database where the edge trigger is replicated.
|
|
47
|
+
trigger: Ref.fromURI(EID.make({ entityId: status.triggerId })),
|
|
48
|
+
environment: 'edge',
|
|
49
|
+
nextExecution: status.nextExecutionTimestamp !== undefined ? new Date(status.nextExecutionTimestamp) : undefined,
|
|
50
|
+
cooldownUntil: status.cooldownUntilTimestamp !== undefined ? new Date(status.cooldownUntilTimestamp) : undefined,
|
|
51
|
+
lastResult: toExit(status.lastResult),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const toExit = (result: EdgeTriggerStatus['lastResult']): Exit.Exit<unknown> | null => {
|
|
55
|
+
if (!result) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
// The original error channel is not reconstructed over the wire, so failures are surfaced as
|
|
59
|
+
// defects carrying the serialized message.
|
|
60
|
+
return result.status === 'success'
|
|
61
|
+
? Exit.void
|
|
62
|
+
: Exit.die(new Error(result.error?.message ?? 'Edge trigger invocation failed'));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const make = (
|
|
66
|
+
getEdgeClient: () => EdgeClient,
|
|
67
|
+
spaceId: SpaceId,
|
|
68
|
+
registry: Registry.Registry,
|
|
69
|
+
): Effect.Effect<RemoteTriggerManager.Manager, never, Scope.Scope> =>
|
|
70
|
+
Effect.gen(function* () {
|
|
71
|
+
const triggers = Atom.make<readonly Trigger.State[]>([]);
|
|
72
|
+
registry.mount(triggers);
|
|
73
|
+
|
|
74
|
+
const refresh = Effect.tryPromise(() => getEdgeClient().getSpaceTriggers(DxosContext.default(), spaceId)).pipe(
|
|
75
|
+
Effect.tap((response) => Effect.sync(() => registry.update(triggers, () => response.triggers.map(toState)))),
|
|
76
|
+
// The endpoint may be unimplemented or unreachable; a failed poll must not tear down the layer.
|
|
77
|
+
Effect.catchAll(() => Effect.void),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Poll the EDGE dispatcher on an interval; the fiber is scoped to the layer's lifetime.
|
|
81
|
+
yield* refresh.pipe(Effect.repeat(Schedule.spaced(POLL_INTERVAL)), Effect.forkScoped);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
triggers,
|
|
85
|
+
invokeTrigger: (options: Trigger.InvokeOptions) =>
|
|
86
|
+
// Manual invocation of a remote trigger maps onto force-running its cron on the EDGE
|
|
87
|
+
// dispatcher; refresh the view promptly afterwards.
|
|
88
|
+
Effect.promise(() =>
|
|
89
|
+
getEdgeClient().forceRunCronTrigger(DxosContext.default(), spaceId, options.trigger.id),
|
|
90
|
+
).pipe(
|
|
91
|
+
Effect.asVoid,
|
|
92
|
+
Effect.orDie,
|
|
93
|
+
Effect.tap(() => refresh),
|
|
94
|
+
),
|
|
95
|
+
} satisfies RemoteTriggerManager.Manager;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* For tests: provide a pre-built edge client.
|
|
28
100
|
*/
|
|
29
|
-
const
|
|
30
|
-
|
|
101
|
+
export const fromEdgeClient = (
|
|
102
|
+
edgeClient: EdgeClient,
|
|
103
|
+
spaceId: SpaceId,
|
|
104
|
+
): Layer.Layer<RemoteTriggerManager.Service, never, Registry.AtomRegistry> =>
|
|
105
|
+
Layer.scoped(
|
|
31
106
|
RemoteTriggerManager.Service,
|
|
32
107
|
Effect.gen(function* () {
|
|
33
108
|
const registry = yield* Registry.AtomRegistry;
|
|
34
|
-
|
|
35
|
-
const triggers = Atom.make<readonly Trigger.State[]>([]);
|
|
36
|
-
registry.mount(triggers);
|
|
37
|
-
return {
|
|
38
|
-
triggers,
|
|
39
|
-
invokeTrigger: () => Effect.die(new Error('Remote (EDGE) trigger invocation is not supported')),
|
|
40
|
-
} satisfies RemoteTriggerManager.Manager;
|
|
109
|
+
return yield* make(() => edgeClient, spaceId, registry);
|
|
41
110
|
}),
|
|
42
111
|
);
|
|
43
112
|
|
|
44
113
|
/**
|
|
45
|
-
* Build from a `Client
|
|
114
|
+
* Build from a `Client`, deferring edge-client creation until first use
|
|
115
|
+
* (identity may be absent at boot).
|
|
46
116
|
*/
|
|
47
117
|
export const fromClient = (
|
|
48
|
-
|
|
49
|
-
spaceId
|
|
50
|
-
): Layer.Layer<RemoteTriggerManager.Service, never, Registry.AtomRegistry> =>
|
|
118
|
+
client: Client,
|
|
119
|
+
spaceId: SpaceId,
|
|
120
|
+
): Layer.Layer<RemoteTriggerManager.Service, never, Registry.AtomRegistry> =>
|
|
121
|
+
Layer.scoped(
|
|
122
|
+
RemoteTriggerManager.Service,
|
|
123
|
+
Effect.gen(function* () {
|
|
124
|
+
const registry = yield* Registry.AtomRegistry;
|
|
125
|
+
let cached: EdgeClient | undefined;
|
|
126
|
+
return yield* make(() => (cached ??= createEdgeClient(client)), spaceId, registry);
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
51
129
|
|
|
52
130
|
/**
|
|
53
131
|
* Build from the ambient `ClientService`.
|
|
54
132
|
*/
|
|
55
133
|
export const layer = (
|
|
56
|
-
spaceId
|
|
134
|
+
spaceId: SpaceId,
|
|
57
135
|
): Layer.Layer<RemoteTriggerManager.Service, never, Registry.AtomRegistry | ClientService> =>
|
|
58
|
-
Layer.
|
|
136
|
+
Layer.scoped(
|
|
59
137
|
RemoteTriggerManager.Service,
|
|
60
138
|
Effect.gen(function* () {
|
|
61
|
-
yield* ClientService;
|
|
139
|
+
const client = yield* ClientService;
|
|
62
140
|
const registry = yield* Registry.AtomRegistry;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
triggers,
|
|
67
|
-
invokeTrigger: () => Effect.die(new Error('Remote (EDGE) trigger invocation is not supported')),
|
|
68
|
-
} satisfies RemoteTriggerManager.Manager;
|
|
141
|
+
let cached: EdgeClient | undefined;
|
|
142
|
+
return yield* make(() => (cached ??= createEdgeClient(client)), spaceId, registry);
|
|
69
143
|
}),
|
|
70
144
|
);
|
package/src/native/bundler.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { statSync } from 'node:fs';
|
|
|
13
13
|
import { writeFile } from 'node:fs/promises';
|
|
14
14
|
import * as fs from 'node:fs/promises';
|
|
15
15
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
16
17
|
|
|
17
18
|
import { BaseError } from '@dxos/errors';
|
|
18
19
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -85,7 +86,13 @@ export const bundleFunction = async (options: BundleOptions): Promise<BundleResu
|
|
|
85
86
|
},
|
|
86
87
|
};
|
|
87
88
|
`,
|
|
88
|
-
|
|
89
|
+
// NOTE: Compute the module directory via `fileURLToPath`, not
|
|
90
|
+
// `new URL('.', import.meta.url)`. Vite's library-mode build treats the latter as an
|
|
91
|
+
// asset reference and base64-inlines the resolved `.ts` module as a
|
|
92
|
+
// `data:video/mp2t;base64,…` data URL, so `resolveDir` becomes garbage in `dist` and
|
|
93
|
+
// the bundler can no longer resolve `@dxos/compute-runtime` /
|
|
94
|
+
// `@dxos/functions-runtime-cloudflare`.
|
|
95
|
+
resolveDir: dirname(fileURLToPath(import.meta.url)),
|
|
89
96
|
}));
|
|
90
97
|
},
|
|
91
98
|
},
|
package/src/testing/services.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import type * as Context from 'effect/Context';
|
|
6
|
+
import * as Layer from 'effect/Layer';
|
|
6
7
|
|
|
7
8
|
import type { Space } from '@dxos/client/echo';
|
|
8
|
-
import {
|
|
9
|
+
import { Credential, Trace } from '@dxos/compute';
|
|
9
10
|
import { ConfiguredCredentialsService } from '@dxos/compute-runtime';
|
|
10
|
-
import { ServiceContainer } from '@dxos/compute-runtime';
|
|
11
11
|
import { Database } from '@dxos/echo';
|
|
12
12
|
import { type EchoDatabase } from '@dxos/echo-client';
|
|
13
13
|
import { assertArgument } from '@dxos/invariant';
|
|
@@ -63,23 +63,29 @@ export type TestServiceOptions = {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* Composes the foundational Effect layers available to compute nodes and operations in tests:
|
|
67
|
+
* credentials, database (from a space or standalone database), and trace. Callers layer AI,
|
|
68
|
+
* `Operation.Service`, and registry on top as needed for the code under test.
|
|
67
69
|
*/
|
|
68
|
-
export const createTestServices = ({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
db,
|
|
72
|
-
logging,
|
|
73
|
-
space,
|
|
74
|
-
}: TestServiceOptions = {}): ServiceContainer => {
|
|
70
|
+
export const createTestServices = ({ credentials, db, logging, space }: TestServiceOptions = {}): Layer.Layer<
|
|
71
|
+
Credential.CredentialsService | Database.Service | Trace.TraceService
|
|
72
|
+
> => {
|
|
75
73
|
assertArgument(!(!!space && !!db), 'space', 'space can be provided only if db is not');
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
const credentialsLayer = Layer.succeed(
|
|
76
|
+
Credential.CredentialsService,
|
|
77
|
+
createCredentialsService(credentials) ?? new ConfiguredCredentialsService(),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const database = space?.db ?? db;
|
|
81
|
+
const databaseLayer = database ? Database.layer(database) : Database.notAvailable;
|
|
82
|
+
|
|
83
|
+
const traceLayer = Layer.succeed(
|
|
84
|
+
Trace.TraceService,
|
|
85
|
+
logging?.trace ?? (logging?.enabled ? consoleTraceWriter : noopTraceWriter),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
return Layer.mergeAll(credentialsLayer, databaseLayer, traceLayer);
|
|
83
89
|
};
|
|
84
90
|
|
|
85
91
|
const createCredentialsService = (
|