@dxos/functions 0.8.4-main.e8ec1fe → 0.8.4-main.ef1bc66f44

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 (57) hide show
  1. package/dist/lib/{browser → neutral}/index.mjs +450 -148
  2. package/dist/lib/neutral/index.mjs.map +7 -0
  3. package/dist/lib/neutral/meta.json +1 -0
  4. package/dist/types/src/errors.d.ts +24 -32
  5. package/dist/types/src/errors.d.ts.map +1 -1
  6. package/dist/types/src/operation-compatibility.test.d.ts +2 -0
  7. package/dist/types/src/operation-compatibility.test.d.ts.map +1 -0
  8. package/dist/types/src/protocol/functions-ai-http-client.d.ts +12 -0
  9. package/dist/types/src/protocol/functions-ai-http-client.d.ts.map +1 -0
  10. package/dist/types/src/protocol/protocol.d.ts.map +1 -1
  11. package/dist/types/src/sdk.d.ts +27 -2
  12. package/dist/types/src/sdk.d.ts.map +1 -1
  13. package/dist/types/src/services/credentials.d.ts +6 -4
  14. package/dist/types/src/services/credentials.d.ts.map +1 -1
  15. package/dist/types/src/services/event-logger.d.ts +25 -31
  16. package/dist/types/src/services/event-logger.d.ts.map +1 -1
  17. package/dist/types/src/services/function-invocation-service.d.ts +5 -0
  18. package/dist/types/src/services/function-invocation-service.d.ts.map +1 -1
  19. package/dist/types/src/services/index.d.ts +0 -1
  20. package/dist/types/src/services/index.d.ts.map +1 -1
  21. package/dist/types/src/services/queues.d.ts +4 -4
  22. package/dist/types/src/services/queues.d.ts.map +1 -1
  23. package/dist/types/src/services/tracing.d.ts +37 -3
  24. package/dist/types/src/services/tracing.d.ts.map +1 -1
  25. package/dist/types/src/types/Function.d.ts +40 -46
  26. package/dist/types/src/types/Function.d.ts.map +1 -1
  27. package/dist/types/src/types/Script.d.ts +9 -16
  28. package/dist/types/src/types/Script.d.ts.map +1 -1
  29. package/dist/types/src/types/Trigger.d.ts +58 -76
  30. package/dist/types/src/types/Trigger.d.ts.map +1 -1
  31. package/dist/types/src/types/TriggerEvent.d.ts +43 -13
  32. package/dist/types/src/types/TriggerEvent.d.ts.map +1 -1
  33. package/dist/types/src/types/url.d.ts +4 -3
  34. package/dist/types/src/types/url.d.ts.map +1 -1
  35. package/dist/types/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +23 -17
  37. package/src/errors.ts +4 -4
  38. package/src/operation-compatibility.test.ts +185 -0
  39. package/src/protocol/functions-ai-http-client.ts +67 -0
  40. package/src/protocol/protocol.ts +184 -67
  41. package/src/sdk.ts +68 -5
  42. package/src/services/credentials.ts +31 -15
  43. package/src/services/event-logger.ts +2 -2
  44. package/src/services/function-invocation-service.ts +14 -0
  45. package/src/services/index.ts +0 -2
  46. package/src/services/queues.ts +5 -7
  47. package/src/services/tracing.ts +63 -4
  48. package/src/types/Function.ts +28 -8
  49. package/src/types/Script.ts +8 -7
  50. package/src/types/Trigger.ts +18 -14
  51. package/src/types/TriggerEvent.ts +29 -29
  52. package/src/types/url.ts +4 -3
  53. package/dist/lib/browser/index.mjs.map +0 -7
  54. package/dist/lib/browser/meta.json +0 -1
  55. package/dist/lib/node-esm/index.mjs +0 -928
  56. package/dist/lib/node-esm/index.mjs.map +0 -7
  57. package/dist/lib/node-esm/meta.json +0 -1
package/src/sdk.ts CHANGED
@@ -8,8 +8,9 @@ import * as Schema from 'effect/Schema';
8
8
 
9
9
  import { type AiService } from '@dxos/ai';
10
10
  import { Obj, Type } from '@dxos/echo';
