@dxos/functions 0.8.4-main.9735255 → 0.8.4-main.9be5663bfe

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 (96) hide show
  1. package/README.md +4 -6
  2. package/dist/lib/neutral/Trace.mjs +38 -0
  3. package/dist/lib/neutral/Trace.mjs.map +7 -0
  4. package/dist/lib/neutral/chunk-5T7GOX2V.mjs +149 -0
  5. package/dist/lib/neutral/chunk-5T7GOX2V.mjs.map +7 -0
  6. package/dist/lib/neutral/chunk-IVDUS56O.mjs +49 -0
  7. package/dist/lib/neutral/chunk-IVDUS56O.mjs.map +7 -0
  8. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  9. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  10. package/dist/lib/neutral/fib-N45KAC7C.mjs +23 -0
  11. package/dist/lib/neutral/fib-N45KAC7C.mjs.map +7 -0
  12. package/dist/lib/{browser → neutral}/index.mjs +670 -633
  13. package/dist/lib/neutral/index.mjs.map +7 -0
  14. package/dist/lib/neutral/meta.json +1 -0
  15. package/dist/lib/neutral/reply-EUEPKNJF.mjs +19 -0
  16. package/dist/lib/neutral/reply-EUEPKNJF.mjs.map +7 -0
  17. package/dist/lib/neutral/sleep-PUK3D4FF.mjs +15 -0
  18. package/dist/lib/neutral/sleep-PUK3D4FF.mjs.map +7 -0
  19. package/dist/types/src/Trace.d.ts +155 -0
  20. package/dist/types/src/Trace.d.ts.map +1 -0
  21. package/dist/types/src/errors.d.ts.map +1 -1
  22. package/dist/types/src/example/definitions.d.ts +11 -0
  23. package/dist/types/src/example/definitions.d.ts.map +1 -0
  24. package/dist/types/src/example/fib.d.ts +3 -2
  25. package/dist/types/src/example/fib.d.ts.map +1 -1
  26. package/dist/types/src/example/index.d.ts +3 -11
  27. package/dist/types/src/example/index.d.ts.map +1 -1
  28. package/dist/types/src/example/reply.d.ts +2 -1
  29. package/dist/types/src/example/reply.d.ts.map +1 -1
  30. package/dist/types/src/example/sleep.d.ts +3 -2
  31. package/dist/types/src/example/sleep.d.ts.map +1 -1
  32. package/dist/types/src/index.d.ts +4 -0
  33. package/dist/types/src/index.d.ts.map +1 -1
  34. package/dist/types/src/process/Process.d.ts +247 -0
  35. package/dist/types/src/process/Process.d.ts.map +1 -0
  36. package/dist/types/src/process/ServiceResolver.d.ts +74 -0
  37. package/dist/types/src/process/ServiceResolver.d.ts.map +1 -0
  38. package/dist/types/src/process/StorageService.d.ts +58 -0
  39. package/dist/types/src/process/StorageService.d.ts.map +1 -0
  40. package/dist/types/src/protocol/protocol.d.ts +2 -2
  41. package/dist/types/src/protocol/protocol.d.ts.map +1 -1
  42. package/dist/types/src/sdk.d.ts +4 -104
  43. package/dist/types/src/sdk.d.ts.map +1 -1
  44. package/dist/types/src/services/event-logger.d.ts +4 -4
  45. package/dist/types/src/services/function-invocation-service.d.ts +6 -5
  46. package/dist/types/src/services/function-invocation-service.d.ts.map +1 -1
  47. package/dist/types/src/services/queues.d.ts +5 -2
  48. package/dist/types/src/services/queues.d.ts.map +1 -1
  49. package/dist/types/src/services/tracing.d.ts +25 -2
  50. package/dist/types/src/services/tracing.d.ts.map +1 -1
  51. package/dist/types/src/types/Script.d.ts +4 -3
  52. package/dist/types/src/types/Script.d.ts.map +1 -1
  53. package/dist/types/src/types/Trigger.d.ts +8 -9
  54. package/dist/types/src/types/Trigger.d.ts.map +1 -1
  55. package/dist/types/src/types/TriggerEvent.d.ts +3 -2
  56. package/dist/types/src/types/TriggerEvent.d.ts.map +1 -1
  57. package/dist/types/src/types/index.d.ts +0 -1
  58. package/dist/types/src/types/index.d.ts.map +1 -1
  59. package/dist/types/src/types/url.d.ts +2 -2
  60. package/dist/types/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +25 -20
  62. package/src/Trace.ts +202 -0
  63. package/src/errors.ts +1 -1
  64. package/src/example/definitions.ts +49 -0
  65. package/src/example/fib.ts +15 -24
  66. package/src/example/forex-effect.ts +1 -1
  67. package/src/example/index.ts +7 -8
  68. package/src/example/reply.ts +11 -13
  69. package/src/example/sleep.ts +9 -16
  70. package/src/index.ts +4 -0
  71. package/src/process/Process.ts +486 -0
  72. package/src/process/ServiceResolver.ts +174 -0
  73. package/src/process/StorageService.ts +99 -0
  74. package/src/protocol/protocol.test.ts +0 -1
  75. package/src/protocol/protocol.ts +33 -28
  76. package/src/sdk.ts +6 -256
  77. package/src/services/event-logger.ts +1 -1
  78. package/src/services/function-invocation-service.ts +7 -5
  79. package/src/services/queues.ts +11 -2
  80. package/src/services/tracing.ts +35 -2
  81. package/src/types/Script.ts +7 -3
  82. package/src/types/Trigger.ts +17 -6
  83. package/src/types/TriggerEvent.ts +2 -2
  84. package/src/types/index.ts +0 -1
  85. package/src/types/url.ts +2 -2
  86. package/dist/lib/browser/index.mjs.map +0 -7
  87. package/dist/lib/browser/meta.json +0 -1
  88. package/dist/lib/node-esm/index.mjs +0 -1230
  89. package/dist/lib/node-esm/index.mjs.map +0 -7
  90. package/dist/lib/node-esm/meta.json +0 -1
  91. package/dist/types/src/operation-compatibility.test.d.ts +0 -2
  92. package/dist/types/src/operation-compatibility.test.d.ts.map +0 -1
  93. package/dist/types/src/types/Function.d.ts +0 -52
  94. package/dist/types/src/types/Function.d.ts.map +0 -1
  95. package/src/operation-compatibility.test.ts +0 -185
  96. package/src/types/Function.ts +0 -82
