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