11
- import { type DatabaseService } from '@dxos/echo-db';
11
+ import { type Database } from '@dxos/echo';
12
12
  import { assertArgument, failedInvariant } from '@dxos/invariant';
13
+ import { Operation } from '@dxos/operation';
13
14
 
14
15
  import {
15
16
  type CredentialsService,
@@ -37,7 +38,7 @@ export type FunctionServices =
37
38
  | InvocationServices
38
39
  | AiService.AiService
39
40
  | CredentialsService
40
- | DatabaseService
41
+ | Database.Service
41
42
  | QueueService
42
43
  | FunctionInvocationService;
43
44
 
@@ -75,6 +76,12 @@ export type FunctionDefinition<T = any, O = any, S extends FunctionServices = Fu
75
76
  inputSchema: Schema.Schema<T, any>;
76
77
  outputSchema?: Schema.Schema<O, any>;
77
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
+
78
85
  /**
79
86
  * Keys of the required services.
80
87
  */
@@ -108,6 +115,12 @@ export type FunctionProps<T, O> = {
108
115
  description?: string;
109
116
  inputSchema: Schema.Schema<T, any>;
110
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[];
111
124
  // TODO(dmaretskyi): This currently doesn't cause a compile-time error if the handler requests a service that is not specified
112
125
  services?: readonly Context.Tag<any, any>[];
113
126
 
@@ -117,7 +130,7 @@ export type FunctionProps<T, O> = {
117
130
  // TODO(dmaretskyi): Output type doesn't get typechecked.
118
131
  export const defineFunction: {
119
132
  <I, O>(params: FunctionProps<I, O>): FunctionDefinition<I, O, FunctionServices>;
120
- } = ({ key, name, description, inputSchema, outputSchema = Schema.Any, handler, services }) => {
133
+ } = ({ key, name, description, inputSchema, outputSchema = Schema.Any, handler, types, services }) => {
121
134
  if (!Schema.isSchema(inputSchema)) {
122
135
  throw new Error('Input schema must be a valid schema');
123
136
  }
@@ -162,6 +175,7 @@ export const defineFunction: {
162
175
  inputSchema,
163
176
  outputSchema,
164
177
  handler: handlerWithSpan,
178
+ types: types ?? [],
165
179
  services: !services ? [] : getServiceKeys(services),
166
180
  } satisfies FunctionDefinition.Any;
167
181
  };
@@ -171,11 +185,58 @@ const getServiceKeys = (services: readonly Context.Tag<any, any>[]) => {
171
185
  if (typeof tag.key === 'string') {
172
186
  return tag.key;
173
187
  }
174
- console.log(tag);
175
188
  failedInvariant();
176
189
  });
177
190
  };
178
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
+
179
240
  export const FunctionDefinition = {
180
241
  make: defineFunction,
181
242
  isFunction: (value: unknown): value is FunctionDefinition.Any => {
@@ -189,6 +250,7 @@ export const FunctionDefinition = {
189
250
  assertArgument(Obj.instanceOf(Function.Function, functionObj), 'functionObj');
190
251
  return deserializeFunction(functionObj);
191
252
  },
253
+ toOperation,
192
254
  };
193
255
 
194
256
  export const serializeFunction = (functionDef: FunctionDefinition.Any): Function.Function => {
@@ -202,7 +264,7 @@ export const serializeFunction = (functionDef: FunctionDefinition.Any): Function
202
264
  services: functionDef.services,
203
265
  });
204
266
  if (functionDef.meta?.deployedFunctionId) {
205
- setUserFunctionIdInMetadata(Obj.getMeta(fn), functionDef.meta.deployedFunctionId);
267
+ Obj.change(fn, (fn) => setUserFunctionIdInMetadata(Obj.getMeta(fn), functionDef.meta!.deployedFunctionId!));
206
268
  }
207
269
  return fn;
208
270
  };
@@ -219,6 +281,7 @@ export const deserializeFunction = (functionObj: Function.Function): FunctionDef
219
281
  // TODO(dmaretskyi): This should throw error.
220
282
  handler: () => {},
221
283
  services: functionObj.services ?? [],
