@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.
Files changed (176) hide show
  1. package/LICENSE +102 -5
  2. package/README.md +36 -1
  3. package/dist/lib/neutral/StorageService.mjs +22 -0
  4. package/dist/lib/neutral/StorageService.mjs.map +7 -0
  5. package/dist/lib/neutral/chunk-2B2KSJT6.mjs +17 -0
  6. package/dist/lib/neutral/chunk-2B2KSJT6.mjs.map +7 -0
  7. package/dist/lib/neutral/chunk-AFBNVFAY.mjs +25 -0
  8. package/dist/lib/neutral/chunk-AFBNVFAY.mjs.map +7 -0
  9. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  10. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-JAU4E4EK.mjs +53 -0
  12. package/dist/lib/neutral/chunk-JAU4E4EK.mjs.map +7 -0
  13. package/dist/lib/neutral/chunk-MEPE4HHX.mjs +21 -0
  14. package/dist/lib/neutral/chunk-MEPE4HHX.mjs.map +7 -0
  15. package/dist/lib/neutral/chunk-QZS4G6PE.mjs +240 -0
  16. package/dist/lib/neutral/chunk-QZS4G6PE.mjs.map +7 -0
  17. package/dist/lib/neutral/chunk-XEVFBI6S.mjs +62 -0
  18. package/dist/lib/neutral/chunk-XEVFBI6S.mjs.map +7 -0
  19. package/dist/lib/neutral/chunk-Z3Y7FQGN.mjs +174 -0
  20. package/dist/lib/neutral/chunk-Z3Y7FQGN.mjs.map +7 -0
  21. package/dist/lib/neutral/fib-HBDUGQW7.mjs +10 -0
  22. package/dist/lib/neutral/fib-HBDUGQW7.mjs.map +7 -0
  23. package/dist/lib/neutral/index.mjs +1179 -51
  24. package/dist/lib/neutral/index.mjs.map +4 -4
  25. package/dist/lib/neutral/meta.json +1 -1
  26. package/dist/lib/neutral/reply-RJIAJ6FV.mjs +10 -0
  27. package/dist/lib/neutral/reply-RJIAJ6FV.mjs.map +7 -0
  28. package/dist/lib/neutral/sleep-3T2CZ7RE.mjs +10 -0
  29. package/dist/lib/neutral/sleep-3T2CZ7RE.mjs.map +7 -0
  30. package/dist/lib/neutral/testing/index.mjs +27 -214
  31. package/dist/lib/neutral/testing/index.mjs.map +4 -4
  32. package/dist/types/src/Blueprint.d.ts +182 -0
  33. package/dist/types/src/Blueprint.d.ts.map +1 -0
  34. package/dist/types/src/Credential.d.ts +27 -0
  35. package/dist/types/src/Credential.d.ts.map +1 -0
  36. package/dist/types/src/Header.d.ts +9 -0
  37. package/dist/types/src/Header.d.ts.map +1 -0
  38. package/dist/types/src/Header.test.d.ts +2 -0
  39. package/dist/types/src/Header.test.d.ts.map +1 -0
  40. package/dist/types/src/LayerSpec.d.ts +56 -0
  41. package/dist/types/src/LayerSpec.d.ts.map +1 -0
  42. package/dist/types/src/McpServer.d.ts +26 -0
  43. package/dist/types/src/McpServer.d.ts.map +1 -0
  44. package/dist/types/src/Operation.d.ts +385 -0
  45. package/dist/types/src/Operation.d.ts.map +1 -0
  46. package/dist/types/src/OperationHandlerSet.d.ts +69 -0
  47. package/dist/types/src/OperationHandlerSet.d.ts.map +1 -0
  48. package/dist/types/src/OperationHandlerSet.test.d.ts +2 -0
  49. package/dist/types/src/OperationHandlerSet.test.d.ts.map +1 -0
  50. package/dist/types/src/OperationRegistry.d.ts +23 -0
  51. package/dist/types/src/OperationRegistry.d.ts.map +1 -0
  52. package/dist/types/src/Process.d.ts +255 -0
  53. package/dist/types/src/Process.d.ts.map +1 -0
  54. package/dist/types/src/Routine.d.ts +54 -0
  55. package/dist/types/src/Routine.d.ts.map +1 -0
  56. package/dist/types/src/Script.d.ts +21 -0
  57. package/dist/types/src/Script.d.ts.map +1 -0
  58. package/dist/types/src/ServiceResolver.d.ts +92 -0
  59. package/dist/types/src/ServiceResolver.d.ts.map +1 -0
  60. package/dist/types/src/StorageService.d.ts +58 -0
  61. package/dist/types/src/StorageService.d.ts.map +1 -0
  62. package/dist/types/src/Template.d.ts +61 -0
  63. package/dist/types/src/Template.d.ts.map +1 -0
  64. package/dist/types/src/Template.test.d.ts +2 -0
  65. package/dist/types/src/Template.test.d.ts.map +1 -0
  66. package/dist/types/src/Trace.d.ts +266 -0
  67. package/dist/types/src/Trace.d.ts.map +1 -0
  68. package/dist/types/src/Trigger.d.ts +192 -0
  69. package/dist/types/src/Trigger.d.ts.map +1 -0
  70. package/dist/types/src/TriggerEvent.d.ts +83 -0
  71. package/dist/types/src/TriggerEvent.d.ts.map +1 -0
  72. package/dist/types/src/errors.d.ts +308 -0
  73. package/dist/types/src/errors.d.ts.map +1 -0
  74. package/dist/types/src/index.d.ts +18 -5
  75. package/dist/types/src/index.d.ts.map +1 -1
  76. package/dist/types/src/testing/definitions.d.ts +11 -0
  77. package/dist/types/src/testing/definitions.d.ts.map +1 -0
  78. package/dist/types/src/testing/fib.d.ts +8 -0
  79. package/dist/types/src/testing/fib.d.ts.map +1 -0
  80. package/dist/types/src/testing/index.d.ts +6 -4
  81. package/dist/types/src/testing/index.d.ts.map +1 -1
  82. package/dist/types/src/testing/reply.d.ts +4 -0
  83. package/dist/types/src/testing/reply.d.ts.map +1 -0
  84. package/dist/types/src/testing/sleep.d.ts +6 -0
  85. package/dist/types/src/testing/sleep.d.ts.map +1 -0
  86. package/dist/types/src/umbrella.test.d.ts +2 -0
  87. package/dist/types/src/umbrella.test.d.ts.map +1 -0
  88. package/dist/types/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +25 -36
  90. package/src/Blueprint.ts +224 -0
  91. package/src/Credential.ts +53 -0
  92. package/src/Header.test.ts +132 -0
  93. package/src/Header.ts +38 -0
  94. package/src/LayerSpec.ts +91 -0
  95. package/src/McpServer.ts +44 -0
  96. package/src/Operation.ts +646 -0
  97. package/src/OperationHandlerSet.test.ts +97 -0
  98. package/src/OperationHandlerSet.ts +181 -0
  99. package/src/OperationRegistry.ts +50 -0
  100. package/src/Process.ts +541 -0
  101. package/src/Routine.ts +68 -0
  102. package/src/Script.ts +33 -0
  103. package/src/ServiceResolver.ts +200 -0
  104. package/src/StorageService.ts +101 -0
  105. package/src/Template.test.ts +164 -0
  106. package/src/Template.ts +125 -0
  107. package/src/Trace.ts +363 -0
  108. package/src/Trigger.ts +196 -0
  109. package/src/TriggerEvent.ts +64 -0
  110. package/src/errors.ts +71 -0
  111. package/src/index.ts +20 -6
  112. package/src/testing/definitions.ts +51 -0
  113. package/src/testing/fib.ts +22 -0
  114. package/src/testing/index.ts +13 -5
  115. package/src/testing/reply.ts +18 -0
  116. package/src/testing/sleep.ts +16 -0
  117. package/src/umbrella.test.ts +51 -0
  118. package/dist/lib/neutral/chunk-JRV42MW3.mjs +0 -3101
  119. package/dist/lib/neutral/chunk-JRV42MW3.mjs.map +0 -7
  120. package/dist/types/src/compute-graph-registry.d.ts +0 -37
  121. package/dist/types/src/compute-graph-registry.d.ts.map +0 -1
  122. package/dist/types/src/compute-graph-registry.test.d.ts +0 -2
  123. package/dist/types/src/compute-graph-registry.test.d.ts.map +0 -1
  124. package/dist/types/src/compute-graph.d.ts +0 -67
  125. package/dist/types/src/compute-graph.d.ts.map +0 -1
  126. package/dist/types/src/compute-graph.test.d.ts +0 -2
  127. package/dist/types/src/compute-graph.test.d.ts.map +0 -1
  128. package/dist/types/src/compute-node.d.ts +0 -24
  129. package/dist/types/src/compute-node.d.ts.map +0 -1
  130. package/dist/types/src/functions/async-function.d.ts +0 -68
  131. package/dist/types/src/functions/async-function.d.ts.map +0 -1
  132. package/dist/types/src/functions/edge-function.d.ts +0 -20
  133. package/dist/types/src/functions/edge-function.d.ts.map +0 -1
  134. package/dist/types/src/functions/function-defs.d.ts +0 -11
  135. package/dist/types/src/functions/function-defs.d.ts.map +0 -1
  136. package/dist/types/src/functions/index.d.ts +0 -4
  137. package/dist/types/src/functions/index.d.ts.map +0 -1
  138. package/dist/types/src/hyperformula.test.d.ts +0 -2
  139. package/dist/types/src/hyperformula.test.d.ts.map +0 -1
  140. package/dist/types/src/testing/test-builder.d.ts +0 -15
  141. package/dist/types/src/testing/test-builder.d.ts.map +0 -1
  142. package/dist/types/src/testing/test-plugin.d.ts +0 -35
  143. package/dist/types/src/testing/test-plugin.d.ts.map +0 -1
  144. package/dist/types/src/testing/test-runtime.d.ts +0 -10
  145. package/dist/types/src/testing/test-runtime.d.ts.map +0 -1
  146. package/dist/types/src/testing/util.d.ts +0 -2
  147. package/dist/types/src/testing/util.d.ts.map +0 -1
  148. package/dist/types/src/types/hyperformula.d.ts +0 -8
  149. package/dist/types/src/types/hyperformula.d.ts.map +0 -1
  150. package/dist/types/src/types/index.d.ts +0 -3
  151. package/dist/types/src/types/index.d.ts.map +0 -1
  152. package/dist/types/src/types/types.d.ts +0 -20
  153. package/dist/types/src/types/types.d.ts.map +0 -1
  154. package/dist/types/src/types/types.test.d.ts +0 -2
  155. package/dist/types/src/types/types.test.d.ts.map +0 -1
  156. package/dist/types/src/util.d.ts +0 -2
  157. package/dist/types/src/util.d.ts.map +0 -1
  158. package/src/compute-graph-registry.test.ts +0 -167
  159. package/src/compute-graph-registry.ts +0 -97
  160. package/src/compute-graph.test.ts +0 -139
  161. package/src/compute-graph.ts +0 -265
  162. package/src/compute-node.ts +0 -59
  163. package/src/functions/async-function.ts +0 -184
  164. package/src/functions/edge-function.ts +0 -108
  165. package/src/functions/function-defs.ts +0 -2427
  166. package/src/functions/index.ts +0 -7
  167. package/src/hyperformula.test.ts +0 -14
  168. package/src/testing/test-builder.ts +0 -59
  169. package/src/testing/test-plugin.ts +0 -98
  170. package/src/testing/test-runtime.ts +0 -53
  171. package/src/testing/util.ts +0 -9
  172. package/src/types/hyperformula.ts +0 -19
  173. package/src/types/index.ts +0 -6
  174. package/src/types/types.test.ts +0 -91
  175. package/src/types/types.ts +0 -80
  176. 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
+ );