@dxos/functions 0.5.3-main.79e0565 → 0.5.3-main.7cd4303

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 (48) hide show
  1. package/dist/lib/browser/index.mjs +238 -285
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +240 -285
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/index.d.ts +1 -1
  8. package/dist/types/src/index.d.ts.map +1 -1
  9. package/dist/types/src/{function → registry}/function-registry.d.ts +4 -4
  10. package/dist/types/src/registry/function-registry.d.ts.map +1 -0
  11. package/dist/types/src/registry/function-registry.test.d.ts.map +1 -0
  12. package/dist/types/src/registry/index.d.ts.map +1 -0
  13. package/dist/types/src/runtime/dev-server.d.ts +1 -1
  14. package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
  15. package/dist/types/src/runtime/scheduler.d.ts +1 -2
  16. package/dist/types/src/runtime/scheduler.d.ts.map +1 -1
  17. package/dist/types/src/trigger/trigger-registry.d.ts.map +1 -1
  18. package/dist/types/src/trigger/type/subscription-trigger.d.ts.map +1 -1
  19. package/dist/types/src/types.d.ts +19 -33
  20. package/dist/types/src/types.d.ts.map +1 -1
  21. package/package.json +16 -15
  22. package/schema/functions.json +9 -18
  23. package/src/index.ts +1 -1
  24. package/src/{function → registry}/function-registry.test.ts +10 -10
  25. package/src/registry/function-registry.ts +84 -0
  26. package/src/runtime/dev-server.test.ts +2 -2
  27. package/src/runtime/dev-server.ts +5 -4
  28. package/src/runtime/scheduler.test.ts +1 -1
  29. package/src/runtime/scheduler.ts +8 -16
  30. package/src/testing/functions-integration.test.ts +1 -1
  31. package/src/testing/setup.ts +1 -1
  32. package/src/trigger/trigger-registry.test.ts +34 -60
  33. package/src/trigger/trigger-registry.ts +6 -19
  34. package/src/trigger/type/subscription-trigger.ts +10 -17
  35. package/src/types.ts +10 -12
  36. package/dist/types/src/function/function-registry.d.ts.map +0 -1
  37. package/dist/types/src/function/function-registry.test.d.ts.map +0 -1
  38. package/dist/types/src/function/index.d.ts.map +0 -1
  39. package/dist/types/src/util.d.ts +0 -15
  40. package/dist/types/src/util.d.ts.map +0 -1
  41. package/dist/types/src/util.test.d.ts +0 -2
  42. package/dist/types/src/util.test.d.ts.map +0 -1
  43. package/src/function/function-registry.ts +0 -90
  44. package/src/util.test.ts +0 -43
  45. package/src/util.ts +0 -48
  46. /package/dist/types/src/{function → registry}/function-registry.test.d.ts +0 -0
  47. /package/dist/types/src/{function → registry}/index.d.ts +0 -0
  48. /package/src/{function → registry}/index.ts +0 -0
@@ -11,7 +11,7 @@ import { type Space } from '@dxos/client/echo';
11
11
  import { TestBuilder } from '@dxos/client/testing';
12
12
  import { Context } from '@dxos/context';
13
13
  import { Filter } from '@dxos/echo-db';
14
- import { create, splitMeta } from '@dxos/echo-schema';
14
+ import { create } from '@dxos/echo-schema';
15
15
  import { describe, test } from '@dxos/test';
16
16
  import { range } from '@dxos/util';
17
17
 
@@ -19,17 +19,9 @@ import { TriggerRegistry } from './trigger-registry';
19
19
  import { createInitializedClients, TestType, triggerWebhook } from '../testing';
20
20
  import { type FunctionManifest, FunctionTrigger } from '../types';
21
21
 
