@dxos/functions 0.8.4-main.e098934 → 0.8.4-main.ead640a
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/browser/bundler/index.mjs +10 -6
- package/dist/lib/browser/bundler/index.mjs.map +3 -3
- package/dist/lib/browser/{chunk-D2XO7XXY.mjs → chunk-LKYT2SAL.mjs} +176 -122
- package/dist/lib/browser/chunk-LKYT2SAL.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +319 -199
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +11 -9
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/bundler/index.mjs +10 -6
- package/dist/lib/node-esm/bundler/index.mjs.map +3 -3
- package/dist/lib/node-esm/{chunk-Z4CJ62WS.mjs → chunk-NAQIKLZB.mjs} +176 -122
- package/dist/lib/node-esm/chunk-NAQIKLZB.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +319 -199
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +11 -9
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/types/src/bundler/bundler.d.ts.map +1 -1
- package/dist/types/src/errors.d.ts +16 -16
- package/dist/types/src/examples/fib.d.ts.map +1 -1
- package/dist/types/src/examples/reply.d.ts.map +1 -1
- package/dist/types/src/examples/sleep.d.ts.map +1 -1
- package/dist/types/src/executor/executor.d.ts +3 -0
- package/dist/types/src/executor/executor.d.ts.map +1 -1
- package/dist/types/src/handler.d.ts +19 -4
- package/dist/types/src/handler.d.ts.map +1 -1
- package/dist/types/src/schema.d.ts +5 -5
- package/dist/types/src/schema.d.ts.map +1 -1
- package/dist/types/src/services/credentials.d.ts +6 -2
- package/dist/types/src/services/credentials.d.ts.map +1 -1
- package/dist/types/src/services/database.d.ts +6 -2
- package/dist/types/src/services/database.d.ts.map +1 -1
- package/dist/types/src/services/event-logger.d.ts +4 -1
- package/dist/types/src/services/event-logger.d.ts.map +1 -1
- package/dist/types/src/services/function-invocation-service.d.ts +28 -0
- package/dist/types/src/services/function-invocation-service.d.ts.map +1 -0
- package/dist/types/src/services/function-invocation-service.test.d.ts +2 -0
- package/dist/types/src/services/function-invocation-service.test.d.ts.map +1 -0
- package/dist/types/src/services/index.d.ts +4 -3
- package/dist/types/src/services/index.d.ts.map +1 -1
- package/dist/types/src/services/local-function-execution.d.ts +18 -9
- package/dist/types/src/services/local-function-execution.d.ts.map +1 -1
- package/dist/types/src/services/queues.d.ts +3 -1
- package/dist/types/src/services/queues.d.ts.map +1 -1
- package/dist/types/src/services/remote-function-execution-service.d.ts +11 -4
- package/dist/types/src/services/remote-function-execution-service.d.ts.map +1 -1
- package/dist/types/src/services/service-container.d.ts +2 -1
- package/dist/types/src/services/service-container.d.ts.map +1 -1
- package/dist/types/src/services/service-registry.d.ts +3 -1
- package/dist/types/src/services/service-registry.d.ts.map +1 -1
- package/dist/types/src/services/tracing.d.ts +5 -3
- package/dist/types/src/services/tracing.d.ts.map +1 -1
- package/dist/types/src/testing/layer.d.ts +6 -3
- package/dist/types/src/testing/layer.d.ts.map +1 -1
- package/dist/types/src/testing/logger.d.ts +1 -1
- package/dist/types/src/testing/logger.d.ts.map +1 -1
- package/dist/types/src/testing/services.d.ts +1 -1
- package/dist/types/src/testing/services.d.ts.map +1 -1
- package/dist/types/src/trace.d.ts +1 -1
- package/dist/types/src/trace.d.ts.map +1 -1
- package/dist/types/src/triggers/invocation-tracer.d.ts +3 -1
- package/dist/types/src/triggers/invocation-tracer.d.ts.map +1 -1
- package/dist/types/src/triggers/trigger-dispatcher.d.ts +7 -4
- package/dist/types/src/triggers/trigger-dispatcher.d.ts.map +1 -1
- package/dist/types/src/triggers/trigger-state-store.d.ts +5 -4
- package/dist/types/src/triggers/trigger-state-store.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +49 -30
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/url.d.ts +1 -1
- package/dist/types/src/url.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -36
- package/src/bundler/bundler.ts +7 -3
- package/src/examples/fib.ts +4 -2
- package/src/examples/reply.ts +5 -2
- package/src/examples/sleep.ts +4 -2
- package/src/executor/executor.ts +5 -1
- package/src/handler.ts +33 -9
- package/src/schema.ts +5 -2
- package/src/services/credentials.ts +9 -2
- package/src/services/database.ts +6 -2
- package/src/services/event-logger.ts +4 -1
- package/src/services/function-invocation-service.test.ts +81 -0
- package/src/services/function-invocation-service.ts +84 -0
- package/src/services/index.ts +4 -3
- package/src/services/local-function-execution.ts +65 -26
- package/src/services/queues.ts +3 -1
- package/src/services/remote-function-execution-service.ts +38 -21
- package/src/services/service-container.ts +2 -1
- package/src/services/service-registry.test.ts +4 -1
- package/src/services/service-registry.ts +7 -3
- package/src/services/tracing.ts +17 -14
- package/src/testing/layer.ts +10 -7
- package/src/testing/logger.ts +2 -1
- package/src/testing/persist-database.test.ts +4 -4
- package/src/testing/services.ts +1 -1
- package/src/trace.ts +2 -2
- package/src/triggers/invocation-tracer.ts +3 -1
- package/src/triggers/trigger-dispatcher.test.ts +29 -16
- package/src/triggers/trigger-dispatcher.ts +29 -8
- package/src/triggers/trigger-state-store.ts +6 -5
- package/src/types.ts +30 -12
- package/src/url.ts +1 -1
- package/dist/lib/browser/chunk-D2XO7XXY.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-Z4CJ62WS.mjs.map +0 -7
|
@@ -2,40 +2,67 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
8
|
+
import * as Schema from 'effect/Schema';
|
|
6
9
|
|
|
10
|
+
import { AiService } from '@dxos/ai';
|
|
7
11
|
import { todo } from '@dxos/debug';
|
|
12
|
+
import { log } from '@dxos/log';
|
|
8
13
|
|
|
9
14
|
import { FunctionError, FunctionNotFoundError } from '../errors';
|
|
10
15
|
import type { FunctionContext, FunctionDefinition } from '../handler';
|
|
11
16
|
|
|
12
|
-
import
|
|
17
|
+
import { CredentialsService } from './credentials';
|
|
18
|
+
import { DatabaseService } from './database';
|
|
19
|
+
import { type ComputeEventLogger } from './event-logger';
|
|
20
|
+
import { QueueService } from './queues';
|
|
21
|
+
import { RemoteFunctionExecutionService } from './remote-function-execution-service';
|
|
22
|
+
import { type Services } from './service-container';
|
|
23
|
+
import { type TracingService } from './tracing';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Services that are provided at the function call site.
|
|
27
|
+
*/
|
|
28
|
+
export type InvocationServices = TracingService | ComputeEventLogger;
|
|
13
29
|
|
|
14
30
|
export class LocalFunctionExecutionService extends Context.Tag('@dxos/functions/LocalFunctionExecutionService')<
|
|
15
31
|
LocalFunctionExecutionService,
|
|
16
32
|
{
|
|
17
33
|
// TODO(dmaretskyi): This should take function id instead of the definition object.
|
|
18
34
|
// TODO(dmaretskyi): Services should be satisfied from environment rather then bubbled up.
|
|
19
|
-
invokeFunction(functionDef: FunctionDefinition<
|
|
35
|
+
invokeFunction<I, O>(functionDef: FunctionDefinition<I, O>, input: I): Effect.Effect<O, never, InvocationServices>;
|
|
20
36
|
}
|
|
21
37
|
>() {
|
|
22
|
-
/**
|
|
23
|
-
* @deprecated Use layerLive instead.
|
|
24
|
-
*/
|
|
25
|
-
static layer = Layer.succeed(LocalFunctionExecutionService, {
|
|
26
|
-
invokeFunction: (functionDef, input) => invokeFunction(functionDef, input),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
38
|
static layerLive = Layer.effect(
|
|
30
39
|
LocalFunctionExecutionService,
|
|
31
40
|
Effect.gen(function* () {
|
|
41
|
+
// TODO(dmaretskyi): Use `yield* Effect.context()`;
|
|
32
42
|
const resolver = yield* FunctionImplementationResolver;
|
|
43
|
+
const ai = yield* AiService.AiService;
|
|
44
|
+
const credentials = yield* CredentialsService;
|
|
45
|
+
const database = yield* DatabaseService;
|
|
46
|
+
const queues = yield* QueueService;
|
|
47
|
+
// TODO(mykola): Delete, should not be required for local execution.
|
|
48
|
+
const functionCallService = yield* RemoteFunctionExecutionService;
|
|
33
49
|
return {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
// TODO(dmaretskyi): Better error types.
|
|
51
|
+
invokeFunction: <I, O>(
|
|
52
|
+
functionDef: FunctionDefinition<I, O>,
|
|
53
|
+
input: I,
|
|
54
|
+
): Effect.Effect<O, never, InvocationServices> =>
|
|
55
|
+
Effect.gen(function* () {
|
|
56
|
+
const resolved = yield* resolver.resolveFunctionImplementation(functionDef).pipe(Effect.orDie);
|
|
57
|
+
const output = yield* invokeFunction(resolved, input);
|
|
58
|
+
return output as O;
|
|
59
|
+
}).pipe(
|
|
60
|
+
Effect.provideService(AiService.AiService, ai),
|
|
61
|
+
Effect.provideService(CredentialsService, credentials),
|
|
62
|
+
Effect.provideService(DatabaseService, database),
|
|
63
|
+
Effect.provideService(QueueService, queues),
|
|
64
|
+
Effect.provideService(RemoteFunctionExecutionService, functionCallService),
|
|
65
|
+
),
|
|
39
66
|
};
|
|
40
67
|
}),
|
|
41
68
|
);
|
|
@@ -52,9 +79,13 @@ const invokeFunction = (
|
|
|
52
79
|
input: any,
|
|
53
80
|
): Effect.Effect<unknown, never, Services> =>
|
|
54
81
|
Effect.gen(function* () {
|
|
55
|
-
// Assert input matches schema
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
// Assert input matches schema.
|
|
83
|
+
try {
|
|
84
|
+
const assertInput = functionDef.inputSchema.pipe(Schema.asserts);
|
|
85
|
+
(assertInput as any)(input);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
throw new FunctionError({ message: 'Invalid function input', context: { name: functionDef.name }, cause: e });
|
|
88
|
+
}
|
|
58
89
|
|
|
59
90
|
const context: FunctionContext = {
|
|
60
91
|
space: undefined,
|
|
@@ -64,6 +95,8 @@ const invokeFunction = (
|
|
|
64
95
|
},
|
|
65
96
|
};
|
|
66
97
|
|
|
98
|
+
log.info('invoking function', { name: functionDef.name, input });
|
|
99
|
+
|
|
67
100
|
// TODO(dmaretskyi): This should be delegated to a function invoker service.
|
|
68
101
|
const data = yield* Effect.gen(function* () {
|
|
69
102
|
const result = functionDef.handler({ context, data: input });
|
|
@@ -86,9 +119,15 @@ const invokeFunction = (
|
|
|
86
119
|
),
|
|
87
120
|
);
|
|
88
121
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
122
|
+
log.info('completed', { function: functionDef.name, input, data });
|
|
123
|
+
|
|
124
|
+
// Assert output matches schema.
|
|
125
|
+
try {
|
|
126
|
+
const assertOutput = functionDef.outputSchema?.pipe(Schema.asserts);
|
|
127
|
+
(assertOutput as any)(data);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
throw new FunctionError({ message: 'Invalid function output', context: { name: functionDef.name }, cause: e });
|
|
130
|
+
}
|
|
92
131
|
|
|
93
132
|
return data;
|
|
94
133
|
}).pipe(Effect.withSpan('invokeFunction', { attributes: { name: functionDef.name } }));
|
|
@@ -96,15 +135,15 @@ const invokeFunction = (
|
|
|
96
135
|
export class FunctionImplementationResolver extends Context.Tag('@dxos/functions/FunctionImplementationResolver')<
|
|
97
136
|
FunctionImplementationResolver,
|
|
98
137
|
{
|
|
99
|
-
resolveFunctionImplementation(
|
|
100
|
-
functionDef: FunctionDefinition<
|
|
101
|
-
): Effect.Effect<FunctionDefinition<
|
|
138
|
+
resolveFunctionImplementation<I, O>(
|
|
139
|
+
functionDef: FunctionDefinition<I, O>,
|
|
140
|
+
): Effect.Effect<FunctionDefinition<I, O>, FunctionNotFoundError>;
|
|
102
141
|
}
|
|
103
142
|
>() {
|
|
104
143
|
static layerTest = ({ functions }: { functions: FunctionDefinition<any, any>[] }) =>
|
|
105
144
|
Layer.succeed(FunctionImplementationResolver, {
|
|
106
|
-
resolveFunctionImplementation: (functionDef) => {
|
|
107
|
-
const resolved = functions.find((f) => f.
|
|
145
|
+
resolveFunctionImplementation: <I, O>(functionDef: FunctionDefinition<I, O>) => {
|
|
146
|
+
const resolved = functions.find((f) => f.key === functionDef.key);
|
|
108
147
|
if (!resolved) {
|
|
109
148
|
return Effect.fail(new FunctionNotFoundError(functionDef.name));
|
|
110
149
|
}
|
package/src/services/queues.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
6
8
|
|
|
7
9
|
import type { Obj, Relation } from '@dxos/echo';
|
|
8
10
|
import type { Queue, QueueAPI, QueueFactory } from '@dxos/echo-db';
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
6
8
|
|
|
7
9
|
import type { SpaceId } from '@dxos/keys';
|
|
8
10
|
|
|
11
|
+
import { FunctionError } from '../errors';
|
|
9
12
|
import { getInvocationUrl } from '../url';
|
|
10
13
|
|
|
11
14
|
/**
|
|
@@ -14,33 +17,47 @@ import { getInvocationUrl } from '../url';
|
|
|
14
17
|
export class RemoteFunctionExecutionService extends Context.Tag('@dxos/functions/RemoteFunctionExecutionService')<
|
|
15
18
|
RemoteFunctionExecutionService,
|
|
16
19
|
{
|
|
17
|
-
callFunction(deployedFunctionId: string, input:
|
|
20
|
+
callFunction<I, O>(deployedFunctionId: string, input: I): Effect.Effect<O>;
|
|
18
21
|
}
|
|
19
22
|
>() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
/**
|
|
24
|
+
* @param baseUrl URL of the EDGE server.
|
|
25
|
+
* @param spaceId - The space ID to invoke the function in. If not provided, the function will be without space context.
|
|
26
|
+
* @returns
|
|
27
|
+
*/
|
|
28
|
+
static fromClient(baseUrl: string, spaceId?: SpaceId): Layer.Layer<RemoteFunctionExecutionService> {
|
|
29
|
+
return Layer.succeed(RemoteFunctionExecutionService, {
|
|
30
|
+
callFunction: <I, O>(deployedFunctionId: string, input: I): Effect.Effect<O> =>
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const url = getInvocationUrl(deployedFunctionId, baseUrl, { spaceId });
|
|
33
|
+
const result = yield* Effect.promise(() =>
|
|
34
|
+
fetch(url, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
body: JSON.stringify(input),
|
|
38
|
+
}),
|
|
39
|
+
);
|
|
40
|
+
if (result.status >= 300 || result.status < 200) {
|
|
41
|
+
const text = yield* Effect.promise(() => result.text());
|
|
42
|
+
return yield* Effect.die(
|
|
43
|
+
new FunctionError({
|
|
44
|
+
message: 'Failed to invoke function',
|
|
45
|
+
cause: new Error(`HTTP error: ${text}`),
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const data = (yield* Effect.promise(() => result.json())) as O;
|
|
50
|
+
return data;
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
35
53
|
}
|
|
36
54
|
|
|
37
55
|
static mock = (): Context.Tag.Service<RemoteFunctionExecutionService> => {
|
|
38
56
|
return {
|
|
39
|
-
callFunction:
|
|
40
|
-
|
|
41
|
-
},
|
|
57
|
+
callFunction: <I, O>(deployedFunctionId: string, input: I): Effect.Effect<O> =>
|
|
58
|
+
Effect.succeed(input as unknown as O),
|
|
42
59
|
};
|
|
43
60
|
};
|
|
44
61
|
|
|
45
|
-
static
|
|
62
|
+
static layerMock = Layer.succeed(RemoteFunctionExecutionService, RemoteFunctionExecutionService.mock());
|
|
46
63
|
}
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { describe, it } from '@effect/vitest';
|
|
6
|
-
import
|
|
6
|
+
import * as Context from 'effect/Context';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import * as Layer from 'effect/Layer';
|
|
9
|
+
import * as Option from 'effect/Option';
|
|
7
10
|
|
|
8
11
|
import { ServiceRegistry } from './service-registry';
|
|
9
12
|
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Function from 'effect/Function';
|
|
8
|
+
import type * as Option from 'effect/Option';
|
|
6
9
|
|
|
7
10
|
import { ServiceNotAvailableError } from '../errors';
|
|
8
11
|
|
|
@@ -40,7 +43,8 @@ export class ServiceRegistry extends Context.Tag('@dxos/functions/ServiceRegistr
|
|
|
40
43
|
E | ServiceNotAvailableError,
|
|
41
44
|
Exclude<R, { [K in keyof Tags]: Context.Tag.Identifier<Tags[K]> }[number]> | ServiceRegistry
|
|
42
45
|
>;
|
|
43
|
-
} = (...tags) =>
|
|
46
|
+
} = (...tags) =>
|
|
47
|
+
(Function.flow as any)(...tags.map((tag) => Effect.provideServiceEffect(tag, ServiceRegistry.resolve(tag))));
|
|
44
48
|
|
|
45
49
|
static provideOrDie: {
|
|
46
50
|
<Tags extends [Context.Tag<any, any>, ...Context.Tag<any, any>[]]>(
|
|
@@ -53,7 +57,7 @@ export class ServiceRegistry extends Context.Tag('@dxos/functions/ServiceRegistr
|
|
|
53
57
|
Exclude<R, { [K in keyof Tags]: Context.Tag.Identifier<Tags[K]> }[number]> | ServiceRegistry
|
|
54
58
|
>;
|
|
55
59
|
} = (...tags) =>
|
|
56
|
-
(flow as any)(
|
|
60
|
+
(Function.flow as any)(
|
|
57
61
|
...tags.map((tag) => Effect.provideServiceEffect(tag, ServiceRegistry.resolve(tag).pipe(Effect.orDie))),
|
|
58
62
|
);
|
|
59
63
|
}
|
package/src/services/tracing.ts
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
6
8
|
|
|
7
9
|
import { AgentStatus } from '@dxos/ai';
|
|
8
10
|
import { Obj } from '@dxos/echo';
|
|
11
|
+
import type { ObjectId } from '@dxos/echo/internal';
|
|
9
12
|
import type { Queue } from '@dxos/echo-db';
|
|
10
|
-
import type { ObjectId } from '@dxos/echo-schema';
|
|
11
13
|
import { log } from '@dxos/log';
|
|
12
14
|
import { DataType } from '@dxos/schema';
|
|
13
15
|
|
|
@@ -36,7 +38,6 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
|
|
|
36
38
|
|
|
37
39
|
static console: Context.Tag.Service<TracingService> = {
|
|
38
40
|
write: (event) => {
|
|
39
|
-
// eslint-disable-next-line no-console
|
|
40
41
|
console.log(event);
|
|
41
42
|
},
|
|
42
43
|
getTraceContext: () => ({}),
|
|
@@ -89,17 +90,19 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
|
|
|
89
90
|
/**
|
|
90
91
|
* Emit the current human-readable execution status.
|
|
91
92
|
*/
|
|
92
|
-
static emitStatus: (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
static emitStatus: (
|
|
94
|
+
data: Omit<Obj.MakeProps<typeof AgentStatus>, 'created'>,
|
|
95
|
+
) => Effect.Effect<void, never, TracingService> = Effect.fnUntraced(function* (data) {
|
|
96
|
+
const tracing = yield* TracingService;
|
|
97
|
+
tracing.write(
|
|
98
|
+
Obj.make(AgentStatus, {
|
|
99
|
+
parentMessage: tracing.getTraceContext().parentMessage,
|
|
100
|
+
toolCallId: tracing.getTraceContext().toolCallId,
|
|
101
|
+
created: new Date().toISOString(),
|
|
102
|
+
...data,
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
});
|
|
103
106
|
|
|
104
107
|
static emitConverationMessage: (
|
|
105
108
|
data: Obj.MakeProps<typeof DataType.Message>,
|
package/src/testing/layer.ts
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
8
|
+
import type * as Schema from 'effect/Schema';
|
|
7
9
|
|
|
8
10
|
import type { EchoDatabaseImpl, QueueFactory } from '@dxos/echo-db';
|
|
9
11
|
import { EchoTestBuilder } from '@dxos/echo-db/testing';
|
|
10
12
|
import type { EchoHostIndexingConfig } from '@dxos/echo-pipeline';
|
|
11
|
-
import {
|
|
13
|
+
import { acquireReleaseResource } from '@dxos/effect';
|
|
12
14
|
import { PublicKey } from '@dxos/keys';
|
|
13
15
|
import type { LevelDB } from '@dxos/kv-store';
|
|
14
16
|
import { createTestLevel } from '@dxos/kv-store/testing';
|
|
@@ -16,7 +18,7 @@ import { log } from '@dxos/log';
|
|
|
16
18
|
|
|
17
19
|
import { DatabaseService, QueueService } from '../services';
|
|
18
20
|
|
|
19
|
-
const testBuilder =
|
|
21
|
+
const testBuilder = acquireReleaseResource(() => new EchoTestBuilder());
|
|
20
22
|
|
|
21
23
|
export const testStoragePath = ({ name = PublicKey.random().toHex() }: { name?: string }) => {
|
|
22
24
|
return `/tmp/dxos-${name}`;
|
|
@@ -25,11 +27,12 @@ export const testStoragePath = ({ name = PublicKey.random().toHex() }: { name?:
|
|
|
25
27
|
export type TestDatabaseOptions = {
|
|
26
28
|
indexing?: Partial<EchoHostIndexingConfig>;
|
|
27
29
|
types?: Schema.Schema.AnyNoContext[];
|
|
30
|
+
spaceKey?: PublicKey;
|
|
28
31
|
storagePath?: string;
|
|
29
32
|
onInit?: () => Effect.Effect<void, never, DatabaseService | QueueService>;
|
|
30
33
|
};
|
|
31
34
|
|
|
32
|
-
export const TestDatabaseLayer = ({ indexing, types, storagePath, onInit }: TestDatabaseOptions = {}) =>
|
|
35
|
+
export const TestDatabaseLayer = ({ indexing, types, spaceKey, storagePath, onInit }: TestDatabaseOptions = {}) =>
|
|
33
36
|
Layer.scopedContext(
|
|
34
37
|
Effect.gen(function* () {
|
|
35
38
|
const builder = yield* testBuilder;
|
|
@@ -59,7 +62,7 @@ export const TestDatabaseLayer = ({ indexing, types, storagePath, onInit }: Test
|
|
|
59
62
|
});
|
|
60
63
|
log('starting persistant test db', { storagePath, testMetadata });
|
|
61
64
|
if (!testMetadata) {
|
|
62
|
-
const key = PublicKey.random();
|
|
65
|
+
const key = spaceKey ?? PublicKey.random();
|
|
63
66
|
db = yield* Effect.promise(() => peer.createDatabase(key));
|
|
64
67
|
queues = peer.client.constructQueueFactory(db.spaceId);
|
|
65
68
|
|
|
@@ -80,7 +83,7 @@ export const TestDatabaseLayer = ({ indexing, types, storagePath, onInit }: Test
|
|
|
80
83
|
queues = peer.client.constructQueueFactory(db.spaceId);
|
|
81
84
|
}
|
|
82
85
|
} else {
|
|
83
|
-
db = yield* Effect.promise(() => peer.createDatabase());
|
|
86
|
+
db = yield* Effect.promise(() => peer.createDatabase(spaceKey));
|
|
84
87
|
queues = peer.client.constructQueueFactory(db.spaceId);
|
|
85
88
|
if (onInit) {
|
|
86
89
|
yield* onInit().pipe(
|
package/src/testing/logger.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { describe, expect, it } from '@effect/vitest';
|
|
6
|
-
import
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
7
|
|
|
8
8
|
import { Filter, Obj, Query, Type } from '@dxos/echo';
|
|
9
9
|
import { DataType } from '@dxos/schema';
|
|
@@ -15,7 +15,7 @@ import { TestDatabaseLayer, testStoragePath } from './layer';
|
|
|
15
15
|
describe('TestDatabaseLayer', { timeout: 600_000 }, () => {
|
|
16
16
|
it.effect(
|
|
17
17
|
'persist database to disk',
|
|
18
|
-
Effect.fnUntraced(function* (
|
|
18
|
+
Effect.fnUntraced(function* (_) {
|
|
19
19
|
const DbLayer = TestDatabaseLayer({
|
|
20
20
|
storagePath: testStoragePath({ name: `feed-test-${Date.now()}` }),
|
|
21
21
|
});
|
|
@@ -34,7 +34,7 @@ describe('TestDatabaseLayer', { timeout: 600_000 }, () => {
|
|
|
34
34
|
|
|
35
35
|
it.effect(
|
|
36
36
|
'reload database -- save index before restart',
|
|
37
|
-
Effect.fnUntraced(function* (
|
|
37
|
+
Effect.fnUntraced(function* (_) {
|
|
38
38
|
const NUM_OBJECTS = 500;
|
|
39
39
|
const DbLayer = TestDatabaseLayer({
|
|
40
40
|
types: [DataType.Person],
|
|
@@ -58,7 +58,7 @@ describe('TestDatabaseLayer', { timeout: 600_000 }, () => {
|
|
|
58
58
|
it.effect.skip(
|
|
59
59
|
'reload database -- save index before restart [manual]',
|
|
60
60
|
Effect.fnUntraced(
|
|
61
|
-
function* (
|
|
61
|
+
function* (_) {
|
|
62
62
|
const NUM_OBJECTS = 500;
|
|
63
63
|
|
|
64
64
|
{
|
package/src/testing/services.ts
CHANGED
package/src/trace.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
|
|
7
7
|
import { type Ref, Type } from '@dxos/echo';
|
|
8
|
+
import { ObjectId } from '@dxos/echo/internal';
|
|
8
9
|
import { Queue } from '@dxos/echo-db';
|
|
9
|
-
import { ObjectId } from '@dxos/echo-schema';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
|
|
12
12
|
import { FunctionTrigger } from './types';
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Context from 'effect/Context';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Layer from 'effect/Layer';
|
|
6
8
|
|
|
7
9
|
import { Obj, Ref } from '@dxos/echo';
|
|
8
10
|
import { type Queue } from '@dxos/echo-db';
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
|
|
6
6
|
import { describe, it } from '@effect/vitest';
|
|
7
|
-
import
|
|
7
|
+
import * as Duration from 'effect/Duration';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import * as Exit from 'effect/Exit';
|
|
10
|
+
import * as Function from 'effect/Function';
|
|
11
|
+
import * as Layer from 'effect/Layer';
|
|
8
12
|
|
|
9
13
|
import { AiService } from '@dxos/ai';
|
|
10
14
|
import { Filter, Obj, Query, Ref } from '@dxos/echo';
|
|
@@ -18,11 +22,10 @@ import {
|
|
|
18
22
|
ComputeEventLogger,
|
|
19
23
|
CredentialsService,
|
|
20
24
|
DatabaseService,
|
|
25
|
+
FunctionInvocationService,
|
|
21
26
|
QueueService,
|
|
22
|
-
RemoteFunctionExecutionService,
|
|
23
27
|
TracingService,
|
|
24
28
|
} from '../services';
|
|
25
|
-
import { FunctionImplementationResolver, LocalFunctionExecutionService } from '../services/local-function-execution';
|
|
26
29
|
import { TestDatabaseLayer } from '../testing';
|
|
27
30
|
import { FunctionTrigger } from '../types';
|
|
28
31
|
|
|
@@ -30,22 +33,22 @@ import { InvocationTracer } from './invocation-tracer';
|
|
|
30
33
|
import { TriggerDispatcher } from './trigger-dispatcher';
|
|
31
34
|
import { TriggerStateStore } from './trigger-state-store';
|
|
32
35
|
|
|
33
|
-
const TestLayer = pipe(
|
|
36
|
+
const TestLayer = Function.pipe(
|
|
34
37
|
Layer.mergeAll(ComputeEventLogger.layerFromTracing, InvocationTracer.layerTest, TriggerStateStore.layerMemory),
|
|
35
38
|
Layer.provideMerge(
|
|
36
39
|
Layer.mergeAll(
|
|
37
40
|
AiService.notAvailable,
|
|
41
|
+
CredentialsService.layerConfig([]),
|
|
42
|
+
FunctionInvocationService.layerTestMocked({ functions: [reply] }).pipe(
|
|
43
|
+
Layer.provideMerge(ComputeEventLogger.layerFromTracing),
|
|
44
|
+
Layer.provideMerge(TracingService.layerLogInfo()),
|
|
45
|
+
),
|
|
46
|
+
FetchHttpClient.layer,
|
|
38
47
|
TestDatabaseLayer({
|
|
39
48
|
types: [FunctionType, FunctionTrigger, DataType.Person, DataType.Task],
|
|
40
49
|
}),
|
|
41
|
-
CredentialsService.layerConfig([]),
|
|
42
|
-
LocalFunctionExecutionService.layerLive,
|
|
43
|
-
RemoteFunctionExecutionService.mockLayer,
|
|
44
|
-
TracingService.layerLogInfo(),
|
|
45
|
-
FetchHttpClient.layer,
|
|
46
50
|
),
|
|
47
51
|
),
|
|
48
|
-
Layer.provideMerge(FunctionImplementationResolver.layerTest({ functions: [reply] })),
|
|
49
52
|
);
|
|
50
53
|
|
|
51
54
|
const TestTriggerDispatcherLayer = Layer.provideMerge(
|
|
@@ -452,7 +455,9 @@ describe('TriggerDispatcher', () => {
|
|
|
452
455
|
enabled: true,
|
|
453
456
|
spec: {
|
|
454
457
|
kind: 'subscription',
|
|
455
|
-
query:
|
|
458
|
+
query: {
|
|
459
|
+
ast: Query.select(Filter.type(DataType.Person)).ast,
|
|
460
|
+
},
|
|
456
461
|
},
|
|
457
462
|
});
|
|
458
463
|
yield* DatabaseService.add(trigger);
|
|
@@ -494,7 +499,9 @@ describe('TriggerDispatcher', () => {
|
|
|
494
499
|
enabled: true,
|
|
495
500
|
spec: {
|
|
496
501
|
kind: 'subscription',
|
|
497
|
-
query:
|
|
502
|
+
query: {
|
|
503
|
+
ast: Query.select(Filter.type(DataType.Person)).ast,
|
|
504
|
+
},
|
|
498
505
|
},
|
|
499
506
|
});
|
|
500
507
|
yield* DatabaseService.add(trigger);
|
|
@@ -530,7 +537,9 @@ describe('TriggerDispatcher', () => {
|
|
|
530
537
|
enabled: true,
|
|
531
538
|
spec: {
|
|
532
539
|
kind: 'subscription',
|
|
533
|
-
query:
|
|
540
|
+
query: {
|
|
541
|
+
ast: Query.select(Filter.type(DataType.Person)).ast,
|
|
542
|
+
},
|
|
534
543
|
},
|
|
535
544
|
});
|
|
536
545
|
yield* DatabaseService.add(trigger);
|
|
@@ -578,7 +587,9 @@ describe('TriggerDispatcher', () => {
|
|
|
578
587
|
enabled: true,
|
|
579
588
|
spec: {
|
|
580
589
|
kind: 'subscription',
|
|
581
|
-
query:
|
|
590
|
+
query: {
|
|
591
|
+
ast: Query.select(Filter.type(DataType.Task)).ast,
|
|
592
|
+
},
|
|
582
593
|
},
|
|
583
594
|
});
|
|
584
595
|
yield* DatabaseService.add(trigger);
|
|
@@ -625,7 +636,9 @@ describe('TriggerDispatcher', () => {
|
|
|
625
636
|
enabled: true,
|
|
626
637
|
spec: {
|
|
627
638
|
kind: 'subscription',
|
|
628
|
-
query:
|
|
639
|
+
query: {
|
|
640
|
+
ast: Query.select(Filter.type(DataType.Person)).ast,
|
|
641
|
+
},
|
|
629
642
|
},
|
|
630
643
|
input: {
|
|
631
644
|
objectId: '{{event.changedObjectId}}',
|