284
+ types: [],
222
285
  meta: {
223
286
  deployedFunctionId: getUserFunctionIdInMetadata(Obj.getMeta(functionObj)),
224
287
  },
@@ -11,7 +11,7 @@ import * as Layer from 'effect/Layer';
11
11
  import * as Redacted from 'effect/Redacted';
12
12
 
13
13
  import { Query } from '@dxos/echo';
14
- import { DatabaseService } from '@dxos/echo-db';
14
+ import { Database } from '@dxos/echo';
15
15
  import { AccessToken } from '@dxos/types';
16
16
 
17
17
  export type CredentialQuery = {
@@ -60,7 +60,12 @@ export class CredentialsService extends Context.Tag('@dxos/functions/Credentials
60
60
  static configuredLayer = (credentials: ServiceCredential[]) =>
61
61
  Layer.succeed(CredentialsService, new ConfiguredCredentialsService(credentials));
62
62
 
63
- static layerConfig = (credentials: { service: string; apiKey: Config.Config<Redacted.Redacted<string>> }[]) =>
63
+ static layerConfig = (
64
+ credentials: {
65
+ service: string;
66
+ apiKey: Config.Config<Redacted.Redacted<string>>;
67
+ }[],
68
+ ) =>
64
69
  Layer.effect(
65
70
  CredentialsService,
66
71
  Effect.gen(function* () {
@@ -77,20 +82,34 @@ export class CredentialsService extends Context.Tag('@dxos/functions/Credentials
77
82
  }),
78
83
  );
79
84
 
80
- static layerFromDatabase = () =>
85
+ static layerFromDatabase = ({ caching = false }: { caching?: boolean } = {}) =>
81
86
  Layer.effect(
82
87
  CredentialsService,
83
88
  Effect.gen(function* () {
84
- const dbService = yield* DatabaseService;
89
+ const dbService = yield* Database.Service;
90
+ const cache = new Map<string, ServiceCredential[]>();
91
+
85
92
  const queryCredentials = async (query: CredentialQuery): Promise<ServiceCredential[]> => {
86
- const { objects: accessTokens } = await dbService.db.query(Query.type(AccessToken.AccessToken)).run();
87
- return accessTokens
93
+ const cacheKey = JSON.stringify(query);
94
+ if (caching && cache.has(cacheKey)) {
95
+ return cache.get(cacheKey)!;
96
+ }
97
+
98
+ const accessTokens = await dbService.db.query(Query.type(AccessToken.AccessToken)).run();
99
+ const credentials = accessTokens
88
100
  .filter((accessToken) => accessToken.source === query.service)
89
101
  .map((accessToken) => ({
90
102
  service: accessToken.source,
91
103
  apiKey: accessToken.token,
92
104
  }));
105
+
106
+ if (caching) {
107
+ cache.set(cacheKey, credentials);
108
+ }
109
+
110
+ return credentials;
93
111
  };
112
+
94
113
  return {
95
114
  getCredential: async (query) => {
96
115
  const credentials = await queryCredentials(query);
@@ -131,13 +150,10 @@ export class ConfiguredCredentialsService implements Context.Tag.Service<Credent
131
150
  }
132
151
 
133
152
  /**
134
- * Maps the request to include the API key from the credential.
153
+ * Maps the request to include the given token in the Authorization header.
135
154
  */
136
- export const withAuthorization = (query: CredentialQuery, kind?: 'Bearer' | 'Basic') =>
137
- HttpClient.mapRequestEffect(
138
- Effect.fnUntraced(function* (request) {
139
- const key = yield* CredentialsService.getApiKey(query).pipe(Effect.map(Redacted.value));
140
- const authorization = kind ? `${kind} ${key}` : key;
141
- return HttpClientRequest.setHeader(request, 'Authorization', authorization);
142
- }),
143
- );
155
+ export const withAuthorization = (token: string, kind?: 'Bearer' | 'Basic') =>
156
+ HttpClient.mapRequest((request) => {
157
+ const authorization = kind ? `${kind} ${token}` : token;
158
+ return HttpClientRequest.setHeader(request, 'Authorization', authorization);
159
+ });
@@ -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.Obj({ typename: 'dxos.org/type/ComputeEvent', version: '0.1.0' }));
55
+ }).pipe(Type.object({ typename: 'dxos.org/type/ComputeEvent', version: '0.1.0' }));
56
56
 
57
57
  /**
58
58
  * Logs event for the compute workflows.
@@ -75,7 +75,7 @@ export class ComputeEventLogger extends Context.Tag('@dxos/functions/ComputeEven
75
75
  const tracing = yield* TracingService;
76
76
  return {
77
77
  log: (event: ComputeEventPayload) => {
78
- tracing.write(Obj.make(ComputeEvent, { payload: event }));
78
+ tracing.write(Obj.make(ComputeEvent, { payload: event }), tracing.getTraceContext());
79
79
  },
80
80
  nodeId: undefined,
81
81
  };
@@ -3,7 +3,9 @@
3
3
  //
4
4
  import * as Context from 'effect/Context';
5
5
  import * as Effect from 'effect/Effect';
6
+ import * as Layer from 'effect/Layer';
6
7
 
8
+ import type { FunctionNotFoundError } from '../errors';
7
9
  import { type FunctionDefinition, type InvocationServices } from '../sdk';
8
10
 
9
11
  export class FunctionInvocationService extends Context.Tag('@dxos/functions/FunctionInvocationService')<
@@ -13,11 +15,23 @@ export class FunctionInvocationService extends Context.Tag('@dxos/functions/Func
13
15
  functionDef: FunctionDefinition<I, O, any>,
14
16
  input: I,
15
17
  ): Effect.Effect<O, never, InvocationServices>;
18
+
19
+ resolveFunction(key: string): Effect.Effect<FunctionDefinition.Any, FunctionNotFoundError>;
16
20
  }
17
21
  >() {
22
+ static layerNotAvailable = Layer.succeed(FunctionInvocationService, {
23
+ invokeFunction: () => Effect.die('FunctionInvocationService is not avaialble.'),
24
+ resolveFunction: () => Effect.die('FunctionInvocationService is not available.'),
25
+ });
26
+
18
27
  static invokeFunction = <I, O>(
19
28
  functionDef: FunctionDefinition<I, O, any>,
20
29
  input: I,
21
30
  ): Effect.Effect<O, never, FunctionInvocationService | InvocationServices> =>
22
31
  Effect.serviceFunctionEffect(FunctionInvocationService, (service) => service.invokeFunction)(functionDef, input);
32
+
33
+ static resolveFunction = (
34
+ key: string,
35
+ ): Effect.Effect<FunctionDefinition.Any, FunctionNotFoundError, FunctionInvocationService> =>
36
+ Effect.serviceFunctionEffect(FunctionInvocationService, (service) => service.resolveFunction)(key);
23
37
  }
@@ -2,8 +2,6 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export { DatabaseService } from '@dxos/echo-db';
6
-
7
5
  export * from './credentials';
8
6
  export { ConfiguredCredentialsService, type ServiceCredential } from './credentials';
9
7
  export * from './event-logger';
@@ -6,7 +6,7 @@ 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 { Obj, Relation } from '@dxos/echo';
9
+ import type { Entity } from '@dxos/echo';
10
10
  import type { Queue, QueueAPI, QueueFactory } from '@dxos/echo-db';
11
11
  import type { DXN, QueueSubspaceTag } from '@dxos/keys';
12
12
 
@@ -53,22 +53,20 @@ export class QueueService extends Context.Tag('@dxos/functions/QueueService')<
53
53
  /**
54
54
  * Gets a queue by its DXN.
55
55
  */
56
- static getQueue = <T extends Obj.Any | Relation.Any = Obj.Any | Relation.Any>(
56
+ static getQueue = <T extends Entity.Unknown = Entity.Unknown>(
57
57
  dxn: DXN,
58
58
  ): Effect.Effect<Queue<T>, never, QueueService> => QueueService.pipe(Effect.map(({ queues }) => queues.get<T>(dxn)));
59
59
 
60
60
  /**
61
61
  * Creates a new queue.
62
62
  */
63
- static createQueue = <T extends Obj.Any | Relation.Any = Obj.Any | Relation.Any>(options?: {
63
+ static createQueue = <T extends Entity.Unknown = Entity.Unknown>(options?: {
64
64
  subspaceTag?: QueueSubspaceTag;
65
65
  }): Effect.Effect<Queue<T>, never, QueueService> =>
66
66
  QueueService.pipe(Effect.map(({ queues }) => queues.create<T>(options)));
67
67
 
68
- static append = <T extends Obj.Any | Relation.Any = Obj.Any | Relation.Any>(
69
- queue: Queue<T>,
70
- objects: T[],
71
- ): Effect.Effect<void> => Effect.promise(() => queue.append(objects));
68
+ static append = <T extends Entity.Unknown = Entity.Unknown>(queue: Queue<T>, objects: T[]): Effect.Effect<void> =>
69
+ Effect.promise(() => queue.append(objects));
72
70
  }
73
71
 
74
72
  /**
@@ -7,10 +7,12 @@ import * as Effect from 'effect/Effect';
7
7
  import * as Layer from 'effect/Layer';
8
8
 
9
9
  import { AgentStatus } from '@dxos/ai';
10
- import { Obj } from '@dxos/echo';
11
- import type { ObjectId } from '@dxos/echo/internal';
10
+ import { type DXN, Obj } from '@dxos/echo';
11
+ import { ObjectId } from '@dxos/keys';
12
12
  import { Message } from '@dxos/types';
13
13
 
14
+ import type { Trigger } from '../types';
15
+
14
16
  /**
15
17
  * Provides a way for compute primitives (functions, workflows, tools)
16
18
  * to emit an execution trace as a series of structured ECHO objects.
@@ -27,15 +29,35 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
27
29
  * Write an event to the tracing queue.
28
30
  * @param event - The event to write. Must be an a typed object.
29
31
  */
30
- write: (event: Obj.Any) => void;
32
+ write: (event: Obj.Unknown, traceContext: TracingService.TraceContext) => void;
33
+
34
+ traceInvocationStart({
35
+ payload,
36
+ target,
37
+ }: {
38
+ payload: TracingService.FunctionInvocationPayload;
39
+ target?: DXN;
40
+ }): Effect.Effect<TracingService.InvocationTraceData>;
41
+
42
+ traceInvocationEnd({
43
+ trace,
44
+ exception,
45
+ }: {
46
+ trace: TracingService.InvocationTraceData;
47
+ exception?: any;
48
+ }): Effect.Effect<void>;
31
49
  }
32
50
  >() {
33
51
  static noop: Context.Tag.Service<TracingService> = {
34
52
  getTraceContext: () => ({}),
35
53
  write: () => {},
54
+ traceInvocationStart: () =>
55
+ Effect.sync(() => ({ invocationId: ObjectId.random(), invocationTraceQueue: undefined })),
56
+ traceInvocationEnd: () => Effect.sync(() => {}),
36
57
  };
37
58
 
38
59
  static layerNoop: Layer.Layer<TracingService> = Layer.succeed(TracingService, TracingService.noop);
60
+
39
61
  /**
40
62
  * Creates a TracingService layer that emits events to the parent tracing service.
41
63
  */
@@ -46,12 +68,25 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
46
68
  const tracing = yield* TracingService;
47
69
  const context = mapContext(tracing.getTraceContext());
48
70
  return {
49
- write: (event) => tracing.write(event),
71
+ write: (event, context) => tracing.write(event, context),
50
72
  getTraceContext: () => context,
73
+ traceInvocationStart: () => Effect.die('Tracing invocation inside another invocation is not supported.'),
74
+ traceInvocationEnd: () => Effect.die('Tracing invocation inside another invocation is not supported.'),
51
75
  };
52
76
  }),
53
77
  );
54
78
 
79
+ /**
80
+ * Create sublayer to trace an invocation.
81
+ * @param data
82
+ * @returns
83
+ */
84
+ static layerInvocation = (data: TracingService.InvocationTraceData) =>
85
+ TracingService.layerSubframe((context) => ({
86
+ ...context,
87
+ currentInvocation: data,
88
+ }));
89
+
55
90
  /**
56
91
  * Emit the current human-readable execution status.
57
92
  */
@@ -66,6 +101,7 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
66
101
  created: new Date().toISOString(),
67
102
  ...data,
68
103
  }),
104
+ tracing.getTraceContext(),
69
105
  );
