@dxos/functions 0.5.3-main.bbd33a9 → 0.5.3-main.bfb5bca
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-P3HPDHNI.mjs → chunk-4D4I3YMJ.mjs} +4 -4
- package/dist/lib/browser/{chunk-P3HPDHNI.mjs.map → chunk-4D4I3YMJ.mjs.map} +3 -3
- package/dist/lib/browser/index.mjs +145 -108
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types.mjs +1 -1
- package/dist/lib/node/{chunk-KTLM3JNV.cjs → chunk-3UYUR5N5.cjs} +7 -7
- package/dist/lib/node/{chunk-KTLM3JNV.cjs.map → chunk-3UYUR5N5.cjs.map} +3 -3
- package/dist/lib/node/index.cjs +156 -119
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +5 -5
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
- package/dist/types/src/testing/setup.d.ts.map +1 -1
- package/dist/types/src/trigger/trigger-registry.d.ts +2 -5
- 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/trigger/type/timer-trigger.d.ts.map +1 -1
- package/dist/types/src/trigger/type/webhook-trigger.d.ts.map +1 -1
- package/dist/types/src/trigger/type/websocket-trigger.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +15 -3
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +14 -14
- package/schema/functions.json +5 -0
- package/src/runtime/dev-server.ts +3 -3
- package/src/runtime/scheduler.test.ts +14 -9
- package/src/runtime/scheduler.ts +7 -7
- package/src/testing/functions-integration.test.ts +1 -0
- package/src/testing/setup.ts +8 -10
- package/src/trigger/trigger-registry.test.ts +30 -13
- package/src/trigger/trigger-registry.ts +49 -39
- package/src/trigger/type/subscription-trigger.ts +13 -7
- package/src/trigger/type/timer-trigger.ts +4 -3
- package/src/trigger/type/webhook-trigger.ts +3 -2
- package/src/trigger/type/websocket-trigger.ts +4 -3
- package/src/types.ts +6 -3
package/schema/functions.json
CHANGED
|
@@ -46,6 +46,7 @@ export class DevServer {
|
|
|
46
46
|
private readonly _functionsRegistry: FunctionRegistry,
|
|
47
47
|
private readonly _options: DevServerOptions,
|
|
48
48
|
) {
|
|
49
|
+
// TODO(burdon): Add/remove listener in start/stop.
|
|
49
50
|
this._functionsRegistry.registered.on(async ({ added }) => {
|
|
50
51
|
added.forEach((def) => this._load(def));
|
|
51
52
|
await this._safeUpdateRegistration();
|
|
@@ -188,8 +189,8 @@ export class DevServer {
|
|
|
188
189
|
registrationId: this._functionServiceRegistration,
|
|
189
190
|
functions: this.functions.map(({ def: { id, route } }) => ({ id, route })),
|
|
190
191
|
});
|
|
191
|
-
} catch (
|
|
192
|
-
log.catch(
|
|
192
|
+
} catch (err) {
|
|
193
|
+
log.catch(err);
|
|
193
194
|
}
|
|
194
195
|
}
|
|
195
196
|
|
|
@@ -211,7 +212,6 @@ export class DevServer {
|
|
|
211
212
|
private async _invoke(path: string, event: FunctionEvent) {
|
|
212
213
|
const { handler } = this._handlers[path] ?? {};
|
|
213
214
|
invariant(handler, `invalid path: ${path}`);
|
|
214
|
-
|
|
215
215
|
const context: FunctionContext = {
|
|
216
216
|
client: this._client,
|
|
217
217
|
dataDir: this._options.dataDir,
|
|
@@ -29,6 +29,15 @@ describe('scheduler', () => {
|
|
|
29
29
|
await testBuilder.destroy();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
const createScheduler = (callback: SchedulerOptions['callback']) => {
|
|
33
|
+
const scheduler = new Scheduler(new FunctionRegistry(client), new TriggerRegistry(client), { callback });
|
|
34
|
+
after(async () => {
|
|
35
|
+
await scheduler.stop();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return scheduler;
|
|
39
|
+
};
|
|
40
|
+
|
|
32
41
|
test('timer', async () => {
|
|
33
42
|
const manifest: FunctionManifest = {
|
|
34
43
|
functions: [
|
|
@@ -41,6 +50,7 @@ describe('scheduler', () => {
|
|
|
41
50
|
triggers: [
|
|
42
51
|
{
|
|
43
52
|
function: 'example.com/function/test',
|
|
53
|
+
enabled: true,
|
|
44
54
|
spec: {
|
|
45
55
|
type: 'timer',
|
|
46
56
|
cron: '0/1 * * * * *', // Every 1s.
|
|
@@ -75,6 +85,7 @@ describe('scheduler', () => {
|
|
|
75
85
|
triggers: [
|
|
76
86
|
{
|
|
77
87
|
function: 'example.com/function/test',
|
|
88
|
+
enabled: true,
|
|
78
89
|
spec: {
|
|
79
90
|
type: 'webhook',
|
|
80
91
|
method: 'GET',
|
|
@@ -108,6 +119,7 @@ describe('scheduler', () => {
|
|
|
108
119
|
triggers: [
|
|
109
120
|
{
|
|
110
121
|
function: 'example.com/function/test',
|
|
122
|
+
enabled: true,
|
|
111
123
|
spec: {
|
|
112
124
|
type: 'websocket',
|
|
113
125
|
// url: 'https://hub.dxos.network/api/mailbox/test',
|
|
@@ -154,6 +166,7 @@ describe('scheduler', () => {
|
|
|
154
166
|
triggers: [
|
|
155
167
|
{
|
|
156
168
|
function: 'example.com/function/test',
|
|
169
|
+
enabled: true,
|
|
157
170
|
spec: {
|
|
158
171
|
type: 'subscription',
|
|
159
172
|
filter: [{ type: TestType.typename }],
|
|
@@ -165,7 +178,7 @@ describe('scheduler', () => {
|
|
|
165
178
|
let count = 0;
|
|
166
179
|
const done = new Trigger();
|
|
167
180
|
const scheduler = createScheduler(async () => {
|
|
168
|
-
if (++count ===
|
|
181
|
+
if (++count === 1) {
|
|
169
182
|
done.wake();
|
|
170
183
|
}
|
|
171
184
|
});
|
|
@@ -180,12 +193,4 @@ describe('scheduler', () => {
|
|
|
180
193
|
|
|
181
194
|
await done.wait();
|
|
182
195
|
});
|
|
183
|
-
|
|
184
|
-
const createScheduler = (callback: SchedulerOptions['callback']) => {
|
|
185
|
-
const scheduler = new Scheduler(new FunctionRegistry(client), new TriggerRegistry(client), { callback });
|
|
186
|
-
after(async () => {
|
|
187
|
-
await scheduler.stop();
|
|
188
|
-
});
|
|
189
|
-
return scheduler;
|
|
190
|
-
};
|
|
191
196
|
});
|
package/src/runtime/scheduler.ts
CHANGED
|
@@ -72,28 +72,28 @@ export class Scheduler {
|
|
|
72
72
|
await Promise.all(mountTasks).catch(log.catch);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
private async activate(space: Space, functions: FunctionDef[],
|
|
76
|
-
const definition = functions.find((def) => def.uri ===
|
|
75
|
+
private async activate(space: Space, functions: FunctionDef[], trigger: FunctionTrigger) {
|
|
76
|
+
const definition = functions.find((def) => def.uri === trigger.function);
|
|
77
77
|
if (!definition) {
|
|
78
|
-
log.info('function is not found for trigger', {
|
|
78
|
+
log.info('function is not found for trigger', { trigger });
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
await this.triggers.activate(
|
|
82
|
+
await this.triggers.activate(space, trigger, async (args) => {
|
|
83
83
|
const mutex = this._functionUriToCallMutex.get(definition.uri) ?? new Mutex();
|
|
84
84
|
this._functionUriToCallMutex.set(definition.uri, mutex);
|
|
85
85
|
|
|
86
86
|
log.info('function triggered, waiting for mutex', { uri: definition.uri });
|
|
87
87
|
return mutex.executeSynchronized(() => {
|
|
88
88
|
log.info('mutex acquired', { uri: definition.uri });
|
|
89
|
-
return this._execFunction(definition,
|
|
90
|
-
meta:
|
|
89
|
+
return this._execFunction(definition, trigger, {
|
|
90
|
+
meta: trigger.meta ?? {},
|
|
91
91
|
data: { ...args, spaceKey: space.key },
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
log('activated trigger', { space: space.key, trigger
|
|
96
|
+
log('activated trigger', { space: space.key, trigger });
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
private async _execFunction<TData, TMeta>(
|
package/src/testing/setup.ts
CHANGED
|
@@ -10,16 +10,16 @@ import { range } from '@dxos/util';
|
|
|
10
10
|
import { TestType } from './types';
|
|
11
11
|
import { FunctionDef, FunctionTrigger } from '../types';
|
|
12
12
|
|
|
13
|
-
// TODO(burdon):
|
|
13
|
+
// TODO(burdon): Extend/wrap TestBuilder.
|
|
14
14
|
|
|
15
15
|
export const createInitializedClients = async (testBuilder: TestBuilder, count: number = 1, config?: Config) => {
|
|
16
16
|
const clients = range(count).map(() => new Client({ config, services: testBuilder.createLocalClientServices() }));
|
|
17
|
-
testBuilder.ctx.onDispose(() => Promise.all(clients.map((
|
|
17
|
+
testBuilder.ctx.onDispose(() => Promise.all(clients.map((client) => client.destroy())));
|
|
18
18
|
return Promise.all(
|
|
19
19
|
clients.map(async (client, index) => {
|
|
20
20
|
await client.initialize();
|
|
21
21
|
await client.halo.createIdentity({ displayName: `Peer ${index}` });
|
|
22
|
-
client.addSchema(
|
|
22
|
+
client.addSchema(FunctionDef, FunctionTrigger, TestType);
|
|
23
23
|
return client;
|
|
24
24
|
}),
|
|
25
25
|
);
|
|
@@ -34,12 +34,10 @@ export const createFunctionRuntime = async (testBuilder: TestBuilder): Promise<C
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
const client =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await functionsPlugin.open();
|
|
43
|
-
testBuilder.ctx.onDispose(() => functionsPlugin.close());
|
|
37
|
+
const [client] = await createInitializedClients(testBuilder, 1, config);
|
|
38
|
+
const plugin = new FunctionsPlugin();
|
|
39
|
+
await plugin.initialize({ client, clientServices: client.services });
|
|
40
|
+
await plugin.open();
|
|
41
|
+
testBuilder.ctx.onDispose(() => plugin.close());
|
|
44
42
|
return client;
|
|
45
43
|
};
|
|
@@ -31,6 +31,7 @@ const manifest: FunctionManifest = {
|
|
|
31
31
|
],
|
|
32
32
|
},
|
|
33
33
|
function: 'example.com/function/webhook-test',
|
|
34
|
+
enabled: true,
|
|
34
35
|
spec: {
|
|
35
36
|
type: 'webhook',
|
|
36
37
|
method: 'GET',
|
|
@@ -46,6 +47,7 @@ const manifest: FunctionManifest = {
|
|
|
46
47
|
],
|
|
47
48
|
},
|
|
48
49
|
function: 'example.com/function/subscription-test',
|
|
50
|
+
enabled: true,
|
|
49
51
|
spec: {
|
|
50
52
|
type: 'subscription',
|
|
51
53
|
filter: [
|
|
@@ -74,7 +76,7 @@ describe('trigger registry', () => {
|
|
|
74
76
|
|
|
75
77
|
describe('register', () => {
|
|
76
78
|
test('creates new triggers', async () => {
|
|
77
|
-
const client =
|
|
79
|
+
const [client] = await createInitializedClients(testBuilder);
|
|
78
80
|
const registry = createRegistry(client);
|
|
79
81
|
const space = await client.spaces.create();
|
|
80
82
|
await registry.register(space, manifest);
|
|
@@ -84,11 +86,24 @@ describe('trigger registry', () => {
|
|
|
84
86
|
const expected = manifest.triggers?.map((trigger) => trigger.function).sort();
|
|
85
87
|
expect(objects.map((object: FunctionTrigger) => object.function).sort()).to.deep.eq(expected);
|
|
86
88
|
});
|
|
89
|
+
|
|
90
|
+
test('set meta', () => {
|
|
91
|
+
const trigger = create(FunctionTrigger, {
|
|
92
|
+
function: 'example.com/function/webhook-test',
|
|
93
|
+
spec: {
|
|
94
|
+
type: 'webhook',
|
|
95
|
+
method: 'GET',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
(trigger.meta ??= {}).test = 100;
|
|
100
|
+
expect(trigger.meta.test).to.eq(100);
|
|
101
|
+
});
|
|
87
102
|
});
|
|
88
103
|
|
|
89
104
|
describe('activate', () => {
|
|
90
105
|
test('invokes the provided callback', async () => {
|
|
91
|
-
const client =
|
|
106
|
+
const [client] = await createInitializedClients(testBuilder);
|
|
92
107
|
const space = await client.spaces.create();
|
|
93
108
|
const registry = createRegistry(client);
|
|
94
109
|
await registry.register(space, manifest);
|
|
@@ -98,7 +113,7 @@ describe('trigger registry', () => {
|
|
|
98
113
|
const callbackInvoked = new Trigger();
|
|
99
114
|
const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
|
|
100
115
|
const webhookTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'webhook')!;
|
|
101
|
-
await registry.activate(
|
|
116
|
+
await registry.activate(space, webhookTrigger, async () => {
|
|
102
117
|
callbackInvoked.wake();
|
|
103
118
|
return 200;
|
|
104
119
|
});
|
|
@@ -108,7 +123,7 @@ describe('trigger registry', () => {
|
|
|
108
123
|
});
|
|
109
124
|
|
|
110
125
|
test('removes from inactive list', async () => {
|
|
111
|
-
const client =
|
|
126
|
+
const [client] = await createInitializedClients(testBuilder);
|
|
112
127
|
const space = await client.spaces.create();
|
|
113
128
|
const registry = createRegistry(client);
|
|
114
129
|
await registry.register(space, manifest);
|
|
@@ -116,17 +131,19 @@ describe('trigger registry', () => {
|
|
|
116
131
|
await waitForInactiveTriggers(registry, space);
|
|
117
132
|
|
|
118
133
|
const inactiveTrigger = registry.getInactiveTriggers(space)[0];
|
|
119
|
-
await registry.activate(
|
|
134
|
+
await registry.activate(space, inactiveTrigger, async () => 200);
|
|
120
135
|
|
|
121
136
|
const updatedInactiveList = registry.getInactiveTriggers(space);
|
|
122
137
|
expect(updatedInactiveList.find((trigger: FunctionTrigger) => trigger.function === inactiveTrigger.function)).to
|
|
123
138
|
.be.undefined;
|
|
124
139
|
});
|
|
140
|
+
|
|
141
|
+
// TODO(burdon): Test enable/disable trigger.
|
|
125
142
|
});
|
|
126
143
|
|
|
127
144
|
describe('deactivate', () => {
|
|
128
145
|
test('trigger object deletion deactivates a trigger', async () => {
|
|
129
|
-
const client =
|
|
146
|
+
const [client] = await createInitializedClients(testBuilder);
|
|
130
147
|
const space = await client.spaces.create();
|
|
131
148
|
const registry = createRegistry(client);
|
|
132
149
|
await registry.register(space, manifest);
|
|
@@ -136,7 +153,7 @@ describe('trigger registry', () => {
|
|
|
136
153
|
const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
|
|
137
154
|
const echoTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'subscription')!;
|
|
138
155
|
let count = 0;
|
|
139
|
-
await registry.activate(
|
|
156
|
+
await registry.activate(space, echoTrigger, async () => {
|
|
140
157
|
count++;
|
|
141
158
|
return 200;
|
|
142
159
|
});
|
|
@@ -152,7 +169,7 @@ describe('trigger registry', () => {
|
|
|
152
169
|
});
|
|
153
170
|
|
|
154
171
|
test('registry closing deactivates a trigger', async () => {
|
|
155
|
-
const client =
|
|
172
|
+
const [client] = await createInitializedClients(testBuilder);
|
|
156
173
|
const space = await client.spaces.create();
|
|
157
174
|
const registry = createRegistry(client);
|
|
158
175
|
await registry.register(space, manifest);
|
|
@@ -162,7 +179,7 @@ describe('trigger registry', () => {
|
|
|
162
179
|
const { objects: allTriggers } = await space.db.query(Filter.schema(FunctionTrigger)).run();
|
|
163
180
|
const echoTrigger = allTriggers.find((trigger: FunctionTrigger) => trigger.spec.type === 'subscription')!;
|
|
164
181
|
let count = 0;
|
|
165
|
-
await registry.activate(
|
|
182
|
+
await registry.activate(space, echoTrigger, async () => {
|
|
166
183
|
count++;
|
|
167
184
|
return 200;
|
|
168
185
|
});
|
|
@@ -176,8 +193,8 @@ describe('trigger registry', () => {
|
|
|
176
193
|
});
|
|
177
194
|
|
|
178
195
|
describe('trigger events', () => {
|
|
179
|
-
test
|
|
180
|
-
const client =
|
|
196
|
+
test('event fired when all registered when opened', async () => {
|
|
197
|
+
const [client] = await createInitializedClients(testBuilder);
|
|
181
198
|
const registry = createRegistry(client);
|
|
182
199
|
const triggers = createTriggers(client.spaces.default, 3);
|
|
183
200
|
|
|
@@ -194,7 +211,7 @@ describe('trigger registry', () => {
|
|
|
194
211
|
});
|
|
195
212
|
|
|
196
213
|
test('event fired when a new trigger is added', async () => {
|
|
197
|
-
const client =
|
|
214
|
+
const [client] = await createInitializedClients(testBuilder);
|
|
198
215
|
const registry = createRegistry(client);
|
|
199
216
|
const space = await client.spaces.create();
|
|
200
217
|
|
|
@@ -210,7 +227,7 @@ describe('trigger registry', () => {
|
|
|
210
227
|
});
|
|
211
228
|
|
|
212
229
|
test('event fired when a new trigger is removed', async () => {
|
|
213
|
-
const client =
|
|
230
|
+
const [client] = await createInitializedClients(testBuilder);
|
|
214
231
|
const registry = createRegistry(client);
|
|
215
232
|
const space = await client.spaces.create();
|
|
216
233
|
const triggers = createTriggers(space, 3);
|
|
@@ -6,11 +6,11 @@ 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
|
|
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, diff
|
|
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';
|
|
@@ -19,12 +19,10 @@ type ResponseCode = number;
|
|
|
19
19
|
|
|
20
20
|
export type TriggerCallback = (args: object) => Promise<ResponseCode>;
|
|
21
21
|
|
|
22
|
-
export type TriggerContext = { space: Space };
|
|
23
|
-
|
|
24
22
|
// TODO(burdon): Make object?
|
|
25
23
|
export type TriggerFactory<Spec extends TriggerSpec, Options = any> = (
|
|
26
24
|
ctx: Context,
|
|
27
|
-
|
|
25
|
+
space: Space,
|
|
28
26
|
spec: Spec,
|
|
29
27
|
callback: TriggerCallback,
|
|
30
28
|
options?: Options,
|
|
@@ -70,19 +68,18 @@ export class TriggerRegistry extends Resource {
|
|
|
70
68
|
return this._getTriggers(space, (t) => t.activationCtx == null);
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
async activate(
|
|
74
|
-
log('activate', { space:
|
|
75
|
-
|
|
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}` });
|
|
76
75
|
this._ctx.onDispose(() => activationCtx.dispose());
|
|
77
|
-
const registeredTrigger = this._triggersBySpaceKey
|
|
78
|
-
.get(triggerCtx.space.key)
|
|
79
|
-
?.find((reg) => reg.trigger.id === trigger.id);
|
|
76
|
+
const registeredTrigger = this._triggersBySpaceKey.get(space.key)?.find((reg) => reg.trigger.id === trigger.id);
|
|
80
77
|
invariant(registeredTrigger, `Trigger is not registered: ${trigger.function}`);
|
|
81
78
|
registeredTrigger.activationCtx = activationCtx;
|
|
82
79
|
|
|
83
80
|
try {
|
|
84
81
|
const options = this._options?.[trigger.spec.type];
|
|
85
|
-
await triggerHandlers[trigger.spec.type](activationCtx,
|
|
82
|
+
await triggerHandlers[trigger.spec.type](activationCtx, space, trigger.spec, callback, options);
|
|
86
83
|
} catch (err) {
|
|
87
84
|
delete registeredTrigger.activationCtx;
|
|
88
85
|
throw err;
|
|
@@ -97,24 +94,35 @@ export class TriggerRegistry extends Resource {
|
|
|
97
94
|
if (!manifest.triggers?.length) {
|
|
98
95
|
return;
|
|
99
96
|
}
|
|
97
|
+
|
|
100
98
|
if (!space.db.graph.runtimeSchemaRegistry.hasSchema(FunctionTrigger)) {
|
|
101
99
|
space.db.graph.runtimeSchemaRegistry.registerSchema(FunctionTrigger);
|
|
102
100
|
}
|
|
103
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
|
+
|
|
104
113
|
// Sync triggers.
|
|
105
114
|
const { objects: existing } = await space.db.query(Filter.schema(FunctionTrigger)).run();
|
|
106
|
-
const { added } = diff(existing,
|
|
107
|
-
// Create FK to enable syncing if none are set.
|
|
108
|
-
// TODO(burdon): Warn if not unique.
|
|
109
|
-
const keys = b[ECHO_ATTR_META]?.keys ?? [foreignKey('manifest', [b.function, b.spec.type].join('-'))];
|
|
110
|
-
return intersection(getMeta(a)?.keys ?? [], keys, foreignKeyEquals).length > 0;
|
|
111
|
-
});
|
|
115
|
+
const { added } = diff(existing, manifestTriggers, compareForeignKeys);
|
|
112
116
|
|
|
113
117
|
// TODO(burdon): Update existing.
|
|
114
118
|
added.forEach((trigger) => {
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
space.db.add(trigger);
|
|
120
|
+
log.info('added', { meta: getMeta(trigger) });
|
|
117
121
|
});
|
|
122
|
+
|
|
123
|
+
if (added.length > 0) {
|
|
124
|
+
await space.db.flush();
|
|
125
|
+
}
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
protected override async _open(): Promise<void> {
|
|
@@ -134,55 +142,52 @@ export class TriggerRegistry extends Resource {
|
|
|
134
142
|
|
|
135
143
|
// Subscribe to updates.
|
|
136
144
|
this._ctx.onDispose(
|
|
137
|
-
space.db.query(Filter.schema(FunctionTrigger)).subscribe(async (
|
|
138
|
-
log.info('update', { space: space.key,
|
|
139
|
-
await this._handleRemovedTriggers(space,
|
|
140
|
-
this._handleNewTriggers(space,
|
|
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);
|
|
141
149
|
}),
|
|
142
150
|
);
|
|
143
151
|
}
|
|
144
152
|
});
|
|
145
153
|
|
|
146
154
|
this._ctx.onDispose(() => spaceListSubscription.unsubscribe());
|
|
155
|
+
log.info('opened');
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
protected override async _close(_: Context): Promise<void> {
|
|
150
159
|
log.info('close...');
|
|
151
160
|
this._triggersBySpaceKey.clear();
|
|
161
|
+
log.info('closed');
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
private _handleNewTriggers(space: Space,
|
|
155
|
-
const
|
|
156
|
-
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;
|
|
157
167
|
});
|
|
158
168
|
|
|
159
|
-
if (
|
|
160
|
-
const newRegisteredTriggers: RegisteredTrigger[] =
|
|
169
|
+
if (added.length > 0) {
|
|
170
|
+
const newRegisteredTriggers: RegisteredTrigger[] = added.map((trigger) => ({ trigger }));
|
|
161
171
|
registered.push(...newRegisteredTriggers);
|
|
162
172
|
log.info('added', () => ({
|
|
163
173
|
spaceKey: space.key,
|
|
164
|
-
triggers:
|
|
174
|
+
triggers: added.map((trigger) => trigger.function),
|
|
165
175
|
}));
|
|
166
|
-
|
|
176
|
+
|
|
177
|
+
this.registered.emit({ space, triggers: added });
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
private async _handleRemovedTriggers(
|
|
171
182
|
space: Space,
|
|
172
|
-
|
|
183
|
+
current: FunctionTrigger[],
|
|
173
184
|
registered: RegisteredTrigger[],
|
|
174
185
|
): Promise<void> {
|
|
175
186
|
const removed: FunctionTrigger[] = [];
|
|
176
187
|
for (let i = registered.length - 1; i >= 0; i--) {
|
|
177
188
|
const wasRemoved =
|
|
178
|
-
|
|
189
|
+
current.filter((trigger) => trigger.enabled).find((trigger) => trigger.id === registered[i].trigger.id) == null;
|
|
179
190
|
if (wasRemoved) {
|
|
180
|
-
if (removed.length) {
|
|
181
|
-
log.info('removed', () => ({
|
|
182
|
-
spaceKey: space.key,
|
|
183
|
-
triggers: removed.map((trigger) => trigger.function),
|
|
184
|
-
}));
|
|
185
|
-
}
|
|
186
191
|
const unregistered = registered.splice(i, 1)[0];
|
|
187
192
|
await unregistered.activationCtx?.dispose();
|
|
188
193
|
removed.push(unregistered.trigger);
|
|
@@ -190,6 +195,11 @@ export class TriggerRegistry extends Resource {
|
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
if (removed.length > 0) {
|
|
198
|
+
log.info('removed', () => ({
|
|
199
|
+
spaceKey: space.key,
|
|
200
|
+
triggers: removed.map((trigger) => trigger.function),
|
|
201
|
+
}));
|
|
202
|
+
|
|
193
203
|
this.removed.emit({ space, triggers: removed });
|
|
194
204
|
}
|
|
195
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,
|
|
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
|
|
13
|
+
import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
|
|
13
14
|
|
|
14
15
|
export const createSubscriptionTrigger: TriggerFactory<SubscriptionTrigger> = async (
|
|
15
16
|
ctx: Context,
|
|
16
|
-
|
|
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):
|
|
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
|
-
|
|
75
|
-
|
|
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
|
|
13
|
+
import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
|
|
13
14
|
|
|
14
15
|
export const createTimerTrigger: TriggerFactory<TimerTrigger> = async (
|
|
15
16
|
ctx: Context,
|
|
16
|
-
|
|
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:
|
|
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
|
|
13
|
+
import { type TriggerCallback, type TriggerFactory } from '../trigger-registry';
|
|
13
14
|
|
|
14
15
|
export const createWebhookTrigger: TriggerFactory<WebhookTrigger> = async (
|
|
15
16
|
ctx: Context,
|
|
16
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
55
|
+
await createWebsocketTrigger(ctx, space, spec, callback, options);
|
|
55
56
|
}, options.retryDelay * 1_000);
|
|
56
57
|
}
|
|
57
58
|
|