@@ -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
+ };
@@ -7,7 +7,6 @@ import { describe, test } from 'vitest';
7
7
  import { FunctionError } from '../errors';
8
8
  import fibFunc from '../example/fib';
9
9
  import replyFunc from '../example/reply';
10
-
11
10
  import { wrapFunctionHandler } from './protocol';
12
11
 
13
12
  describe('wrapFunctionHandler', () => {
@@ -11,40 +11,45 @@ import * as SchemaAST from 'effect/SchemaAST';
11
11
  import { AiModelResolver, AiService } from '@dxos/ai';
12
12
  import { AnthropicResolver } from '@dxos/ai/resolvers';
13
13
  import { LifecycleState, Resource } from '@dxos/context';
14
- import { Database, Ref, Type } from '@dxos/echo';
14
+ import { Database, Feed, JsonSchema, Ref, type Type } from '@dxos/echo';
15
+ import { EchoClient, type EchoDatabaseImpl, type QueueFactory, createFeedServiceLayer } from '@dxos/echo-db';
15
16
  import { refFromEncodedReference } from '@dxos/echo/internal';
16
- import { EchoClient, type EchoDatabaseImpl, type QueueFactory } from '@dxos/echo-db';
17
17
  import { runAndForwardErrors } from '@dxos/effect';
18
18
  import { assertState, failedInvariant, invariant } from '@dxos/invariant';
19
19
  import { PublicKey } from '@dxos/keys';
20
+ import { Operation } from '@dxos/operation';
20
21
  import { type FunctionProtocol } from '@dxos/protocols';
21
22
 
22
23
  import { FunctionError } from '../errors';
23
- import { FunctionDefinition, type FunctionServices } from '../sdk';
24
+ import { type FunctionServices } from '../sdk';
24
25
  import { CredentialsService, FunctionInvocationService, QueueService, TracingService } from '../services';
25
-
26
+ import * as Trace from '../Trace';
26
27
  import { FunctionsAiHttpClient } from './functions-ai-http-client';
27
28
 
28
29
  /**
29
30
  * Wraps a function handler made with `defineFunction` to a protocol that the functions-runtime expects.
30
31
  */
31
- export const wrapFunctionHandler = (func: FunctionDefinition): FunctionProtocol.Func => {
32
- if (!FunctionDefinition.isFunction(func)) {
33
- throw new TypeError('Invalid function definition');
32
+ export const wrapFunctionHandler = (func: Operation.WithHandler<Operation.Definition.Any>): FunctionProtocol.Func => {
33
+ if (!Operation.isOperationWithHandler(func)) {
34
+ throw new TypeError('Expected operation with handler');
34
35
  }
35
36
 
37
+ const serviceTags = func.services.map((service) => service.key);
38
+
36
39
  return {
37
40
  meta: {
38
- key: func.key,
39
- name: func.name,
40
- description: func.description,
41
- inputSchema: Type.toJsonSchema(func.inputSchema),
42
- outputSchema: func.outputSchema === undefined ? undefined : Type.toJsonSchema(func.outputSchema),
43
- services: func.services,
41
+ key: func.meta.key,
42
+ name: func.meta.name,
43
+ description: func.meta.description,
44
+ inputSchema: JsonSchema.toJsonSchema(func.input),
45
+ outputSchema: func.output === undefined ? undefined : JsonSchema.toJsonSchema(func.output),
46
+ services: func.services.map((service) => service.key),
44
47
  },
45
48
  handler: async ({ data, context }) => {
46
49
  if (
47
- (func.services.includes(Database.Service.key) || func.services.includes(QueueService.key)) &&
50
+ (serviceTags.includes(Database.Service.key) ||
51
+ serviceTags.includes(QueueService.key) ||
52
+ serviceTags.includes(Feed.FeedService.key)) &&
48
53
  (!context.services.dataService || !context.services.queryService)
49
54
  ) {
50
55
  throw new FunctionError({
@@ -54,9 +59,9 @@ export const wrapFunctionHandler = (func: FunctionDefinition): FunctionProtocol.
54
59
 
55
60
  // eslint-disable-next-line no-useless-catch
56
61
  try {
57
- if (!SchemaAST.isAnyKeyword(func.inputSchema.ast)) {
62
+ if (!SchemaAST.isAnyKeyword(func.input.ast)) {
58
63
  try {
59
- Schema.validateSync(func.inputSchema)(data);
64
+ Schema.validateSync(func.input)(data);
60
65
  } catch (error) {
61
66
  throw new FunctionError({ message: 'Invalid input schema', cause: error });
62
67
  }
@@ -66,19 +71,15 @@ export const wrapFunctionHandler = (func: FunctionDefinition): FunctionProtocol.
66
71
 
67
72
  if (func.types.length > 0) {
68
73
  invariant(funcContext.db, 'Database is required for functions with types');
69
- await funcContext.db.graph.schemaRegistry.register(func.types as Type.Entity.Any[]);
74
+ await funcContext.db.graph.schemaRegistry.register(func.types as Type.AnyEntity[]);
70
75
  }
71
76
 
72
77
  const dataWithDecodedRefs =
73
- funcContext.db && !SchemaAST.isAnyKeyword(func.inputSchema.ast)
74
- ? decodeRefsFromSchema(func.inputSchema.ast, data, funcContext.db)
78
+ funcContext.db && !SchemaAST.isAnyKeyword(func.input.ast)
79
+ ? decodeRefsFromSchema(func.input.ast, data, funcContext.db)
75
80
  : data;
76
81
 
77
- let result = await func.handler({
78
- // TODO(dmaretskyi): Fix the types.
79
- context: context as any,
80
- data: dataWithDecodedRefs,
81
- });
82
+ let result: any = await func.handler(dataWithDecodedRefs);
82
83
 
83
84
  if (Effect.isEffect(result)) {
84
85
  result = await runAndForwardErrors(
@@ -89,8 +90,8 @@ export const wrapFunctionHandler = (func: FunctionDefinition): FunctionProtocol.
89
90
  );
90
91
  }
91
92
 
92
- if (func.outputSchema && !SchemaAST.isAnyKeyword(func.outputSchema.ast)) {
93
- Schema.validateSync(func.outputSchema)(result);
93
+ if (func.output && !SchemaAST.isAnyKeyword(func.output.ast)) {
94
+ Schema.validateSync(func.output)(result);
94
95
  }
95
96
 
96
97
  return result;
@@ -149,8 +150,9 @@ class FunctionContext extends Resource {
149
150
  createLayer(): Layer.Layer<FunctionServices> {
150
151
  assertState(this._lifecycleState === LifecycleState.OPEN, 'FunctionContext is not open');
151
152
 
152
- const dbLayer = this.db ? Database.Service.layer(this.db) : Database.Service.notAvailable;
153
+ const dbLayer = this.db ? Database.layer(this.db) : Database.notAvailable;
153
154
  const queuesLayer = this.queues ? QueueService.layer(this.queues) : QueueService.notAvailable;
155
+ const feedLayer = this.queues ? createFeedServiceLayer(this.queues) : Feed.notAvailable;
154
156
  const credentials = dbLayer
155
157
  ? CredentialsService.layerFromDatabase({ caching: true }).pipe(Layer.provide(dbLayer))
156
158
  : CredentialsService.configuredLayer([]);
@@ -173,12 +175,15 @@ class FunctionContext extends Resource {
173
175
  : AiService.notAvailable;
174
176
 
175
177
  return Layer.mergeAll(
176
- dbLayer, //
178
+ dbLayer,
177
179
  queuesLayer,
180
+ feedLayer,
178
181
  credentials,
179
182
  functionInvocationService,
180
183
  aiLayer,
181
184
  tracing,
185
+ // TODO(dmaretskyi): Forward trace events.
186
+ Trace.writerLayerNoop,
182
187
  );
183
188
  }
184
189
  }
package/src/sdk.ts CHANGED
@@ -2,15 +2,9 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import type * as Context from 'effect/Context';
6
- import * as Effect from 'effect/Effect';
7
- import * as Schema from 'effect/Schema';
8
-
9
5
  import { type AiService } from '@dxos/ai';
10
- import { Obj, Type } from '@dxos/echo';
11
- import { type Database } from '@dxos/echo';
12
- import { assertArgument, failedInvariant } from '@dxos/invariant';
13
- import { Operation } from '@dxos/operation';
6
+ import { type Database, type Feed } from '@dxos/echo';
7
+ import { type Trace } from '@dxos/functions';
14
8
 
15
9
  import {
16
10
  type CredentialsService,
@@ -18,8 +12,6 @@ import {
18
12
  type QueueService,
19
13
  type TracingService,
20
14
  } from './services';
21
- import { Function } from './types';
22
- import { getUserFunctionIdInMetadata, setUserFunctionIdInMetadata } from './types';
23
15
 
24
16
  // TODO(burdon): Model after http request. Ref Lambda/OpenFaaS.
25
17
  // https://docs.aws.amazon.com/lambda/latest/dg/typescript-handler.html
@@ -33,257 +25,15 @@ export type InvocationServices = TracingService;
33
25
 
34
26
  /**
35
27
  * Services that are available to invoked functions.
28
+ * @deprecated
36
29
  */
37
30
  export type FunctionServices =
38
31
  | InvocationServices
39
32
  | AiService.AiService
40
33
  | CredentialsService
41
34
  | Database.Service
35
+ // TODO(wittjosiah): Remove QueueService — use Feed.FeedService instead.
42
36
  | QueueService
37
+ | Feed.FeedService
38
+ | Trace.TraceService
43
39
  | FunctionInvocationService;
44
-
45
- /**
46
- * Function handler.
47
- */
48
- export type FunctionHandler<TData = {}, TOutput = any, S extends FunctionServices = FunctionServices> = (params: {
49
- /**
50
- * Context available to the function.
51
- */
52
- context: FunctionContext;
53
-
54
- /**
55
- * Data passed as the input to the function.
56
- * Must match the function's input schema.
57
- * This will be the payload from the trigger or other data passed into the function in a workflow.
58
- */
59
- data: TData;
60
- }) => TOutput | Promise<TOutput> | Effect.Effect<TOutput, any, S>;
61
-
62
- /**
63
- * Function context.
64
- */
65
- export interface FunctionContext {
66
- // TODO(dmaretskyi): Consider what we should put into context.
67
- }
68
-
69
- const typeId = Symbol.for('@dxos/functions/FunctionDefinition');
70
-
71
- export type FunctionDefinition<T = any, O = any, S extends FunctionServices = FunctionServices> = {
72
- [typeId]: true;
73
- key: string;
74
- name: string;
75
- description?: string;
76
- inputSchema: Schema.Schema<T, any>;
77
- outputSchema?: Schema.Schema<O, any>;
78
-
79
- /**
80
- * List of types the function uses.
81
- * This is used to ensure that the types are available when the function is executed.
82
- */
83
- types: readonly Type.Entity.Any[];
84
-
85
- /**
86
- * Keys of the required services.
87
- */
88
- services: readonly string[];
89
-
90
- handler: FunctionHandler<T, O, S>;
91
- meta?: {
92
- /**
93
- * Tools that are projected from functions have this annotation.
94
- *
95
- * deployedFunctionId:
96
- * - Backend deployment ID assigned by the EDGE function service (typically a UUID).
97
- * - Used for remote invocation via `FunctionInvocationService` → `RemoteFunctionExecutionService`.
98
- * - Persisted on the corresponding ECHO `Function.Function` object's metadata under the
99
- * `FUNCTIONS_META_KEY` and retrieved with `getUserFunctionIdInMetadata`.
100
- */
101
- deployedFunctionId?: string;
102
- };
103
- };
104
-
105
- export declare namespace FunctionDefinition {
106
- export type Any = FunctionDefinition<any, any, any>;
107
- export type Input<T extends Any> = T extends FunctionDefinition<infer I, infer _O, infer _S> ? I : never;
108
- export type Output<T extends Any> = T extends FunctionDefinition<infer _I, infer O, infer _S> ? O : never;
109
- export type Services<T extends Any> = T extends FunctionDefinition<infer _I, infer _O, infer S> ? S : never;
110
- }
111
-
112
- export type FunctionProps<T, O> = {
113
- key: string;
114
- name: string;
115
- description?: string;
116
- inputSchema: Schema.Schema<T, any>;
117
- outputSchema?: Schema.Schema<O, any>;
118
-
119
- /**
120
- * List of types the function uses.
121
- * This is used to ensure that the types are available when the function is executed.
122
- */
123
- types?: readonly Type.Entity.Any[];
124
- // TODO(dmaretskyi): This currently doesn't cause a compile-time error if the handler requests a service that is not specified
125
- services?: readonly Context.Tag<any, any>[];
126
-
127
- handler: FunctionHandler<T, O, FunctionServices>;
128
- };
129
-
130
- // TODO(dmaretskyi): Output type doesn't get typechecked.
131
- export const defineFunction: {
132
- <I, O>(params: FunctionProps<I, O>): FunctionDefinition<I, O, FunctionServices>;
133
- } = ({ key, name, description, inputSchema, outputSchema = Schema.Any, handler, types, services }) => {
134
- if (!Schema.isSchema(inputSchema)) {
135
- throw new Error('Input schema must be a valid schema');
136
- }
137
- if (typeof handler !== 'function') {
138
- throw new Error('Handler must be a function');
139
- }
140
-
141
- // Captures the function definition location.
142
- const limit = Error.stackTraceLimit;
143
- Error.stackTraceLimit = 2;
144
- const traceError = new Error();
145
- Error.stackTraceLimit = limit;
146
- let cache: false | string = false;
147
- const captureStackTrace = () => {
148
- if (cache !== false) {
149
- return cache;
150
- }
151
- if (traceError.stack !== undefined) {
152
- const stack = traceError.stack.split('\n');
153
- if (stack[2] !== undefined) {
154
- cache = stack[2].trim();
155
- return cache;
156
- }
157
- }
158
- };
159
-
160
- const handlerWithSpan = (...args: any[]) => {
161
- const result = (handler as any)(...args);
162
- if (Effect.isEffect(result)) {
163
- return Effect.withSpan(result, `${key ?? name}`, {
164
- captureStackTrace,
165
- });
166
- }
167
- return result;
168
- };
169
-
170
- return {
171
- [typeId]: true,
172
- key,
173
- name,
174
- description,
175
- inputSchema,
176
- outputSchema,
177
- handler: handlerWithSpan,
178
- types: types ?? [],
179
- services: !services ? [] : getServiceKeys(services),
180
- } satisfies FunctionDefinition.Any;
181
- };
182
-
183
- const getServiceKeys = (services: readonly Context.Tag<any, any>[]) => {
184
- return services.map((tag: any) => {
185
- if (typeof tag.key === 'string') {
186
- return tag.key;
187
- }
188
- failedInvariant();
189
- });
190
- };
191
-
192
- /**
193
- * Converts a FunctionDefinition to an OperationDefinition with handler.
194
- * The function handler is adapted to the OperationHandler format.
195
- *
196
- * Note: FunctionDefinition stores service keys as strings, not Tag types,
197
- * so we can't use Operation.withHandler's type inference here.
198
- */
199
- export const toOperation = <T, O, S extends FunctionServices = FunctionServices>(
200
- functionDef: FunctionDefinition<T, O, S>,
201
- ): Operation.Definition<T, O> & { handler: Operation.Handler<T, O, any, S> } => {
202
- const op = Operation.make({
203
- schema: {
204
- input: functionDef.inputSchema,
205
- output: functionDef.outputSchema ?? Schema.Any,
206
- },
207
- meta: {
208
- key: functionDef.key,
209
- name: functionDef.name,
210
- description: functionDef.description,
211
- },
212
- });
213
-
214
- // Adapt FunctionHandler signature to OperationHandler format.
215
- // FunctionHandler expects { context, data }, OperationHandler expects just input.
216
- const operationHandler: Operation.Handler<T, O, any, S> = (input: T) => {
217
- const result = functionDef.handler({
218
- context: {} as FunctionContext,
219
- data: input,
220
- });
221
-
222
- // Convert Promise or plain value to Effect.
223
- if (Effect.isEffect(result)) {
224
- return result;
225
- }
226
- if (result instanceof Promise) {
227
- return Effect.tryPromise(() => result);
228
- }
229
- return Effect.succeed(result as O);
230
- };
231
-
232
- // Manually attach handler since FunctionDefinition stores service keys as strings,
233
- // not Tag types, so withHandler's type inference doesn't apply.
234
- return {
235
- ...op,
236
- handler: operationHandler,
237
- };
238
- };
239
-
240
- export const FunctionDefinition = {
241
- make: defineFunction,
242
- isFunction: (value: unknown): value is FunctionDefinition.Any => {
243
- return typeof value === 'object' && value !== null && Symbol.for('@dxos/functions/FunctionDefinition') in value;
244
- },
245
- serialize: (functionDef: FunctionDefinition.Any): Function.Function => {
246
- assertArgument(FunctionDefinition.isFunction(functionDef), 'functionDef');
247
- return serializeFunction(functionDef);
248
- },
249
- deserialize: (functionObj: Function.Function): FunctionDefinition.Any => {
250
- assertArgument(Obj.instanceOf(Function.Function, functionObj), 'functionObj');
251
- return deserializeFunction(functionObj);
252
- },
253
- toOperation,
254
- };
255
-
256
- export const serializeFunction = (functionDef: FunctionDefinition.Any): Function.Function => {
257
- const fn = Function.make({
258
- key: functionDef.key,
259
- name: functionDef.name,
260
- version: '0.1.0',
261
- description: functionDef.description,
262
- inputSchema: Type.toJsonSchema(functionDef.inputSchema),
263
- outputSchema: !functionDef.outputSchema ? undefined : Type.toJsonSchema(functionDef.outputSchema),
264
- services: functionDef.services,
265
- });
266
- if (functionDef.meta?.deployedFunctionId) {
267
- Obj.change(fn, (fn) => setUserFunctionIdInMetadata(Obj.getMeta(fn), functionDef.meta!.deployedFunctionId!));
268
- }
269
- return fn;
270
- };
271
-
272
- export const deserializeFunction = (functionObj: Function.Function): FunctionDefinition<unknown, unknown> => {
273
- return {
274
- [typeId]: true,
275
- // TODO(dmaretskyi): Fix key.
276
- key: functionObj.key ?? functionObj.name,
277
- name: functionObj.name,
278
- description: functionObj.description,
279
- inputSchema: !functionObj.inputSchema ? Schema.Unknown : Type.toEffectSchema(functionObj.inputSchema),
280
- outputSchema: !functionObj.outputSchema ? undefined : Type.toEffectSchema(functionObj.outputSchema),
281
- // TODO(dmaretskyi): This should throw error.
282
- handler: () => {},
283
- services: functionObj.services ?? [],
284
- types: [],
285
- meta: {
286
- deployedFunctionId: getUserFunctionIdInMetadata(Obj.getMeta(functionObj)),
287
- },
288
- };
289
- };
@@ -52,7 +52,7 @@ export type ComputeEventPayload = Schema.Schema.Type<typeof ComputeEventPayload>
52
52
 
53
53
  export const ComputeEvent = Schema.Struct({
54
54
  payload: ComputeEventPayload,
55
- }).pipe(Type.object({ typename: 'dxos.org/type/ComputeEvent', version: '0.1.0' }));
55
+ }).pipe(Type.object({ typename: 'org.dxos.type.computeEvent', version: '0.1.0' }));
56
56
 
57
57
  /**
58
58
  * Logs event for the compute workflows.
@@ -5,18 +5,20 @@ import * as Context from 'effect/Context';
5
5
  import * as Effect from 'effect/Effect';
6
6
  import * as Layer from 'effect/Layer';
7
7
 
8
+ import { Operation } from '@dxos/operation';
9
+
8
10
  import type { FunctionNotFoundError } from '../errors';
9
- import { type FunctionDefinition, type InvocationServices } from '../sdk';
11
+ import { type InvocationServices } from '../sdk';
10
12
 
11
13
  export class FunctionInvocationService extends Context.Tag('@dxos/functions/FunctionInvocationService')<
12
14
  FunctionInvocationService,
13
15
  {
14
16
  invokeFunction<I, O>(
15
- functionDef: FunctionDefinition<I, O, any>,
17
+ functionDef: Operation.Definition<I, O, any>,
16
18
  input: I,
17
19
  ): Effect.Effect<O, never, InvocationServices>;
18
20
 
19
- resolveFunction(key: string): Effect.Effect<FunctionDefinition.Any, FunctionNotFoundError>;
21
+ resolveFunction(key: string): Effect.Effect<Operation.Definition.Any, FunctionNotFoundError>;
20
22
  }
21
23
  >() {
22
24
  static layerNotAvailable = Layer.succeed(FunctionInvocationService, {
@@ -25,13 +27,13 @@ export class FunctionInvocationService extends Context.Tag('@dxos/functions/Func
25
27
  });
26
28
 
27
29
  static invokeFunction = <I, O>(
28
- functionDef: FunctionDefinition<I, O, any>,
30
+ functionDef: Operation.Definition<I, O, any>,
29
31
  input: I,
30
32
  ): Effect.Effect<O, never, FunctionInvocationService | InvocationServices> =>
31
33
  Effect.serviceFunctionEffect(FunctionInvocationService, (service) => service.invokeFunction)(functionDef, input);
32
34
 
33
35
  static resolveFunction = (
34
36
  key: string,
35
- ): Effect.Effect<FunctionDefinition.Any, FunctionNotFoundError, FunctionInvocationService> =>
37
+ ): Effect.Effect<Operation.Definition.Any, FunctionNotFoundError, FunctionInvocationService> =>
36
38
  Effect.serviceFunctionEffect(FunctionInvocationService, (service) => service.resolveFunction)(key);
37
39
  }
@@ -6,12 +6,13 @@ import * as Context from 'effect/Context';
6
6
  import * as Effect from 'effect/Effect';
7
7
  import * as Layer from 'effect/Layer';
8
8
 
9
- import type { Entity } from '@dxos/echo';
10
- import type { Queue, QueueAPI, QueueFactory } from '@dxos/echo-db';
9
+ import { type Entity } from '@dxos/echo';
10
+ import { createFeedServiceLayer, type Queue, type QueueAPI, type QueueFactory } from '@dxos/echo-db';
11
11
  import type { DXN, QueueSubspaceTag } from '@dxos/keys';
12
12
 
13
13
  /**
14
14
  * Gives access to all queues.
15
+ * @deprecated Use Feed.FeedService instead.
15
16
  */
16
17
  export class QueueService extends Context.Tag('@dxos/functions/QueueService')<
17
18
  QueueService,
@@ -71,6 +72,7 @@ export class QueueService extends Context.Tag('@dxos/functions/QueueService')<
71
72
 
72
73
  /**
73
74
  * Gives access to a specific queue passed as a context.
75
+ * @deprecated Use Feed.FeedService instead.
74
76
  */
75
77
  export class ContextQueueService extends Context.Tag('@dxos/functions/ContextQueueService')<
76
78
  ContextQueueService,
@@ -80,3 +82,10 @@ export class ContextQueueService extends Context.Tag('@dxos/functions/ContextQue
80
82
  >() {
81
83
  static layer = (queue: Queue) => Layer.succeed(ContextQueueService, { queue });
82
84
  }
85
+
86
+ export const feedServiceFromQueueServiceLayer = Layer.unwrapEffect(
87
+ Effect.gen(function* () {
88
+ const { queues } = yield* QueueService;
89
+ return createFeedServiceLayer(queues);
90
+ }),
91
+ );