@dxos/functions 0.3.8 → 0.3.9-main.03b62b6

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.
@@ -14,7 +14,7 @@ import { join } from "@dxos/node-std/path";
14
14
  import { Trigger } from "@dxos/async";
15
15
  import { invariant } from "@dxos/invariant";
16
16
  import { log } from "@dxos/log";
17
- var __dxlog_file = "/home/circleci/project/packages/core/functions/src/runtime/dev-server.ts";
17
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
18
18
  var DevServer = class {
19
19
  // prettier-ignore
20
20
  constructor(_client, _options) {
@@ -26,7 +26,7 @@ var DevServer = class {
26
26
  get endpoint() {
27
27
  invariant(this._port, void 0, {
28
28
  F: __dxlog_file,
29
- L: 45,
29
+ L: 46,
30
30
  S: this,
31
31
  A: [
32
32
  "this._port",
@@ -48,7 +48,7 @@ var DevServer = class {
48
48
  } catch (err) {
49
49
  log.error("parsing function (check manifest)", err, {
50
50
  F: __dxlog_file,
51
- L: 62,
51
+ L: 63,
52
52
  S: this,
53
53
  C: (f, a) => f(...a)
54
54
  });
@@ -68,9 +68,9 @@ var DevServer = class {
68
68
  res.statusCode = await this._invoke(name, req.body);
69
69
  res.end();
70
70
  } catch (err) {
71
- log.error(err, void 0, {
71
+ log.catch(err, void 0, {
72
72
  F: __dxlog_file,
73
- L: 82,
73
+ L: 83,
74
74
  S: this,
75
75
  C: (f, a) => f(...a)
76
76
  });
@@ -79,6 +79,7 @@ var DevServer = class {
79
79
  }
80
80
  });
81
81
  this._port = await getPort({
82
+ host: "localhost",
82
83
  port: 7200,
83
84
  portRange: [
84
85
  7200,
@@ -98,7 +99,7 @@ var DevServer = class {
98
99
  endpoint
99
100
  }, {
100
101
  F: __dxlog_file,
101
- L: 98,
102
+ L: 99,
102
103
  S: this,
103
104
  C: (f, a) => f(...a)
104
105
  });
@@ -120,7 +121,7 @@ var DevServer = class {
120
121
  registrationId: this._registrationId
121
122
  }, {
122
123
  F: __dxlog_file,
123
- L: 115,
124
+ L: 116,
124
125
  S: this,
125
126
  C: (f, a) => f(...a)
126
127
  });
@@ -143,7 +144,7 @@ var DevServer = class {
143
144
  id
144
145
  }, {
145
146
  F: __dxlog_file,
146
- L: 134,
147
+ L: 135,
147
148
  S: this,
148
149
  C: (f, a) => f(...a)
149
150
  });
@@ -170,13 +171,14 @@ var DevServer = class {
170
171
  name
171
172
  }, {
172
173
  F: __dxlog_file,
173
- L: 159,
174
+ L: 160,
174
175
  S: this,
175
176
  C: (f, a) => f(...a)
176
177
  });
177
178
  const { handler } = this._handlers[name];
178
179
  const context = {
179
- client: this._client
180
+ client: this._client,
181
+ dataDir: this._options.dataDir
180
182
  };
181
183
  let statusCode = 200;
182
184
  const response = {
@@ -197,7 +199,7 @@ var DevServer = class {
197
199
  duration: Date.now() - now
198
200
  }, {
199
201
  F: __dxlog_file,
200
- L: 175,
202
+ L: 177,
201
203
  S: this,
202
204
  C: (f, a) => f(...a)
203
205
  });
@@ -207,13 +209,14 @@ var DevServer = class {
207
209
 
208
210
  // packages/core/functions/src/runtime/scheduler.ts
209
211
  import { CronJob } from "cron";
210
- import { DeferredTask } from "@dxos/async";
212
+ import { debounce, DeferredTask } from "@dxos/async";
213
+ import { TextObject } from "@dxos/client/echo";
211
214
  import { Context } from "@dxos/context";
212
- import { Filter, createSubscription } from "@dxos/echo-schema";
215
+ import { Filter, createSubscription, subscribe } from "@dxos/echo-schema";
213
216
  import { invariant as invariant2 } from "@dxos/invariant";
214
217
  import { log as log2 } from "@dxos/log";
215
218
  import { ComplexMap } from "@dxos/util";
216
- var __dxlog_file2 = "/home/circleci/project/packages/core/functions/src/runtime/scheduler.ts";
219
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/scheduler.ts";
217
220
  var Scheduler = class {
218
221
  constructor(_client, _manifest, _options) {
219
222
  this._client = _client;
@@ -287,6 +290,7 @@ var Scheduler = class {
287
290
  objects: Array.from(objectIds)
288
291
  });
289
292
  });
293
+ const subscriptions = [];
290
294
  const subscription = createSubscription(({ added, updated }) => {
291
295
  for (const object of added) {
292
296
  objectIds.add(object.id);
@@ -296,14 +300,35 @@ var Scheduler = class {
296
300
  }
297
301
  task.schedule();
298
302
  });
299
- const { type, props } = trigger.subscription;
300
- const query = space.db.query(Filter.typename(type, props));
301
- const unsubscribe = query.subscribe(({ objects }) => {
303
+ subscriptions.push(() => subscription.unsubscribe());
304
+ const { type, props, deep, delay } = trigger.subscription;
305
+ const update = ({ objects }) => {
302
306
  subscription.update(objects);
303
- }, true);
307
+ if (deep) {
308
+ log2.info("update", {
309
+ type,
310
+ deep,
311
+ objects: objects.length
312
+ }, {
313
+ F: __dxlog_file2,
314
+ L: 114,
315
+ S: this,
316
+ C: (f, a) => f(...a)
317
+ });
318
+ for (const object of objects) {
319
+ const content = object.content;
320
+ if (content instanceof TextObject) {
321
+ subscriptions.push(content[subscribe](debounce(() => subscription.update([
322
+ object
323
+ ]), 1e3)));
324
+ }
325
+ }
326
+ }
327
+ };
328
+ const query = space.db.query(Filter.typename(type, props));
329
+ subscriptions.push(query.subscribe(delay ? debounce(update, delay * 1e3) : update));
304
330
  ctx.onDispose(() => {
305
- subscription.unsubscribe();
306
- unsubscribe();
331
+ subscriptions.forEach((unsubscribe) => unsubscribe());
307
332
  });
308
333
  }
309
334
  }
@@ -325,7 +350,7 @@ var Scheduler = class {
325
350
  function: def.id
326
351
  }, {
327
352
  F: __dxlog_file2,
328
- L: 130,
353
+ L: 147,
329
354
  S: this,
330
355
  C: (f, a) => f(...a)
331
356
  });
@@ -341,7 +366,7 @@ var Scheduler = class {
341
366
  result: response.status
342
367
  }, {
343
368
  F: __dxlog_file2,
344
- L: 140,
369
+ L: 157,
345
370
  S: this,
346
371
  C: (f, a) => f(...a)
347
372
  });
@@ -351,7 +376,7 @@ var Scheduler = class {
351
376
  error: err.message
352
377
  }, {
353
378
  F: __dxlog_file2,
354
- L: 142,
379
+ L: 159,
355
380
  S: this,
356
381
  C: (f, a) => f(...a)
357
382
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/runtime/dev-server.ts", "../../../src/runtime/scheduler.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport express from 'express';\nimport { getPort } from 'get-port-please';\nimport type http from 'http';\nimport { join } from 'node:path';\n\nimport { Trigger } from '@dxos/async';\nimport { type Client } from '@dxos/client';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\n\nimport { type FunctionContext, type FunctionHandler, type Response } from '../handler';\nimport { type FunctionDef, type FunctionManifest } from '../manifest';\n\nexport type DevServerOptions = {\n port?: number;\n directory: string;\n manifest: FunctionManifest;\n reload?: boolean;\n};\n\n/**\n * Functions dev server provides a local HTTP server for testing functions.\n */\nexport class DevServer {\n // Function handlers indexed by name (URL path).\n private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};\n\n private _server?: http.Server;\n private _port?: number;\n private _registrationId?: string;\n private _proxy?: string;\n private _seq = 0;\n\n // prettier-ignore\n constructor(\n private readonly _client: Client,\n private readonly _options: DevServerOptions,\n ) {}\n\n get endpoint() {\n invariant(this._port);\n return `http://localhost:${this._port}`;\n }\n\n get proxy() {\n return this._proxy;\n }\n\n get functions() {\n return Object.values(this._handlers);\n }\n\n async initialize() {\n for (const def of this._options.manifest.functions) {\n try {\n await this._load(def);\n } catch (err) {\n log.error('parsing function (check manifest)', err);\n }\n }\n }\n\n async start() {\n const app = express();\n app.use(express.json());\n\n app.post('/:name', async (req, res) => {\n const { name } = req.params;\n try {\n if (this._options.reload) {\n const { def } = this._handlers[name];\n await this._load(def, true);\n }\n\n res.statusCode = await this._invoke(name, req.body);\n res.end();\n } catch (err: any) {\n log.error(err);\n res.statusCode = 500;\n res.end();\n }\n });\n\n this._port = await getPort({ port: 7200, portRange: [7200, 7299] });\n this._server = app.listen(this._port);\n\n try {\n // Register functions.\n const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({\n endpoint: this.endpoint,\n functions: this.functions.map(({ def: { name } }) => ({ name })),\n });\n\n log.info('registered', { registrationId, endpoint });\n this._registrationId = registrationId;\n this._proxy = endpoint;\n } catch (err: any) {\n await this.stop();\n throw new Error('FunctionRegistryService not available (check plugin is configured).');\n }\n }\n\n async stop() {\n const trigger = new Trigger();\n this._server?.close(async () => {\n if (this._registrationId) {\n await this._client.services.services.FunctionRegistryService!.unregister({\n registrationId: this._registrationId,\n });\n\n log.info('unregistered', { registrationId: this._registrationId });\n this._registrationId = undefined;\n this._proxy = undefined;\n }\n\n trigger.wake();\n });\n\n await trigger.wait();\n this._port = undefined;\n this._server = undefined;\n }\n\n /**\n * Load function.\n */\n private async _load(def: FunctionDef, flush = false) {\n const { id, name, handler } = def;\n const path = join(this._options.directory, handler);\n log.info('loading', { id });\n\n // Remove from cache.\n if (flush) {\n Object.keys(require.cache)\n .filter((key) => key.startsWith(path))\n .forEach((key) => delete require.cache[key]);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const module = require(path);\n if (typeof module.default !== 'function') {\n throw new Error(`Handler must export default function: ${id}`);\n }\n\n this._handlers[name] = { def, handler: module.default };\n }\n\n /**\n * Invoke function handler.\n */\n private async _invoke(name: string, event: any) {\n const seq = ++this._seq;\n const now = Date.now();\n\n log.info('req', { seq, name });\n const { handler } = this._handlers[name];\n\n const context: FunctionContext = {\n client: this._client,\n };\n\n let statusCode = 200;\n const response: Response = {\n status: (code: number) => {\n statusCode = code;\n return response;\n },\n };\n\n await handler({ context, event, response });\n log.info('res', { seq, name, statusCode, duration: Date.now() - now });\n\n return statusCode;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { CronJob } from 'cron';\n\nimport { DeferredTask } from '@dxos/async';\nimport { type Client, type PublicKey } from '@dxos/client';\nimport { type Space } from '@dxos/client/echo';\nimport { Context } from '@dxos/context';\nimport { Filter, createSubscription } from '@dxos/echo-schema';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { ComplexMap } from '@dxos/util';\n\nimport { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../manifest';\n\ntype SchedulerOptions = {\n endpoint: string;\n};\n\n/**\n * Functions scheduler.\n */\n// TODO(burdon): Create tests.\nexport class Scheduler {\n // Map of mounted functions.\n private readonly _mounts = new ComplexMap<\n { id: string; spaceKey: PublicKey },\n { ctx: Context; trigger: FunctionTrigger }\n >(({ id, spaceKey }) => `${spaceKey.toHex()}:${id}`);\n\n constructor(\n private readonly _client: Client,\n private readonly _manifest: FunctionManifest,\n private readonly _options: SchedulerOptions,\n ) {}\n\n async start() {\n this._client.spaces.subscribe(async (spaces) => {\n for (const space of spaces) {\n await space.waitUntilReady();\n for (const trigger of this._manifest.triggers ?? []) {\n await this.mount(new Context(), space, trigger);\n }\n }\n });\n }\n\n async stop() {\n for (const { id, spaceKey } of this._mounts.keys()) {\n await this.unmount(id, spaceKey);\n }\n }\n\n private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {\n const key = { id: trigger.function, spaceKey: space.key };\n const def = this._manifest.functions.find((config) => config.id === trigger.function);\n invariant(def, `Function not found: ${trigger.function}`);\n\n const exists = this._mounts.get(key);\n if (!exists) {\n this._mounts.set(key, { ctx, trigger });\n log('mount', { space: space.key, trigger });\n if (ctx.disposed) {\n return;\n }\n\n // Cron schedule.\n if (trigger.schedule) {\n const task = new DeferredTask(ctx, async () => {\n await this.execFunction(def, {\n space: space.key,\n });\n });\n\n // TODO(burdon): Check greater than 30s min (use cron-parser).\n const job = new CronJob(trigger.schedule, () => task.schedule());\n\n job.start();\n ctx.onDispose(() => job.stop());\n }\n\n // ECHO subscription.\n if (trigger.subscription) {\n const objectIds = new Set<string>();\n const task = new DeferredTask(ctx, async () => {\n await this.execFunction(def, {\n space: space.key,\n objects: Array.from(objectIds),\n });\n });\n\n const subscription = createSubscription(({ added, updated }) => {\n for (const object of added) {\n objectIds.add(object.id);\n }\n for (const object of updated) {\n objectIds.add(object.id);\n }\n\n task.schedule();\n });\n\n const { type, props } = trigger.subscription;\n const query = space.db.query(Filter.typename(type, props));\n const unsubscribe = query.subscribe(({ objects }) => {\n subscription.update(objects);\n }, true);\n\n ctx.onDispose(() => {\n subscription.unsubscribe();\n unsubscribe();\n });\n }\n }\n }\n\n private async unmount(id: string, spaceKey: PublicKey) {\n const key = { id, spaceKey };\n const { ctx } = this._mounts.get(key) ?? {};\n if (ctx) {\n this._mounts.delete(key);\n await ctx.dispose();\n }\n }\n\n private async execFunction(def: FunctionDef, data: any) {\n try {\n log('request', { function: def.id });\n const response = await fetch(`${this._options.endpoint}/${def.name}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n });\n\n // const result = await response.json();\n log('result', { function: def.id, result: response.status });\n } catch (err: any) {\n log.error('error', { function: def.id, error: err.message });\n }\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["express", "getPort", "join", "Trigger", "invariant", "log", "DevServer", "constructor", "_client", "_options", "_handlers", "_seq", "endpoint", "invariant", "_port", "proxy", "_proxy", "functions", "Object", "values", "initialize", "def", "manifest", "_load", "err", "log", "error", "start", "app", "express", "use", "json", "post", "req", "res", "name", "params", "reload", "statusCode", "_invoke", "body", "end", "getPort", "port", "portRange", "_server", "listen", "registrationId", "services", "FunctionRegistryService", "register", "map", "info", "_registrationId", "stop", "Error", "trigger", "Trigger", "close", "unregister", "undefined", "wake", "wait", "flush", "id", "handler", "path", "join", "directory", "keys", "require", "cache", "filter", "key", "startsWith", "forEach", "module", "default", "event", "seq", "now", "Date", "context", "client", "response", "status", "code", "duration", "CronJob", "DeferredTask", "Context", "Filter", "createSubscription", "invariant", "log", "ComplexMap", "Scheduler", "constructor", "_client", "_manifest", "_options", "_mounts", "id", "spaceKey", "toHex", "start", "spaces", "subscribe", "space", "waitUntilReady", "trigger", "triggers", "mount", "stop", "keys", "unmount", "ctx", "key", "function", "def", "functions", "find", "config", "exists", "get", "set", "disposed", "schedule", "task", "execFunction", "job", "onDispose", "subscription", "objectIds", "Set", "objects", "Array", "from", "added", "updated", "object", "add", "type", "props", "query", "db", "typename", "unsubscribe", "update", "delete", "dispose", "data", "response", "fetch", "endpoint", "name", "method", "headers", "body", "JSON", "stringify", "result", "status", "err", "error", "message"]
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport express from 'express';\nimport { getPort } from 'get-port-please';\nimport type http from 'http';\nimport { join } from 'node:path';\n\nimport { Trigger } from '@dxos/async';\nimport { type Client } from '@dxos/client';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\n\nimport { type FunctionContext, type FunctionHandler, type Response } from '../handler';\nimport { type FunctionDef, type FunctionManifest } from '../manifest';\n\nexport type DevServerOptions = {\n port?: number;\n directory: string;\n manifest: FunctionManifest;\n reload?: boolean;\n dataDir?: string;\n};\n\n/**\n * Functions dev server provides a local HTTP server for testing functions.\n */\nexport class DevServer {\n // Function handlers indexed by name (URL path).\n private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};\n\n private _server?: http.Server;\n private _port?: number;\n private _registrationId?: string;\n private _proxy?: string;\n private _seq = 0;\n\n // prettier-ignore\n constructor(\n private readonly _client: Client,\n private readonly _options: DevServerOptions,\n ) {}\n\n get endpoint() {\n invariant(this._port);\n return `http://localhost:${this._port}`;\n }\n\n get proxy() {\n return this._proxy;\n }\n\n get functions() {\n return Object.values(this._handlers);\n }\n\n async initialize() {\n for (const def of this._options.manifest.functions) {\n try {\n await this._load(def);\n } catch (err) {\n log.error('parsing function (check manifest)', err);\n }\n }\n }\n\n async start() {\n const app = express();\n app.use(express.json());\n\n app.post('/:name', async (req, res) => {\n const { name } = req.params;\n try {\n if (this._options.reload) {\n const { def } = this._handlers[name];\n await this._load(def, true);\n }\n\n res.statusCode = await this._invoke(name, req.body);\n res.end();\n } catch (err: any) {\n log.catch(err);\n res.statusCode = 500;\n res.end();\n }\n });\n\n this._port = await getPort({ host: 'localhost', port: 7200, portRange: [7200, 7299] });\n this._server = app.listen(this._port);\n\n try {\n // Register functions.\n const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({\n endpoint: this.endpoint,\n functions: this.functions.map(({ def: { name } }) => ({ name })),\n });\n\n log.info('registered', { registrationId, endpoint });\n this._registrationId = registrationId;\n this._proxy = endpoint;\n } catch (err: any) {\n await this.stop();\n throw new Error('FunctionRegistryService not available (check plugin is configured).');\n }\n }\n\n async stop() {\n const trigger = new Trigger();\n this._server?.close(async () => {\n if (this._registrationId) {\n await this._client.services.services.FunctionRegistryService!.unregister({\n registrationId: this._registrationId,\n });\n\n log.info('unregistered', { registrationId: this._registrationId });\n this._registrationId = undefined;\n this._proxy = undefined;\n }\n\n trigger.wake();\n });\n\n await trigger.wait();\n this._port = undefined;\n this._server = undefined;\n }\n\n /**\n * Load function.\n */\n private async _load(def: FunctionDef, flush = false) {\n const { id, name, handler } = def;\n const path = join(this._options.directory, handler);\n log.info('loading', { id });\n\n // Remove from cache.\n if (flush) {\n Object.keys(require.cache)\n .filter((key) => key.startsWith(path))\n .forEach((key) => delete require.cache[key]);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const module = require(path);\n if (typeof module.default !== 'function') {\n throw new Error(`Handler must export default function: ${id}`);\n }\n\n this._handlers[name] = { def, handler: module.default };\n }\n\n /**\n * Invoke function handler.\n */\n private async _invoke(name: string, event: any) {\n const seq = ++this._seq;\n const now = Date.now();\n\n log.info('req', { seq, name });\n const { handler } = this._handlers[name];\n\n const context: FunctionContext = {\n client: this._client,\n dataDir: this._options.dataDir,\n };\n\n let statusCode = 200;\n const response: Response = {\n status: (code: number) => {\n statusCode = code;\n return response;\n },\n };\n\n await handler({ context, event, response });\n log.info('res', { seq, name, statusCode, duration: Date.now() - now });\n\n return statusCode;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { CronJob } from 'cron';\n\nimport { debounce, DeferredTask } from '@dxos/async';\nimport { type Client, type PublicKey } from '@dxos/client';\nimport { type Space, TextObject } from '@dxos/client/echo';\nimport { Context } from '@dxos/context';\nimport { Filter, createSubscription, type Query, subscribe } from '@dxos/echo-schema';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { ComplexMap } from '@dxos/util';\n\nimport { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../manifest';\n\ntype SchedulerOptions = {\n endpoint: string;\n};\n\n/**\n * Functions scheduler.\n */\n// TODO(burdon): Create tests.\nexport class Scheduler {\n // Map of mounted functions.\n private readonly _mounts = new ComplexMap<\n { id: string; spaceKey: PublicKey },\n { ctx: Context; trigger: FunctionTrigger }\n >(({ id, spaceKey }) => `${spaceKey.toHex()}:${id}`);\n\n constructor(\n private readonly _client: Client,\n private readonly _manifest: FunctionManifest,\n private readonly _options: SchedulerOptions,\n ) {}\n\n async start() {\n this._client.spaces.subscribe(async (spaces) => {\n for (const space of spaces) {\n await space.waitUntilReady();\n for (const trigger of this._manifest.triggers ?? []) {\n await this.mount(new Context(), space, trigger);\n }\n }\n });\n }\n\n async stop() {\n for (const { id, spaceKey } of this._mounts.keys()) {\n await this.unmount(id, spaceKey);\n }\n }\n\n private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {\n const key = { id: trigger.function, spaceKey: space.key };\n const def = this._manifest.functions.find((config) => config.id === trigger.function);\n invariant(def, `Function not found: ${trigger.function}`);\n\n const exists = this._mounts.get(key);\n if (!exists) {\n this._mounts.set(key, { ctx, trigger });\n log('mount', { space: space.key, trigger });\n if (ctx.disposed) {\n return;\n }\n\n // Cron schedule.\n if (trigger.schedule) {\n const task = new DeferredTask(ctx, async () => {\n await this.execFunction(def, {\n space: space.key,\n });\n });\n\n // TODO(burdon): Check greater than 30s min (use cron-parser).\n const job = new CronJob(trigger.schedule, () => task.schedule());\n\n job.start();\n ctx.onDispose(() => job.stop());\n }\n\n // ECHO subscription.\n if (trigger.subscription) {\n const objectIds = new Set<string>();\n const task = new DeferredTask(ctx, async () => {\n await this.execFunction(def, {\n space: space.key,\n objects: Array.from(objectIds),\n });\n });\n\n // TODO(burdon): Standardize subscription handles.\n const subscriptions: (() => void)[] = [];\n const subscription = createSubscription(({ added, updated }) => {\n for (const object of added) {\n objectIds.add(object.id);\n }\n for (const object of updated) {\n objectIds.add(object.id);\n }\n\n task.schedule();\n });\n subscriptions.push(() => subscription.unsubscribe());\n\n const { type, props, deep, delay } = trigger.subscription;\n const update = ({ objects }: Query) => {\n subscription.update(objects);\n\n // TODO(burdon): Hack to monitor changes to Document's text object.\n if (deep) {\n log.info('update', { type, deep, objects: objects.length });\n for (const object of objects) {\n const content = object.content;\n if (content instanceof TextObject) {\n subscriptions.push(content[subscribe](debounce(() => subscription.update([object]), 1_000)));\n }\n }\n }\n };\n\n // TODO(burdon): [Bug]: all callbacks are fired on the first mutation.\n // TODO(burdon): [Bug]: not updated when document is deleted (either top or hierarchically).\n const query = space.db.query(Filter.typename(type, props));\n subscriptions.push(query.subscribe(delay ? debounce(update, delay * 1_000) : update));\n\n ctx.onDispose(() => {\n subscriptions.forEach((unsubscribe) => unsubscribe());\n });\n }\n }\n }\n\n private async unmount(id: string, spaceKey: PublicKey) {\n const key = { id, spaceKey };\n const { ctx } = this._mounts.get(key) ?? {};\n if (ctx) {\n this._mounts.delete(key);\n await ctx.dispose();\n }\n }\n\n private async execFunction(def: FunctionDef, data: any) {\n try {\n log('request', { function: def.id });\n const response = await fetch(`${this._options.endpoint}/${def.name}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n });\n\n // const result = await response.json();\n log('result', { function: def.id, result: response.status });\n } catch (err: any) {\n log.error('error', { function: def.id, error: err.message });\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["express", "getPort", "join", "Trigger", "invariant", "log", "DevServer", "constructor", "_client", "_options", "_handlers", "_seq", "endpoint", "invariant", "_port", "proxy", "_proxy", "functions", "Object", "values", "initialize", "def", "manifest", "_load", "err", "log", "error", "start", "app", "express", "use", "json", "post", "req", "res", "name", "params", "reload", "statusCode", "_invoke", "body", "end", "catch", "getPort", "host", "port", "portRange", "_server", "listen", "registrationId", "services", "FunctionRegistryService", "register", "map", "info", "_registrationId", "stop", "Error", "trigger", "Trigger", "close", "unregister", "undefined", "wake", "wait", "flush", "id", "handler", "path", "join", "directory", "keys", "require", "cache", "filter", "key", "startsWith", "forEach", "module", "default", "event", "seq", "now", "Date", "context", "client", "dataDir", "response", "status", "code", "duration", "CronJob", "debounce", "DeferredTask", "TextObject", "Context", "Filter", "createSubscription", "subscribe", "invariant", "log", "ComplexMap", "Scheduler", "constructor", "_client", "_manifest", "_options", "_mounts", "id", "spaceKey", "toHex", "start", "spaces", "space", "waitUntilReady", "trigger", "triggers", "mount", "stop", "keys", "unmount", "ctx", "key", "function", "def", "functions", "find", "config", "exists", "get", "set", "disposed", "schedule", "task", "execFunction", "job", "onDispose", "subscription", "objectIds", "Set", "objects", "Array", "from", "subscriptions", "added", "updated", "object", "add", "push", "unsubscribe", "type", "props", "deep", "delay", "update", "info", "length", "content", "query", "db", "typename", "forEach", "delete", "dispose", "data", "response", "fetch", "endpoint", "name", "method", "headers", "body", "JSON", "stringify", "result", "status", "err", "error", "message"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/core/functions/src/handler.ts":{"bytes":1308,"imports":[],"format":"esm"},"packages/core/functions/src/manifest.ts":{"bytes":1730,"imports":[],"format":"esm"},"packages/core/functions/src/runtime/dev-server.ts":{"bytes":18548,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"@dxos/node-std/path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/scheduler.ts":{"bytes":16636,"imports":[{"path":"cron","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/index.ts":{"bytes":561,"imports":[{"path":"packages/core/functions/src/runtime/dev-server.ts","kind":"import-statement","original":"./dev-server"},{"path":"packages/core/functions/src/runtime/scheduler.ts","kind":"import-statement","original":"./scheduler"}],"format":"esm"},"packages/core/functions/src/index.ts":{"bytes":632,"imports":[{"path":"packages/core/functions/src/handler.ts","kind":"import-statement","original":"./handler"},{"path":"packages/core/functions/src/manifest.ts","kind":"import-statement","original":"./manifest"},{"path":"packages/core/functions/src/runtime/index.ts","kind":"import-statement","original":"./runtime"}],"format":"esm"}},"outputs":{"packages/core/functions/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16609},"packages/core/functions/dist/lib/browser/index.mjs":{"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"@dxos/node-std/path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"cron","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["DevServer","Scheduler"],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/dev-server.ts":{"bytesInOutput":4734},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/scheduler.ts":{"bytesInOutput":4184}},"bytes":9499}}}
1
+ {"inputs":{"packages/core/functions/src/handler.ts":{"bytes":1341,"imports":[],"format":"esm"},"packages/core/functions/src/manifest.ts":{"bytes":1795,"imports":[],"format":"esm"},"packages/core/functions/src/runtime/dev-server.ts":{"bytes":18840,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"@dxos/node-std/path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/scheduler.ts":{"bytes":20292,"imports":[{"path":"cron","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/client/echo","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/index.ts":{"bytes":566,"imports":[{"path":"packages/core/functions/src/runtime/dev-server.ts","kind":"import-statement","original":"./dev-server"},{"path":"packages/core/functions/src/runtime/scheduler.ts","kind":"import-statement","original":"./scheduler"}],"format":"esm"},"packages/core/functions/src/index.ts":{"bytes":637,"imports":[{"path":"packages/core/functions/src/handler.ts","kind":"import-statement","original":"./handler"},{"path":"packages/core/functions/src/manifest.ts","kind":"import-statement","original":"./manifest"},{"path":"packages/core/functions/src/runtime/index.ts","kind":"import-statement","original":"./runtime"}],"format":"esm"}},"outputs":{"packages/core/functions/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":18300},"packages/core/functions/dist/lib/browser/index.mjs":{"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"@dxos/node-std/path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"cron","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/client/echo","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["DevServer","Scheduler"],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/dev-server.ts":{"bytesInOutput":4802},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/scheduler.ts":{"bytesInOutput":5040}},"bytes":10423}}}
@@ -40,6 +40,7 @@ var import_invariant = require("@dxos/invariant");
40
40
  var import_log = require("@dxos/log");
41
41
  var import_cron = require("cron");
42
42
  var import_async2 = require("@dxos/async");
43
+ var import_echo = require("@dxos/client/echo");
43
44
  var import_context = require("@dxos/context");
44
45
  var import_echo_schema = require("@dxos/echo-schema");
45
46
  var import_invariant2 = require("@dxos/invariant");
@@ -52,7 +53,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
52
53
  return require.apply(this, arguments);
53
54
  throw Error('Dynamic require of "' + x + '" is not supported');
54
55
  });
55
- var __dxlog_file = "/home/circleci/project/packages/core/functions/src/runtime/dev-server.ts";
56
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/dev-server.ts";
56
57
  var DevServer = class {
57
58
  // prettier-ignore
58
59
  constructor(_client, _options) {
@@ -64,7 +65,7 @@ var DevServer = class {
64
65
  get endpoint() {
65
66
  (0, import_invariant.invariant)(this._port, void 0, {
66
67
  F: __dxlog_file,
67
- L: 45,
68
+ L: 46,
68
69
  S: this,
69
70
  A: [
70
71
  "this._port",
@@ -86,7 +87,7 @@ var DevServer = class {
86
87
  } catch (err) {
87
88
  import_log.log.error("parsing function (check manifest)", err, {
88
89
  F: __dxlog_file,
89
- L: 62,
90
+ L: 63,
90
91
  S: this,
91
92
  C: (f, a) => f(...a)
92
93
  });
@@ -106,9 +107,9 @@ var DevServer = class {
106
107
  res.statusCode = await this._invoke(name, req.body);
107
108
  res.end();
108
109
  } catch (err) {
109
- import_log.log.error(err, void 0, {
110
+ import_log.log.catch(err, void 0, {
110
111
  F: __dxlog_file,
111
- L: 82,
112
+ L: 83,
112
113
  S: this,
113
114
  C: (f, a) => f(...a)
114
115
  });
@@ -117,6 +118,7 @@ var DevServer = class {
117
118
  }
118
119
  });
119
120
  this._port = await (0, import_get_port_please.getPort)({
121
+ host: "localhost",
120
122
  port: 7200,
121
123
  portRange: [
122
124
  7200,
@@ -136,7 +138,7 @@ var DevServer = class {
136
138
  endpoint
137
139
  }, {
138
140
  F: __dxlog_file,
139
- L: 98,
141
+ L: 99,
140
142
  S: this,
141
143
  C: (f, a) => f(...a)
142
144
  });
@@ -158,7 +160,7 @@ var DevServer = class {
158
160
  registrationId: this._registrationId
159
161
  }, {
160
162
  F: __dxlog_file,
161
- L: 115,
163
+ L: 116,
162
164
  S: this,
163
165
  C: (f, a) => f(...a)
164
166
  });
@@ -181,7 +183,7 @@ var DevServer = class {
181
183
  id
182
184
  }, {
183
185
  F: __dxlog_file,
184
- L: 134,
186
+ L: 135,
185
187
  S: this,
186
188
  C: (f, a) => f(...a)
187
189
  });
@@ -208,13 +210,14 @@ var DevServer = class {
208
210
  name
209
211
  }, {
210
212
  F: __dxlog_file,
211
- L: 159,
213
+ L: 160,
212
214
  S: this,
213
215
  C: (f, a) => f(...a)
214
216
  });
215
217
  const { handler } = this._handlers[name];
216
218
  const context = {
217
- client: this._client
219
+ client: this._client,
220
+ dataDir: this._options.dataDir
218
221
  };
219
222
  let statusCode = 200;
220
223
  const response = {
@@ -235,14 +238,14 @@ var DevServer = class {
235
238
  duration: Date.now() - now
236
239
  }, {
237
240
  F: __dxlog_file,
238
- L: 175,
241
+ L: 177,
239
242
  S: this,
240
243
  C: (f, a) => f(...a)
241
244
  });
242
245
  return statusCode;
243
246
  }
244
247
  };
245
- var __dxlog_file2 = "/home/circleci/project/packages/core/functions/src/runtime/scheduler.ts";
248
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/functions/src/runtime/scheduler.ts";
246
249
  var Scheduler = class {
247
250
  constructor(_client, _manifest, _options) {
248
251
  this._client = _client;
@@ -316,6 +319,7 @@ var Scheduler = class {
316
319
  objects: Array.from(objectIds)
317
320
  });
318
321
  });
322
+ const subscriptions = [];
319
323
  const subscription = (0, import_echo_schema.createSubscription)(({ added, updated }) => {
320
324
  for (const object of added) {
321
325
  objectIds.add(object.id);
@@ -325,14 +329,35 @@ var Scheduler = class {
325
329
  }
326
330
  task.schedule();
327
331
  });
328
- const { type, props } = trigger.subscription;
329
- const query = space.db.query(import_echo_schema.Filter.typename(type, props));
330
- const unsubscribe = query.subscribe(({ objects }) => {
332
+ subscriptions.push(() => subscription.unsubscribe());
333
+ const { type, props, deep, delay } = trigger.subscription;
334
+ const update = ({ objects }) => {
331
335
  subscription.update(objects);
332
- }, true);
336
+ if (deep) {
337
+ import_log2.log.info("update", {
338
+ type,
339
+ deep,
340
+ objects: objects.length
341
+ }, {
342
+ F: __dxlog_file2,
343
+ L: 114,
344
+ S: this,
345
+ C: (f, a) => f(...a)
346
+ });
347
+ for (const object of objects) {
348
+ const content = object.content;
349
+ if (content instanceof import_echo.TextObject) {
350
+ subscriptions.push(content[import_echo_schema.subscribe]((0, import_async2.debounce)(() => subscription.update([
351
+ object
352
+ ]), 1e3)));
353
+ }
354
+ }
355
+ }
356
+ };
357
+ const query = space.db.query(import_echo_schema.Filter.typename(type, props));
358
+ subscriptions.push(query.subscribe(delay ? (0, import_async2.debounce)(update, delay * 1e3) : update));
333
359
  ctx.onDispose(() => {
334
- subscription.unsubscribe();
335
- unsubscribe();
360
+ subscriptions.forEach((unsubscribe) => unsubscribe());
336
361
  });
337
362
  }
338
363
  }
@@ -354,7 +379,7 @@ var Scheduler = class {
354
379
  function: def.id
355
380
  }, {
356
381
  F: __dxlog_file2,
357
- L: 130,
382
+ L: 147,
358
383
  S: this,
359
384
  C: (f, a) => f(...a)
360
385
  });
@@ -370,7 +395,7 @@ var Scheduler = class {
370
395
  result: response.status
371
396
  }, {
372
397
  F: __dxlog_file2,
373
- L: 140,
398
+ L: 157,
374
399
  S: this,
375
400
  C: (f, a) => f(...a)
376
401
  });
@@ -380,7 +405,7 @@ var Scheduler = class {
380
405
  error: err.message
381
406
  }, {
382
407
  F: __dxlog_file2,
383
- L: 142,
408
+ L: 159,
384
409
  S: this,
385
410
  C: (f, a) => f(...a)
386
411
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/runtime/dev-server.ts", "../../../src/runtime/scheduler.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport express from 'express';\nimport { getPort } from 'get-port-please';\nimport type http from 'http';\nimport { join } from 'node:path';\n\nimport { Trigger } from '@dxos/async';\nimport { type Client } from '@dxos/client';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\n\nimport { type FunctionContext, type FunctionHandler, type Response } from '../handler';\nimport { type FunctionDef, type FunctionManifest } from '../manifest';\n\nexport type DevServerOptions = {\n port?: number;\n directory: string;\n manifest: FunctionManifest;\n reload?: boolean;\n};\n\n/**\n * Functions dev server provides a local HTTP server for testing functions.\n */\nexport class DevServer {\n // Function handlers indexed by name (URL path).\n private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};\n\n private _server?: http.Server;\n private _port?: number;\n private _registrationId?: string;\n private _proxy?: string;\n private _seq = 0;\n\n // prettier-ignore\n constructor(\n private readonly _client: Client,\n private readonly _options: DevServerOptions,\n ) {}\n\n get endpoint() {\n invariant(this._port);\n return `http://localhost:${this._port}`;\n }\n\n get proxy() {\n return this._proxy;\n }\n\n get functions() {\n return Object.values(this._handlers);\n }\n\n async initialize() {\n for (const def of this._options.manifest.functions) {\n try {\n await this._load(def);\n } catch (err) {\n log.error('parsing function (check manifest)', err);\n }\n }\n }\n\n async start() {\n const app = express();\n app.use(express.json());\n\n app.post('/:name', async (req, res) => {\n const { name } = req.params;\n try {\n if (this._options.reload) {\n const { def } = this._handlers[name];\n await this._load(def, true);\n }\n\n res.statusCode = await this._invoke(name, req.body);\n res.end();\n } catch (err: any) {\n log.error(err);\n res.statusCode = 500;\n res.end();\n }\n });\n\n this._port = await getPort({ port: 7200, portRange: [7200, 7299] });\n this._server = app.listen(this._port);\n\n try {\n // Register functions.\n const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({\n endpoint: this.endpoint,\n functions: this.functions.map(({ def: { name } }) => ({ name })),\n });\n\n log.info('registered', { registrationId, endpoint });\n this._registrationId = registrationId;\n this._proxy = endpoint;\n } catch (err: any) {\n await this.stop();\n throw new Error('FunctionRegistryService not available (check plugin is configured).');\n }\n }\n\n async stop() {\n const trigger = new Trigger();\n this._server?.close(async () => {\n if (this._registrationId) {\n await this._client.services.services.FunctionRegistryService!.unregister({\n registrationId: this._registrationId,\n });\n\n log.info('unregistered', { registrationId: this._registrationId });\n this._registrationId = undefined;\n this._proxy = undefined;\n }\n\n trigger.wake();\n });\n\n await trigger.wait();\n this._port = undefined;\n this._server = undefined;\n }\n\n /**\n * Load function.\n */\n private async _load(def: FunctionDef, flush = false) {\n const { id, name, handler } = def;\n const path = join(this._options.directory, handler);\n log.info('loading', { id });\n\n // Remove from cache.\n if (flush) {\n Object.keys(require.cache)\n .filter((key) => key.startsWith(path))\n .forEach((key) => delete require.cache[key]);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const module = require(path);\n if (typeof module.default !== 'function') {\n throw new Error(`Handler must export default function: ${id}`);\n }\n\n this._handlers[name] = { def, handler: module.default };\n }\n\n /**\n * Invoke function handler.\n */\n private async _invoke(name: string, event: any) {\n const seq = ++this._seq;\n const now = Date.now();\n\n log.info('req', { seq, name });\n const { handler } = this._handlers[name];\n\n const context: FunctionContext = {\n client: this._client,\n };\n\n let statusCode = 200;\n const response: Response = {\n status: (code: number) => {\n statusCode = code;\n return response;\n },\n };\n\n await handler({ context, event, response });\n log.info('res', { seq, name, statusCode, duration: Date.now() - now });\n\n return statusCode;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { CronJob } from 'cron';\n\nimport { DeferredTask } from '@dxos/async';\nimport { type Client, type PublicKey } from '@dxos/client';\nimport { type Space } from '@dxos/client/echo';\nimport { Context } from '@dxos/context';\nimport { Filter, createSubscription } from '@dxos/echo-schema';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { ComplexMap } from '@dxos/util';\n\nimport { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../manifest';\n\ntype SchedulerOptions = {\n endpoint: string;\n};\n\n/**\n * Functions scheduler.\n */\n// TODO(burdon): Create tests.\nexport class Scheduler {\n // Map of mounted functions.\n private readonly _mounts = new ComplexMap<\n { id: string; spaceKey: PublicKey },\n { ctx: Context; trigger: FunctionTrigger }\n >(({ id, spaceKey }) => `${spaceKey.toHex()}:${id}`);\n\n constructor(\n private readonly _client: Client,\n private readonly _manifest: FunctionManifest,\n private readonly _options: SchedulerOptions,\n ) {}\n\n async start() {\n this._client.spaces.subscribe(async (spaces) => {\n for (const space of spaces) {\n await space.waitUntilReady();\n for (const trigger of this._manifest.triggers ?? []) {\n await this.mount(new Context(), space, trigger);\n }\n }\n });\n }\n\n async stop() {\n for (const { id, spaceKey } of this._mounts.keys()) {\n await this.unmount(id, spaceKey);\n }\n }\n\n private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {\n const key = { id: trigger.function, spaceKey: space.key };\n const def = this._manifest.functions.find((config) => config.id === trigger.function);\n invariant(def, `Function not found: ${trigger.function}`);\n\n const exists = this._mounts.get(key);\n if (!exists) {\n this._mounts.set(key, { ctx, trigger });\n log('mount', { space: space.key, trigger });\n if (ctx.disposed) {\n return;\n }\n\n // Cron schedule.\n if (trigger.schedule) {\n const task = new DeferredTask(ctx, async () => {\n await this.execFunction(def, {\n space: space.key,\n });\n });\n\n // TODO(burdon): Check greater than 30s min (use cron-parser).\n const job = new CronJob(trigger.schedule, () => task.schedule());\n\n job.start();\n ctx.onDispose(() => job.stop());\n }\n\n // ECHO subscription.\n if (trigger.subscription) {\n const objectIds = new Set<string>();\n const task = new DeferredTask(ctx, async () => {\n await this.execFunction(def, {\n space: space.key,\n objects: Array.from(objectIds),\n });\n });\n\n const subscription = createSubscription(({ added, updated }) => {\n for (const object of added) {\n objectIds.add(object.id);\n }\n for (const object of updated) {\n objectIds.add(object.id);\n }\n\n task.schedule();\n });\n\n const { type, props } = trigger.subscription;\n const query = space.db.query(Filter.typename(type, props));\n const unsubscribe = query.subscribe(({ objects }) => {\n subscription.update(objects);\n }, true);\n\n ctx.onDispose(() => {\n subscription.unsubscribe();\n unsubscribe();\n });\n }\n }\n }\n\n private async unmount(id: string, spaceKey: PublicKey) {\n const key = { id, spaceKey };\n const { ctx } = this._mounts.get(key) ?? {};\n if (ctx) {\n this._mounts.delete(key);\n await ctx.dispose();\n }\n }\n\n private async execFunction(def: FunctionDef, data: any) {\n try {\n log('request', { function: def.id });\n const response = await fetch(`${this._options.endpoint}/${def.name}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n });\n\n // const result = await response.json();\n log('result', { function: def.id, result: response.status });\n } catch (err: any) {\n log.error('error', { function: def.id, error: err.message });\n }\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,qBAAoB;AACpB,6BAAwB;AAExB,uBAAqB;AAErB,mBAAwB;AAExB,uBAA0B;AAC1B,iBAAoB;ACRpB,kBAAwB;AAExB,IAAAA,gBAA6B;AAG7B,qBAAwB;AACxB,yBAA2C;AAC3C,IAAAC,oBAA0B;AAC1B,IAAAC,cAAoB;AACpB,kBAA2B;;;;;;;;;ADcpB,IAAMC,YAAN,MAAMA;;EAWXC,YACmBC,SACAC,UACjB;mBAFiBD;oBACAC;SAXFC,YAAiF,CAAC;SAM3FC,OAAO;EAMZ;EAEH,IAAIC,WAAW;AACbC,oCAAU,KAAKC,OAAK,QAAA;;;;;;;;;AACpB,WAAO,oBAAoB,KAAKA,KAAK;EACvC;EAEA,IAAIC,QAAQ;AACV,WAAO,KAAKC;EACd;EAEA,IAAIC,YAAY;AACd,WAAOC,OAAOC,OAAO,KAAKT,SAAS;EACrC;EAEA,MAAMU,aAAa;AACjB,eAAWC,OAAO,KAAKZ,SAASa,SAASL,WAAW;AAClD,UAAI;AACF,cAAM,KAAKM,MAAMF,GAAAA;MACnB,SAASG,KAAK;AACZC,uBAAIC,MAAM,qCAAqCF,KAAAA;;;;;;MACjD;IACF;EACF;EAEA,MAAMG,QAAQ;AACZ,UAAMC,UAAMC,eAAAA,SAAAA;AACZD,QAAIE,IAAID,eAAAA,QAAQE,KAAI,CAAA;AAEpBH,QAAII,KAAK,UAAU,OAAOC,KAAKC,QAAAA;AAC7B,YAAM,EAAEC,KAAI,IAAKF,IAAIG;AACrB,UAAI;AACF,YAAI,KAAK3B,SAAS4B,QAAQ;AACxB,gBAAM,EAAEhB,IAAG,IAAK,KAAKX,UAAUyB,IAAAA;AAC/B,gBAAM,KAAKZ,MAAMF,KAAK,IAAA;QACxB;AAEAa,YAAII,aAAa,MAAM,KAAKC,QAAQJ,MAAMF,IAAIO,IAAI;AAClDN,YAAIO,IAAG;MACT,SAASjB,KAAU;AACjBC,uBAAIC,MAAMF,KAAAA,QAAAA;;;;;;AACVU,YAAII,aAAa;AACjBJ,YAAIO,IAAG;MACT;IACF,CAAA;AAEA,SAAK3B,QAAQ,UAAM4B,gCAAQ;MAAEC,MAAM;MAAMC,WAAW;QAAC;QAAM;;IAAM,CAAA;AACjE,SAAKC,UAAUjB,IAAIkB,OAAO,KAAKhC,KAAK;AAEpC,QAAI;AAEF,YAAM,EAAEiC,gBAAgBnC,SAAQ,IAAK,MAAM,KAAKJ,QAAQwC,SAASA,SAASC,wBAAyBC,SAAS;QAC1GtC,UAAU,KAAKA;QACfK,WAAW,KAAKA,UAAUkC,IAAI,CAAC,EAAE9B,KAAK,EAAEc,KAAI,EAAE,OAAQ;UAAEA;QAAK,EAAA;MAC/D,CAAA;AAEAV,qBAAI2B,KAAK,cAAc;QAAEL;QAAgBnC;MAAS,GAAA;;;;;;AAClD,WAAKyC,kBAAkBN;AACvB,WAAK/B,SAASJ;IAChB,SAASY,KAAU;AACjB,YAAM,KAAK8B,KAAI;AACf,YAAM,IAAIC,MAAM,qEAAA;IAClB;EACF;EAEA,MAAMD,OAAO;AACX,UAAME,UAAU,IAAIC,qBAAAA;AACpB,SAAKZ,SAASa,MAAM,YAAA;AAClB,UAAI,KAAKL,iBAAiB;AACxB,cAAM,KAAK7C,QAAQwC,SAASA,SAASC,wBAAyBU,WAAW;UACvEZ,gBAAgB,KAAKM;QACvB,CAAA;AAEA5B,uBAAI2B,KAAK,gBAAgB;UAAEL,gBAAgB,KAAKM;QAAgB,GAAA;;;;;;AAChE,aAAKA,kBAAkBO;AACvB,aAAK5C,SAAS4C;MAChB;AAEAJ,cAAQK,KAAI;IACd,CAAA;AAEA,UAAML,QAAQM,KAAI;AAClB,SAAKhD,QAAQ8C;AACb,SAAKf,UAAUe;EACjB;;;;EAKA,MAAcrC,MAAMF,KAAkB0C,QAAQ,OAAO;AACnD,UAAM,EAAEC,IAAI7B,MAAM8B,QAAO,IAAK5C;AAC9B,UAAM6C,WAAOC,uBAAK,KAAK1D,SAAS2D,WAAWH,OAAAA;AAC3CxC,mBAAI2B,KAAK,WAAW;MAAEY;IAAG,GAAA;;;;;;AAGzB,QAAID,OAAO;AACT7C,aAAOmD,KAAKC,UAAQC,KAAK,EACtBC,OAAO,CAACC,QAAQA,IAAIC,WAAWR,IAAAA,CAAAA,EAC/BS,QAAQ,CAACF,QAAQ,OAAOH,UAAQC,MAAME,GAAAA,CAAI;IAC/C;AAGA,UAAMG,UAASN,UAAQJ,IAAAA;AACvB,QAAI,OAAOU,QAAOC,YAAY,YAAY;AACxC,YAAM,IAAItB,MAAM,yCAAyCS,EAAAA,EAAI;IAC/D;AAEA,SAAKtD,UAAUyB,IAAAA,IAAQ;MAAEd;MAAK4C,SAASW,QAAOC;IAAQ;EACxD;;;;EAKA,MAActC,QAAQJ,MAAc2C,OAAY;AAC9C,UAAMC,MAAM,EAAE,KAAKpE;AACnB,UAAMqE,MAAMC,KAAKD,IAAG;AAEpBvD,mBAAI2B,KAAK,OAAO;MAAE2B;MAAK5C;IAAK,GAAA;;;;;;AAC5B,UAAM,EAAE8B,QAAO,IAAK,KAAKvD,UAAUyB,IAAAA;AAEnC,UAAM+C,UAA2B;MAC/BC,QAAQ,KAAK3E;IACf;AAEA,QAAI8B,aAAa;AACjB,UAAM8C,WAAqB;MACzBC,QAAQ,CAACC,SAAAA;AACPhD,qBAAagD;AACb,eAAOF;MACT;IACF;AAEA,UAAMnB,QAAQ;MAAEiB;MAASJ;MAAOM;IAAS,CAAA;AACzC3D,mBAAI2B,KAAK,OAAO;MAAE2B;MAAK5C;MAAMG;MAAYiD,UAAUN,KAAKD,IAAG,IAAKA;IAAI,GAAA;;;;;;AAEpE,WAAO1C;EACT;AACF;;ACzJO,IAAMkD,YAAN,MAAMA;EAOXjF,YACmBC,SACAiF,WACAhF,UACjB;mBAHiBD;qBACAiF;oBACAhF;SARFiF,UAAU,IAAIC,uBAG7B,CAAC,EAAE3B,IAAI4B,SAAQ,MAAO,GAAGA,SAASC,MAAK,CAAA,IAAM7B,EAAAA,EAAI;EAMhD;EAEH,MAAMrC,QAAQ;AACZ,SAAKnB,QAAQsF,OAAOC,UAAU,OAAOD,WAAAA;AACnC,iBAAWE,SAASF,QAAQ;AAC1B,cAAME,MAAMC,eAAc;AAC1B,mBAAWzC,WAAW,KAAKiC,UAAUS,YAAY,CAAA,GAAI;AACnD,gBAAM,KAAKC,MAAM,IAAIC,uBAAAA,GAAWJ,OAAOxC,OAAAA;QACzC;MACF;IACF,CAAA;EACF;EAEA,MAAMF,OAAO;AACX,eAAW,EAAEU,IAAI4B,SAAQ,KAAM,KAAKF,QAAQrB,KAAI,GAAI;AAClD,YAAM,KAAKgC,QAAQrC,IAAI4B,QAAAA;IACzB;EACF;EAEA,MAAcO,MAAMG,KAAcN,OAAcxC,SAA0B;AACxE,UAAMiB,MAAM;MAAET,IAAIR,QAAQ+C;MAAUX,UAAUI,MAAMvB;IAAI;AACxD,UAAMpD,MAAM,KAAKoE,UAAUxE,UAAUuF,KAAK,CAACC,WAAWA,OAAOzC,OAAOR,QAAQ+C,QAAQ;AACpF1F,0BAAAA,WAAUQ,KAAK,uBAAuBmC,QAAQ+C,QAAQ,IAAE;;;;;;;;;AAExD,UAAMG,SAAS,KAAKhB,QAAQiB,IAAIlC,GAAAA;AAChC,QAAI,CAACiC,QAAQ;AACX,WAAKhB,QAAQkB,IAAInC,KAAK;QAAE6B;QAAK9C;MAAQ,CAAA;AACrC/B,sBAAAA,KAAI,SAAS;QAAEuE,OAAOA,MAAMvB;QAAKjB;MAAQ,GAAA;;;;;;AACzC,UAAI8C,IAAIO,UAAU;AAChB;MACF;AAGA,UAAIrD,QAAQsD,UAAU;AACpB,cAAMC,OAAO,IAAIC,2BAAaV,KAAK,YAAA;AACjC,gBAAM,KAAKW,aAAa5F,KAAK;YAC3B2E,OAAOA,MAAMvB;UACf,CAAA;QACF,CAAA;AAGA,cAAMyC,MAAM,IAAIC,oBAAQ3D,QAAQsD,UAAU,MAAMC,KAAKD,SAAQ,CAAA;AAE7DI,YAAIvF,MAAK;AACT2E,YAAIc,UAAU,MAAMF,IAAI5D,KAAI,CAAA;MAC9B;AAGA,UAAIE,QAAQ6D,cAAc;AACxB,cAAMC,YAAY,oBAAIC,IAAAA;AACtB,cAAMR,OAAO,IAAIC,2BAAaV,KAAK,YAAA;AACjC,gBAAM,KAAKW,aAAa5F,KAAK;YAC3B2E,OAAOA,MAAMvB;YACb+C,SAASC,MAAMC,KAAKJ,SAAAA;UACtB,CAAA;QACF,CAAA;AAEA,cAAMD,mBAAeM,uCAAmB,CAAC,EAAEC,OAAOC,QAAO,MAAE;AACzD,qBAAWC,UAAUF,OAAO;AAC1BN,sBAAUS,IAAID,OAAO9D,EAAE;UACzB;AACA,qBAAW8D,UAAUD,SAAS;AAC5BP,sBAAUS,IAAID,OAAO9D,EAAE;UACzB;AAEA+C,eAAKD,SAAQ;QACf,CAAA;AAEA,cAAM,EAAEkB,MAAMC,MAAK,IAAKzE,QAAQ6D;AAChC,cAAMa,QAAQlC,MAAMmC,GAAGD,MAAME,0BAAOC,SAASL,MAAMC,KAAAA,CAAAA;AACnD,cAAMK,cAAcJ,MAAMnC,UAAU,CAAC,EAAEyB,QAAO,MAAE;AAC9CH,uBAAakB,OAAOf,OAAAA;QACtB,GAAG,IAAA;AAEHlB,YAAIc,UAAU,MAAA;AACZC,uBAAaiB,YAAW;AACxBA,sBAAAA;QACF,CAAA;MACF;IACF;EACF;EAEA,MAAcjC,QAAQrC,IAAY4B,UAAqB;AACrD,UAAMnB,MAAM;MAAET;MAAI4B;IAAS;AAC3B,UAAM,EAAEU,IAAG,IAAK,KAAKZ,QAAQiB,IAAIlC,GAAAA,KAAQ,CAAC;AAC1C,QAAI6B,KAAK;AACP,WAAKZ,QAAQ8C,OAAO/D,GAAAA;AACpB,YAAM6B,IAAImC,QAAO;IACnB;EACF;EAEA,MAAcxB,aAAa5F,KAAkBqH,MAAW;AACtD,QAAI;AACFjH,sBAAAA,KAAI,WAAW;QAAE8E,UAAUlF,IAAI2C;MAAG,GAAA;;;;;;AAClC,YAAMoB,WAAW,MAAMuD,MAAM,GAAG,KAAKlI,SAASG,QAAQ,IAAIS,IAAIc,IAAI,IAAI;QACpEyG,QAAQ;QACRC,SAAS;UACP,gBAAgB;QAClB;QACArG,MAAMsG,KAAKC,UAAUL,IAAAA;MACvB,CAAA;AAGAjH,sBAAAA,KAAI,UAAU;QAAE8E,UAAUlF,IAAI2C;QAAIgF,QAAQ5D,SAASC;MAAO,GAAA;;;;;;IAC5D,SAAS7D,KAAU;AACjBC,kBAAAA,IAAIC,MAAM,SAAS;QAAE6E,UAAUlF,IAAI2C;QAAItC,OAAOF,IAAIyH;MAAQ,GAAA;;;;;;IAC5D;EACF;AACF;",
6
- "names": ["import_async", "import_invariant", "import_log", "DevServer", "constructor", "_client", "_options", "_handlers", "_seq", "endpoint", "invariant", "_port", "proxy", "_proxy", "functions", "Object", "values", "initialize", "def", "manifest", "_load", "err", "log", "error", "start", "app", "express", "use", "json", "post", "req", "res", "name", "params", "reload", "statusCode", "_invoke", "body", "end", "getPort", "port", "portRange", "_server", "listen", "registrationId", "services", "FunctionRegistryService", "register", "map", "info", "_registrationId", "stop", "Error", "trigger", "Trigger", "close", "unregister", "undefined", "wake", "wait", "flush", "id", "handler", "path", "join", "directory", "keys", "require", "cache", "filter", "key", "startsWith", "forEach", "module", "default", "event", "seq", "now", "Date", "context", "client", "response", "status", "code", "duration", "Scheduler", "_manifest", "_mounts", "ComplexMap", "spaceKey", "toHex", "spaces", "subscribe", "space", "waitUntilReady", "triggers", "mount", "Context", "unmount", "ctx", "function", "find", "config", "exists", "get", "set", "disposed", "schedule", "task", "DeferredTask", "execFunction", "job", "CronJob", "onDispose", "subscription", "objectIds", "Set", "objects", "Array", "from", "createSubscription", "added", "updated", "object", "add", "type", "props", "query", "db", "Filter", "typename", "unsubscribe", "update", "delete", "dispose", "data", "fetch", "method", "headers", "JSON", "stringify", "result", "message"]
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport express from 'express';\nimport { getPort } from 'get-port-please';\nimport type http from 'http';\nimport { join } from 'node:path';\n\nimport { Trigger } from '@dxos/async';\nimport { type Client } from '@dxos/client';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\n\nimport { type FunctionContext, type FunctionHandler, type Response } from '../handler';\nimport { type FunctionDef, type FunctionManifest } from '../manifest';\n\nexport type DevServerOptions = {\n port?: number;\n directory: string;\n manifest: FunctionManifest;\n reload?: boolean;\n dataDir?: string;\n};\n\n/**\n * Functions dev server provides a local HTTP server for testing functions.\n */\nexport class DevServer {\n // Function handlers indexed by name (URL path).\n private readonly _handlers: Record<string, { def: FunctionDef; handler: FunctionHandler<any> }> = {};\n\n private _server?: http.Server;\n private _port?: number;\n private _registrationId?: string;\n private _proxy?: string;\n private _seq = 0;\n\n // prettier-ignore\n constructor(\n private readonly _client: Client,\n private readonly _options: DevServerOptions,\n ) {}\n\n get endpoint() {\n invariant(this._port);\n return `http://localhost:${this._port}`;\n }\n\n get proxy() {\n return this._proxy;\n }\n\n get functions() {\n return Object.values(this._handlers);\n }\n\n async initialize() {\n for (const def of this._options.manifest.functions) {\n try {\n await this._load(def);\n } catch (err) {\n log.error('parsing function (check manifest)', err);\n }\n }\n }\n\n async start() {\n const app = express();\n app.use(express.json());\n\n app.post('/:name', async (req, res) => {\n const { name } = req.params;\n try {\n if (this._options.reload) {\n const { def } = this._handlers[name];\n await this._load(def, true);\n }\n\n res.statusCode = await this._invoke(name, req.body);\n res.end();\n } catch (err: any) {\n log.catch(err);\n res.statusCode = 500;\n res.end();\n }\n });\n\n this._port = await getPort({ host: 'localhost', port: 7200, portRange: [7200, 7299] });\n this._server = app.listen(this._port);\n\n try {\n // Register functions.\n const { registrationId, endpoint } = await this._client.services.services.FunctionRegistryService!.register({\n endpoint: this.endpoint,\n functions: this.functions.map(({ def: { name } }) => ({ name })),\n });\n\n log.info('registered', { registrationId, endpoint });\n this._registrationId = registrationId;\n this._proxy = endpoint;\n } catch (err: any) {\n await this.stop();\n throw new Error('FunctionRegistryService not available (check plugin is configured).');\n }\n }\n\n async stop() {\n const trigger = new Trigger();\n this._server?.close(async () => {\n if (this._registrationId) {\n await this._client.services.services.FunctionRegistryService!.unregister({\n registrationId: this._registrationId,\n });\n\n log.info('unregistered', { registrationId: this._registrationId });\n this._registrationId = undefined;\n this._proxy = undefined;\n }\n\n trigger.wake();\n });\n\n await trigger.wait();\n this._port = undefined;\n this._server = undefined;\n }\n\n /**\n * Load function.\n */\n private async _load(def: FunctionDef, flush = false) {\n const { id, name, handler } = def;\n const path = join(this._options.directory, handler);\n log.info('loading', { id });\n\n // Remove from cache.\n if (flush) {\n Object.keys(require.cache)\n .filter((key) => key.startsWith(path))\n .forEach((key) => delete require.cache[key]);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const module = require(path);\n if (typeof module.default !== 'function') {\n throw new Error(`Handler must export default function: ${id}`);\n }\n\n this._handlers[name] = { def, handler: module.default };\n }\n\n /**\n * Invoke function handler.\n */\n private async _invoke(name: string, event: any) {\n const seq = ++this._seq;\n const now = Date.now();\n\n log.info('req', { seq, name });\n const { handler } = this._handlers[name];\n\n const context: FunctionContext = {\n client: this._client,\n dataDir: this._options.dataDir,\n };\n\n let statusCode = 200;\n const response: Response = {\n status: (code: number) => {\n statusCode = code;\n return response;\n },\n };\n\n await handler({ context, event, response });\n log.info('res', { seq, name, statusCode, duration: Date.now() - now });\n\n return statusCode;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { CronJob } from 'cron';\n\nimport { debounce, DeferredTask } from '@dxos/async';\nimport { type Client, type PublicKey } from '@dxos/client';\nimport { type Space, TextObject } from '@dxos/client/echo';\nimport { Context } from '@dxos/context';\nimport { Filter, createSubscription, type Query, subscribe } from '@dxos/echo-schema';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { ComplexMap } from '@dxos/util';\n\nimport { type FunctionDef, type FunctionManifest, type FunctionTrigger } from '../manifest';\n\ntype SchedulerOptions = {\n endpoint: string;\n};\n\n/**\n * Functions scheduler.\n */\n// TODO(burdon): Create tests.\nexport class Scheduler {\n // Map of mounted functions.\n private readonly _mounts = new ComplexMap<\n { id: string; spaceKey: PublicKey },\n { ctx: Context; trigger: FunctionTrigger }\n >(({ id, spaceKey }) => `${spaceKey.toHex()}:${id}`);\n\n constructor(\n private readonly _client: Client,\n private readonly _manifest: FunctionManifest,\n private readonly _options: SchedulerOptions,\n ) {}\n\n async start() {\n this._client.spaces.subscribe(async (spaces) => {\n for (const space of spaces) {\n await space.waitUntilReady();\n for (const trigger of this._manifest.triggers ?? []) {\n await this.mount(new Context(), space, trigger);\n }\n }\n });\n }\n\n async stop() {\n for (const { id, spaceKey } of this._mounts.keys()) {\n await this.unmount(id, spaceKey);\n }\n }\n\n private async mount(ctx: Context, space: Space, trigger: FunctionTrigger) {\n const key = { id: trigger.function, spaceKey: space.key };\n const def = this._manifest.functions.find((config) => config.id === trigger.function);\n invariant(def, `Function not found: ${trigger.function}`);\n\n const exists = this._mounts.get(key);\n if (!exists) {\n this._mounts.set(key, { ctx, trigger });\n log('mount', { space: space.key, trigger });\n if (ctx.disposed) {\n return;\n }\n\n // Cron schedule.\n if (trigger.schedule) {\n const task = new DeferredTask(ctx, async () => {\n await this.execFunction(def, {\n space: space.key,\n });\n });\n\n // TODO(burdon): Check greater than 30s min (use cron-parser).\n const job = new CronJob(trigger.schedule, () => task.schedule());\n\n job.start();\n ctx.onDispose(() => job.stop());\n }\n\n // ECHO subscription.\n if (trigger.subscription) {\n const objectIds = new Set<string>();\n const task = new DeferredTask(ctx, async () => {\n await this.execFunction(def, {\n space: space.key,\n objects: Array.from(objectIds),\n });\n });\n\n // TODO(burdon): Standardize subscription handles.\n const subscriptions: (() => void)[] = [];\n const subscription = createSubscription(({ added, updated }) => {\n for (const object of added) {\n objectIds.add(object.id);\n }\n for (const object of updated) {\n objectIds.add(object.id);\n }\n\n task.schedule();\n });\n subscriptions.push(() => subscription.unsubscribe());\n\n const { type, props, deep, delay } = trigger.subscription;\n const update = ({ objects }: Query) => {\n subscription.update(objects);\n\n // TODO(burdon): Hack to monitor changes to Document's text object.\n if (deep) {\n log.info('update', { type, deep, objects: objects.length });\n for (const object of objects) {\n const content = object.content;\n if (content instanceof TextObject) {\n subscriptions.push(content[subscribe](debounce(() => subscription.update([object]), 1_000)));\n }\n }\n }\n };\n\n // TODO(burdon): [Bug]: all callbacks are fired on the first mutation.\n // TODO(burdon): [Bug]: not updated when document is deleted (either top or hierarchically).\n const query = space.db.query(Filter.typename(type, props));\n subscriptions.push(query.subscribe(delay ? debounce(update, delay * 1_000) : update));\n\n ctx.onDispose(() => {\n subscriptions.forEach((unsubscribe) => unsubscribe());\n });\n }\n }\n }\n\n private async unmount(id: string, spaceKey: PublicKey) {\n const key = { id, spaceKey };\n const { ctx } = this._mounts.get(key) ?? {};\n if (ctx) {\n this._mounts.delete(key);\n await ctx.dispose();\n }\n }\n\n private async execFunction(def: FunctionDef, data: any) {\n try {\n log('request', { function: def.id });\n const response = await fetch(`${this._options.endpoint}/${def.name}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n });\n\n // const result = await response.json();\n log('result', { function: def.id, result: response.status });\n } catch (err: any) {\n log.error('error', { function: def.id, error: err.message });\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["import_async", "import_invariant", "import_log", "DevServer", "constructor", "_client", "_options", "_handlers", "_seq", "endpoint", "invariant", "_port", "proxy", "_proxy", "functions", "Object", "values", "initialize", "def", "manifest", "_load", "err", "log", "error", "start", "app", "express", "use", "json", "post", "req", "res", "name", "params", "reload", "statusCode", "_invoke", "body", "end", "catch", "getPort", "host", "port", "portRange", "_server", "listen", "registrationId", "services", "FunctionRegistryService", "register", "map", "info", "_registrationId", "stop", "Error", "trigger", "Trigger", "close", "unregister", "undefined", "wake", "wait", "flush", "id", "handler", "path", "join", "directory", "keys", "require", "cache", "filter", "key", "startsWith", "forEach", "module", "default", "event", "seq", "now", "Date", "context", "client", "dataDir", "response", "status", "code", "duration", "Scheduler", "_manifest", "_mounts", "ComplexMap", "spaceKey", "toHex", "spaces", "subscribe", "space", "waitUntilReady", "triggers", "mount", "Context", "unmount", "ctx", "function", "find", "config", "exists", "get", "set", "disposed", "schedule", "task", "DeferredTask", "execFunction", "job", "CronJob", "onDispose", "subscription", "objectIds", "Set", "objects", "Array", "from", "subscriptions", "createSubscription", "added", "updated", "object", "add", "push", "unsubscribe", "type", "props", "deep", "delay", "update", "length", "content", "TextObject", "debounce", "query", "db", "Filter", "typename", "delete", "dispose", "data", "fetch", "method", "headers", "JSON", "stringify", "result", "message"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/core/functions/src/handler.ts":{"bytes":1308,"imports":[],"format":"esm"},"packages/core/functions/src/manifest.ts":{"bytes":1730,"imports":[],"format":"esm"},"packages/core/functions/src/runtime/dev-server.ts":{"bytes":18548,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/scheduler.ts":{"bytes":16636,"imports":[{"path":"cron","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/index.ts":{"bytes":561,"imports":[{"path":"packages/core/functions/src/runtime/dev-server.ts","kind":"import-statement","original":"./dev-server"},{"path":"packages/core/functions/src/runtime/scheduler.ts","kind":"import-statement","original":"./scheduler"}],"format":"esm"},"packages/core/functions/src/index.ts":{"bytes":632,"imports":[{"path":"packages/core/functions/src/handler.ts","kind":"import-statement","original":"./handler"},{"path":"packages/core/functions/src/manifest.ts","kind":"import-statement","original":"./manifest"},{"path":"packages/core/functions/src/runtime/index.ts","kind":"import-statement","original":"./runtime"}],"format":"esm"}},"outputs":{"packages/core/functions/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16608},"packages/core/functions/dist/lib/node/index.cjs":{"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"cron","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["DevServer","Scheduler"],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/dev-server.ts":{"bytesInOutput":4724},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/scheduler.ts":{"bytesInOutput":4184}},"bytes":9456}}}
1
+ {"inputs":{"packages/core/functions/src/handler.ts":{"bytes":1341,"imports":[],"format":"esm"},"packages/core/functions/src/manifest.ts":{"bytes":1795,"imports":[],"format":"esm"},"packages/core/functions/src/runtime/dev-server.ts":{"bytes":18840,"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/scheduler.ts":{"bytes":20292,"imports":[{"path":"cron","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/client/echo","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"packages/core/functions/src/runtime/index.ts":{"bytes":566,"imports":[{"path":"packages/core/functions/src/runtime/dev-server.ts","kind":"import-statement","original":"./dev-server"},{"path":"packages/core/functions/src/runtime/scheduler.ts","kind":"import-statement","original":"./scheduler"}],"format":"esm"},"packages/core/functions/src/index.ts":{"bytes":637,"imports":[{"path":"packages/core/functions/src/handler.ts","kind":"import-statement","original":"./handler"},{"path":"packages/core/functions/src/manifest.ts","kind":"import-statement","original":"./manifest"},{"path":"packages/core/functions/src/runtime/index.ts","kind":"import-statement","original":"./runtime"}],"format":"esm"}},"outputs":{"packages/core/functions/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":18299},"packages/core/functions/dist/lib/node/index.cjs":{"imports":[{"path":"express","kind":"import-statement","external":true},{"path":"get-port-please","kind":"import-statement","external":true},{"path":"node:path","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"cron","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/client/echo","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/echo-schema","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["DevServer","Scheduler"],"entryPoint":"packages/core/functions/src/index.ts","inputs":{"packages/core/functions/src/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/dev-server.ts":{"bytesInOutput":4792},"packages/core/functions/src/runtime/index.ts":{"bytesInOutput":0},"packages/core/functions/src/runtime/scheduler.ts":{"bytesInOutput":5040}},"bytes":10380}}}
@@ -4,6 +4,7 @@ export interface Response {
4
4
  }
5
5
  export interface FunctionContext {
6
6
  client: Client;
7
+ dataDir?: string;
7
8
  }
8
9
  export type FunctionHandler<T extends {}> = (params: {
9
10
  event: T;
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;CAChC;AAGD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE;IACnD,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,QAAQ,CAAC;CACpB,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;AAE/B,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;CAChC;AAGD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE;IACnD,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,QAAQ,EAAE,QAAQ,CAAC;CACpB,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;AAE/B,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC"}
@@ -8,7 +8,8 @@ export type TriggerSubscription = {
8
8
  type: string;
9
9
  spaceKey: string;
10
10
  props?: Record<string, any>;
11
- nested?: string[];
11
+ deep?: boolean;
12
+ delay?: number;
12
13
  };
13
14
  export type FunctionTrigger = {
14
15
  function: string;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/manifest.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG;IAExB,EAAE,EAAE,MAAM,CAAC;IAEX,IAAI,EAAE,MAAM,CAAC;IAEb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAKF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/manifest.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG;IAExB,EAAE,EAAE,MAAM,CAAC;IAEX,IAAI,EAAE,MAAM,CAAC;IAEb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAGF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAKF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC"}
@@ -6,6 +6,7 @@ export type DevServerOptions = {
6
6
  directory: string;
7
7
  manifest: FunctionManifest;
8
8
  reload?: boolean;
9
+ dataDir?: string;
9
10
  };
10
11
  /**
11
12
  * Functions dev server provides a local HTTP server for testing functions.
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../../../src/runtime/dev-server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAI3C,OAAO,EAAwB,KAAK,eAAe,EAAiB,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,qBAAa,SAAS;IAYlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAErG,OAAO,CAAC,OAAO,CAAC,CAAc;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;gBAIE,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,gBAAgB;IAG7C,IAAI,QAAQ,WAGX;IAED,IAAI,KAAK,uBAER;IAED,IAAI,SAAS;;;QAEZ;IAEK,UAAU;IAUV,KAAK;IAwCL,IAAI;IAqBV;;OAEG;YACW,KAAK;IAqBnB;;OAEG;YACW,OAAO;CAwBtB"}
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../../../src/runtime/dev-server.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAI3C,OAAO,EAAwB,KAAK,eAAe,EAAiB,MAAM,YAAY,CAAC;AACvF,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AACH,qBAAa,SAAS;IAYlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAX3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2E;IAErG,OAAO,CAAC,OAAO,CAAC,CAAc;IAC9B,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,IAAI,CAAK;gBAIE,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,gBAAgB;IAG7C,IAAI,QAAQ,WAGX;IAED,IAAI,KAAK,uBAER;IAED,IAAI,SAAS;;;QAEZ;IAEK,UAAU;IAUV,KAAK;IAwCL,IAAI;IAqBV;;OAEG;YACW,KAAK;IAqBnB;;OAEG;YACW,OAAO;CAyBtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../../src/runtime/scheduler.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,MAAM,EAAkB,MAAM,cAAc,CAAC;AAQ3D,OAAO,EAAoB,KAAK,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAE5F,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AAEH,qBAAa,SAAS;IAQlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAG6B;gBAGlC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,EAAE,gBAAgB;IAGvC,KAAK;IAWL,IAAI;YAMI,KAAK;YA+DL,OAAO;YASP,YAAY;CAiB3B"}
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../../src/runtime/scheduler.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,MAAM,EAAkB,MAAM,cAAc,CAAC;AAQ3D,OAAO,EAAoB,KAAK,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAE5F,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;GAEG;AAEH,qBAAa,SAAS;IAQlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAG6B;gBAGlC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,gBAAgB,EAC3B,QAAQ,EAAE,gBAAgB;IAGvC,KAAK;IAWL,IAAI;YAMI,KAAK;YAgFL,OAAO;YASP,YAAY;CAiB3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/functions",
3
- "version": "0.3.8",
3
+ "version": "0.3.9-main.03b62b6",
4
4
  "description": "Functions SDK and runtime.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -19,17 +19,19 @@
19
19
  "src"
20
20
  ],
21
21
  "dependencies": {
22
+ "@preact/signals-core": "^1.5.0",
22
23
  "cron": "^3.1.6",
24
+ "deepsignal": "1.4.0-shallow.0",
23
25
  "express": "^4.17.1",
24
26
  "get-port-please": "^3.1.1",
25
- "@dxos/async": "0.3.8",
26
- "@dxos/context": "0.3.8",
27
- "@dxos/client": "0.3.8",
28
- "@dxos/echo-schema": "0.3.8",
29
- "@dxos/invariant": "0.3.8",
30
- "@dxos/log": "0.3.8",
31
- "@dxos/node-std": "0.3.8",
32
- "@dxos/util": "0.3.8"
27
+ "@dxos/async": "0.3.9-main.03b62b6",
28
+ "@dxos/client": "0.3.9-main.03b62b6",
29
+ "@dxos/invariant": "0.3.9-main.03b62b6",
30
+ "@dxos/log": "0.3.9-main.03b62b6",
31
+ "@dxos/context": "0.3.9-main.03b62b6",
32
+ "@dxos/util": "0.3.9-main.03b62b6",
33
+ "@dxos/node-std": "0.3.9-main.03b62b6",
34
+ "@dxos/echo-schema": "0.3.9-main.03b62b6"
33
35
  },
34
36
  "devDependencies": {
35
37
  "@types/express": "^4.17.17"
package/src/handler.ts CHANGED
@@ -12,6 +12,7 @@ export interface Response {
12
12
  // TODO(burdon): Limit access to individual space?
13
13
  export interface FunctionContext {
14
14
  client: Client;
15
+ dataDir?: string;
15
16
  }
16
17
 
17
18
  // TODO(burdon): Model after http request. Ref Lambda/OpenFaaS.
package/src/manifest.ts CHANGED
@@ -15,11 +15,13 @@ export type FunctionDef = {
15
15
  description?: string;
16
16
  };
17
17
 
18
+ // TODO(burdon): Query DSL.
18
19
  export type TriggerSubscription = {
19
20
  type: string;
20
21
  spaceKey: string;
21
22
  props?: Record<string, any>;
22
- nested?: string[];
23
+ deep?: boolean;
24
+ delay?: number;
23
25
  };
24
26
 
25
27
  // TODO(burdon): Generalize binding.
@@ -20,6 +20,7 @@ export type DevServerOptions = {
20
20
  directory: string;
21
21
  manifest: FunctionManifest;
22
22
  reload?: boolean;
23
+ dataDir?: string;
23
24
  };
24
25
 
25
26
  /**
@@ -79,13 +80,13 @@ export class DevServer {
79
80
  res.statusCode = await this._invoke(name, req.body);
80
81
  res.end();
81
82
  } catch (err: any) {
82
- log.error(err);
83
+ log.catch(err);
83
84
  res.statusCode = 500;
84
85
  res.end();
85
86
  }
86
87
  });
87
88
 
88
- this._port = await getPort({ port: 7200, portRange: [7200, 7299] });
89
+ this._port = await getPort({ host: 'localhost', port: 7200, portRange: [7200, 7299] });
89
90
  this._server = app.listen(this._port);
90
91
 
91
92
  try {
@@ -161,6 +162,7 @@ export class DevServer {
161
162
 
162
163
  const context: FunctionContext = {
163
164
  client: this._client,
165
+ dataDir: this._options.dataDir,
164
166
  };
165
167
 
166
168
  let statusCode = 200;
@@ -4,11 +4,11 @@
4
4
 
5
5
  import { CronJob } from 'cron';
6
6
 
7
- import { DeferredTask } from '@dxos/async';
7
+ import { debounce, DeferredTask } from '@dxos/async';
8
8
  import { type Client, type PublicKey } from '@dxos/client';
9
- import { type Space } from '@dxos/client/echo';
9
+ import { type Space, TextObject } from '@dxos/client/echo';
10
10
  import { Context } from '@dxos/context';
11
- import { Filter, createSubscription } from '@dxos/echo-schema';
11
+ import { Filter, createSubscription, type Query, subscribe } from '@dxos/echo-schema';
12
12
  import { invariant } from '@dxos/invariant';
13
13
  import { log } from '@dxos/log';
14
14
  import { ComplexMap } from '@dxos/util';
@@ -91,6 +91,8 @@ export class Scheduler {
91
91
  });
92
92
  });
93
93
 
94
+ // TODO(burdon): Standardize subscription handles.
95
+ const subscriptions: (() => void)[] = [];
94
96
  const subscription = createSubscription(({ added, updated }) => {
95
97
  for (const object of added) {
96
98
  objectIds.add(object.id);
@@ -101,16 +103,31 @@ export class Scheduler {
101
103
 
102
104
  task.schedule();
103
105
  });
106
+ subscriptions.push(() => subscription.unsubscribe());
104
107
 
105
- const { type, props } = trigger.subscription;
106
- const query = space.db.query(Filter.typename(type, props));
107
- const unsubscribe = query.subscribe(({ objects }) => {
108
+ const { type, props, deep, delay } = trigger.subscription;
109
+ const update = ({ objects }: Query) => {
108
110
  subscription.update(objects);
109
- }, true);
111
+
112
+ // TODO(burdon): Hack to monitor changes to Document's text object.
113
+ if (deep) {
114
+ log.info('update', { type, deep, objects: objects.length });
115
+ for (const object of objects) {
116
+ const content = object.content;
117
+ if (content instanceof TextObject) {
118
+ subscriptions.push(content[subscribe](debounce(() => subscription.update([object]), 1_000)));
119
+ }
120
+ }
121
+ }
122
+ };
123
+
124
+ // TODO(burdon): [Bug]: all callbacks are fired on the first mutation.
125
+ // TODO(burdon): [Bug]: not updated when document is deleted (either top or hierarchically).
126
+ const query = space.db.query(Filter.typename(type, props));
127
+ subscriptions.push(query.subscribe(delay ? debounce(update, delay * 1_000) : update));
110
128
 
111
129
  ctx.onDispose(() => {
112
- subscription.unsubscribe();
113
- unsubscribe();
130
+ subscriptions.forEach((unsubscribe) => unsubscribe());
114
131
  });
115
132
  }
116
133
  }