@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.
Files changed (106) hide show
  1. package/dist/lib/browser/bundler/index.mjs +10 -6
  2. package/dist/lib/browser/bundler/index.mjs.map +3 -3
  3. package/dist/lib/browser/{chunk-D2XO7XXY.mjs → chunk-LKYT2SAL.mjs} +176 -122
  4. package/dist/lib/browser/chunk-LKYT2SAL.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +319 -199
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/testing/index.mjs +11 -9
  9. package/dist/lib/browser/testing/index.mjs.map +3 -3
  10. package/dist/lib/node-esm/bundler/index.mjs +10 -6
  11. package/dist/lib/node-esm/bundler/index.mjs.map +3 -3
  12. package/dist/lib/node-esm/{chunk-Z4CJ62WS.mjs → chunk-NAQIKLZB.mjs} +176 -122
  13. package/dist/lib/node-esm/chunk-NAQIKLZB.mjs.map +7 -0
  14. package/dist/lib/node-esm/index.mjs +319 -199
  15. package/dist/lib/node-esm/index.mjs.map +4 -4
  16. package/dist/lib/node-esm/meta.json +1 -1
  17. package/dist/lib/node-esm/testing/index.mjs +11 -9
  18. package/dist/lib/node-esm/testing/index.mjs.map +3 -3
  19. package/dist/types/src/bundler/bundler.d.ts.map +1 -1
  20. package/dist/types/src/errors.d.ts +16 -16
  21. package/dist/types/src/examples/fib.d.ts.map +1 -1
  22. package/dist/types/src/examples/reply.d.ts.map +1 -1
  23. package/dist/types/src/examples/sleep.d.ts.map +1 -1
  24. package/dist/types/src/executor/executor.d.ts +3 -0
  25. package/dist/types/src/executor/executor.d.ts.map +1 -1
  26. package/dist/types/src/handler.d.ts +19 -4
  27. package/dist/types/src/handler.d.ts.map +1 -1
  28. package/dist/types/src/schema.d.ts +5 -5
  29. package/dist/types/src/schema.d.ts.map +1 -1
  30. package/dist/types/src/services/credentials.d.ts +6 -2
  31. package/dist/types/src/services/credentials.d.ts.map +1 -1
  32. package/dist/types/src/services/database.d.ts +6 -2
  33. package/dist/types/src/services/database.d.ts.map +1 -1
  34. package/dist/types/src/services/event-logger.d.ts +4 -1
  35. package/dist/types/src/services/event-logger.d.ts.map +1 -1
  36. package/dist/types/src/services/function-invocation-service.d.ts +28 -0
  37. package/dist/types/src/services/function-invocation-service.d.ts.map +1 -0
  38. package/dist/types/src/services/function-invocation-service.test.d.ts +2 -0
  39. package/dist/types/src/services/function-invocation-service.test.d.ts.map +1 -0
  40. package/dist/types/src/services/index.d.ts +4 -3
  41. package/dist/types/src/services/index.d.ts.map +1 -1
  42. package/dist/types/src/services/local-function-execution.d.ts +18 -9
  43. package/dist/types/src/services/local-function-execution.d.ts.map +1 -1
  44. package/dist/types/src/services/queues.d.ts +3 -1
  45. package/dist/types/src/services/queues.d.ts.map +1 -1
  46. package/dist/types/src/services/remote-function-execution-service.d.ts +11 -4
  47. package/dist/types/src/services/remote-function-execution-service.d.ts.map +1 -1
  48. package/dist/types/src/services/service-container.d.ts +2 -1
  49. package/dist/types/src/services/service-container.d.ts.map +1 -1
  50. package/dist/types/src/services/service-registry.d.ts +3 -1
  51. package/dist/types/src/services/service-registry.d.ts.map +1 -1
  52. package/dist/types/src/services/tracing.d.ts +5 -3
  53. package/dist/types/src/services/tracing.d.ts.map +1 -1
  54. package/dist/types/src/testing/layer.d.ts +6 -3
  55. package/dist/types/src/testing/layer.d.ts.map +1 -1
  56. package/dist/types/src/testing/logger.d.ts +1 -1
  57. package/dist/types/src/testing/logger.d.ts.map +1 -1
  58. package/dist/types/src/testing/services.d.ts +1 -1
  59. package/dist/types/src/testing/services.d.ts.map +1 -1
  60. package/dist/types/src/trace.d.ts +1 -1
  61. package/dist/types/src/trace.d.ts.map +1 -1
  62. package/dist/types/src/triggers/invocation-tracer.d.ts +3 -1
  63. package/dist/types/src/triggers/invocation-tracer.d.ts.map +1 -1
  64. package/dist/types/src/triggers/trigger-dispatcher.d.ts +7 -4
  65. package/dist/types/src/triggers/trigger-dispatcher.d.ts.map +1 -1
  66. package/dist/types/src/triggers/trigger-state-store.d.ts +5 -4
  67. package/dist/types/src/triggers/trigger-state-store.d.ts.map +1 -1
  68. package/dist/types/src/types.d.ts +49 -30
  69. package/dist/types/src/types.d.ts.map +1 -1
  70. package/dist/types/src/url.d.ts +1 -1
  71. package/dist/types/src/url.d.ts.map +1 -1
  72. package/dist/types/tsconfig.tsbuildinfo +1 -1
  73. package/package.json +27 -36
  74. package/src/bundler/bundler.ts +7 -3
  75. package/src/examples/fib.ts +4 -2
  76. package/src/examples/reply.ts +5 -2
  77. package/src/examples/sleep.ts +4 -2
  78. package/src/executor/executor.ts +5 -1
  79. package/src/handler.ts +33 -9
  80. package/src/schema.ts +5 -2
  81. package/src/services/credentials.ts +9 -2
  82. package/src/services/database.ts +6 -2
  83. package/src/services/event-logger.ts +4 -1
  84. package/src/services/function-invocation-service.test.ts +81 -0
  85. package/src/services/function-invocation-service.ts +84 -0
  86. package/src/services/index.ts +4 -3
  87. package/src/services/local-function-execution.ts +65 -26
  88. package/src/services/queues.ts +3 -1
  89. package/src/services/remote-function-execution-service.ts +38 -21
  90. package/src/services/service-container.ts +2 -1
  91. package/src/services/service-registry.test.ts +4 -1
  92. package/src/services/service-registry.ts +7 -3
  93. package/src/services/tracing.ts +17 -14
  94. package/src/testing/layer.ts +10 -7
  95. package/src/testing/logger.ts +2 -1
  96. package/src/testing/persist-database.test.ts +4 -4
  97. package/src/testing/services.ts +1 -1
  98. package/src/trace.ts +2 -2
  99. package/src/triggers/invocation-tracer.ts +3 -1
  100. package/src/triggers/trigger-dispatcher.test.ts +29 -16
  101. package/src/triggers/trigger-dispatcher.ts +29 -8
  102. package/src/triggers/trigger-state-store.ts +6 -5
  103. package/src/types.ts +30 -12
  104. package/src/url.ts +1 -1
  105. package/dist/lib/browser/chunk-D2XO7XXY.mjs.map +0 -7
  106. package/dist/lib/node-esm/chunk-Z4CJ62WS.mjs.map +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/functions",
