@dxos/compute 0.8.4-main.a4bbb77 → 0.8.4-main.abd8ff62ef
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/README.md +36 -1
- package/dist/lib/neutral/StorageService.mjs +22 -0
- package/dist/lib/neutral/chunk-4N4JAXCP.mjs +17 -0
- package/dist/lib/neutral/chunk-4N4JAXCP.mjs.map +7 -0
- package/dist/lib/neutral/chunk-DDEBBRCI.mjs +62 -0
- package/dist/lib/neutral/chunk-DDEBBRCI.mjs.map +7 -0
- package/dist/lib/neutral/chunk-FEABF5XY.mjs +25 -0
- package/dist/lib/neutral/chunk-FEABF5XY.mjs.map +7 -0
- package/dist/lib/neutral/chunk-H6EHIVDN.mjs +21 -0
- package/dist/lib/neutral/chunk-H6EHIVDN.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-N462A6NO.mjs +138 -0
- package/dist/lib/neutral/chunk-N462A6NO.mjs.map +7 -0
- package/dist/lib/neutral/chunk-QPCDM7MF.mjs +179 -0
- package/dist/lib/neutral/chunk-QPCDM7MF.mjs.map +7 -0
- package/dist/lib/neutral/chunk-S24LL2D5.mjs +52 -0
- package/dist/lib/neutral/chunk-S24LL2D5.mjs.map +7 -0
- package/dist/lib/neutral/fib-HDHBZHPE.mjs +10 -0
- package/dist/lib/neutral/fib-HDHBZHPE.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +1034 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/reply-XSQMXAPX.mjs +10 -0
- package/dist/lib/neutral/reply-XSQMXAPX.mjs.map +7 -0
- package/dist/lib/neutral/sleep-WHTJQ56P.mjs +10 -0
- package/dist/lib/neutral/sleep-WHTJQ56P.mjs.map +7 -0
- package/dist/lib/neutral/testing/index.mjs +32 -0
- package/dist/lib/neutral/testing/index.mjs.map +7 -0
- package/dist/types/src/Blueprint.d.ts +148 -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/Operation.d.ts +303 -0
- package/dist/types/src/Operation.d.ts.map +1 -0
- package/dist/types/src/OperationHandlerSet.d.ts +60 -0
- package/dist/types/src/OperationHandlerSet.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 +247 -0
- package/dist/types/src/Process.d.ts.map +1 -0
- package/dist/types/src/Routine.d.ts +61 -0
- package/dist/types/src/Routine.d.ts.map +1 -0
- package/dist/types/src/Script.d.ts +22 -0
- package/dist/types/src/Script.d.ts.map +1 -0
- package/dist/types/src/ServiceResolver.d.ts +74 -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 +56 -0
- package/dist/types/src/Template.d.ts.map +1 -0
- package/dist/types/src/Trace.d.ts +174 -0
- package/dist/types/src/Trace.d.ts.map +1 -0
- package/dist/types/src/Trigger.d.ts +150 -0
- package/dist/types/src/Trigger.d.ts.map +1 -0
- package/dist/types/src/TriggerEvent.d.ts +75 -0
- package/dist/types/src/TriggerEvent.d.ts.map +1 -0
- package/dist/types/src/errors.d.ts +239 -0
- package/dist/types/src/errors.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +15 -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 -3
- 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 +27 -39
- package/src/Blueprint.ts +221 -0
- package/src/Credential.ts +51 -0
- package/src/Operation.ts +513 -0
- package/src/OperationHandlerSet.ts +132 -0
- package/src/OperationRegistry.ts +48 -0
- package/src/Process.ts +490 -0
- package/src/Routine.ts +84 -0
- package/src/Script.ts +38 -0
- package/src/ServiceResolver.ts +174 -0
- package/src/StorageService.ts +99 -0
- package/src/Template.ts +105 -0
- package/src/Trace.ts +231 -0
- package/src/Trigger.ts +207 -0
- package/src/TriggerEvent.ts +62 -0
- package/src/errors.ts +50 -0
- package/src/index.ts +16 -33
- package/src/testing/definitions.ts +49 -0
- package/src/testing/fib.ts +22 -0
- package/src/testing/index.ts +13 -4
- package/src/testing/reply.ts +18 -0
- package/src/testing/sleep.ts +16 -0
- package/src/umbrella.test.ts +51 -0
- package/dist/lib/browser/chunk-XQYPCRZP.mjs +0 -3140
- package/dist/lib/browser/chunk-XQYPCRZP.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -55
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs +0 -186
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-2XWWSGAU.mjs +0 -3142
- package/dist/lib/node-esm/chunk-2XWWSGAU.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -56
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -187
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/types/src/compute-graph-registry.d.ts +0 -32
- package/dist/types/src/compute-graph-registry.d.ts.map +0 -1
- package/dist/types/src/compute-graph.d.ts +0 -64
- 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 -66
- 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/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.ts +0 -88
- package/src/compute-graph.test.ts +0 -88
- package/src/compute-graph.ts +0 -258
- package/src/compute-node.ts +0 -59
- package/src/functions/async-function.ts +0 -183
- package/src/functions/edge-function.ts +0 -111
- 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 -54
- package/src/testing/test-plugin.ts +0 -98
- 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
- /package/dist/lib/{browser/index.mjs.map → neutral/StorageService.mjs.map} +0 -0
- /package/dist/lib/{node-esm/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
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 Either from 'effect/Either';
|
|
10
|
+
import * as Layer from 'effect/Layer';
|
|
11
|
+
import * as Option from 'effect/Option';
|
|
12
|
+
import * as Scope from 'effect/Scope';
|
|
13
|
+
|
|
14
|
+
import type { DXN, SpaceId } from '@dxos/keys';
|
|
15
|
+
|
|
16
|
+
import { ServiceNotAvailableError } from './errors';
|
|
17
|
+
import * as Process from './Process';
|
|
18
|
+
|
|
19
|
+
const ServiceResolverTypeId = '~@dxos/functions/ServiceResolver' as const;
|
|
20
|
+
type ServiceResolverTypeId = typeof ServiceResolverTypeId;
|
|
21
|
+
|
|
22
|
+
export interface ServiceResolver {
|
|
23
|
+
readonly [ServiceResolverTypeId]: ServiceResolverTypeId;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a set of services identified by their tags.
|
|
27
|
+
* Returns a Context containing all requested services, or fails with ServiceNotAvailableError.
|
|
28
|
+
*/
|
|
29
|
+
resolve<Tag extends Context.Tag<any, any>>(
|
|
30
|
+
tag: Tag,
|
|
31
|
+
context: ResolutionContext,
|
|
32
|
+
): Effect.Effect<Context.Tag.Service<Tag>, ServiceNotAvailableError, Scope.Scope>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tag for the ServiceResolver service.
|
|
37
|
+
*/
|
|
38
|
+
export const ServiceResolver = Context.GenericTag<ServiceResolver>('@dxos/functions/ServiceResolver');
|
|
39
|
+
|
|
40
|
+
export const resolve = Effect.serviceFunctionEffect(ServiceResolver, (_) => _.resolve);
|
|
41
|
+
|
|
42
|
+
export const resolveAll = <const Tags extends readonly Context.Tag<any, any>[]>(
|
|
43
|
+
tags: Tags,
|
|
44
|
+
context: ResolutionContext,
|
|
45
|
+
): Effect.Effect<Context.Context<Tags[number]>, ServiceNotAvailableError, Scope.Scope | ServiceResolver> =>
|
|
46
|
+
Effect.gen(function* () {
|
|
47
|
+
const services = yield* Effect.forEach(tags, (tag) =>
|
|
48
|
+
resolve(tag, context).pipe(Effect.map((service) => Context.make(tag, service))),
|
|
49
|
+
);
|
|
50
|
+
return Context.mergeAll(...services);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Provides context for service resolution.
|
|
55
|
+
*/
|
|
56
|
+
export interface ResolutionContext {
|
|
57
|
+
/**
|
|
58
|
+
* Under which identity the process is running.
|
|
59
|
+
*/
|
|
60
|
+
readonly identity?: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Under which space the process is running.
|
|
64
|
+
*/
|
|
65
|
+
readonly space?: SpaceId;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* DXN of the conversation feed the process is running in.
|
|
69
|
+
*/
|
|
70
|
+
readonly conversation?: DXN.String;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Under which process the process is running.
|
|
74
|
+
*/
|
|
75
|
+
readonly process?: Process.ID;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const succeed = <I, S>(
|
|
79
|
+
tag: Context.Tag<I, S>,
|
|
80
|
+
getService: (context: ResolutionContext) => Effect.Effect<S, ServiceNotAvailableError, Scope.Scope>,
|
|
81
|
+
): ServiceResolver => {
|
|
82
|
+
return make((tag1, context) => {
|
|
83
|
+
if (tag1.key !== tag.key) {
|
|
84
|
+
return Effect.fail(new ServiceNotAvailableError(`Service not available: ${String(tag.key ?? tag)}`));
|
|
85
|
+
}
|
|
86
|
+
const service = getService(context);
|
|
87
|
+
return service as any;
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a ServiceResolver from a custom resolution function.
|
|
93
|
+
*/
|
|
94
|
+
export const make = (
|
|
95
|
+
resolveFn: <I, S>(
|
|
96
|
+
tag: Context.Tag<I, S>,
|
|
97
|
+
context: ResolutionContext,
|
|
98
|
+
) => Effect.Effect<S, ServiceNotAvailableError, Scope.Scope>,
|
|
99
|
+
): ServiceResolver => ({
|
|
100
|
+
[ServiceResolverTypeId]: ServiceResolverTypeId,
|
|
101
|
+
resolve: resolveFn,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create a ServiceResolver backed by a static Context.
|
|
106
|
+
* Tags present in the context are resolved; missing tags fail with ServiceNotAvailableError.
|
|
107
|
+
*/
|
|
108
|
+
export const fromContext = (ctx: Context.Context<any>): ServiceResolver =>
|
|
109
|
+
make((tag, context) =>
|
|
110
|
+
Effect.gen(function* () {
|
|
111
|
+
const service = Context.getOption(ctx, tag);
|
|
112
|
+
if (Option.isNone(service)) {
|
|
113
|
+
return yield* Effect.fail(new ServiceNotAvailableError(String(tag.key ?? tag)));
|
|
114
|
+
}
|
|
115
|
+
return service.value;
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Create a ServiceResolver that resolves tags from the current Effect context.
|
|
121
|
+
* Only the specified tags are available; requests for other tags fail.
|
|
122
|
+
*/
|
|
123
|
+
export const fromRequirements = <const Tags extends readonly Context.Tag<any, any>[]>(
|
|
124
|
+
...tags: Tags
|
|
125
|
+
): Effect.Effect<ServiceResolver, never, Context.Tag.Identifier<Tags[number]>> =>
|
|
126
|
+
Effect.contextWith((parentCtx: Context.Context<any>) => {
|
|
127
|
+
const available = new Set(tags.map((tag) => tag.key));
|
|
128
|
+
return make((tag, context) =>
|
|
129
|
+
Effect.gen(function* () {
|
|
130
|
+
let result: Context.Context<never> = Context.empty() as Context.Context<never>;
|
|
131
|
+
if (!available.has(tag.key)) {
|
|
132
|
+
return yield* Effect.fail(new ServiceNotAvailableError(String(tag.key ?? tag)));
|
|
133
|
+
}
|
|
134
|
+
const service = Context.getOption(parentCtx, tag);
|
|
135
|
+
if (Option.isNone(service)) {
|
|
136
|
+
return yield* Effect.fail(new ServiceNotAvailableError(String(tag.key ?? tag)));
|
|
137
|
+
}
|
|
138
|
+
return service.value;
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Like {@link fromRequirements} but returns a Layer that provides ServiceResolver.
|
|
145
|
+
*/
|
|
146
|
+
export const layerRequirements = <const Tags extends readonly Context.Tag<any, any>[]>(
|
|
147
|
+
...tags: Tags
|
|
148
|
+
): Layer.Layer<ServiceResolver, never, Context.Tag.Identifier<Tags[number]>> =>
|
|
149
|
+
Layer.effect(ServiceResolver, fromRequirements(...tags));
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Compose multiple resolvers left to right. Earlier resolvers take precedence:
|
|
153
|
+
* the first resolver that can satisfy a tag wins.
|
|
154
|
+
*/
|
|
155
|
+
export const compose = (...resolvers: readonly ServiceResolver[]): ServiceResolver =>
|
|
156
|
+
make((tag, context) =>
|
|
157
|
+
Effect.gen(function* () {
|
|
158
|
+
for (const resolver of resolvers) {
|
|
159
|
+
const single = yield* resolver.resolve(tag, context).pipe(Effect.either);
|
|
160
|
+
if (Either.isRight(single)) {
|
|
161
|
+
return single.right;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return yield* Effect.fail(new ServiceNotAvailableError(String(tag.key ?? tag)));
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* An empty resolver that fails for any requested service.
|
|
171
|
+
*/
|
|
172
|
+
export const empty: ServiceResolver = make((tag, context) => {
|
|
173
|
+
return Effect.fail(new ServiceNotAvailableError(String(tag.key ?? tag)));
|
|
174
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Option from 'effect/Option';
|
|
8
|
+
import * as Pipeable from 'effect/Pipeable';
|
|
9
|
+
import * as Schema from 'effect/Schema';
|
|
10
|
+
|
|
11
|
+
export interface Service {
|
|
12
|
+
/** Read a value by key. Returns `None` if key does not exist. */
|
|
13
|
+
get<S extends Schema.Schema<any, string, any>>(
|
|
14
|
+
schema: S,
|
|
15
|
+
key: string,
|
|
16
|
+
): Effect.Effect<Option.Option<Schema.Schema.Type<S>>, never, Schema.Schema.Context<S>>;
|
|
17
|
+
|
|
18
|
+
/** Write a value for the given key. */
|
|
19
|
+
set<S extends Schema.Schema<any, string, any>>(
|
|
20
|
+
schema: S,
|
|
21
|
+
key: string,
|
|
22
|
+
value: Schema.Schema.Type<S>,
|
|
23
|
+
): Effect.Effect<void, never, Schema.Schema.Context<S>>;
|
|
24
|
+
|
|
25
|
+
/** Remove a key. */
|
|
26
|
+
delete(key: string): Effect.Effect<void>;
|
|
27
|
+
|
|
28
|
+
/** List all keys, optionally filtered by prefix. */
|
|
29
|
+
list(prefix?: string): Effect.Effect<readonly string[]>;
|
|
30
|
+
|
|
31
|
+
/** Remove all keys managed by this scoped store. */
|
|
32
|
+
clear(): Effect.Effect<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Scoped key-value storage service for processes.
|
|
37
|
+
* Each process receives its own namespaced instance via the process manager.
|
|
38
|
+
* Construct a live implementation with `StorageService.layer` from `@dxos/functions-runtime`.
|
|
39
|
+
*/
|
|
40
|
+
export class StorageService extends Context.Tag('@dxos/functions/StorageService')<StorageService, Service>() {}
|
|
41
|
+
|
|
42
|
+
export const get = Effect.serviceFunctionEffect(StorageService, (_) => _.get);
|
|
43
|
+
export const set = Effect.serviceFunctionEffect(StorageService, (_) => _.set);
|
|
44
|
+
export const deleteKey = Effect.serviceFunctionEffect(StorageService, (_) => _.delete);
|
|
45
|
+
export const list = Effect.serviceFunctionEffect(StorageService, (_) => _.list);
|
|
46
|
+
export const clear = Effect.serviceFunctionEffect(StorageService, (_) => _.clear);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Typed key in a storage service.
|
|
50
|
+
*/
|
|
51
|
+
export interface Key<T> extends Pipeable.Pipeable {
|
|
52
|
+
readonly key: string;
|
|
53
|
+
|
|
54
|
+
get: Effect.Effect<Option.Option<T>, never, StorageService>;
|
|
55
|
+
set(value: T): Effect.Effect<void, never, StorageService>;
|
|
56
|
+
delete(): Effect.Effect<void, never, StorageService>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a typed key in a storage service.
|
|
61
|
+
*/
|
|
62
|
+
export const key = <S extends Schema.Schema<any, string, any>>(schema: S, key: string): Key<Schema.Schema.Type<S>> => {
|
|
63
|
+
return {
|
|
64
|
+
key,
|
|
65
|
+
get: get(schema, key),
|
|
66
|
+
set: (value: Schema.Schema.Type<S>) => set(schema, key, value),
|
|
67
|
+
delete: () => deleteKey(key),
|
|
68
|
+
pipe(...args: any) {
|
|
69
|
+
return Pipeable.pipeArguments(this, arguments);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Typed key in a storage service with a default value.
|
|
76
|
+
*/
|
|
77
|
+
export interface KeyWithDefault<T, U> extends Pipeable.Pipeable {
|
|
78
|
+
readonly key: string;
|
|
79
|
+
get: Effect.Effect<T | U, never, StorageService>;
|
|
80
|
+
set(value: U): Effect.Effect<void, never, StorageService>;
|
|
81
|
+
delete(): Effect.Effect<void, never, StorageService>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Assign a default value to a key if it is not present.
|
|
86
|
+
*/
|
|
87
|
+
export const withDefault =
|
|
88
|
+
<T>(getDefault: () => NoInfer<T>) =>
|
|
89
|
+
(key: Key<T>): KeyWithDefault<T, T> => {
|
|
90
|
+
return {
|
|
91
|
+
key: key.key,
|
|
92
|
+
get: key.get.pipe(Effect.map(Option.getOrElse(() => getDefault()))),
|
|
93
|
+
set: (value) => key.set(value),
|
|
94
|
+
delete: () => key.delete(),
|
|
95
|
+
pipe(...args: any) {
|
|
96
|
+
return Pipeable.pipeArguments(this, arguments);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
};
|
package/src/Template.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import * as Record from 'effect/Record';
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
import handlebars from 'handlebars';
|
|
9
|
+
|
|
10
|
+
import { Database, Ref } from '@dxos/echo';
|
|
11
|
+
import type { ObjectNotFoundError } from '@dxos/echo/Err';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
import { log } from '@dxos/log';
|
|
14
|
+
import { Text } from '@dxos/schema';
|
|
15
|
+
|
|
16
|
+
import { FunctionNotFoundError } from './errors';
|
|
17
|
+
import * as Operation from './Operation';
|
|
18
|
+
import * as OperationRegistry from './OperationRegistry';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Template input kind determines how template variables are resolved.
|
|
22
|
+
*/
|
|
23
|
+
export const InputKind = Schema.Literal(
|
|
24
|
+
'value', // Literal value.
|
|
25
|
+
'pass-through',
|
|
26
|
+
'retriever',
|
|
27
|
+
'function',
|
|
28
|
+
'query',
|
|
29
|
+
'resolver',
|
|
30
|
+
'context',
|
|
31
|
+
'schema',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export type InputKind = Schema.Schema.Type<typeof InputKind>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Template input variable.
|
|
38
|
+
* E.g., {{foo}}
|
|
39
|
+
*/
|
|
40
|
+
export const Input = Schema.Struct({
|
|
41
|
+
name: Schema.String,
|
|
42
|
+
kind: Schema.optional(InputKind),
|
|
43
|
+
default: Schema.optional(Schema.Any),
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Function to call if the kind is 'function'.
|
|
47
|
+
*/
|
|
48
|
+
function: Schema.optional(Schema.String),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export type Input = Schema.Schema.Type<typeof Input>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Template type.
|
|
55
|
+
*/
|
|
56
|
+
export const Template = Schema.Struct({
|
|
57
|
+
source: Ref.Ref(Text.Text).annotations({ description: 'Handlebars template source' }),
|
|
58
|
+
inputs: Schema.optional(Schema.Array(Input)),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export interface Template extends Schema.Schema.Type<typeof Template> {}
|
|
62
|
+
|
|
63
|
+
export const make = ({
|
|
64
|
+
id,
|
|
65
|
+
source,
|
|
66
|
+
inputs = [],
|
|
67
|
+
}: { id?: string; source?: string; inputs?: Input[] } = {}): Template => ({
|
|
68
|
+
source: Ref.make(Text.make({ id, content: source })),
|
|
69
|
+
inputs,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Process Handlebars template.
|
|
74
|
+
*/
|
|
75
|
+
export const process = <Options extends {}>(source: string, variables: Partial<Options> = {}): string => {
|
|
76
|
+
invariant(typeof source === 'string');
|
|
77
|
+
let section = 0;
|
|
78
|
+
handlebars.registerHelper('section', () => String(++section));
|
|
79
|
+
const template = handlebars.compile(source.trim());
|
|
80
|
+
const output = template(variables);
|
|
81
|
+
return output.trim().replace(/(\n\s*){3,}/g, '\n\n');
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const processTemplate = (
|
|
85
|
+
template: Template,
|
|
86
|
+
): Effect.Effect<string, ObjectNotFoundError | FunctionNotFoundError, OperationRegistry.Service | Operation.Service> =>
|
|
87
|
+
Effect.gen(function* () {
|
|
88
|
+
const variables = yield* Effect.forEach(template.inputs ?? [], (input) =>
|
|
89
|
+
Effect.gen(function* () {
|
|
90
|
+
if (input.kind === 'function') {
|
|
91
|
+
const fn = yield* OperationRegistry.resolve(input.function!).pipe(
|
|
92
|
+
Effect.flatten,
|
|
93
|
+
Effect.catchTag('NoSuchElementException', () => Effect.fail(new FunctionNotFoundError(input.function!))),
|
|
94
|
+
);
|
|
95
|
+
const result = yield* Operation.invoke(fn, {} as any).pipe(Effect.orDie);
|
|
96
|
+
return [input.name, result] as const;
|
|
97
|
+
} else {
|
|
98
|
+
return yield* Effect.dieMessage(`Unsupported input kind: ${input.kind}`);
|
|
99
|
+
}
|
|
100
|
+
}),
|
|
101
|
+
).pipe(Effect.map(Record.fromEntries));
|
|
102
|
+
|
|
103
|
+
log('processTemplate', { variables });
|
|
104
|
+
return process((yield* Database.load(template.source)).content, variables);
|
|
105
|
+
});
|
package/src/Trace.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
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 Schema from 'effect/Schema';
|
|
11
|
+
|
|
12
|
+
import { Annotation, Obj, Type } from '@dxos/echo';
|
|
13
|
+
import { log } from '@dxos/log';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Writes ephemeral or persistent events to the trace.
|
|
17
|
+
* Exposed to processes and operations to record events to the trace.
|
|
18
|
+
*/
|
|
19
|
+
export interface TraceWriter {
|
|
20
|
+
write<T>(eventType: EventType<T>, payload: NoInfer<T>): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Service that writes events to the trace.
|
|
25
|
+
* Exposed to processes and operations to record events to the trace.
|
|
26
|
+
*/
|
|
27
|
+
export class TraceService extends Context.Tag('@dxos/functions/TraceService')<TraceService, TraceWriter>() {}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Writes an event to the trace.
|
|
31
|
+
*/
|
|
32
|
+
export function write<T>(eventType: EventType<T>, payload: NoInfer<T>): Effect.Effect<void, never, TraceService> {
|
|
33
|
+
return Effect.gen(function* () {
|
|
34
|
+
log('trace write', { key: eventType.key, isEphemeral: eventType.isEphemeral });
|
|
35
|
+
const writer = yield* TraceService;
|
|
36
|
+
writer.write(eventType, payload);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Defines an event type for the trace.
|
|
42
|
+
*/
|
|
43
|
+
export interface EventType<T> {
|
|
44
|
+
readonly key: string;
|
|
45
|
+
readonly schema: Schema.Schema<T, any>;
|
|
46
|
+
readonly isEphemeral: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const EventType = <T>(
|
|
50
|
+
key: string,
|
|
51
|
+
opts: { schema: Schema.Schema<T, any>; isEphemeral: boolean },
|
|
52
|
+
): EventType<T> => {
|
|
53
|
+
return {
|
|
54
|
+
key,
|
|
55
|
+
schema: opts.schema,
|
|
56
|
+
isEphemeral: opts.isEphemeral,
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Extracts the payload type from an event type.
|
|
62
|
+
*/
|
|
63
|
+
export type PayloadType<E extends EventType<any>> = E extends EventType<infer T> ? T : never;
|
|
64
|
+
|
|
65
|
+
export const Event = Schema.Struct({
|
|
66
|
+
timestamp: Schema.Number,
|
|
67
|
+
type: Schema.String,
|
|
68
|
+
data: Schema.Unknown, // Type-specific payload;
|
|
69
|
+
});
|
|
70
|
+
export interface Event extends Schema.Schema.Type<typeof Event> {}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Checks if an event is of a given type.
|
|
74
|
+
*/
|
|
75
|
+
export const isOfType = <T, E extends Event>(eventType: EventType<T>, event: E): event is E & { data: T } => {
|
|
76
|
+
return event.type === eventType.key;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Metadata on the context of a trace message.
|
|
81
|
+
*/
|
|
82
|
+
// TODO(dmaretskyi): Expand on this: conversation id, tool call id, etc.
|
|
83
|
+
export const Meta = Schema.Struct({
|
|
84
|
+
pid: Schema.optional(Schema.String), // NOTE: Not Process.ID to avoid circular dependency.
|
|
85
|
+
parentPid: Schema.optional(Schema.String),
|
|
86
|
+
processName: Schema.optional(Schema.String),
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* ID of the conversation feed object if present.
|
|
90
|
+
*/
|
|
91
|
+
conversationId: Schema.optional(Obj.ID),
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* ID of the trigger object if invocation resulted from a trigger.
|
|
95
|
+
*/
|
|
96
|
+
triggerId: Schema.optional(Obj.ID),
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* ID of the tool call that created the current process.
|
|
100
|
+
*/
|
|
101
|
+
toolCallId: Schema.optional(Schema.String),
|
|
102
|
+
});
|
|
103
|
+
export interface Meta extends Schema.Schema.Type<typeof Meta> {}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Envelope for a set of events.
|
|
107
|
+
*/
|
|
108
|
+
export const MessageData = Schema.Struct({
|
|
109
|
+
meta: Meta,
|
|
110
|
+
|
|
111
|
+
isEphemeral: Schema.Boolean,
|
|
112
|
+
events: Schema.Array(Event),
|
|
113
|
+
});
|
|
114
|
+
export interface MessageData extends Schema.Schema.Type<typeof MessageData> {}
|
|
115
|
+
|
|
116
|
+
export const Message = MessageData.pipe(
|
|
117
|
+
Type.object({
|
|
118
|
+
typename: 'org.dxos.type.traceMessage',
|
|
119
|
+
version: '0.1.0',
|
|
120
|
+
}),
|
|
121
|
+
Annotation.IconAnnotation.set({
|
|
122
|
+
icon: 'ph--note--regular',
|
|
123
|
+
hue: 'rose',
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
export interface Message extends Schema.Schema.Type<typeof Message> {}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sink for complete trace messages.
|
|
130
|
+
*/
|
|
131
|
+
export interface Sink {
|
|
132
|
+
write(message: Message): void;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Sink for complete trace messages.
|
|
137
|
+
* The Process Manager forwards trace messages to it.
|
|
138
|
+
*/
|
|
139
|
+
// TODO(dmaretskyi): Consider moving sink to the Process Manager.
|
|
140
|
+
export class TraceSink extends Context.Tag('@dxos/functions/TraceSink')<TraceSink, Sink>() {}
|
|
141
|
+
|
|
142
|
+
export const noopWriter: TraceWriter = {
|
|
143
|
+
write: () => {},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const writerLayerNoop: Layer.Layer<TraceService> = Layer.succeed(TraceService, noopWriter);
|
|
147
|
+
|
|
148
|
+
export const layerNoop: Layer.Layer<TraceSink> = Layer.succeed(TraceSink, {
|
|
149
|
+
write: () => {},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export const layerConsole: Layer.Layer<TraceSink> = Layer.succeed(TraceSink, {
|
|
153
|
+
write: (message) => {
|
|
154
|
+
console.log(message);
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
export const testTraceService = (opts: { meta?: Meta } = {}): Layer.Layer<TraceService, never, TraceSink> =>
|
|
159
|
+
Layer.effect(
|
|
160
|
+
TraceService,
|
|
161
|
+
Effect.gen(function* () {
|
|
162
|
+
const sink = yield* TraceSink;
|
|
163
|
+
return {
|
|
164
|
+
write: (event, data) => {
|
|
165
|
+
sink.write(
|
|
166
|
+
Obj.make(Message, {
|
|
167
|
+
meta: opts.meta ?? {},
|
|
168
|
+
isEphemeral: event.isEphemeral,
|
|
169
|
+
events: [{ type: event.key, timestamp: Date.now(), data }],
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
//
|
|
178
|
+
// Operation Trace Events
|
|
179
|
+
//
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Outcome of an operation invocation.
|
|
183
|
+
*/
|
|
184
|
+
export type OperationOutcome = 'success' | 'failure';
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Operation invocation started.
|
|
188
|
+
*/
|
|
189
|
+
export const OperationStart = EventType('operation.start', {
|
|
190
|
+
schema: Schema.Struct({
|
|
191
|
+
/** Operation key. */
|
|
192
|
+
key: Schema.String,
|
|
193
|
+
/** Human-readable operation name. */
|
|
194
|
+
name: Schema.optional(Schema.String),
|
|
195
|
+
}),
|
|
196
|
+
isEphemeral: false,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Operation invocation ended.
|
|
201
|
+
*/
|
|
202
|
+
export const OperationEnd = EventType('operation.end', {
|
|
203
|
+
schema: Schema.Struct({
|
|
204
|
+
/** Operation key. */
|
|
205
|
+
key: Schema.String,
|
|
206
|
+
/** Human-readable operation name. */
|
|
207
|
+
name: Schema.optional(Schema.String),
|
|
208
|
+
/** Outcome of the operation. */
|
|
209
|
+
outcome: Schema.Literal('success', 'failure'),
|
|
210
|
+
/** Error message if the operation failed. */
|
|
211
|
+
error: Schema.optional(Schema.String),
|
|
212
|
+
}),
|
|
213
|
+
isEphemeral: false,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Human-readable status update emitted by an agent or operation.
|
|
218
|
+
*/
|
|
219
|
+
export const StatusUpdate = EventType('status.update', {
|
|
220
|
+
schema: Schema.Struct({
|
|
221
|
+
/** Human-readable status message. */
|
|
222
|
+
message: Schema.String,
|
|
223
|
+
}),
|
|
224
|
+
isEphemeral: true,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Emit the current human-readable execution status to the trace.
|
|
229
|
+
*/
|
|
230
|
+
export const emitStatus: (message: string) => Effect.Effect<void, never, TraceService> = (message) =>
|
|
231
|
+
write(StatusUpdate, { message });
|