22
- const manifest: FunctionManifest = {
22
+ const testManifest: FunctionManifest = {
23
23
  triggers: [
24
24
  {
25
- '@meta': {
26
- keys: [
27
- {
28
- source: 'example.com',
29
- id: 'trigger-1',
30
- },
31
- ],
32
- },
33
25
  function: 'example.com/function/webhook-test',
34
26
  spec: {
35
27
  type: 'webhook',
@@ -37,22 +29,10 @@ const manifest: FunctionManifest = {
37
29
  },
38
30
  },
39
31
  {
40
- '@meta': {
41
- keys: [
42
- {
43
- source: 'example.com',
44
- id: 'trigger-2',
45
- },
46
- ],
47
- },
48
32
  function: 'example.com/function/subscription-test',
49
33
  spec: {
50
34
  type: 'subscription',
51
- filter: [
52
- {
53
- type: TestType.typename,
54
- },
55
- ],
35
+ filter: [{ type: TestType.typename }],
56
36
  },
57
37
  },
58
38
  ],
@@ -77,12 +57,11 @@ describe('trigger registry', () => {
77
57
  const client = (await createInitializedClients(testBuilder))[0];
78
58
  const registry = createRegistry(client);
79
59
  const space = await client.spaces.create();
80
- await registry.register(space, manifest);
60
+ await registry.register(space, testManifest);
81
61
  const { objects } = await space.db.query(Filter.schema(FunctionTrigger)).run();
82
- expect(objects.length).to.eq(manifest.triggers?.length);
83
-
84
- const expected = manifest.triggers?.map((trigger) => trigger.function).sort();
85
- expect(objects.map((object: FunctionTrigger) => object.function).sort()).to.deep.eq(expected);
62
+ expect(objects.length).to.eq(testManifest.triggers?.length);
63
+ const expected = testManifest.triggers?.map((t) => t.function).sort();
64
+ expect(objects.map((o: FunctionTrigger) => o.function).sort()).to.deep.eq(expected);
86
65
  });
87
66
  });
88
67
 
@@ -91,13 +70,13 @@ describe('trigger registry', () => {
91
70
  const client = (await createInitializedClients(testBuilder))[0];
92
71
  const space = await client.spaces.create();
93
72
  const registry = createRegistry(client);
94
- await registry.register(space, manifest);
73
+ await registry.register(space, testManifest);
95
74
  await registry.open(ctx);
96
- await waitForInactiveTriggers(registry, space);
75
+ await waitHasInactiveTriggers(registry, space);
97
76
 
98
77
  const callbackInvoked = new Trigger();
99
78
  const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
100
- const webhookTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'webhook')!;
79
+ const webhookTrigger = allTriggers.find((t: FunctionTrigger) => t.spec.type === 'webhook')!;
101
80
  await registry.activate({ space }, webhookTrigger, async () => {
102
81
  callbackInvoked.wake();
103
82
  return 200;
@@ -111,16 +90,15 @@ describe('trigger registry', () => {
111
90
  const client = (await createInitializedClients(testBuilder))[0];
112
91
  const space = await client.spaces.create();
113
92
  const registry = createRegistry(client);
114
- await registry.register(space, manifest);
93
+ await registry.register(space, testManifest);
115
94
  await registry.open(ctx);
116
- await waitForInactiveTriggers(registry, space);
95
+ await waitHasInactiveTriggers(registry, space);
117
96
 
118
97
  const inactiveTrigger = registry.getInactiveTriggers(space)[0];
119
98
  await registry.activate({ space }, inactiveTrigger, async () => 200);
120
99
 
121
100
  const updatedInactiveList = registry.getInactiveTriggers(space);
122
- expect(updatedInactiveList.find((trigger: FunctionTrigger) => trigger.function === inactiveTrigger.function)).to
123
- .be.undefined;
101
+ expect(updatedInactiveList.find((t: FunctionTrigger) => t.function === inactiveTrigger.function)).to.be.undefined;
124
102
  });
125
103
  });
126
104
 
@@ -129,12 +107,12 @@ describe('trigger registry', () => {
129
107
  const client = (await createInitializedClients(testBuilder))[0];
130
108
  const space = await client.spaces.create();
131
109
  const registry = createRegistry(client);
132
- await registry.register(space, manifest);
110
+ await registry.register(space, testManifest);
133
111
  await registry.open(ctx);
134
- await waitForInactiveTriggers(registry, space);
112
+ await waitHasInactiveTriggers(registry, space);
135
113
 
136
114
  const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
137
- const echoTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'subscription')!;
115
+ const echoTrigger = allTriggers.find((t: FunctionTrigger) => t.spec.type === 'subscription')!;
138
116
  let count = 0;
139
117
  await registry.activate({ space }, echoTrigger, async () => {
140
118
  count++;
@@ -146,6 +124,7 @@ describe('trigger registry', () => {
146
124
  expect(count).to.eq(1);
147
125
 
148
126
  space.db.remove(echoTrigger);
127
+
149
128
  space.db.add(create(TestType, { title: '2' }));
150
129
  await sleep(20);
151
130
  expect(count).to.eq(1);
@@ -155,12 +134,12 @@ describe('trigger registry', () => {
155
134
  const client = (await createInitializedClients(testBuilder))[0];
156
135
  const space = await client.spaces.create();
157
136
  const registry = createRegistry(client);
158
- await registry.register(space, manifest);
137
+ await registry.register(space, testManifest);
159
138
  await registry.open(ctx);
160
- await waitForInactiveTriggers(registry, space);
139
+ await waitHasInactiveTriggers(registry, space);
161
140
 
162
141
  const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
163
- const echoTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'subscription')!;
142
+ const echoTrigger = allTriggers.find((t: FunctionTrigger) => t.spec.type === 'subscription')!;
164
143
  let count = 0;
165
144
  await registry.activate({ space }, echoTrigger, async () => {
166
145
  count++;
@@ -176,20 +155,19 @@ describe('trigger registry', () => {
176
155
  });
177
156
 
178
157
  describe('trigger events', () => {
179
- test.only('event fired when all registered when opened', async () => {
158
+ test('event fired when all registered when opened', async () => {
180
159
  const client = (await createInitializedClients(testBuilder))[0];
181
160
  const registry = createRegistry(client);
182
- const triggers = createTriggers(client.spaces.default, 3);
161
+ const triggers = createTriggersInSpace(client.spaces.default, 3);
183
162
 
184
163
  const triggersRegistered = new Trigger<FunctionTrigger[]>();
185
164
  registry.registered.on((fn) => {
186
165
  expect(fn.space.key.toHex()).to.eq(client.spaces.default.key.toHex());
187
166
  triggersRegistered.wake(fn.triggers);
188
167
  });
189
-
190
168
  void registry.open(ctx);
191
169
  const functions = await triggersRegistered.wait();
192
- const expected = triggers.map((object) => object.id).sort();
170
+ const expected = triggers.map((obj) => obj.id).sort();
193
171
  expect(functions.map((fn) => fn.id).sort()).to.deep.eq(expected);
194
172
  });
195
173
 
@@ -204,16 +182,16 @@ describe('trigger registry', () => {
204
182
  triggerRegistered.wake(fn.triggers[0]);
205
183
  });
206
184
  await registry.open(ctx);
207
- await registry.register(space, { triggers: manifest?.triggers?.slice(0, 1) });
185
+ await registry.register(space, { triggers: testManifest?.triggers?.slice(0, 1) });
208
186
  const registered = await triggerRegistered.wait();
209
- expect(registered.function).to.eq(manifest.triggers![0].function);
187
+ expect(registered.function).to.eq(testManifest.triggers![0].function);
210
188
  });
211
189
 
212
190
  test('event fired when a new trigger is removed', async () => {
213
191
  const client = (await createInitializedClients(testBuilder))[0];
214
192
  const registry = createRegistry(client);
215
193
  const space = await client.spaces.create();
216
- const triggers = createTriggers(space, 3);
194
+ const triggers = createTriggersInSpace(space, 3);
217
195
 
218
196
  const triggerLoaded = new Trigger();
219
197
  registry.registered.on((fn) => triggerLoaded.wake());
@@ -223,7 +201,7 @@ describe('trigger registry', () => {
223
201
  expect(fn.triggers.length).to.eq(1);
224
202
  triggerRemoved.wake(fn.triggers[0]);
225
203
  });
226
- await registry.register(space, manifest);
204
+ await registry.register(space, testManifest);
227
205
  await registry.open(ctx);
228
206
  await triggerLoaded.wait();
229
207
 
@@ -233,23 +211,19 @@ describe('trigger registry', () => {
233
211
  });
234
212
  });
235
213
 
214
+ const waitHasInactiveTriggers = async (registry: TriggerRegistry, space: Space) => {
215
+ await waitForCondition({ condition: () => registry.getInactiveTriggers(space).length > 0 });
216
+ };
217
+
236
218
  const createRegistry = (client: Client) => {
237
219
  const registry = new TriggerRegistry(client);
238
220
  ctx.onDispose(() => registry.close());
239
221
  return registry;
240
222
  };
241
223
 
242
- const createTriggers = (space: Space, count: number) => {
243
- const triggers = range(count, () => {
244
- const { meta, object } = splitMeta(manifest.triggers![0]);
245
- return create(FunctionTrigger, object, meta);
246
- });
247
-
248
- triggers.forEach((trigger) => space.db.add(trigger));
224
+ const createTriggersInSpace = (space: Space, count: number) => {
225
+ const triggers = range(count, () => create(FunctionTrigger, { ...testManifest.triggers![0] }));
226
+ triggers.forEach((def) => space.db.add(def));
249
227
  return triggers;
250
228
  };
251
-
252
- const waitForInactiveTriggers = async (registry: TriggerRegistry, space: Space) => {
253
- await waitForCondition({ condition: () => registry.getInactiveTriggers(space).length > 0 });
254
- };
255
229
  });
@@ -4,9 +4,8 @@
4
4
 
5
5
  import { Event } from '@dxos/async';
6
6
  import { type Client } from '@dxos/client';
7
- import { create, Filter, getMeta, type Space } from '@dxos/client/echo';
7
+ import { create, Filter, 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';
10
9
  import { invariant } from '@dxos/invariant';
11
10
  import { PublicKey } from '@dxos/keys';
12
11
  import { log } from '@dxos/log';
@@ -14,7 +13,6 @@ import { ComplexMap } from '@dxos/util';
14
13
 
15
14
  import { createSubscriptionTrigger, createTimerTrigger, createWebhookTrigger, createWebsocketTrigger } from './type';
16
15
  import { type FunctionManifest, FunctionTrigger, type FunctionTriggerType, type TriggerSpec } from '../types';
17
- import { diff, intersection } from '../util';
18
16
 
19
17
  type ResponseCode = number;
20
18
 
@@ -98,25 +96,14 @@ export class TriggerRegistry extends Resource {
98
96
  if (!manifest.triggers?.length) {
99
97
  return;
100
98
  }
101
- if (!space.db.graph.runtimeSchemaRegistry.hasSchema(FunctionTrigger)) {
99
+ if (!space.db.graph.runtimeSchemaRegistry.isSchemaRegistered(FunctionTrigger)) {
102
100
  space.db.graph.runtimeSchemaRegistry.registerSchema(FunctionTrigger);
103
101
  }
104
102
 
105
- // Sync triggers.
106
- 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
- });
113
-
114
- added.forEach((trigger) => {
115
- const { meta, object } = splitMeta(trigger);
116
- space.db.add(create(FunctionTrigger, object, meta));
117
- });
118
- // TODO(burdon): Update existing triggers.
119
- removed.forEach((trigger) => space.db.remove(trigger));
103
+ const reactiveObjects = manifest.triggers.map((template: Omit<FunctionTrigger, 'id'>) =>
104
+ create(FunctionTrigger, { ...template }),
105
+ );
106
+ reactiveObjects.forEach((obj) => space.db.add(obj));
120
107
  }
121
108
 
122
109
  protected override async _open(): Promise<void> {
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { TextV0Type } from '@braneframe/types';
6
- import { debounce, UpdateScheduler } from '@dxos/async';
6
+ import { debounce, DeferredTask } from '@dxos/async';
7
7
  import { type Context } from '@dxos/context';
8
8
  import { createSubscription, Filter, getAutomergeObjectCore, type Query } from '@dxos/echo-db';
9
9
  import { log } from '@dxos/log';
@@ -18,33 +18,26 @@ export const createSubscriptionTrigger: TriggerFactory<SubscriptionTrigger> = as
18
18
  callback: TriggerCallback,
19
19
  ) => {
20
20
  const objectIds = new Set<string>();
21
- const task = new UpdateScheduler(
22
- ctx,
23
- async () => {
24
- if (objectIds.size > 0) {
25
- const objects = Array.from(objectIds);
26
- objectIds.clear();
27
- await callback({ objects });
28
- }
29
- },
30
- { maxFrequency: 4 },
31
- );
21
+ const task = new DeferredTask(ctx, async () => {
22
+ if (objectIds.size > 0) {
23
+ await callback({ objects: Array.from(objectIds) });
24
+ objectIds.clear();
25
+ }
26
+ });
32
27
 
33
28
  // TODO(burdon): Don't fire initially?
34
29
  // TODO(burdon): Create queue. Only allow one invocation per trigger at a time?
35
30
  const subscriptions: (() => void)[] = [];
36
31
  const subscription = createSubscription(({ added, updated }) => {
37
- const sizeBefore = objectIds.size;
32
+ log.info('updated', { added: added.length, updated: updated.length });
38
33
  for (const object of added) {
39
34
  objectIds.add(object.id);
40
35
  }
41
36
  for (const object of updated) {
42
37
  objectIds.add(object.id);
43
38
  }
44
- if (objectIds.size > sizeBefore) {
45
- log.info('updated', { added: added.length, updated: updated.length });
46
- task.trigger();
47
- }
39
+
40
+ task.schedule();
48
41
  });
49
42
 
50
43
  subscriptions.push(() => subscription.unsubscribe());
package/src/types.ts CHANGED
@@ -2,7 +2,10 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { RawObject, S, TypedObject } from '@dxos/echo-schema';
5
+ import { AST, S, TypedObject } from '@dxos/echo-schema';
6
+
7
+ // TODO(burdon): Factor out.
8
+ const omitEchoId = <T>(schema: S.Schema<T>): S.Schema<Omit<T, 'id'>> => S.make(AST.omit(schema.ast, ['id']));
6
9
 
7
10
  /**
8
11
  * Type discriminator for TriggerSpec.
@@ -14,7 +17,7 @@ export type FunctionTriggerType = 'subscription' | 'timer' | 'webhook' | 'websoc
14
17
 
15
18
  const SubscriptionTriggerSchema = S.struct({
16
19
  type: S.literal('subscription'),
17
- // TODO(burdon): Define query DSL (from ECHO).
20
+ // TODO(burdon): Define query DSL.
18
21
  filter: S.array(
19
22
  S.struct({
20
23
  type: S.string,
@@ -30,14 +33,12 @@ const SubscriptionTriggerSchema = S.struct({
30
33
  }),
31
34
  ),
32
35
  });
33
-
34
36
  export type SubscriptionTrigger = S.Schema.Type<typeof SubscriptionTriggerSchema>;
35
37
 
36
38
  const TimerTriggerSchema = S.struct({
37
39
  type: S.literal('timer'),
38
40
  cron: S.string,
39
41
  });
40
-
41
42
  export type TimerTrigger = S.Schema.Type<typeof TimerTriggerSchema>;
42
43
 
43
44
  const WebhookTriggerSchema = S.mutable(
@@ -48,7 +49,6 @@ const WebhookTriggerSchema = S.mutable(
48
49
  port: S.optional(S.number),
49
50
  }),
50
51
  );
51
-
52
52
  export type WebhookTrigger = S.Schema.Type<typeof WebhookTriggerSchema>;
53
53
 
54
54
  const WebsocketTriggerSchema = S.struct({
@@ -56,7 +56,6 @@ const WebsocketTriggerSchema = S.struct({
56
56
  url: S.string,
57
57
  init: S.optional(S.record(S.string, S.any)),
58
58
  });
59
-
60
59
  export type WebsocketTrigger = S.Schema.Type<typeof WebsocketTriggerSchema>;
61
60
 
62
61
  const TriggerSpecSchema = S.union(
@@ -65,7 +64,6 @@ const TriggerSpecSchema = S.union(
65
64
  WebsocketTriggerSchema,
66
65
  SubscriptionTriggerSchema,
67
66
  );
68
-
69
67
  export type TriggerSpec = TimerTrigger | WebhookTrigger | WebsocketTrigger | SubscriptionTrigger;
70
68
 
71
69
  /**
@@ -86,9 +84,9 @@ export class FunctionTrigger extends TypedObject({
86
84
  typename: 'dxos.org/type/FunctionTrigger',
87
85
  version: '0.1.0',
88
86
  })({
89
- 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),
87
+ function: S.string.pipe(S.description('Function ID/URI.')),
88
+ // Context passed to a function.
89
+ meta: S.optional(S.record(S.string, S.any)),
92
90
  spec: TriggerSpecSchema,
93
91
  }) {}
94
92
 
@@ -96,8 +94,8 @@ export class FunctionTrigger extends TypedObject({
96
94
  * Function manifest file.
97
95
  */
98
96
  export const FunctionManifestSchema = S.struct({
99
- functions: S.optional(S.mutable(S.array(RawObject(FunctionDef)))),
100
- triggers: S.optional(S.mutable(S.array(RawObject(FunctionTrigger)))),
97
+ functions: S.optional(S.mutable(S.array(omitEchoId(FunctionDef)))),
98
+ triggers: S.optional(S.mutable(S.array(omitEchoId(FunctionTrigger)))),
101
99
  });
102
100
 
103
101
  export type FunctionManifest = S.Schema.Type<typeof FunctionManifestSchema>;
@@ -1 +0,0 @@
1
- {"version":3,"file":"function-registry.d.ts","sourceRoot":"","sources":["../../../../src/function/function-registry.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAkB,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,KAAK,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAKvD,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG9D,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,QAAQ;IAKhC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA4D;IAEhG,SAAgB,UAAU,kCAAyC;gBAEtC,OAAO,EAAE,MAAM;IAIrC,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,WAAW,EAAE;IAIhD;;;OAGG;IACU,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;cAiBnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;cAgCtB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAG3D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"function-registry.test.d.ts","sourceRoot":"","sources":["../../../../src/function/function-registry.test.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/function/index.ts"],"names":[],"mappings":"AAIA,cAAc,qBAAqB,CAAC"}
@@ -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":""}
@@ -1,90 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { Event } from '@dxos/async';
6
- import { type Client } from '@dxos/client';
7
- import { create, Filter, type Space } from '@dxos/client/echo';
8
- import { type Context, Resource } from '@dxos/context';
9
- import { PublicKey } from '@dxos/keys';
10
- import { log } from '@dxos/log';
11
- import { ComplexMap } from '@dxos/util';
12
-
13
- import { FunctionDef, type FunctionManifest } from '../types';
14
- import { diff } from '../util';
15
-
16
- export type FunctionsRegisteredEvent = {
17
- space: Space;
18
- added: FunctionDef[];
19
- };
20
-
21
- export class FunctionRegistry extends Resource {
22
- private readonly _functionBySpaceKey = new ComplexMap<PublicKey, FunctionDef[]>(PublicKey.hash);
23
-
24
- public readonly registered = new Event<FunctionsRegisteredEvent>();
25
-
26
- constructor(private readonly _client: Client) {
27
- super();
28
- }
29
-
30
- public getFunctions(space: Space): FunctionDef[] {
31
- return this._functionBySpaceKey.get(space.key) ?? [];
32
- }
33
-
34
- /**
35
- * Loads function definitions from the manifest into the space.
36
- * We first load all the definitions from the space to deduplicate by functionId.
37
- */
38
- public async register(space: Space, functions: FunctionManifest['functions']): Promise<void> {
39
- log('register', { space: space.key, functions: functions?.length ?? 0 });
40
- if (!functions?.length) {
41
- return;
42
- }
43
- if (!space.db.graph.runtimeSchemaRegistry.hasSchema(FunctionDef)) {
44
- space.db.graph.runtimeSchemaRegistry.registerSchema(FunctionDef);
45
- }
46
-
47
- // Sync definitions.
48
- const { objects: existing } = await space.db.query(Filter.schema(FunctionDef)).run();
49
- const { added, removed } = diff(existing, functions, (a, b) => a.uri === b.uri);
50
- added.forEach((def) => space.db.add(create(FunctionDef, def)));
51
- // TODO(burdon): Update existing templates.
52
- removed.forEach((def) => space.db.remove(def));
53
- }
54
-
55
- protected override async _open(): Promise<void> {
56
- const spacesSubscription = this._client.spaces.subscribe(async (spaces) => {
57
- for (const space of spaces) {
58
- if (this._functionBySpaceKey.has(space.key)) {
59
- continue;
60
- }
61
-
62
- const registered: FunctionDef[] = [];
63
- this._functionBySpaceKey.set(space.key, registered);
64
- await space.waitUntilReady();
65
- if (this._ctx.disposed) {
66
- break;
67
- }
68
-
69
- // Subscribe to updates.
70
- this._ctx.onDispose(
71
- space.db.query(Filter.schema(FunctionDef)).subscribe(({ objects }) => {
72
- const { added } = diff(registered, objects, (a, b) => a.uri === b.uri);
73
- // TODO(burdon): Update and remove.
74
- if (added.length > 0) {
75
- registered.push(...added);
76
- this.registered.emit({ space, added });
77
- }
78
- }),
79
- );
80
- }
81
- });
82
-
83
- // TODO(burdon): API: Normalize unsubscribe methods.
84
- this._ctx.onDispose(() => spacesSubscription.unsubscribe());
85
- }
86
-
87
- protected override async _close(_: Context): Promise<void> {
88
- this._functionBySpaceKey.clear();
89
- }
90
- }
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);
File without changes