3
- "version": "0.8.4-main.e098934",
3
+ "version": "0.8.4-main.ead640a",
4
4
  "description": "Functions API and runtime.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -51,46 +51,37 @@
51
51
  "src"
52
52
  ],
53
53
  "dependencies": {
54
- "@effect/platform": "0.90.2",
55
- "@preact/signals-core": "^1.9.0",
56
- "cron": "^3.1.6",
57
- "cron-schedule": "^5.0.4",
58
- "effect": "3.17.7",
54
+ "@effect/platform": "0.92.1",
55
+ "effect": "3.18.3",
59
56
  "esbuild-wasm": "^0.16.14",
60
- "express": "^4.19.2",
61
- "get-port-please": "^3.1.1",
62
57
  "i18next": "^24.2.1",
63
58
  "iso-did": "^1.6.0",
64
- "ws": "^8.14.2",
65
- "@dxos/ai": "0.8.4-main.e098934",
66
- "@dxos/client": "0.8.4-main.e098934",
67
- "@dxos/async": "0.8.4-main.e098934",
68
- "@dxos/client-protocol": "0.8.4-main.e098934",
69
- "@dxos/context": "0.8.4-main.e098934",
70
- "@dxos/debug": "0.8.4-main.e098934",
71
- "@dxos/crypto": "0.8.4-main.e098934",
72
- "@dxos/echo-db": "0.8.4-main.e098934",
73
- "@dxos/echo-pipeline": "0.8.4-main.e098934",
74
- "@dxos/echo": "0.8.4-main.e098934",
75
- "@dxos/echo-protocol": "0.8.4-main.e098934",
76
- "@dxos/echo-schema": "0.8.4-main.e098934",
77
- "@dxos/edge-client": "0.8.4-main.e098934",
78
- "@dxos/effect": "0.8.4-main.e098934",
79
- "@dxos/errors": "0.8.4-main.e098934",
80
- "@dxos/invariant": "0.8.4-main.e098934",
81
- "@dxos/keys": "0.8.4-main.e098934",
82
- "@dxos/live-object": "0.8.4-main.e098934",
83
- "@dxos/kv-store": "0.8.4-main.e098934",
84
- "@dxos/log": "0.8.4-main.e098934",
85
- "@dxos/node-std": "0.8.4-main.e098934",
86
- "@dxos/protocols": "0.8.4-main.e098934",
87
- "@dxos/schema": "0.8.4-main.e098934",
88
- "@dxos/util": "0.8.4-main.e098934"
59
+ "@dxos/ai": "0.8.4-main.ead640a",
60
+ "@dxos/async": "0.8.4-main.ead640a",
61
+ "@dxos/crypto": "0.8.4-main.ead640a",
62
+ "@dxos/client": "0.8.4-main.ead640a",
63
+ "@dxos/context": "0.8.4-main.ead640a",
64
+ "@dxos/debug": "0.8.4-main.ead640a",
65
+ "@dxos/echo": "0.8.4-main.ead640a",
66
+ "@dxos/echo-pipeline": "0.8.4-main.ead640a",
67
+ "@dxos/echo-db": "0.8.4-main.ead640a",
68
+ "@dxos/echo-protocol": "0.8.4-main.ead640a",
69
+ "@dxos/client-protocol": "0.8.4-main.ead640a",
70
+ "@dxos/edge-client": "0.8.4-main.ead640a",
71
+ "@dxos/invariant": "0.8.4-main.ead640a",
72
+ "@dxos/kv-store": "0.8.4-main.ead640a",
73
+ "@dxos/keys": "0.8.4-main.ead640a",
74
+ "@dxos/live-object": "0.8.4-main.ead640a",
75
+ "@dxos/effect": "0.8.4-main.ead640a",
76
+ "@dxos/errors": "0.8.4-main.ead640a",
77
+ "@dxos/node-std": "0.8.4-main.ead640a",
78
+ "@dxos/log": "0.8.4-main.ead640a",
79
+ "@dxos/schema": "0.8.4-main.ead640a",
80
+ "@dxos/util": "0.8.4-main.ead640a",
81
+ "@dxos/protocols": "0.8.4-main.ead640a"
89
82
  },
