@dxos/functions 0.5.3-next.57eca40 → 0.5.3-next.63cdcad

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 (53) hide show
  1. package/dist/lib/browser/{chunk-366QG6IX.mjs → chunk-4D4I3YMJ.mjs} +16 -11
  2. package/dist/lib/browser/chunk-4D4I3YMJ.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +206 -133
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types.mjs +3 -1
  7. package/dist/lib/node/{chunk-3VSJ57ZZ.cjs → chunk-3UYUR5N5.cjs} +19 -13
  8. package/dist/lib/node/chunk-3UYUR5N5.cjs.map +7 -0
  9. package/dist/lib/node/index.cjs +216 -139
  10. package/dist/lib/node/index.cjs.map +4 -4
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types.cjs +6 -4
  13. package/dist/lib/node/types.cjs.map +2 -2
  14. package/dist/types/src/browser/index.d.ts +2 -0
  15. package/dist/types/src/browser/index.d.ts.map +1 -0
  16. package/dist/types/src/function/function-registry.d.ts.map +1 -1
  17. package/dist/types/src/handler.d.ts.map +1 -1
  18. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  19. package/dist/types/src/runtime/scheduler.d.ts +1 -1
  20. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  21. package/dist/types/src/testing/setup.d.ts.map +1 -1
  22. package/dist/types/src/trigger/trigger-registry.d.ts +2 -5
  23. package/dist/types/src/trigger/trigger-registry.d.ts.map +1 -1
  24. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +1 -1
  25. package/dist/types/src/trigger/type/timer-trigger.d.ts.map +1 -1
  26. package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +1 -1
  27. package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -1
  28. package/dist/types/src/types.d.ts +46 -33
  29. package/dist/types/src/types.d.ts.map +1 -1
  30. package/package.json +14 -14
  31. package/schema/functions.json +5 -0
  32. package/src/browser/index.ts +5 -0
  33. package/src/function/function-registry.ts +5 -5
  34. package/src/runtime/dev-server.ts +3 -3
  35. package/src/runtime/scheduler.test.ts +14 -9
  36. package/src/runtime/scheduler.ts +14 -9
  37. package/src/testing/functions-integration.test.ts +1 -0
  38. package/src/testing/setup.ts +8 -10
  39. package/src/trigger/trigger-registry.test.ts +30 -13
  40. package/src/trigger/trigger-registry.ts +59 -37
  41. package/src/trigger/type/subscription-trigger.ts +13 -7
  42. package/src/trigger/type/timer-trigger.ts +4 -3
  43. package/src/trigger/type/webhook-trigger.ts +3 -2
  44. package/src/trigger/type/websocket-trigger.ts +4 -3
  45. package/src/types.ts +42 -30
  46. package/dist/lib/browser/chunk-366QG6IX.mjs.map +0 -7
  47. package/dist/lib/node/chunk-3VSJ57ZZ.cjs.map +0 -7
  48. package/dist/types/src/util.d.ts +0 -15
  49. package/dist/types/src/util.d.ts.map +0 -1
  50. package/dist/types/src/util.test.d.ts +0 -2
  51. package/dist/types/src/util.test.d.ts.map +0 -1
  52. package/src/util.test.ts +0 -43
  53. package/src/util.ts +0 -48
@@ -6,26 +6,23 @@ import { Event } from '@dxos/async';
6
6
  import { type Client } from '@dxos/client';
7
7
  import { create, Filter, getMeta, type Space } from '@dxos/client/echo';
8
8
  import { Context, Resource } from '@dxos/context';
9
- import { ECHO_ATTR_META, foreignKey, foreignKeyEquals, splitMeta } from '@dxos/echo-schema';
9
+ import { compareForeignKeys, ECHO_ATTR_META, foreignKey } from '@dxos/echo-schema';
10
10
  import { invariant } from '@dxos/invariant';
11
11
  import { PublicKey } from '@dxos/keys';
12
12
  import { log } from '@dxos/log';
13
- import { ComplexMap } from '@dxos/util';
13
+ import { ComplexMap, diff } from '@dxos/util';
14
14
 
15
15
  import { createSubscriptionTrigger, createTimerTrigger, createWebhookTrigger, createWebsocketTrigger } from './type';
16
16
  import { type FunctionManifest, FunctionTrigger, type FunctionTriggerType, type TriggerSpec } from '../types';
17
- import { diff, intersection } from '../util';
18
17
 
19
18
  type ResponseCode = number;
20
19
 
21
20
  export type TriggerCallback = (args: object) => Promise<ResponseCode>;
22
21
 
23
- export type TriggerContext = { space: Space };
24
-
25
22
  // TODO(burdon): Make object?
