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