@dxos/compute 0.8.4-main.fcfe5033a5 → 0.8.4-staging.60fe92afc8
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 +102 -5
- package/README.md +36 -1
- package/dist/lib/neutral/StorageService.mjs +22 -0
- package/dist/lib/neutral/StorageService.mjs.map +7 -0
- package/dist/lib/neutral/chunk-2B2KSJT6.mjs +17 -0
- package/dist/lib/neutral/chunk-2B2KSJT6.mjs.map +7 -0
- package/dist/lib/neutral/chunk-AFBNVFAY.mjs +25 -0
- package/dist/lib/neutral/chunk-AFBNVFAY.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-JAU4E4EK.mjs +53 -0
- package/dist/lib/neutral/chunk-JAU4E4EK.mjs.map +7 -0
- package/dist/lib/neutral/chunk-MEPE4HHX.mjs +21 -0
- package/dist/lib/neutral/chunk-MEPE4HHX.mjs.map +7 -0
- package/dist/lib/neutral/chunk-QZS4G6PE.mjs +240 -0
- package/dist/lib/neutral/chunk-QZS4G6PE.mjs.map +7 -0
- package/dist/lib/neutral/chunk-XEVFBI6S.mjs +62 -0
- package/dist/lib/neutral/chunk-XEVFBI6S.mjs.map +7 -0
- package/dist/lib/neutral/chunk-Z3Y7FQGN.mjs +174 -0
- package/dist/lib/neutral/chunk-Z3Y7FQGN.mjs.map +7 -0
- package/dist/lib/neutral/fib-HBDUGQW7.mjs +10 -0
- package/dist/lib/neutral/fib-HBDUGQW7.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +1179 -51
- package/dist/lib/neutral/index.mjs.map +4 -4
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/reply-RJIAJ6FV.mjs +10 -0
- package/dist/lib/neutral/reply-RJIAJ6FV.mjs.map +7 -0
- package/dist/lib/neutral/sleep-3T2CZ7RE.mjs +10 -0
- package/dist/lib/neutral/sleep-3T2CZ7RE.mjs.map +7 -0
- package/dist/lib/neutral/testing/index.mjs +27 -214
- package/dist/lib/neutral/testing/index.mjs.map +4 -4
- package/dist/types/src/Blueprint.d.ts +182 -0
- package/dist/types/src/Blueprint.d.ts.map +1 -0
- package/dist/types/src/Credential.d.ts +27 -0
- package/dist/types/src/Credential.d.ts.map +1 -0
- package/dist/types/src/Header.d.ts +9 -0
- package/dist/types/src/Header.d.ts.map +1 -0
- package/dist/types/src/Header.test.d.ts +2 -0
- package/dist/types/src/Header.test.d.ts.map +1 -0
- package/dist/types/src/LayerSpec.d.ts +56 -0
- package/dist/types/src/LayerSpec.d.ts.map +1 -0
- package/dist/types/src/McpServer.d.ts +26 -0
- package/dist/types/src/McpServer.d.ts.map +1 -0
- package/dist/types/src/Operation.d.ts +385 -0
- package/dist/types/src/Operation.d.ts.map +1 -0
- package/dist/types/src/OperationHandlerSet.d.ts +69 -0
- package/dist/types/src/OperationHandlerSet.d.ts.map +1 -0
- package/dist/types/src/OperationHandlerSet.test.d.ts +2 -0
- package/dist/types/src/OperationHandlerSet.test.d.ts.map +1 -0
- package/dist/types/src/OperationRegistry.d.ts +23 -0
- package/dist/types/src/OperationRegistry.d.ts.map +1 -0
- package/dist/types/src/Process.d.ts +255 -0
- package/dist/types/src/Process.d.ts.map +1 -0
- package/dist/types/src/Routine.d.ts +54 -0
- package/dist/types/src/Routine.d.ts.map +1 -0
- package/dist/types/src/Script.d.ts +21 -0
- package/dist/types/src/Script.d.ts.map +1 -0
- package/dist/types/src/ServiceResolver.d.ts +92 -0
- package/dist/types/src/ServiceResolver.d.ts.map +1 -0
- package/dist/types/src/StorageService.d.ts +58 -0
- package/dist/types/src/StorageService.d.ts.map +1 -0
- package/dist/types/src/Template.d.ts +61 -0
- package/dist/types/src/Template.d.ts.map +1 -0
- package/dist/types/src/Template.test.d.ts +2 -0
- package/dist/types/src/Template.test.d.ts.map +1 -0
- package/dist/types/src/Trace.d.ts +266 -0
- package/dist/types/src/Trace.d.ts.map +1 -0
- package/dist/types/src/Trigger.d.ts +192 -0
- package/dist/types/src/Trigger.d.ts.map +1 -0
- package/dist/types/src/TriggerEvent.d.ts +83 -0
- package/dist/types/src/TriggerEvent.d.ts.map +1 -0
- package/dist/types/src/errors.d.ts +308 -0
- package/dist/types/src/errors.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +18 -5
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing/definitions.d.ts +11 -0
- package/dist/types/src/testing/definitions.d.ts.map +1 -0
- package/dist/types/src/testing/fib.d.ts +8 -0
- package/dist/types/src/testing/fib.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +6 -4
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/reply.d.ts +4 -0
- package/dist/types/src/testing/reply.d.ts.map +1 -0
- package/dist/types/src/testing/sleep.d.ts +6 -0
- package/dist/types/src/testing/sleep.d.ts.map +1 -0
- package/dist/types/src/umbrella.test.d.ts +2 -0
- package/dist/types/src/umbrella.test.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -36
- package/src/Blueprint.ts +224 -0
- package/src/Credential.ts +53 -0
- package/src/Header.test.ts +132 -0
- package/src/Header.ts +38 -0
- package/src/LayerSpec.ts +91 -0
- package/src/McpServer.ts +44 -0
- package/src/Operation.ts +646 -0
- package/src/OperationHandlerSet.test.ts +97 -0
- package/src/OperationHandlerSet.ts +181 -0
- package/src/OperationRegistry.ts +50 -0
- package/src/Process.ts +541 -0
- package/src/Routine.ts +68 -0
- package/src/Script.ts +33 -0
- package/src/ServiceResolver.ts +200 -0
- package/src/StorageService.ts +101 -0
- package/src/Template.test.ts +164 -0
- package/src/Template.ts +125 -0
- package/src/Trace.ts +363 -0
- package/src/Trigger.ts +196 -0
- package/src/TriggerEvent.ts +64 -0
- package/src/errors.ts +71 -0
- package/src/index.ts +20 -6
- package/src/testing/definitions.ts +51 -0
- package/src/testing/fib.ts +22 -0
- package/src/testing/index.ts +13 -5
- package/src/testing/reply.ts +18 -0
- package/src/testing/sleep.ts +16 -0
- package/src/umbrella.test.ts +51 -0
- package/dist/lib/neutral/chunk-JRV42MW3.mjs +0 -3101
- package/dist/lib/neutral/chunk-JRV42MW3.mjs.map +0 -7
- package/dist/types/src/compute-graph-registry.d.ts +0 -37
- package/dist/types/src/compute-graph-registry.d.ts.map +0 -1
- package/dist/types/src/compute-graph-registry.test.d.ts +0 -2
- package/dist/types/src/compute-graph-registry.test.d.ts.map +0 -1
- package/dist/types/src/compute-graph.d.ts +0 -67
- package/dist/types/src/compute-graph.d.ts.map +0 -1
- package/dist/types/src/compute-graph.test.d.ts +0 -2
- package/dist/types/src/compute-graph.test.d.ts.map +0 -1
- package/dist/types/src/compute-node.d.ts +0 -24
- package/dist/types/src/compute-node.d.ts.map +0 -1
- package/dist/types/src/functions/async-function.d.ts +0 -68
- package/dist/types/src/functions/async-function.d.ts.map +0 -1
- package/dist/types/src/functions/edge-function.d.ts +0 -20
- package/dist/types/src/functions/edge-function.d.ts.map +0 -1
- package/dist/types/src/functions/function-defs.d.ts +0 -11
- package/dist/types/src/functions/function-defs.d.ts.map +0 -1
- package/dist/types/src/functions/index.d.ts +0 -4
- package/dist/types/src/functions/index.d.ts.map +0 -1
- package/dist/types/src/hyperformula.test.d.ts +0 -2
- package/dist/types/src/hyperformula.test.d.ts.map +0 -1
- package/dist/types/src/testing/test-builder.d.ts +0 -15
- package/dist/types/src/testing/test-builder.d.ts.map +0 -1
- package/dist/types/src/testing/test-plugin.d.ts +0 -35
- package/dist/types/src/testing/test-plugin.d.ts.map +0 -1
- package/dist/types/src/testing/test-runtime.d.ts +0 -10
- package/dist/types/src/testing/test-runtime.d.ts.map +0 -1
- package/dist/types/src/testing/util.d.ts +0 -2
- package/dist/types/src/testing/util.d.ts.map +0 -1
- package/dist/types/src/types/hyperformula.d.ts +0 -8
- package/dist/types/src/types/hyperformula.d.ts.map +0 -1
- package/dist/types/src/types/index.d.ts +0 -3
- package/dist/types/src/types/index.d.ts.map +0 -1
- package/dist/types/src/types/types.d.ts +0 -20
- package/dist/types/src/types/types.d.ts.map +0 -1
- package/dist/types/src/types/types.test.d.ts +0 -2
- package/dist/types/src/types/types.test.d.ts.map +0 -1
- package/dist/types/src/util.d.ts +0 -2
- package/dist/types/src/util.d.ts.map +0 -1
- package/src/compute-graph-registry.test.ts +0 -167
- package/src/compute-graph-registry.ts +0 -97
- package/src/compute-graph.test.ts +0 -139
- package/src/compute-graph.ts +0 -265
- package/src/compute-node.ts +0 -59
- package/src/functions/async-function.ts +0 -184
- package/src/functions/edge-function.ts +0 -108
- package/src/functions/function-defs.ts +0 -2427
- package/src/functions/index.ts +0 -7
- package/src/hyperformula.test.ts +0 -14
- package/src/testing/test-builder.ts +0 -59
- package/src/testing/test-plugin.ts +0 -98
- package/src/testing/test-runtime.ts +0 -53
- package/src/testing/util.ts +0 -9
- package/src/types/hyperformula.ts +0 -19
- package/src/types/index.ts +0 -6
- package/src/types/types.test.ts +0 -91
- package/src/types/types.ts +0 -80
- package/src/util.ts +0 -8
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, Registry } from '@effect-atom/atom';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
import { describe, test } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import { DXN } from '@dxos/keys';
|
|
11
|
+
|
|
12
|
+
import * as Operation from './Operation';
|
|
13
|
+
import * as OperationHandlerSet from './OperationHandlerSet';
|
|
14
|
+
|
|
15
|
+
const KEY_A = DXN.make('org.example.test.a');
|
|
16
|
+
const KEY_B = DXN.make('org.example.test.b');
|
|
17
|
+
|
|
18
|
+
const makeHandler = (key: DXN.DXN, output: string) =>
|
|
19
|
+
Operation.withHandler(Operation.make({ input: Schema.Void, output: Schema.String, meta: { key } }), () =>
|
|
20
|
+
Effect.succeed(output),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
describe('OperationHandlerSet.reactive', () => {
|
|
24
|
+
test('merges handlers from contributed sets', async ({ expect }) => {
|
|
25
|
+
const registry = Registry.make();
|
|
26
|
+
const setA = OperationHandlerSet.make(makeHandler(KEY_A, 'A'));
|
|
27
|
+
const setB = OperationHandlerSet.make(makeHandler(KEY_B, 'B'));
|
|
28
|
+
const atom = Atom.make<readonly OperationHandlerSet.OperationHandlerSet[]>([setA, setB]).pipe(Atom.keepAlive);
|
|
29
|
+
registry.mount(atom);
|
|
30
|
+
|
|
31
|
+
const reactive = OperationHandlerSet.reactive(registry, atom);
|
|
32
|
+
const handlers = await reactive.getHandlers();
|
|
33
|
+
expect(handlers.map((handler) => handler.meta.key).sort()).toEqual([KEY_A, KEY_B]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('caches the merged result across calls', async ({ expect }) => {
|
|
37
|
+
const registry = Registry.make();
|
|
38
|
+
let resolveCount = 0;
|
|
39
|
+
const trackingSet: OperationHandlerSet.OperationHandlerSet = {
|
|
40
|
+
[OperationHandlerSet.TypeId]: OperationHandlerSet.TypeId,
|
|
41
|
+
getHandlers: () => Promise.resolve([makeHandler(KEY_A, 'A')]),
|
|
42
|
+
handlers: Effect.sync(() => {
|
|
43
|
+
resolveCount++;
|
|
44
|
+
return [makeHandler(KEY_A, 'A')];
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
const atom = Atom.make<readonly OperationHandlerSet.OperationHandlerSet[]>([trackingSet]).pipe(Atom.keepAlive);
|
|
48
|
+
registry.mount(atom);
|
|
49
|
+
|
|
50
|
+
const reactive = OperationHandlerSet.reactive(registry, atom);
|
|
51
|
+
await reactive.getHandlers();
|
|
52
|
+
await reactive.getHandlers();
|
|
53
|
+
await reactive.getHandlers();
|
|
54
|
+
expect(resolveCount).toBe(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('retries after a rejection rather than caching the failure', async ({ expect }) => {
|
|
58
|
+
const registry = Registry.make();
|
|
59
|
+
let callCount = 0;
|
|
60
|
+
const flakySet: OperationHandlerSet.OperationHandlerSet = {
|
|
61
|
+
[OperationHandlerSet.TypeId]: OperationHandlerSet.TypeId,
|
|
62
|
+
getHandlers: () => Promise.resolve([]),
|
|
63
|
+
handlers: Effect.suspend(() => {
|
|
64
|
+
callCount++;
|
|
65
|
+
if (callCount === 1) {
|
|
66
|
+
return Effect.promise(() => Promise.reject(new Error('transient')));
|
|
67
|
+
}
|
|
68
|
+
return Effect.succeed([makeHandler(KEY_A, 'A')]);
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
const atom = Atom.make<readonly OperationHandlerSet.OperationHandlerSet[]>([flakySet]).pipe(Atom.keepAlive);
|
|
72
|
+
registry.mount(atom);
|
|
73
|
+
|
|
74
|
+
const reactive = OperationHandlerSet.reactive(registry, atom);
|
|
75
|
+
await expect(reactive.getHandlers()).rejects.toThrow('transient');
|
|
76
|
+
const retried = await reactive.getHandlers();
|
|
77
|
+
expect(retried.map((handler) => handler.meta.key)).toEqual([KEY_A]);
|
|
78
|
+
expect(callCount).toBe(2);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('invalidates and re-resolves when the atom changes', async ({ expect }) => {
|
|
82
|
+
const registry = Registry.make();
|
|
83
|
+
const setA = OperationHandlerSet.make(makeHandler(KEY_A, 'A'));
|
|
84
|
+
const setB = OperationHandlerSet.make(makeHandler(KEY_B, 'B'));
|
|
85
|
+
const atom = Atom.make<readonly OperationHandlerSet.OperationHandlerSet[]>([setA]).pipe(Atom.keepAlive);
|
|
86
|
+
registry.mount(atom);
|
|
87
|
+
|
|
88
|
+
const reactive = OperationHandlerSet.reactive(registry, atom);
|
|
89
|
+
expect((await reactive.getHandlers()).map((handler) => handler.meta.key)).toEqual([KEY_A]);
|
|
90
|
+
|
|
91
|
+
registry.set(atom, [setA, setB]);
|
|
92
|
+
expect((await reactive.getHandlers()).map((handler) => handler.meta.key).sort()).toEqual([KEY_A, KEY_B]);
|
|
93
|
+
|
|
94
|
+
registry.set(atom, []);
|
|
95
|
+
expect(await reactive.getHandlers()).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// @import-as-namespace
|
|
6
|
+
|
|
7
|
+
import { type Atom, type Registry } from '@effect-atom/atom';
|
|
8
|
+
import * as Context from 'effect/Context';
|
|
9
|
+
import * as Effect from 'effect/Effect';
|
|
10
|
+
import { pipe } from 'effect/Function';
|
|
11
|
+
import * as Layer from 'effect/Layer';
|
|
12
|
+
|
|
13
|
+
import { EffectEx } from '@dxos/effect';
|
|
14
|
+
import { assertArgument } from '@dxos/invariant';
|
|
15
|
+
import { DXN } from '@dxos/keys';
|
|
16
|
+
|
|
17
|
+
import { NoHandlerError } from './errors';
|
|
18
|
+
import type * as Operation from './Operation';
|
|
19
|
+
|
|
20
|
+
export const TypeId = '~@dxos/operation/OperationHandlerSet' as const;
|
|
21
|
+
export type TypeId = typeof TypeId;
|
|
22
|
+
|
|
23
|
+
export interface OperationHandlerSet {
|
|
24
|
+
[TypeId]: TypeId;
|
|
25
|
+
|
|
26
|
+
readonly handlers: Effect.Effect<Operation.WithHandler<Operation.Definition.Any>[]>;
|
|
27
|
+
|
|
28
|
+
getHandlers(): Promise<Operation.WithHandler<Operation.Definition.Any>[]>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const isOperationHandlerSet = (value: unknown): value is OperationHandlerSet => {
|
|
32
|
+
return typeof value === 'object' && value !== null && TypeId in value;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const empty: OperationHandlerSet = {
|
|
36
|
+
[TypeId]: TypeId,
|
|
37
|
+
handlers: Effect.succeed([]),
|
|
38
|
+
getHandlers: () => Promise.resolve([]),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new operation handler set from a list of handlers.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const set = OperationHandlerSet.make(
|
|
47
|
+
* Operation.withHandler(Operation.make({ input: Schema.Void, output: Schema.Void }), (input) => Effect.succeed({})),
|
|
48
|
+
* Operation.withHandler(Operation.make({ input: Schema.Void, output: Schema.Void }), (input) => Effect.succeed({})),
|
|
49
|
+
* );
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export const make = (...handlers: Operation.WithHandler<Operation.Definition.Any>[]): OperationHandlerSet => {
|
|
53
|
+
return async(() => Promise.resolve(handlers));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const async = (
|
|
57
|
+
getHandlers: () => Promise<Operation.WithHandler<Operation.Definition.Any>[]>,
|
|
58
|
+
): OperationHandlerSet => {
|
|
59
|
+
// NOTE: Re-runing async module imports has a big performance penalty in Chrome.
|
|
60
|
+
let promise: Promise<Operation.WithHandler<Operation.Definition.Any>[]> | null = null;
|
|
61
|
+
const getHandlersCached = () => {
|
|
62
|
+
if (!promise) {
|
|
63
|
+
promise = getHandlers();
|
|
64
|
+
}
|
|
65
|
+
return promise;
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
[TypeId]: TypeId,
|
|
69
|
+
getHandlers,
|
|
70
|
+
handlers: Effect.promise(getHandlersCached),
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Builds a set backed by an atom of contributed sets. The merged result is
|
|
76
|
+
* cached and invalidated whenever the atom changes, so most accesses are
|
|
77
|
+
* cheap but newly registered handlers are picked up.
|
|
78
|
+
*/
|
|
79
|
+
export const reactive = (
|
|
80
|
+
registry: Registry.Registry,
|
|
81
|
+
atom: Atom.Atom<readonly OperationHandlerSet[]>,
|
|
82
|
+
): OperationHandlerSet => {
|
|
83
|
+
let cached: Promise<Operation.WithHandler<Operation.Definition.Any>[]> | null = null;
|
|
84
|
+
registry.subscribe(atom, () => {
|
|
85
|
+
cached = null;
|
|
86
|
+
});
|
|
87
|
+
// `suspend` defers `registry.get(atom)` until each run, so re-evaluations
|
|
88
|
+
// after cache invalidation see the current contributed sets.
|
|
89
|
+
const compute = Effect.suspend(() =>
|
|
90
|
+
pipe(
|
|
91
|
+
registry.get(atom),
|
|
92
|
+
Effect.forEach((set) => set.handlers, { concurrency: 'unbounded' }),
|
|
93
|
+
Effect.map((groups) => groups.flat()),
|
|
94
|
+
// Reset cached on failure so a transient error doesn't permanently
|
|
95
|
+
// poison subsequent calls.
|
|
96
|
+
Effect.tapErrorCause(() =>
|
|
97
|
+
Effect.sync(() => {
|
|
98
|
+
cached = null;
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
const getHandlers = () => (cached ??= EffectEx.runAndForwardErrors(compute));
|
|
104
|
+
return {
|
|
105
|
+
[TypeId]: TypeId,
|
|
106
|
+
getHandlers,
|
|
107
|
+
handlers: Effect.promise(getHandlers),
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Merges multiple operation handler sets into a single set.
|
|
113
|
+
*
|
|
114
|
+
*/
|
|
115
|
+
export const merge = (...sets: OperationHandlerSet[]): OperationHandlerSet => {
|
|
116
|
+
assertArgument(sets.every(isOperationHandlerSet), 'sets', 'sets must be an array of OperationHandlerSet');
|
|
117
|
+
return async(() => Promise.all(sets.map((set) => set.getHandlers())).then((handlers) => handlers.flat()));
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Creates a new operation handler set from a list of lazy-loaded modules.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* const set = OperationHandlerSet.lazy(
|
|
126
|
+
* () => import('./my-handler'),
|
|
127
|
+
* () => import('./my-other-handler'),
|
|
128
|
+
* );
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export const lazy = (
|
|
132
|
+
...modules: (() => Promise<{ default: Operation.WithHandler<Operation.Definition.Any> }>)[]
|
|
133
|
+
): OperationHandlerSet => {
|
|
134
|
+
return async(() => Promise.all(modules.map((module) => module().then(({ default: handler }) => handler))));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Gets a handler for an operation by definition.
|
|
139
|
+
*/
|
|
140
|
+
export const getHandler = <const Op extends Operation.Definition.Any>(
|
|
141
|
+
set: OperationHandlerSet,
|
|
142
|
+
definition: Op,
|
|
143
|
+
): Effect.Effect<Operation.WithHandler<Op>, NoHandlerError> =>
|
|
144
|
+
Effect.gen(function* () {
|
|
145
|
+
const handlers = yield* set.handlers;
|
|
146
|
+
const handler = handlers.find((handler) => handler.meta.key === definition.meta.key);
|
|
147
|
+
if (!handler) {
|
|
148
|
+
return yield* Effect.fail(new NoHandlerError(definition.meta.key));
|
|
149
|
+
}
|
|
150
|
+
return handler as any;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Gets a handler for an operation by key.
|
|
155
|
+
* Accepts either a plain NSID (`org.dxos.function.database.contextAdd`) or a
|
|
156
|
+
* full DXN string (`dxn:org.dxos.function.database.contextAdd`).
|
|
157
|
+
*/
|
|
158
|
+
export const getHandlerByKey = (
|
|
159
|
+
set: OperationHandlerSet,
|
|
160
|
+
key: string,
|
|
161
|
+
): Effect.Effect<Operation.WithHandler<Operation.Definition.Any>, NoHandlerError> =>
|
|
162
|
+
Effect.gen(function* () {
|
|
163
|
+
const handlers = yield* set.handlers;
|
|
164
|
+
// Normalize both sides to plain NSID for comparison so callers can pass
|
|
165
|
+
// either a ToolId (plain NSID) or a full DXN string.
|
|
166
|
+
const normalizeKey = (k: string) => (DXN.isDXN(k) ? DXN.getName(k) : k);
|
|
167
|
+
const normalizedKey = normalizeKey(key);
|
|
168
|
+
const handler = handlers.find((handler) => normalizeKey(handler.meta.key) === normalizedKey);
|
|
169
|
+
if (!handler) {
|
|
170
|
+
return yield* Effect.fail(new NoHandlerError(key));
|
|
171
|
+
}
|
|
172
|
+
return handler as any;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export class OperationHandlerProvider extends Context.Tag('@dxos/operation/OperationHandlerProvider')<
|
|
176
|
+
OperationHandlerProvider,
|
|
177
|
+
OperationHandlerSet
|
|
178
|
+
>() {}
|
|
179
|
+
|
|
180
|
+
export const provide = (handlers: OperationHandlerSet): Layer.Layer<OperationHandlerProvider, never, never> =>
|
|
181
|
+
Layer.succeed(OperationHandlerProvider, handlers);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// @import-as-namespace
|
|
6
|
+
|
|
7
|
+
import * as Context from 'effect/Context';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Layer from 'effect/Layer';
|
|
10
|
+
import * as Option from 'effect/Option';
|
|
11
|
+
|
|
12
|
+
import { Database, Filter, Query } from '@dxos/echo';
|
|
13
|
+
|
|
14
|
+
import * as Operation from './Operation';
|
|
15
|
+
import * as OperationHandlerSet from './OperationHandlerSet';
|
|
16
|
+
|
|
17
|
+
export interface OperationRegistry {
|
|
18
|
+
/**
|
|
19
|
+
* Resolve an operation by key.
|
|
20
|
+
*/
|
|
21
|
+
resolve(key: string): Effect.Effect<Option.Option<Operation.Definition.Any>>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class Service extends Context.Tag('@dxos/operation/OperationRegistry')<Service, OperationRegistry>() {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resolve an operation by key.
|
|
28
|
+
*/
|
|
29
|
+
export const resolve: (key: string) => Effect.Effect<Option.Option<Operation.Definition.Any>, never, Service> =
|
|
30
|
+
Effect.serviceFunctionEffect(Service, (service) => service.resolve);
|
|
31
|
+
|
|
32
|
+
export const layer: Layer.Layer<Service, never, Database.Service | OperationHandlerSet.OperationHandlerProvider> =
|
|
33
|
+
Layer.effect(
|
|
34
|
+
Service,
|
|
35
|
+
Effect.gen(function* () {
|
|
36
|
+
const database = yield* Database.Service;
|
|
37
|
+
const handlerSet = yield* OperationHandlerSet.OperationHandlerProvider;
|
|
38
|
+
return {
|
|
39
|
+
resolve: (key: string) =>
|
|
40
|
+
Database.query(Query.select(Filter.and(Filter.type(Operation.PersistentOperation), Filter.key(key))))
|
|
41
|
+
.first.pipe(
|
|
42
|
+
Effect.flatten,
|
|
43
|
+
Effect.map(Operation.deserialize),
|
|
44
|
+
Effect.catchTag('NoSuchElementException', () => OperationHandlerSet.getHandlerByKey(handlerSet, key)),
|
|
45
|
+
Effect.option,
|
|
46
|
+
)
|
|
47
|
+
.pipe(Effect.provideService(Database.Service, database)),
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
);
|