26
23
  export type TriggerFactory<Spec extends TriggerSpec, Options = any> = (
27
24
  ctx: Context,
28
- context: TriggerContext,
25
+ space: Space,
29
26
  spec: Spec,
30
27
  callback: TriggerCallback,
31
28
  options?: Options,
@@ -71,19 +68,18 @@ export class TriggerRegistry extends Resource {
71
68
  return this._getTriggers(space, (t) => t.activationCtx == null);
72
69
  }
73
70
 
74
- async activate(triggerCtx: TriggerContext, trigger: FunctionTrigger, callback: TriggerCallback): Promise<void> {
75
- log('activate', { space: triggerCtx.space.key, trigger });
76
- const activationCtx = new Context({ name: `trigger_${trigger.function}` });
71
+ async activate(space: Space, trigger: FunctionTrigger, callback: TriggerCallback): Promise<void> {
72
+ log('activate', { space: space.key, trigger });
73
+
74
+ const activationCtx = new Context({ name: `FunctionTrigger-${trigger.function}` });
77
75
  this._ctx.onDispose(() => activationCtx.dispose());
78
- const registeredTrigger = this._triggersBySpaceKey
79
- .get(triggerCtx.space.key)
80
- ?.find((reg) => reg.trigger.id === trigger.id);
76
+ const registeredTrigger = this._triggersBySpaceKey.get(space.key)?.find((reg) => reg.trigger.id === trigger.id);
81
77
  invariant(registeredTrigger, `Trigger is not registered: ${trigger.function}`);
82
78
  registeredTrigger.activationCtx = activationCtx;
83
79
 
84
80
  try {
85
81
  const options = this._options?.[trigger.spec.type];
86
- await triggerHandlers[trigger.spec.type](activationCtx, triggerCtx, trigger.spec, callback, options);
82
+ await triggerHandlers[trigger.spec.type](activationCtx, space, trigger.spec, callback, options);
87
83
  } catch (err) {
88
84
  delete registeredTrigger.activationCtx;
89
85
  throw err;
@@ -98,28 +94,39 @@ export class TriggerRegistry extends Resource {
98
94
  if (!manifest.triggers?.length) {
99
95
  return;
100
96
  }
97
+
101
98
  if (!space.db.graph.runtimeSchemaRegistry.hasSchema(FunctionTrigger)) {
102
99
  space.db.graph.runtimeSchemaRegistry.registerSchema(FunctionTrigger);
103
100
  }
104
101
 
102
+ // Create FK to enable syncing if none are set (NOTE: Possible collision).
103
+ const manifestTriggers = manifest.triggers.map((trigger) => {
104
+ let keys = trigger[ECHO_ATTR_META]?.keys;
105
+ delete trigger[ECHO_ATTR_META];
106
+ if (!keys?.length) {
107
+ keys = [foreignKey('manifest', [trigger.function, trigger.spec.type].join(':'))];
108
+ }
109
+
110
+ return create(FunctionTrigger, trigger, { keys });
111
+ });
112
+
105
113
  // Sync triggers.
106
114
  const { objects: existing } = await space.db.query(Filter.schema(FunctionTrigger)).run();
107
- const { added, removed } = diff(existing, manifest.triggers, (a, b) => {
108
- // Create FK to enable syncing if none are set.
109
- // TODO(burdon): Warn if not unique.
110
- const keys = b[ECHO_ATTR_META]?.keys ?? [foreignKey('manifest', [b.function, b.spec.type].join('-'))];
111
- return intersection(getMeta(a)?.keys ?? [], keys, foreignKeyEquals).length > 0;
112
- });
115
+ const { added } = diff(existing, manifestTriggers, compareForeignKeys);
113
116
 
117
+ // TODO(burdon): Update existing.
114
118
  added.forEach((trigger) => {
115
- const { meta, object } = splitMeta(trigger);
116
- space.db.add(create(FunctionTrigger, object, meta));
119
+ space.db.add(trigger);
120
+ log.info('added', { meta: getMeta(trigger) });
117
121
  });
118
- // TODO(burdon): Update existing triggers.
119
- removed.forEach((trigger) => space.db.remove(trigger));
122
+
123
+ if (added.length > 0) {
124
+ await space.db.flush();
125
+ }
120
126
  }
121
127
 
122
128
  protected override async _open(): Promise<void> {
129
+ log.info('open...');
123
130
  const spaceListSubscription = this._client.spaces.subscribe(async (spaces) => {
124
131
  for (const space of spaces) {
125
132
  if (this._triggersBySpaceKey.has(space.key)) {
@@ -132,44 +139,54 @@ export class TriggerRegistry extends Resource {
132
139
  if (this._ctx.disposed) {
133
140
  break;
134
141
  }
135
- const functionsSubscription = space.db.query(Filter.schema(FunctionTrigger)).subscribe(async (triggers) => {
136
- await this._handleRemovedTriggers(space, triggers.objects, registered);
137
- this._handleNewTriggers(space, triggers.objects, registered);
138
- });
139
142
 
140
- this._ctx.onDispose(functionsSubscription);
143
+ // Subscribe to updates.
144
+ this._ctx.onDispose(
145
+ space.db.query(Filter.schema(FunctionTrigger)).subscribe(async ({ objects: current }) => {
146
+ log.info('update', { space: space.key, registered: registered.length, current: current.length });
147
+ await this._handleRemovedTriggers(space, current, registered);
148
+ this._handleNewTriggers(space, current, registered);
149
+ }),
150
+ );
141
151
  }
142
152
  });
143
153
 
144
154
  this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
155
+ log.info('opened');
145
156
  }
146
157
 
147
158
  protected override async _close(_: Context): Promise<void> {
159
+ log.info('close...');
148
160
  this._triggersBySpaceKey.clear();
161
+ log.info('closed');
149
162
  }
150
163
 
151
- private _handleNewTriggers(space: Space, allTriggers: FunctionTrigger[], registered: RegisteredTrigger[]) {
152
- const newTriggers = allTriggers.filter((candidate) => {
153
- return registered.find((reg) => reg.trigger.id === candidate.id) == null;
164
+ private _handleNewTriggers(space: Space, current: FunctionTrigger[], registered: RegisteredTrigger[]) {
165
+ const added = current.filter((candidate) => {
166
+ return candidate.enabled && registered.find((reg) => reg.trigger.id === candidate.id) == null;
154
167
  });
155
168
 
156
- if (newTriggers.length > 0) {
157
- const newRegisteredTriggers: RegisteredTrigger[] = newTriggers.map((trigger) => ({ trigger }));
169
+ if (added.length > 0) {
170
+ const newRegisteredTriggers: RegisteredTrigger[] = added.map((trigger) => ({ trigger }));
158
171
  registered.push(...newRegisteredTriggers);
159
- log('registered new triggers', () => ({ spaceKey: space.key, functions: newTriggers.map((t) => t.function) }));
160
- this.registered.emit({ space, triggers: newTriggers });
172
+ log.info('added', () => ({
173
+ spaceKey: space.key,
174
+ triggers: added.map((trigger) => trigger.function),
175
+ }));
176
+
177
+ this.registered.emit({ space, triggers: added });
161
178
  }
162
179
  }
163
180
 
164
181
  private async _handleRemovedTriggers(
165
182
  space: Space,
166
- allTriggers: FunctionTrigger[],
183
+ current: FunctionTrigger[],
167
184
  registered: RegisteredTrigger[],
168
185
  ): Promise<void> {
169
186
  const removed: FunctionTrigger[] = [];
170
187
  for (let i = registered.length - 1; i >= 0; i--) {
171
188
  const wasRemoved =
172
- allTriggers.find((trigger: FunctionTrigger) => trigger.id === registered[i].trigger.id) == null;
189
+ current.filter((trigger) => trigger.enabled).find((trigger) => trigger.id === registered[i].trigger.id) == null;
173
190
  if (wasRemoved) {
174
191
  const unregistered = registered.splice(i, 1)[0];
175
192
  await unregistered.activationCtx?.dispose();
@@ -178,6 +195,11 @@ export class TriggerRegistry extends Resource {
178
195
  }
179
196
 
180
197
  if (removed.length > 0) {
198
+ log.info('removed', () => ({
199
+ spaceKey: space.key,
200
+ triggers: removed.map((trigger) => trigger.function),
201
+ }));
202
+
181
203
  this.removed.emit({ space, triggers: removed });
182
204
  }
183
205
  }
@@ -4,16 +4,17 @@
4
4
 
5
5
  import { TextV0Type } from '@braneframe/types';
6
6
  import { debounce, UpdateScheduler } from '@dxos/async';
7
+ import { Filter, type Space } from '@dxos/client/echo';
7
8
  import { type Context } from '@dxos/context';
8
- import { createSubscription, Filter, getAutomergeObjectCore, type Query } from '@dxos/echo-db';
9
+ import { createSubscription, getAutomergeObjectCore, type Query } from '@dxos/echo-db';
9
10
  import { log } from '@dxos/log';
10
11
 
11
12
  import type { SubscriptionTrigger } from '../../types';
12
- import { type TriggerCallback, type TriggerContext, type TriggerFactory } from '../trigger-registry';
13
+ import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
13
14
 
14
15
  export const createSubscriptionTrigger: TriggerFactory<SubscriptionTrigger> = async (
15
16
  ctx: Context,
16
- triggerCtx: TriggerContext,
17
+ space: Space,
17
18
  spec: SubscriptionTrigger,
18
19
  callback: TriggerCallback,
19
20
  ) => {
@@ -30,6 +31,7 @@ export const createSubscriptionTrigger: TriggerFactory<SubscriptionTrigger> = as
30
31
  { maxFrequency: 4 },
31
32
  );
32
33
 
34
+ // TODO(burdon): Factor out diff.
33
35
  // TODO(burdon): Don't fire initially?
34
36
  // TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
35
37
  const subscriptions: (() => void)[] = [];
@@ -52,11 +54,11 @@ export const createSubscriptionTrigger: TriggerFactory<SubscriptionTrigger> = as
52
54
  // TODO(burdon): Disable trigger if keeps failing.
53
55
  const { filter, options: { deep, delay } = {} } = spec;
54
56
  const update = ({ objects }: Query) => {
57
+ log.info('update', { objects: objects.length });
55
58
  subscription.update(objects);
56
59
 
57
60
  // TODO(burdon): Hack to monitor changes to Document's text object.
58
61
  if (deep) {
59
- log.info('update', { objects: objects.length });
60
62
  for (const object of objects) {
61
63
  const content = object.content;
62
64
  if (content instanceof TextV0Type) {
@@ -68,11 +70,15 @@ export const createSubscriptionTrigger: TriggerFactory<SubscriptionTrigger> = as
68
70
  }
69
71
  };
70
72
 
71
- // TODO(burdon): Is Filter.or implemented?
73
+ // TODO(burdon): OR not working.
72
74
  // TODO(burdon): [Bug]: all callbacks are fired on the first mutation.
73
75
  // TODO(burdon): [Bug]: not updated when document is deleted (either top or hierarchically).
74
- const query = triggerCtx.space.db.query(Filter.or(filter.map(({ type, props }) => Filter.typename(type, props))));
75
- subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
76
+ log.info('subscription', { filter });
77
+ // const query = triggerCtx.space.db.query(Filter.or(filter.map(({ type, props }) => Filter.typename(type, props))));
78
+ if (filter) {
79
+ const query = space.db.query(Filter.typename(filter[0].type, filter[0].props));
80
+ subscriptions.push(query.subscribe(delay ? debounce(update, delay) : update));
81
+ }
76
82
 
77
83
  ctx.onDispose(() => {
78
84
  subscriptions.forEach((unsubscribe) => unsubscribe());
@@ -5,15 +5,16 @@
5
5
  import { CronJob } from 'cron';
6
6
 
7
7
  import { DeferredTask } from '@dxos/async';
8
+ import { type Space } from '@dxos/client/echo';
8
9
  import { type Context } from '@dxos/context';
9
10
  import { log } from '@dxos/log';
10
11
 
11
12
  import type { TimerTrigger } from '../../types';
12
- import { type TriggerCallback, type TriggerContext, type TriggerFactory } from '../trigger-registry';
13
+ import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
13
14
 
14
15
  export const createTimerTrigger: TriggerFactory<TimerTrigger> = async (
15
16
  ctx: Context,
16
- triggerContext: TriggerContext,
17
+ space: Space,
17
18
  spec: TimerTrigger,
18
19
  callback: TriggerCallback,
19
20
  ) => {
@@ -34,7 +35,7 @@ export const createTimerTrigger: TriggerFactory<TimerTrigger> = async (
34
35
  last = now;
35
36
 
36
37
  run++;
37
- log.info('tick', { space: triggerContext.space.key.truncate(), count: run, delta });
38
+ log.info('tick', { space: space.key.truncate(), count: run, delta });
38
39
  task.schedule();
39
40
  },
40
41
  });
@@ -5,15 +5,16 @@
5
5
  import { getPort } from 'get-port-please';
6
6
  import http from 'node:http';
7
7
 
8
+ import { type Space } from '@dxos/client/echo';
8
9
  import { type Context } from '@dxos/context';
9
10
  import { log } from '@dxos/log';
10
11
 
11
12
  import type { WebhookTrigger } from '../../types';
12
- import { type TriggerCallback, type TriggerContext, type TriggerFactory } from '../trigger-registry';
13
+ import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
13
14
 
14
15
  export const createWebhookTrigger: TriggerFactory<WebhookTrigger> = async (
15
16
  ctx: Context,
16
- _: TriggerContext,
17
+ space: Space,
17
18
  spec: WebhookTrigger,
18
19
  callback: TriggerCallback,
19
20
  ) => {
@@ -5,11 +5,12 @@
5
5
  import WebSocket from 'ws';
6
6
 
7
7
  import { sleep, Trigger } from '@dxos/async';
8
+ import { type Space } from '@dxos/client/echo';
8
9
  import { type Context } from '@dxos/context';
9
10
  import { log } from '@dxos/log';
10
11
 
11
12
  import { type WebsocketTrigger } from '../../types';
12
- import { type TriggerCallback, type TriggerContext, type TriggerFactory } from '../trigger-registry';
13
+ import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
13
14
 
14
15
  interface WebsocketTriggerOptions {
15
16
  retryDelay: number;
@@ -22,7 +23,7 @@ interface WebsocketTriggerOptions {
22
23
  */
23
24
  export const createWebsocketTrigger: TriggerFactory<WebsocketTrigger, WebsocketTriggerOptions> = async (
24
25
  ctx: Context,
25
- triggerCtx: TriggerContext,
26
+ space: Space,
26
27
  spec: WebsocketTrigger,
27
28
  callback: TriggerCallback,
28
29
  options: WebsocketTriggerOptions = { retryDelay: 2, maxAttempts: 5 },
@@ -51,7 +52,7 @@ export const createWebsocketTrigger: TriggerFactory<WebsocketTrigger, WebsocketT
51
52
  if (event.code === 1006) {
52
53
  setTimeout(async () => {
53
54
  log.info(`reconnecting in ${options.retryDelay}s...`, { url });
54
- await createWebsocketTrigger(ctx, triggerCtx, spec, callback, options);
55
+ await createWebsocketTrigger(ctx, space, spec, callback, options);
55
56
  }, options.retryDelay * 1_000);
56
57
  }
57
58
 
package/src/types.ts CHANGED
@@ -12,31 +12,35 @@ import { RawObject, S, TypedObject } from '@dxos/echo-schema';
12
12
  */
13
13
  export type FunctionTriggerType = 'subscription' | 'timer' | 'webhook' | 'websocket';
14
14
 
15
- const SubscriptionTriggerSchema = S.struct({
16
- type: S.literal('subscription'),
17
- // TODO(burdon): Define query DSL (from ECHO).
18
- filter: S.array(
19
- S.struct({
20
- type: S.string,
21
- props: S.optional(S.record(S.string, S.any)),
22
- }),
23
- ),
24
- options: S.optional(
25
- S.struct({
26
- // Watch changes to object (not just creation).
27
- deep: S.optional(S.boolean),
28
- // Debounce changes (delay in ms).
29
- delay: S.optional(S.number),
30
- }),
31
- ),
32
- });
15
+ const SubscriptionTriggerSchema = S.mutable(
16
+ S.struct({
17
+ type: S.literal('subscription'),
18
+ // TODO(burdon): Define query DSL (from ECHO).
19
+ filter: S.array(
20
+ S.struct({
21
+ type: S.string,
22
+ props: S.optional(S.record(S.string, S.any)),
23
+ }),
24
+ ),
25
+ options: S.optional(
26
+ S.struct({
27
+ // Watch changes to object (not just creation).
28
+ deep: S.optional(S.boolean),
29
+ // Debounce changes (delay in ms).
30
+ delay: S.optional(S.number),
31
+ }),
32
+ ),
33
+ }),
34
+ );
33
35
 
34
36
  export type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;
35
37
 
36
- const TimerTriggerSchema = S.struct({
37
- type: S.literal('timer'),
38
- cron: S.string,
39
- });
38
+ const TimerTriggerSchema = S.mutable(
39
+ S.struct({
40
+ type: S.literal('timer'),
41
+ cron: S.string,
42
+ }),
43
+ );
40
44
 
41
45
  export type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;
42
46
 
@@ -51,11 +55,13 @@ const WebhookTriggerSchema = S.mutable(
51
55
 
52
56
  export type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;
53
57
 
54
- const WebsocketTriggerSchema = S.struct({
55
- type: S.literal('websocket'),
56
- url: S.string,
57
- init: S.optional(S.record(S.string, S.any)),
58
- });
58
+ const WebsocketTriggerSchema = S.mutable(
59
+ S.struct({
60
+ type: S.literal('websocket'),
61
+ url: S.string,
62
+ init: S.optional(S.record(S.string, S.any)),
63
+ }),
64
+ );
59
65
 
60
66
  export type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;
61
67
 
@@ -78,17 +84,20 @@ export class FunctionDef extends TypedObject({
78
84
  uri: S.string,
79
85
  description: S.optional(S.string),
80
86
  route: S.string,
81
- // TODO(burdon): NPM/GitHub/Docker/CF URL?
82
87
  handler: S.string,
83
88
  }) {}
84
89
 
90
+ /**
91
+ * Function trigger.
92
+ */
85
93
  export class FunctionTrigger extends TypedObject({
86
94
  typename: 'dxos.org/type/FunctionTrigger',
87
95
  version: '0.1.0',
88
96
  })({
97
+ enabled: S.optional(S.boolean),
89
98
  function: S.string.pipe(S.description('Function URI.')),
90
- // Context is merged into the event data passed to the function.
91
- meta: S.optional(S.object),
99
+ // The `meta` property is merged into the event data passed to the function.
100
+ meta: S.optional(S.mutable(S.any)),
92
101
  spec: TriggerSpecSchema,
93
102
  }) {}
94
103
 
@@ -101,3 +110,6 @@ export const FunctionManifestSchema = S.struct({
101
110
  });
102
111
 
103
112
  export type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;
113
+
114
+ // TODO(burdon): Standards?
115
+ export const FUNCTION_SCHEMA = [FunctionDef, FunctionTrigger];
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { RawObject, S, TypedObject } from '@dxos/echo-schema';\n\n/**\n * Type discriminator for TriggerSpec.\n * Every spec has a type field of type FunctionTriggerType that we can use to understand which\n * type we're working with.\n * https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions\n */\nexport type FunctionTriggerType = 'subscription' | 'timer' | 'webhook' | 'websocket';\n\nconst SubscriptionTriggerSchema = S.struct({\n type: S.literal('subscription'),\n // TODO(burdon): Define query DSL (from ECHO).\n filter: S.array(\n S.struct({\n type: S.string,\n props: S.optional(S.record(S.string, S.any)),\n }),\n ),\n options: S.optional(\n S.struct({\n // Watch changes to object (not just creation).\n deep: S.optional(S.boolean),\n // Debounce changes (delay in ms).\n delay: S.optional(S.number),\n }),\n ),\n});\n\nexport type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;\n\nconst TimerTriggerSchema = S.struct({\n type: S.literal('timer'),\n cron: S.string,\n});\n\nexport type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;\n\nconst WebhookTriggerSchema = S.mutable(\n S.struct({\n type: S.literal('webhook'),\n method: S.string,\n // Assigned port.\n port: S.optional(S.number),\n }),\n);\n\nexport type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;\n\nconst WebsocketTriggerSchema = S.struct({\n type: S.literal('websocket'),\n url: S.string,\n init: S.optional(S.record(S.string, S.any)),\n});\n\nexport type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;\n\nconst TriggerSpecSchema = S.union(\n TimerTriggerSchema,\n WebhookTriggerSchema,\n WebsocketTriggerSchema,\n SubscriptionTriggerSchema,\n);\n\nexport type TriggerSpec = TimerTrigger | WebhookTrigger | WebsocketTrigger | SubscriptionTrigger;\n\n/**\n * Function definition.\n */\nexport class FunctionDef extends TypedObject({\n typename: 'dxos.org/type/FunctionDef',\n version: '0.1.0',\n})({\n uri: S.string,\n description: S.optional(S.string),\n route: S.string,\n // TODO(burdon): NPM/GitHub/Docker/CF URL?\n handler: S.string,\n}) {}\n\nexport class FunctionTrigger extends TypedObject({\n typename: 'dxos.org/type/FunctionTrigger',\n version: '0.1.0',\n})({\n function: S.string.pipe(S.description('Function URI.')),\n // Context is merged into the event data passed to the function.\n meta: S.optional(S.object),\n spec: TriggerSpecSchema,\n}) {}\n\n/**\n * Function manifest file.\n */\nexport const FunctionManifestSchema = S.struct({\n functions: S.optional(S.mutable(S.array(RawObject(FunctionDef)))),\n triggers: S.optional(S.mutable(S.array(RawObject(FunctionTrigger)))),\n});\n\nexport type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;AAIA,SAASA,WAAWC,GAAGC,mBAAmB;AAU1C,IAAMC,4BAA4BC,EAAEC,OAAO;EACzCC,MAAMF,EAAEG,QAAQ,cAAA;;EAEhBC,QAAQJ,EAAEK,MACRL,EAAEC,OAAO;IACPC,MAAMF,EAAEM;IACRC,OAAOP,EAAEQ,SAASR,EAAES,OAAOT,EAAEM,QAAQN,EAAEU,GAAG,CAAA;EAC5C,CAAA,CAAA;EAEFC,SAASX,EAAEQ,SACTR,EAAEC,OAAO;;IAEPW,MAAMZ,EAAEQ,SAASR,EAAEa,OAAO;;IAE1BC,OAAOd,EAAEQ,SAASR,EAAEe,MAAM;EAC5B,CAAA,CAAA;AAEJ,CAAA;AAIA,IAAMC,qBAAqBhB,EAAEC,OAAO;EAClCC,MAAMF,EAAEG,QAAQ,OAAA;EAChBc,MAAMjB,EAAEM;AACV,CAAA;AAIA,IAAMY,uBAAuBlB,EAAEmB,QAC7BnB,EAAEC,OAAO;EACPC,MAAMF,EAAEG,QAAQ,SAAA;EAChBiB,QAAQpB,EAAEM;;EAEVe,MAAMrB,EAAEQ,SAASR,EAAEe,MAAM;AAC3B,CAAA,CAAA;AAKF,IAAMO,yBAAyBtB,EAAEC,OAAO;EACtCC,MAAMF,EAAEG,QAAQ,WAAA;EAChBoB,KAAKvB,EAAEM;EACPkB,MAAMxB,EAAEQ,SAASR,EAAES,OAAOT,EAAEM,QAAQN,EAAEU,GAAG,CAAA;AAC3C,CAAA;AAIA,IAAMe,oBAAoBzB,EAAE0B,MAC1BV,oBACAE,sBACAI,wBACAvB,yBAAAA;AAQK,IAAM4B,cAAN,cAA0BC,YAAY;EAC3CC,UAAU;EACVC,SAAS;AACX,CAAA,EAAG;EACDC,KAAK/B,EAAEM;EACP0B,aAAahC,EAAEQ,SAASR,EAAEM,MAAM;EAChC2B,OAAOjC,EAAEM;;EAET4B,SAASlC,EAAEM;AACb,CAAA,EAAA;AAAI;AAEG,IAAM6B,kBAAN,cAA8BP,YAAY;EAC/CC,UAAU;EACVC,SAAS;AACX,CAAA,EAAG;EACDM,UAAUpC,EAAEM,OAAO+B,KAAKrC,EAAEgC,YAAY,eAAA,CAAA;;EAEtCM,MAAMtC,EAAEQ,SAASR,EAAEuC,MAAM;EACzBC,MAAMf;AACR,CAAA,EAAA;AAAI;AAKG,IAAMgB,yBAAyBzC,EAAEC,OAAO;EAC7CyC,WAAW1C,EAAEQ,SAASR,EAAEmB,QAAQnB,EAAEK,MAAMsC,UAAUhB,WAAAA,CAAAA,CAAAA,CAAAA;EAClDiB,UAAU5C,EAAEQ,SAASR,EAAEmB,QAAQnB,EAAEK,MAAMsC,UAAUR,eAAAA,CAAAA,CAAAA,CAAAA;AACnD,CAAA;",
6
- "names": ["RawObject", "S", "TypedObject", "SubscriptionTriggerSchema", "S", "struct", "type", "literal", "filter", "array", "string", "props", "optional", "record", "any", "options", "deep", "boolean", "delay", "number", "TimerTriggerSchema", "cron", "WebhookTriggerSchema", "mutable", "method", "port", "WebsocketTriggerSchema", "url", "init", "TriggerSpecSchema", "union", "FunctionDef", "TypedObject", "typename", "version", "uri", "description", "route", "handler", "FunctionTrigger", "function", "pipe", "meta", "object", "spec", "FunctionManifestSchema", "functions", "RawObject", "triggers"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/types.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { RawObject, S, TypedObject } from '@dxos/echo-schema';\n\n/**\n * Type discriminator for TriggerSpec.\n * Every spec has a type field of type FunctionTriggerType that we can use to understand which\n * type we're working with.\n * https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions\n */\nexport type FunctionTriggerType = 'subscription' | 'timer' | 'webhook' | 'websocket';\n\nconst SubscriptionTriggerSchema = S.struct({\n type: S.literal('subscription'),\n // TODO(burdon): Define query DSL (from ECHO).\n filter: S.array(\n S.struct({\n type: S.string,\n props: S.optional(S.record(S.string, S.any)),\n }),\n ),\n options: S.optional(\n S.struct({\n // Watch changes to object (not just creation).\n deep: S.optional(S.boolean),\n // Debounce changes (delay in ms).\n delay: S.optional(S.number),\n }),\n ),\n});\n\nexport type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;\n\nconst TimerTriggerSchema = S.struct({\n type: S.literal('timer'),\n cron: S.string,\n});\n\nexport type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;\n\nconst WebhookTriggerSchema = S.mutable(\n S.struct({\n type: S.literal('webhook'),\n method: S.string,\n // Assigned port.\n port: S.optional(S.number),\n }),\n);\n\nexport type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;\n\nconst WebsocketTriggerSchema = S.struct({\n type: S.literal('websocket'),\n url: S.string,\n init: S.optional(S.record(S.string, S.any)),\n});\n\nexport type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;\n\nconst TriggerSpecSchema = S.union(\n TimerTriggerSchema,\n WebhookTriggerSchema,\n WebsocketTriggerSchema,\n SubscriptionTriggerSchema,\n);\n\nexport type TriggerSpec = TimerTrigger | WebhookTrigger | WebsocketTrigger | SubscriptionTrigger;\n\n/**\n * Function definition.\n */\nexport class FunctionDef extends TypedObject({\n typename: 'dxos.org/type/FunctionDef',\n version: '0.1.0',\n})({\n uri: S.string,\n description: S.optional(S.string),\n route: S.string,\n // TODO(burdon): NPM/GitHub/Docker/CF URL?\n handler: S.string,\n}) {}\n\nexport class FunctionTrigger extends TypedObject({\n typename: 'dxos.org/type/FunctionTrigger',\n version: '0.1.0',\n})({\n function: S.string.pipe(S.description('Function URI.')),\n // Context is merged into the event data passed to the function.\n meta: S.optional(S.object),\n spec: TriggerSpecSchema,\n}) {}\n\n/**\n * Function manifest file.\n */\nexport const FunctionManifestSchema = S.struct({\n functions: S.optional(S.mutable(S.array(RawObject(FunctionDef)))),\n triggers: S.optional(S.mutable(S.array(RawObject(FunctionTrigger)))),\n});\n\nexport type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,yBAA0C;;;;;;;;AAU1C,IAAMA,4BAA4BC,qBAAEC,OAAO;EACzCC,MAAMF,qBAAEG,QAAQ,cAAA;;EAEhBC,QAAQJ,qBAAEK,MACRL,qBAAEC,OAAO;IACPC,MAAMF,qBAAEM;IACRC,OAAOP,qBAAEQ,SAASR,qBAAES,OAAOT,qBAAEM,QAAQN,qBAAEU,GAAG,CAAA;EAC5C,CAAA,CAAA;EAEFC,SAASX,qBAAEQ,SACTR,qBAAEC,OAAO;;IAEPW,MAAMZ,qBAAEQ,SAASR,qBAAEa,OAAO;;IAE1BC,OAAOd,qBAAEQ,SAASR,qBAAEe,MAAM;EAC5B,CAAA,CAAA;AAEJ,CAAA;AAIA,IAAMC,qBAAqBhB,qBAAEC,OAAO;EAClCC,MAAMF,qBAAEG,QAAQ,OAAA;EAChBc,MAAMjB,qBAAEM;AACV,CAAA;AAIA,IAAMY,uBAAuBlB,qBAAEmB,QAC7BnB,qBAAEC,OAAO;EACPC,MAAMF,qBAAEG,QAAQ,SAAA;EAChBiB,QAAQpB,qBAAEM;;EAEVe,MAAMrB,qBAAEQ,SAASR,qBAAEe,MAAM;AAC3B,CAAA,CAAA;AAKF,IAAMO,yBAAyBtB,qBAAEC,OAAO;EACtCC,MAAMF,qBAAEG,QAAQ,WAAA;EAChBoB,KAAKvB,qBAAEM;EACPkB,MAAMxB,qBAAEQ,SAASR,qBAAES,OAAOT,qBAAEM,QAAQN,qBAAEU,GAAG,CAAA;AAC3C,CAAA;AAIA,IAAMe,oBAAoBzB,qBAAE0B,MAC1BV,oBACAE,sBACAI,wBACAvB,yBAAAA;AAQK,IAAM4B,cAAN,kBAA0BC,gCAAY;EAC3CC,UAAU;EACVC,SAAS;AACX,CAAA,EAAG;EACDC,KAAK/B,qBAAEM;EACP0B,aAAahC,qBAAEQ,SAASR,qBAAEM,MAAM;EAChC2B,OAAOjC,qBAAEM;;EAET4B,SAASlC,qBAAEM;AACb,CAAA,EAAA;AAAI;AAEG,IAAM6B,kBAAN,kBAA8BP,gCAAY;EAC/CC,UAAU;EACVC,SAAS;AACX,CAAA,EAAG;EACDM,UAAUpC,qBAAEM,OAAO+B,KAAKrC,qBAAEgC,YAAY,eAAA,CAAA;;EAEtCM,MAAMtC,qBAAEQ,SAASR,qBAAEuC,MAAM;EACzBC,MAAMf;AACR,CAAA,EAAA;AAAI;AAKG,IAAMgB,yBAAyBzC,qBAAEC,OAAO;EAC7CyC,WAAW1C,qBAAEQ,SAASR,qBAAEmB,QAAQnB,qBAAEK,UAAMsC,8BAAUhB,WAAAA,CAAAA,CAAAA,CAAAA;EAClDiB,UAAU5C,qBAAEQ,SAASR,qBAAEmB,QAAQnB,qBAAEK,UAAMsC,8BAAUR,eAAAA,CAAAA,CAAAA,CAAAA;AACnD,CAAA;",
6
- "names": ["SubscriptionTriggerSchema", "S", "struct", "type", "literal", "filter", "array", "string", "props", "optional", "record", "any", "options", "deep", "boolean", "delay", "number", "TimerTriggerSchema", "cron", "WebhookTriggerSchema", "mutable", "method", "port", "WebsocketTriggerSchema", "url", "init", "TriggerSpecSchema", "union", "FunctionDef", "TypedObject", "typename", "version", "uri", "description", "route", "handler", "FunctionTrigger", "function", "pipe", "meta", "object", "spec", "FunctionManifestSchema", "functions", "RawObject", "triggers"]
7
- }
@@ -1,15 +0,0 @@
1
- export type Comparator<A, B = A> = (a: A, b: B) => boolean;
2
- export type DiffResult<A, B = A> = {
3
- added: B[];
4
- updated: A[];
5
- removed: A[];
6
- };
7
- /**
8
- *
9
- * @param previous
10
- * @param next
11
- * @param comparator
12
- */
13
- export declare const diff: <A, B = A>(previous: readonly A[], next: readonly B[], comparator: Comparator<A, B>) => DiffResult<A, B>;
14
- export declare const intersection: <A, B = A>(a: A[], b: B[], comparator: Comparator<A, B>) => A[];
15
- //# sourceMappingURL=util.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/util.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC;AAE3D,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI;IACjC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,OAAO,EAAE,CAAC,EAAE,CAAC;IACb,OAAO,EAAE,CAAC,EAAE,CAAC;CACd,CAAC;AAEF;;;;;GAKG;AAEH,eAAO,MAAM,IAAI,0GAwBhB,CAAC;AAGF,eAAO,MAAM,YAAY,iEACuC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=util.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.test.d.ts","sourceRoot":"","sources":["../../../src/util.test.ts"],"names":[],"mappings":""}
package/src/util.test.ts DELETED
@@ -1,43 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { expect } from 'chai';
6
-
7
- import { describe, test } from '@dxos/test';
8
-
9
- import { diff, intersection } from './util';
10
-
11
- describe('diff', () => {
12
- test('returns the difference between two sets', () => {
13
- {
14
- const { added, updated, removed } = diff<number>([], [], (a, b) => a === b);
15
- expect(added).to.deep.eq([]);
16
- expect(updated).to.deep.eq([]);
17
- expect(removed).to.deep.eq([]);
18
- }
19
- {
20
- const previous = [1, 2, 3];
21
- const next = [2, 3, 4];
22
- const { added, updated, removed } = diff(previous, next, (a, b) => a === b);
23
- expect(added).to.deep.eq([4]);
24
- expect(updated).to.deep.eq([2, 3]);
25
- expect(removed).to.deep.eq([1]);
26
- }
27
- {
28
- const previous = [{ x: 1 }, { x: 2 }, { x: 3 }];
29
- const next = [{ x: 2 }, { x: 3 }, { x: 4 }];
30
- const { added, updated, removed } = diff(previous, next, (a, b) => a.x === b.x);
31
- expect(added).to.deep.eq([{ x: 4 }]);
32
- expect(updated).to.deep.eq([{ x: 2 }, { x: 3 }]);
33
- expect(removed).to.deep.eq([{ x: 1 }]);
34
- }
35
- });
36
-
37
- test('intersection', () => {
38
- expect(intersection([1, 2, 3], [2, 3, 4], (a, b) => a === b)).to.deep.eq([2, 3]);
39
- expect(
40
- intersection([{ x: 1 }, { x: 2 }, { x: 3 }], [{ x: 2 }, { x: 3 }, { x: 4 }], (a, b) => a.x === b.x),
41
- ).to.deep.eq([{ x: 2 }, { x: 3 }]);
42
- });
43
- });
package/src/util.ts DELETED
@@ -1,48 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export type Comparator<A, B = A> = (a: A, b: B) => boolean;
6
-
7
- export type DiffResult<A, B = A> = {
8
- added: B[];
9
- updated: A[];
10
- removed: A[];
11
- };
12
-
13
- /**
14
- *
15
- * @param previous
16
- * @param next
17
- * @param comparator
18
- */
19
- // TODO(burdon): Factor out.
20
- export const diff = <A, B = A>(
21
- previous: readonly A[],
22
- next: readonly B[],
23
- comparator: Comparator<A, B>,
24
- ): DiffResult<A, B> => {
25
- const remaining = [...previous];
26
- const result: DiffResult<A, B> = {
27
- added: [],
28
- updated: [],
29
- removed: remaining,
30
- };
31
-
32
- // TODO(burdon): Mark and sweep.
33
- for (const object of next) {
34
- const index = remaining.findIndex((item) => comparator(item, object));
35
- if (index === -1) {
36
- result.added.push(object);
37
- } else {
38
- result.updated.push(remaining[index]);
39
- remaining.splice(index, 1);
40
- }
41
- }
42
-
43
- return result;
44
- };
45
-
46
- // TODO(burdon): Factor out.
47
- export const intersection = <A, B = A>(a: A[], b: B[], comparator: Comparator<A, B>): A[] =>
48
- a.filter((a) => b.find((b) => comparator(a, b)) !== undefined);