@dxos/functions 0.3.8-next.f4e0086 → 0.3.8
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/README.md +2 -2
- package/dist/lib/browser/index.mjs +203 -164
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +218 -178
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/handler.d.ts +2 -6
- package/dist/types/src/handler.d.ts.map +1 -1
- package/dist/types/src/manifest.d.ts +3 -2
- package/dist/types/src/manifest.d.ts.map +1 -1
- package/dist/types/src/runtime/dev-server.d.ts +14 -2
- package/dist/types/src/runtime/dev-server.d.ts.map +1 -1
- package/dist/types/src/runtime/index.d.ts +1 -1
- package/dist/types/src/runtime/index.d.ts.map +1 -1
- package/dist/types/src/runtime/{trigger-manager.d.ts → scheduler.d.ts} +9 -7
- package/dist/types/src/runtime/scheduler.d.ts.map +1 -0
- package/package.json +11 -10
- package/src/handler.ts +7 -8
- package/src/manifest.ts +8 -4
- package/src/runtime/dev-server.ts +91 -58
- package/src/runtime/index.ts +1 -1
- package/src/runtime/scheduler.ts +145 -0
- package/dist/types/src/runtime/trigger-manager.d.ts.map +0 -1
- package/src/runtime/trigger-manager.ts +0 -140
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { CronJob } from 'cron';
|
|
6
|
+
|
|
7
|
+
import { DeferredTask } from '@dxos/async';
|
|
8
|
+
import { type Client, type PublicKey } from '@dxos/client';
|
|
9
|
+
import { type Space } from '@dxos/client/echo';
|
|
10
|
+
import { Context } from '@dxos/context';
|
|
11
|
+
import { Filter, createSubscription } from '@dxos/echo-schema';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
import { log } from '@dxos/log';
|
|
14
|
+
import { ComplexMap } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../manifest';
|
|
17
|
+
|
|
18
|
+
type SchedulerOptions = {
|
|
19
|
+
endpoint: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Functions scheduler.
|
|
24
|
+
*/
|
|
25
|
+
// TODO(burdon): Create tests.
|
|
26
|
+
export class Scheduler {
|
|
27
|
+
// Map of mounted functions.
|
|
28
|
+
private readonly _mounts = new ComplexMap<
|
|
29
|
+
{ id: string; spaceKey: PublicKey },
|
|
30
|
+
{ ctx: Context; trigger: FunctionTrigger }
|
|
31
|
+
>(({ id, spaceKey }) => `${spaceKey.toHex()}:${id}`);
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly _client: Client,
|
|
35
|
+
private readonly _manifest: FunctionManifest,
|
|
36
|
+
private readonly _options: SchedulerOptions,
|
|
37
|
+
) {}
|
|
38
|
+
|
|
39
|
+
async start() {
|
|
40
|
+
this._client.spaces.subscribe(async (spaces) => {
|
|
41
|
+
for (const space of spaces) {
|
|
42
|
+
await space.waitUntilReady();
|
|
43
|
+
for (const trigger of this._manifest.triggers ?? []) {
|
|
44
|
+
await this.mount(new Context(), space, trigger);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async stop() {
|
|
51
|
+
for (const { id, spaceKey } of this._mounts.keys()) {
|
|
52
|
+
await this.unmount(id, spaceKey);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {
|
|
57
|
+
const key = { id: trigger.function, spaceKey: space.key };
|
|
58
|
+
const def = this._manifest.functions.find((config) => config.id === trigger.function);
|
|
59
|
+
invariant(def, `Function not found: ${trigger.function}`);
|
|
60
|
+
|
|
61
|
+
const exists = this._mounts.get(key);
|
|
62
|
+
if (!exists) {
|
|
63
|
+
this._mounts.set(key, { ctx, trigger });
|
|
64
|
+
log('mount', { space: space.key, trigger });
|
|
65
|
+
if (ctx.disposed) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Cron schedule.
|
|
70
|
+
if (trigger.schedule) {
|
|
71
|
+
const task = new DeferredTask(ctx, async () => {
|
|
72
|
+
await this.execFunction(def, {
|
|
73
|
+
space: space.key,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// TODO(burdon): Check greater than 30s min (use cron-parser).
|
|
78
|
+
const job = new CronJob(trigger.schedule, () => task.schedule());
|
|
79
|
+
|
|
80
|
+
job.start();
|
|
81
|
+
ctx.onDispose(() => job.stop());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ECHO subscription.
|
|
85
|
+
if (trigger.subscription) {
|
|
86
|
+
const objectIds = new Set<string>();
|
|
87
|
+
const task = new DeferredTask(ctx, async () => {
|
|
88
|
+
await this.execFunction(def, {
|
|
89
|
+
space: space.key,
|
|
90
|
+
objects: Array.from(objectIds),
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const subscription = createSubscription(({ added, updated }) => {
|
|
95
|
+
for (const object of added) {
|
|
96
|
+
objectIds.add(object.id);
|
|
97
|
+
}
|
|
98
|
+
for (const object of updated) {
|
|
99
|
+
objectIds.add(object.id);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
task.schedule();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const { type, props } = trigger.subscription;
|
|
106
|
+
const query = space.db.query(Filter.typename(type, props));
|
|
107
|
+
const unsubscribe = query.subscribe(({ objects }) => {
|
|
108
|
+
subscription.update(objects);
|
|
109
|
+
}, true);
|
|
110
|
+
|
|
111
|
+
ctx.onDispose(() => {
|
|
112
|
+
subscription.unsubscribe();
|
|
113
|
+
unsubscribe();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private async unmount(id: string, spaceKey: PublicKey) {
|
|
120
|
+
const key = { id, spaceKey };
|
|
121
|
+
const { ctx } = this._mounts.get(key) ?? {};
|
|
122
|
+
if (ctx) {
|
|
123
|
+
this._mounts.delete(key);
|
|
124
|
+
await ctx.dispose();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async execFunction(def: FunctionDef, data: any) {
|
|
129
|
+
try {
|
|
130
|
+
log('request', { function: def.id });
|
|
131
|
+
const response = await fetch(`${this._options.endpoint}/${def.name}`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify(data),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// const result = await response.json();
|
|
140
|
+
log('result', { function: def.id, result: response.status });
|
|
141
|
+
} catch (err: any) {
|
|
142
|
+
log.error('error', { function: def.id, error: err.message });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"trigger-manager.d.ts","sourceRoot":"","sources":["../../../../src/runtime/trigger-manager.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,MAAM,EAAkB,MAAM,cAAc,CAAC;AAQ3D,OAAO,EAAE,KAAK,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAG1E,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,cAAc;IASvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAVjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAGiC;IAEzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;gBAG1B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB,EAC3B,cAAc,EAAE,aAAa;IAG1C,KAAK;IAaL,IAAI;YAMI,KAAK;YAuDL,OAAO;YASP,YAAY;CAqB3B"}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Copyright 2023 DXOS.org
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
import { DeferredTask } from '@dxos/async';
|
|
6
|
-
import { type Client, type PublicKey } from '@dxos/client';
|
|
7
|
-
import type { Query, Space } from '@dxos/client/echo';
|
|
8
|
-
import { Context } from '@dxos/context';
|
|
9
|
-
import { Filter, createSubscription } from '@dxos/echo-schema';
|
|
10
|
-
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import { log } from '@dxos/log';
|
|
12
|
-
import { ComplexMap } from '@dxos/util';
|
|
13
|
-
|
|
14
|
-
import { type FunctionManifest, type FunctionTrigger } from '../manifest';
|
|
15
|
-
|
|
16
|
-
// TODO(burdon): Rename.
|
|
17
|
-
export type InvokeOptions = {
|
|
18
|
-
endpoint: string;
|
|
19
|
-
runtime: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export class TriggerManager {
|
|
23
|
-
private readonly _mounts = new ComplexMap<
|
|
24
|
-
{ name: string; spaceKey: PublicKey },
|
|
25
|
-
{ ctx: Context; trigger: FunctionTrigger }
|
|
26
|
-
>(({ name, spaceKey }) => `${spaceKey.toHex()}:${name}`);
|
|
27
|
-
|
|
28
|
-
private readonly _queries = new Set<Query>();
|
|
29
|
-
|
|
30
|
-
constructor(
|
|
31
|
-
private readonly _client: Client,
|
|
32
|
-
private readonly _manifest: FunctionManifest,
|
|
33
|
-
private readonly _invokeOptions: InvokeOptions,
|
|
34
|
-
) {}
|
|
35
|
-
|
|
36
|
-
async start() {
|
|
37
|
-
// TODO(burdon): Make runtime configurable (via CLI)?
|
|
38
|
-
this._client.spaces.subscribe(async (spaces) => {
|
|
39
|
-
for (const space of spaces) {
|
|
40
|
-
await space.waitUntilReady();
|
|
41
|
-
for (const trigger of this._manifest.triggers ?? []) {
|
|
42
|
-
// TODO(burdon): New context? Shared?
|
|
43
|
-
await this.mount(new Context(), space, trigger);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async stop() {
|
|
50
|
-
for (const { name, spaceKey } of this._mounts.keys()) {
|
|
51
|
-
await this.unmount(name, spaceKey);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {
|
|
56
|
-
const key = { name: trigger.function, spaceKey: space.key };
|
|
57
|
-
const config = this._manifest.functions.find((config) => config.id === trigger.function);
|
|
58
|
-
invariant(config, `Function not found: ${trigger.function}`);
|
|
59
|
-
const exists = this._mounts.get(key);
|
|
60
|
-
if (!exists) {
|
|
61
|
-
this._mounts.set(key, { ctx, trigger });
|
|
62
|
-
log('mount', { space: space.key, trigger });
|
|
63
|
-
if (ctx.disposed) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// TODO(burdon): Why DeferredTask? How to pass objectIds to function?
|
|
68
|
-
const objectIds = new Set<string>();
|
|
69
|
-
const task = new DeferredTask(ctx, async () => {
|
|
70
|
-
await this.execFunction(this._invokeOptions, config.endpoint, {
|
|
71
|
-
space: space.key,
|
|
72
|
-
objects: Array.from(objectIds),
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
let count = 0;
|
|
77
|
-
const subscription = createSubscription(({ added, updated }) => {
|
|
78
|
-
for (const object of added) {
|
|
79
|
-
objectIds.add(object.id);
|
|
80
|
-
}
|
|
81
|
-
for (const object of updated) {
|
|
82
|
-
objectIds.add(object.id);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
log('updated', {
|
|
86
|
-
trigger,
|
|
87
|
-
space: space.key,
|
|
88
|
-
objects: objectIds.size,
|
|
89
|
-
count,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
task.schedule();
|
|
93
|
-
count++;
|
|
94
|
-
});
|
|
95
|
-
// TODO(burdon): DSL for query (replace props).
|
|
96
|
-
const query = space.db.query(Filter.typename(trigger.subscription.type, trigger.subscription.props));
|
|
97
|
-
this._queries.add(query);
|
|
98
|
-
const unsubscribe = query.subscribe(({ objects }) => {
|
|
99
|
-
subscription.update(objects);
|
|
100
|
-
}, true);
|
|
101
|
-
|
|
102
|
-
ctx.onDispose(() => {
|
|
103
|
-
subscription.unsubscribe();
|
|
104
|
-
unsubscribe();
|
|
105
|
-
this._queries.delete(query);
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private async unmount(name: string, spaceKey: PublicKey) {
|
|
111
|
-
const key = { name, spaceKey };
|
|
112
|
-
const { ctx } = this._mounts.get(key) ?? {};
|
|
113
|
-
if (ctx) {
|
|
114
|
-
this._mounts.delete(key);
|
|
115
|
-
await ctx.dispose();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private async execFunction(options: InvokeOptions, functionName: string, data: any) {
|
|
120
|
-
const { endpoint, runtime } = options;
|
|
121
|
-
invariant(endpoint, 'Missing endpoint');
|
|
122
|
-
invariant(runtime, 'Missing runtime');
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
log('invoke', { function: functionName });
|
|
126
|
-
const url = `${endpoint}/${runtime}/${functionName}`;
|
|
127
|
-
const res = await fetch(url, {
|
|
128
|
-
method: 'POST',
|
|
129
|
-
body: JSON.stringify(data),
|
|
130
|
-
headers: {
|
|
131
|
-
'Content-Type': 'application/json',
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
log('result', { function: functionName, result: await res.json() });
|
|
136
|
-
} catch (err: any) {
|
|
137
|
-
log.error('error', { function: functionName, error: err.message });
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|