70
106
  });
71
107
 
@@ -82,12 +118,15 @@ export class TracingService extends Context.Tag('@dxos/functions/TracingService'
82
118
  ...data.properties,
83
119
  },
84
120
  }),
121
+ tracing.getTraceContext(),
85
122
  );
86
123
  });
87
124
  }
88
125
 
89
126
  export namespace TracingService {
90
127
  export interface TraceContext {
128
+ currentInvocation?: InvocationTraceData;
129
+
91
130
  /**
92
131
  * If this thread sprung from a tool call, this is the ID of the message containing the tool call.
93
132
  */
@@ -100,6 +139,26 @@ export namespace TracingService {
100
139
 
101
140
  debugInfo?: unknown;
102
141
  }
142
+
143
+ /**
144
+ * Trace data for a function/trigger invocation.
145
+ */
146
+ export interface InvocationTraceData {
147
+ invocationId: ObjectId;
148
+ invocationTraceQueue?: DXN.String;
149
+ }
150
+
151
+ /**
152
+ * Payload for a function/trigger invocation.
153
+ */
154
+ export interface FunctionInvocationPayload {
155
+ data?: any;
156
+ inputNodeId?: string;
157
+ trigger?: {
158
+ id: string;
159
+ kind: Trigger.Kind;
160
+ };
161
+ }
103
162
  }
104
163
 
105
164
  /**
@@ -4,8 +4,8 @@
4
4
 
5
5
  import * as Schema from 'effect/Schema';
6
6
 
7
- import { Obj, Type } from '@dxos/echo';
8
- import { JsonSchemaType, LabelAnnotation, Ref } from '@dxos/echo/internal';
7
+ import { Annotation, JsonSchema, Obj, Type } from '@dxos/echo';
8
+ import { SystemTypeAnnotation } from '@dxos/echo/internal';
9
9
 
10
10
  import { Script } from './Script';
11
11
 
@@ -24,7 +24,6 @@ export const Function = Schema.Struct({
24
24
  description: 'Unique registration key for the blueprint',
25
25
  }),
26
26
 
27
- // TODO(burdon): Rename to id/uri?
28
27
  name: Schema.NonEmptyString,
29
28
  version: Schema.String,
30
29
 
@@ -37,10 +36,10 @@ export const Function = Schema.Struct({
37
36
 
38
37
  // Reference to a source script if it exists within ECHO.
39
38
  // TODO(burdon): Don't ref ScriptType directly (core).
40
- source: Schema.optional(Ref(Script)),
39
+ source: Schema.optional(Type.Ref(Script)),
41
40
 
42
- inputSchema: Schema.optional(JsonSchemaType),
43
- outputSchema: Schema.optional(JsonSchemaType),
41
+ inputSchema: Schema.optional(JsonSchema.JsonSchema),
42
+ outputSchema: Schema.optional(JsonSchema.JsonSchema),
44
43
 
45
44
  /**
46
45
  * List of required services.
@@ -51,12 +50,33 @@ export const Function = Schema.Struct({
51
50
  // Local binding to a function name.
52
51
  binding: Schema.optional(Schema.String),
53
52
  }).pipe(
54
- Type.Obj({
53
+ Type.object({
55
54
  typename: 'dxos.org/type/Function',
56
55
  version: '0.1.0',
57
56
  }),
58
- LabelAnnotation.set(['name']),
57
+ Annotation.LabelAnnotation.set(['name']),
58
+ SystemTypeAnnotation.set(true),
59
59
  );
60
+
60
61
  export interface Function extends Schema.Schema.Type<typeof Function> {}
61
62
 
62
63
  export const make = (props: Obj.MakeProps<typeof Function>) => Obj.make(Function, props);
64
+
65
+ /**
66
+ * Copies properties from source to target.
67
+ * @param target - Target object to copy properties to.
68
+ * @param source - Source object to copy properties from.
69
+ */
70
+ export const setFrom = (target: Function, source: Function) => {
71
+ Obj.change(target, (t) => {
72
+ t.key = source.key ?? target.key;
73
+ t.name = source.name ?? target.name;
74
+ t.version = source.version;
75
+ t.description = source.description;
76
+ t.updated = source.updated;
77
+ // TODO(dmaretskyi): A workaround for an ECHO bug.
78
+ t.inputSchema = source.inputSchema ? JSON.parse(JSON.stringify(source.inputSchema)) : undefined;
79
+ t.outputSchema = source.outputSchema ? JSON.parse(JSON.stringify(source.outputSchema)) : undefined;
80
+ Obj.getMeta(t).keys = JSON.parse(JSON.stringify(Obj.getMeta(source).keys));
81
+ });
82
+ };
@@ -4,8 +4,8 @@
4
4
 
5
5
  import * as Schema from 'effect/Schema';
6
6
 
7
- import { Obj, Ref, Type } from '@dxos/echo';
8
- import { FormAnnotation, LabelAnnotation } from '@dxos/echo/internal';
7
+ import { Annotation, Obj, Ref, Type } from '@dxos/echo';
8
+ import { FormInputAnnotation } from '@dxos/echo/internal';
9
9
  import { Text } from '@dxos/schema';
10
10
 
11
11
  /**
@@ -16,18 +16,19 @@ export const Script = Schema.Struct({
16
16
  description: Schema.String.pipe(Schema.optional),
17
17
  // TODO(burdon): Change to hash of deployed content.
18
18
  // Whether source has changed since last deploy.
19
- changed: Schema.Boolean.pipe(FormAnnotation.set(false), Schema.optional),
20
- source: Type.Ref(Text.Text).pipe(FormAnnotation.set(false)),
19
+ changed: Schema.Boolean.pipe(FormInputAnnotation.set(false), Schema.optional),
20
+ source: Type.Ref(Text.Text).pipe(FormInputAnnotation.set(false)),
21
21
  }).pipe(
22
- Type.Obj({
22
+ Type.object({
23
23
  typename: 'dxos.org/type/Script',
24
24
  version: '0.1.0',
25
25
  }),
26
- LabelAnnotation.set(['name']),
26
+ Annotation.LabelAnnotation.set(['name']),
27
27
  );
28
+
28
29
  export interface Script extends Schema.Schema.Type<typeof Script> {}
29
30
 
30
31
  type Props = Omit<Obj.MakeProps<typeof Script>, 'source'> & { source?: string };
31
32
 
32
- export const make = ({ source = '', ...props }: Props = {}) =>
33
+ export const make = ({ source = '', ...props }: Props = {}): Script =>
33
34
  Obj.make(Script, { ...props, source: Ref.make(Text.make(source)) });
@@ -6,8 +6,9 @@ import * as Schema from 'effect/Schema';
6
6
  import * as SchemaAST from 'effect/SchemaAST';
7
7
 
8
8
  import { Obj, QueryAST, Type } from '@dxos/echo';
9
- import { Expando, OptionsAnnotationId, Ref } from '@dxos/echo/internal';
9
+ import { OptionsAnnotationId, SystemTypeAnnotation } from '@dxos/echo/internal';
10
10
  import { DXN } from '@dxos/keys';
11
+ import { Expando } from '@dxos/schema';
11
12
 
12
13
  /**
13
14
  * Type discriminator for TriggerType.
@@ -21,7 +22,7 @@ const kindLiteralAnnotations = { title: 'Kind' };
21
22
 
22
23
  export const EmailSpec = Schema.Struct({
23
24
  kind: Schema.Literal('email').annotations(kindLiteralAnnotations),
24
- }).pipe(Schema.mutable);
25
+ });
25
26
  export type EmailSpec = Schema.Schema.Type<typeof EmailSpec>;
26
27
 
27
28
  export const QueueSpec = Schema.Struct({
@@ -29,7 +30,7 @@ export const QueueSpec = Schema.Struct({
29
30
 
30
31
  // TODO(dmaretskyi): Change to a reference.
31
32
  queue: DXN.Schema,
32
- }).pipe(Schema.mutable);
33
+ });
33
34
  export type QueueSpec = Schema.Schema.Type<typeof QueueSpec>;
34
35
 
35
36
  /**
@@ -40,7 +41,7 @@ export const SubscriptionSpec = Schema.Struct({
40
41
  query: Schema.Struct({
41
42
  raw: Schema.optional(Schema.String.annotations({ title: 'Query' })),
42
43
  ast: QueryAST.Query,
43
- }).pipe(Schema.mutable),
44
+ }),
44
45
  options: Schema.optional(
45
46
  Schema.Struct({
46
47
  // Watch changes to object (not just creation).
@@ -49,7 +50,7 @@ export const SubscriptionSpec = Schema.Struct({
49
50
  delay: Schema.optional(Schema.Number.annotations({ title: 'Delay' })),
50
51
  }).annotations({ title: 'Options' }),
51
52
  ),
52
- }).pipe(Schema.mutable);
53
+ });
53
54
  export type SubscriptionSpec = Schema.Schema.Type<typeof SubscriptionSpec>;
54
55
 
55
56
  /**
@@ -61,7 +62,7 @@ export const TimerSpec = Schema.Struct({
61
62
  title: 'Cron',
62
63
  [SchemaAST.ExamplesAnnotationId]: ['0 0 * * *'],
63
64
  }),
64
- }).pipe(Schema.mutable);
65
+ });
65
66
  export type TimerSpec = Schema.Schema.Type<typeof TimerSpec>;
66
67
 
67
68
  /**
@@ -80,7 +81,7 @@ export const WebhookSpec = Schema.Struct({
80
81
  title: 'Port',
81
82
  }),
82
83
  ),
83
- }).pipe(Schema.mutable);
84
+ });
84
85
  export type WebhookSpec = Schema.Schema.Type<typeof WebhookSpec>;
85
86
 
86
87
  /**
@@ -96,12 +97,12 @@ export type Spec = Schema.Schema.Type<typeof Spec>;
96
97
  * Function is invoked with the `payload` passed as input data.
97
98
  * The event that triggers the function is available in the function context.
98
99
  */
99
- const Trigger_ = Schema.Struct({
100
+ const TriggerSchema = Schema.Struct({
100
101
  /**
101
102
  * Function or workflow to invoke.
102
103
  */
103
104
  // TODO(dmaretskyi): Can be a Ref(FunctionType) or Ref(ComputeGraphType).
104
- function: Schema.optional(Ref(Expando).annotations({ title: 'Function' })),
105
+ function: Schema.optional(Type.Ref(Expando.Expando).annotations({ title: 'Function' })),
105
106
 
106
107
  /**
107
108
  * Only used for workflowSchema.
@@ -110,6 +111,8 @@ const Trigger_ = Schema.Struct({
110
111
  */
111
112
  inputNodeId: Schema.optional(Schema.String.annotations({ title: 'Input Node ID' })),
112
113
 
114
+ // TODO(burdon): NO BOOLEAN PROPERTIES (enabld/disabled/paused, etc.)
115
+ // Need lint rule; or agent rule to require PR review for "boolean" key word.
113
116
  enabled: Schema.optional(Schema.Boolean.annotations({ title: 'Enabled' })),
114
117
 
115
118
  spec: Schema.optional(Spec),
@@ -125,15 +128,16 @@ const Trigger_ = Schema.Struct({
125
128
  * mailbox: { '/': 'dxn:echo:AAA:ZZZ' }
126
129
  * }
127
130
  */
128
- input: Schema.optional(Schema.mutable(Schema.Record({ key: Schema.String, value: Schema.Any }))),
131
+ input: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.Any })),
129
132
  }).pipe(
130
- Type.Obj({
133
+ Type.object({
131
134
  typename: 'dxos.org/type/Trigger',
132
135
  version: '0.1.0',
133
136
  }),
137
+ SystemTypeAnnotation.set(true),
134
138
  );
135
- export interface Trigger extends Schema.Schema.Type<typeof Trigger_> {}
136
- export interface TriggerEncoded extends Schema.Schema.Encoded<typeof Trigger_> {}
137
- export const Trigger: Schema.Schema<Trigger, TriggerEncoded> = Trigger_;
139
+
140
+ export interface Trigger extends Schema.Schema.Type<typeof TriggerSchema> {}
141
+ export const Trigger: Type.Obj<Trigger> = TriggerSchema as any;
138
142
 
139
143
  export const make = (props: Obj.MakeProps<typeof Trigger>) => Obj.make(Trigger, props);