90
83
  "devDependencies": {
91
- "@types/express": "^4.17.17",
92
- "@types/ws": "^7.4.0",
93
- "@dxos/agent": "0.8.4-main.e098934"
84
+ "@dxos/agent": "0.8.4-main.ead640a"
94
85
  },
95
86
  "publishConfig": {
96
87
  "access": "public"
@@ -2,8 +2,12 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { FetchHttpClient, HttpClient } from '@effect/platform';
6
- import { Duration, Effect, Schedule, pipe } from 'effect';
5
+ import * as FetchHttpClient from '@effect/platform/FetchHttpClient';
6
+ import * as HttpClient from '@effect/platform/HttpClient';
7
+ import * as Duration from 'effect/Duration';
8
+ import * as Effect from 'effect/Effect';
9
+ import * as Function from 'effect/Function';
10
+ import * as Schedule from 'effect/Schedule';
7
11
  import { type BuildOptions, type BuildResult, type Loader, type Plugin, build, initialize } from 'esbuild-wasm';
8
12
 
9
13
  import { subtleCrypto } from '@dxos/crypto';
@@ -277,7 +281,7 @@ const httpPlugin: Plugin = {
277
281
  return { contents: text, loader: 'jsx' as Loader };
278
282
  }).pipe(
279
283
  Effect.retry(
280
- pipe(
284
+ Function.pipe(
281
285
  Schedule.exponential(Duration.millis(INITIAL_DELAY)),
282
286
  Schedule.jittered,
283
287
  Schedule.intersect(Schedule.recurs(MAX_RETRIES - 1)),
@@ -2,12 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Effect, Schema } from 'effect';
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Schema from 'effect/Schema';
6
7
 
7
8
  import { defineFunction } from '../handler';
8
9
 
9
10
  export default defineFunction({
10
- name: 'example.org/function/fib',
11
+ key: 'example.org/function/fib',
12
+ name: 'Fibonacci',
11
13
  description: 'Function that calculates a Fibonacci number',
12
14
  inputSchema: Schema.Struct({
13
15
  iterations: Schema.optional(Schema.Number).annotations({
@@ -2,12 +2,15 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Console, Effect, Schema } from 'effect';
5
+ import * as Console from 'effect/Console';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Schema from 'effect/Schema';
6
8
 
7
9
  import { defineFunction } from '../handler';
8
10
 
9
11
  export default defineFunction({
10
- name: 'example.org/function/reply',
12
+ key: 'example.org/function/reply',
13
+ name: 'Reply',
11
14
  description: 'Function that echoes the input',
12
15
  inputSchema: Schema.Any,
13
16
  outputSchema: Schema.Any,
@@ -2,12 +2,14 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Effect, Schema } from 'effect';
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Schema from 'effect/Schema';
6
7
 
7
8
  import { defineFunction } from '../handler';
8
9
 
9
10
  export default defineFunction({
10
- name: 'example.org/function/sleep',
11
+ key: 'example.org/function/sleep',
12
+ name: 'Sleep',
11
13
  description: 'Function that sleeps for a given amount of time',
12
14
  inputSchema: Schema.Struct({
13
15
  duration: Schema.optional(Schema.Number).annotations({
@@ -2,7 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Effect, Schema } from 'effect';
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Schema from 'effect/Schema';
6
7
 
7
8
  import { type SpaceId } from '@dxos/client/echo';
8
9
  import { runAndForwardErrors } from '@dxos/effect';
@@ -10,6 +11,9 @@ import { runAndForwardErrors } from '@dxos/effect';
10
11
  import type { FunctionContext, FunctionDefinition } from '../handler';
11
12
  import type { ServiceContainer, Services } from '../services';
12
13
 
14
+ /**
15
+ * @deprecated Use `FunctionInvocationService`
16
+ */
13
17
  export class FunctionExecutor {
14
18
  constructor(private readonly _services: ServiceContainer) {}
15
19
 
package/src/handler.ts CHANGED
@@ -2,17 +2,21 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type Context, Effect, Schema, type Types } from 'effect';
5
+ import type * as Context from 'effect/Context';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Schema from 'effect/Schema';
8
+ import type * as Types from 'effect/Types';
6
9
 
7
10
  import { Obj, Type } from '@dxos/echo';
11
+ import { type HasId } from '@dxos/echo/internal';
8
12
  import { type EchoDatabase } from '@dxos/echo-db';
9
- import { type HasId } from '@dxos/echo-schema';
10
13
  import { assertArgument } from '@dxos/invariant';
11
14
  import { type DXN, type SpaceId } from '@dxos/keys';
12
15
  import { type QueryResult } from '@dxos/protocols';
13
16
 
14
17
  import { FunctionType } from './schema';
15
18
  import { type Services } from './services';
19
+ import { getUserFunctionIdInMetadata, setUserFunctionIdInMetadata } from './url';
16
20
 
17
21
  // TODO(burdon): Model after http request. Ref Lambda/OpenFaaS.
18
22
  // https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
@@ -90,19 +94,30 @@ const typeId = Symbol.for('@dxos/functions/FunctionDefinition');
90
94
 
91
95
  export type FunctionDefinition<T = any, O = any> = {
92
96
  [typeId]: true;
93
- // TODO(dmaretskyi): Use `key` for FQN and `name` for human-readable-name.
94
97
  key: string;
95
98
  name: string;
96
99
  description?: string;
97
100
  inputSchema: Schema.Schema<T, any>;
98
101
  outputSchema?: Schema.Schema<O, any>;
99
102
  handler: FunctionHandler<T, O>;
103
+ meta?: {
104
+ /**
105
+ * Tools that are projected from functions have this annotation.
106
+ *
107
+ * deployedFunctionId:
108
+ * - Backend deployment ID assigned by the EDGE function service (typically a UUID).
109
+ * - Used for remote invocation via `FunctionInvocationService` → `RemoteFunctionExecutionService`.
110
+ * - Persisted on the corresponding ECHO `FunctionType` object's metadata under the
111
+ * `FUNCTIONS_META_KEY` and retrieved with `getUserFunctionIdInMetadata`.
112
+ */
113
+ deployedFunctionId?: string;
114
+ };
100
115
  };
101
116
 
117
+ // TODO(dmaretskyi): Output type doesn't get typechecked.
102
118
  export const defineFunction: {
103
119
  <I, O>(params: {
104
- // TODO(dmaretskyi): Make `key` required.
105
- key?: string;
120
+ key: string;
106
121
  name: string;
107
122
  description?: string;
108
123
  inputSchema: Schema.Schema<I, any>;
@@ -148,7 +163,7 @@ export const defineFunction: {
148
163
 
149
164
  return {
150
165
  [typeId]: true,
151
- key: key ?? name,
166
+ key,
152
167
  name,
153
168
  description,
154
169
  inputSchema,
@@ -177,15 +192,20 @@ export declare namespace FunctionDefinition {
177
192
  export type Output<T extends FunctionDefinition> = T extends FunctionDefinition<any, infer O> ? O : never;
178
193
  }
179
194
 
180
- export const serializeFunction = (functionDef: FunctionDefinition<any, any>): FunctionType =>
181
- Obj.make(FunctionType, {
182
- key: functionDef.name,
195
+ export const serializeFunction = (functionDef: FunctionDefinition<any, any>): FunctionType => {
196
+ const fn = Obj.make(FunctionType, {
197
+ key: functionDef.key,
183
198
  name: functionDef.name,
184
199
  version: '0.1.0',
185
200
  description: functionDef.description,
186
201
  inputSchema: Type.toJsonSchema(functionDef.inputSchema),
187
202
  outputSchema: !functionDef.outputSchema ? undefined : Type.toJsonSchema(functionDef.outputSchema),
188
203
  });
204
+ if (functionDef.meta?.deployedFunctionId) {
205
+ setUserFunctionIdInMetadata(Obj.getMeta(fn), functionDef.meta.deployedFunctionId);
206
+ }
207
+ return fn;
208
+ };
189
209
 
190
210
  export const deserializeFunction = (functionObj: FunctionType): FunctionDefinition<unknown, unknown> => {
191
211
  return {
@@ -196,6 +216,10 @@ export const deserializeFunction = (functionObj: FunctionType): FunctionDefiniti
196
216
  description: functionObj.description,
197
217
  inputSchema: !functionObj.inputSchema ? Schema.Unknown : Type.toEffectSchema(functionObj.inputSchema),
198
218
  outputSchema: !functionObj.outputSchema ? undefined : Type.toEffectSchema(functionObj.outputSchema),
219
+ // TODO(dmaretskyi): This should throw error.
199
220
  handler: () => {},
221
+ meta: {
222
+ deployedFunctionId: getUserFunctionIdInMetadata(Obj.getMeta(functionObj)),
223
+ },
200
224
  };
201
225
  };
package/src/schema.ts CHANGED
@@ -2,12 +2,14 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
 
7
7
  import { Type } from '@dxos/echo';
8
- import { JsonSchemaType, LabelAnnotation, Ref } from '@dxos/echo-schema';
8
+ import { JsonSchemaType, LabelAnnotation, Ref } from '@dxos/echo/internal';
9
9
  import { DataType } from '@dxos/schema';
10
10
 
11
+ // TODO(burdon): Create namespace and remove Type suffix.
12
+
11
13
  /**
12
14
  * Source script.
13
15
  */
@@ -65,4 +67,5 @@ export const FunctionType = Schema.Struct({
65
67
  }),
66
68
  LabelAnnotation.set(['name']),
67
69
  );
70
+
68
71
  export interface FunctionType extends Schema.Schema.Type<typeof FunctionType> {}
@@ -2,8 +2,13 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { HttpClient, HttpClientRequest } from '@effect/platform';
6
- import { type Config, Context, Effect, Layer, Redacted } from 'effect';
5
+ import * as HttpClient from '@effect/platform/HttpClient';
6
+ import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
7
+ import type * as Config from 'effect/Config';
8
+ import * as Context from 'effect/Context';
9
+ import * as Effect from 'effect/Effect';
10
+ import * as Layer from 'effect/Layer';
11
+ import * as Redacted from 'effect/Redacted';
7
12
 
8
13
  import { Query } from '@dxos/echo';
9
14
  import { DataType } from '@dxos/schema';
@@ -93,6 +98,7 @@ export class CredentialsService extends Context.Tag('@dxos/functions/Credentials
93
98
  if (credentials.length === 0) {
94
99
  throw new Error(`Credential not found for service: ${query.service}`);
95
100
  }
101
+
96
102
  return credentials[0];
97
103
  },
98
104
  queryCredentials: async (query) => {
@@ -120,6 +126,7 @@ export class ConfiguredCredentialsService implements Context.Tag.Service<Credent
120
126
  if (!credential) {
121
127
  throw new Error(`Credential not found for service: ${query.service}`);
122
128
  }
129
+
123
130
  return credential;
124
131
  }
125
132
  }
@@ -2,7 +2,11 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Context, Effect, Layer, Option, type Schema } from 'effect';
5
+ import * as Context from 'effect/Context';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Layer from 'effect/Layer';
8
+ import * as Option from 'effect/Option';
9
+ import type * as Schema from 'effect/Schema';
6
10
 
7
11
  import {
8
12
  type Filter,
@@ -14,9 +18,9 @@ import {
14
18
  type Relation,
15
19
  type Type,
16
20
  } from '@dxos/echo';
21
+ import type { EchoSchema } from '@dxos/echo/internal';
17
22
  import type { EchoDatabase, FlushOptions, OneShotQueryResult, QueryResult, SchemaRegistryQuery } from '@dxos/echo-db';
18
23
  import type { SchemaRegistryPreparedQuery } from '@dxos/echo-db';
19
- import type { EchoSchema } from '@dxos/echo-schema';
20
24
  import { promiseWithCauseCapture } from '@dxos/effect';
21
25
  import { invariant } from '@dxos/invariant';
22
26
  import type { DXN } from '@dxos/keys';
@@ -2,7 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Context, Effect, Layer, Schema } from 'effect';
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
 
7
10
  import { Obj, Type } from '@dxos/echo';
8
11
  import { invariant } from '@dxos/invariant';
@@ -0,0 +1,81 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { describe, expect, it } from '@effect/vitest';
6
+ import * as Effect from 'effect/Effect';
7
+ import * as Layer from 'effect/Layer';
8
+ import * as Schema from 'effect/Schema';
9
+
10
+ import { AiService } from '@dxos/ai';
11
+
12
+ import { defineFunction } from '../handler';
13
+ import { TestDatabaseLayer } from '../testing';
14
+
15
+ import { FunctionInvocationService } from './function-invocation-service';
16
+ import { FunctionImplementationResolver } from './local-function-execution';
17
+
18
+ const TestLayer = Layer.mergeAll(AiService.model('@anthropic/claude-opus-4-0')).pipe(
19
+ Layer.provideMerge(
20
+ Layer.mergeAll(
21
+ TestDatabaseLayer({
22
+ indexing: { vector: true },
23
+ types: [],
24
+ }),
25
+ FunctionInvocationService.layer,
26
+ ),
27
+ ),
28
+ );
29
+
30
+ describe('FunctionInvocationService', () => {
31
+ it(
32
+ 'should be defined',
33
+ Effect.fnUntraced(function* () {
34
+ const service = yield* FunctionInvocationService;
35
+ expect(service).toBeDefined();
36
+ }, Effect.provide(TestLayer)),
37
+ );
38
+
39
+ it(
40
+ 'routes to local when implementation is available',
41
+ Effect.fnUntraced(function* () {
42
+ const add = defineFunction({
43
+ key: 'example.org/function/add',
44
+ name: 'add',
45
+ inputSchema: Schema.Struct({ a: Schema.Number, b: Schema.Number }),
46
+ outputSchema: Schema.Number,
47
+ handler: ({ data }) => data.a + data.b,
48
+ });
49
+
50
+ const layer = TestLayer.pipe(Layer.provideMerge(FunctionImplementationResolver.layerTest({ functions: [add] })));
51
+
52
+ const result = yield* Effect.gen(function* () {
53
+ return yield* FunctionInvocationService.invokeFunction(add, { a: 2, b: 3 });
54
+ }).pipe(Effect.provide(layer));
55
+
56
+ expect(result).toEqual(5);
57
+ }),
58
+ );
59
+
60
+ it(
61
+ 'routes to remote when no local implementation is found',
62
+ Effect.fnUntraced(function* () {
63
+ // This function is not deployed, so mock layer will be used.
64
+ const echo = defineFunction({
65
+ key: 'example.org/function/echo',
66
+ name: 'function-that-is-deployed',
67
+ inputSchema: Schema.Unknown,
68
+ outputSchema: Schema.Unknown,
69
+ handler: () => {},
70
+ });
71
+
72
+ // No resolver provided → resolveFunctionImplementation will fail → remote path is used.
73
+ const result = yield* Effect.gen(function* () {
74
+ return yield* FunctionInvocationService.invokeFunction(echo, { hello: 'world' });
75
+ }).pipe(Effect.provide(TestLayer));
76
+
77
+ // RemoteFunctionExecutionService.mock echos input back.
78
+ expect(result).toEqual({ hello: 'world', resolved: 'remote' });
79
+ }),
80
+ );
81
+ });
@@ -0,0 +1,84 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+ import * as Context from 'effect/Context';
5
+ import * as Effect from 'effect/Effect';
6
+ import * as Layer from 'effect/Layer';
7
+
8
+ import { AiService } from '@dxos/ai';
9
+
10
+ import { type FunctionDefinition } from '../handler';
11
+
12
+ import { CredentialsService } from './credentials';
13
+ import { DatabaseService } from './database';
14
+ import {
15
+ FunctionImplementationResolver,
16
+ type InvocationServices,
17
+ LocalFunctionExecutionService,
18
+ } from './local-function-execution';
19
+ import { QueueService } from './queues';
20
+ import { RemoteFunctionExecutionService } from './remote-function-execution-service';
21
+
22
+ export class FunctionInvocationService extends Context.Tag('@dxos/functions/FunctionInvocationService')<
23
+ FunctionInvocationService,
24
+ {
25
+ invokeFunction<I, O>(functionDef: FunctionDefinition<I, O>, input: I): Effect.Effect<O, never, InvocationServices>;
26
+ }
27
+ >() {
28
+ static invokeFunction = Effect.serviceFunctionEffect(FunctionInvocationService, (_) => _.invokeFunction);
29
+
30
+ static layer = Layer.effect(
31
+ FunctionInvocationService,
32
+ Effect.gen(function* () {
33
+ const localExecutioner = yield* LocalFunctionExecutionService;
34
+ const remoteExecutioner = yield* RemoteFunctionExecutionService;
35
+
36
+ return {
37
+ invokeFunction: <I, O>(
38
+ functionDef: FunctionDefinition<I, O>,
39
+ input: I,
40
+ ): Effect.Effect<O, never, InvocationServices> =>
41
+ Effect.gen(function* () {
42
+ if (functionDef.meta?.deployedFunctionId) {
43
+ return yield* remoteExecutioner.callFunction<I, O>(functionDef.meta.deployedFunctionId, input);
44
+ }
45
+
46
+ return yield* localExecutioner.invokeFunction(functionDef, input);
47
+ }),
48
+ } satisfies Context.Tag.Service<FunctionInvocationService>;
49
+ }),
50
+ );
51
+
52
+ // TODO(dmaretskyi): Don't provide `FunctionImplementationResolver`.
53
+ static layerTest = ({
54
+ functions = [],
55
+ }: {
56
+ functions?: FunctionDefinition<any, any>[];
57
+ } = {}): Layer.Layer<
58
+ FunctionInvocationService,
59
+ never,
60
+ AiService.AiService | CredentialsService | DatabaseService | QueueService
61
+ > =>
62
+ FunctionInvocationService.layer.pipe(
63
+ Layer.provide(LocalFunctionExecutionService.layerLive),
64
+ Layer.provide(FunctionImplementationResolver.layerTest({ functions })),
65
+ Layer.provide(RemoteFunctionExecutionService.layerMock),
66
+ );
67
+
68
+ // TODO(dmaretskyi): This shouldn't default to all services being not available.
69
+ // TODO(dmaretskyi): Don't provide `FunctionImplementationResolver`.
70
+ /**
71
+ * @deprecated Use {@link layerTest} instead.
72
+ */
73
+ static layerTestMocked = ({
74
+ functions,
75
+ }: {
76
+ functions: FunctionDefinition<any, any>[];
77
+ }): Layer.Layer<FunctionInvocationService> =>
78
+ FunctionInvocationService.layerTest({ functions }).pipe(
79
+ Layer.provide(AiService.notAvailable),
80
+ Layer.provide(CredentialsService.configuredLayer([])),
81
+ Layer.provide(DatabaseService.notAvailable),
82
+ Layer.provide(QueueService.notAvailable),
83
+ );
84
+ }
@@ -2,11 +2,12 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ export * from './credentials';
5
6
  export * from './database';
7
+ export * from './event-logger';
8
+ export * from './function-invocation-service';
9
+ export * from './local-function-execution';
6
10
  export * from './queues';
7
11
  export * from './service-container';
8
- export * from './credentials';
9
12
  export * from './tracing';
10
- export * from './event-logger';
11
13
  export * from './remote-function-execution-service';
12
- export * from './local-function-execution';