@synopackageland/app 0.15.2 → 0.17.0

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/app.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type Context } from './context.js';
3
3
  /** The channel protocol version this SDK speaks. */
4
4
  export declare const VERSION = 1;
5
5
  /** The SDK's name in hello: node/<package version>. */
6
- export declare const SDK = "node/0.15.2";
6
+ export declare const SDK = "node/0.17.0";
7
7
  /**
8
8
  * A method's implementation. params is the request's params as the browser
9
9
  * sent them, not checked against P. The result is the data the browser gets,
package/dist/app.js CHANGED
@@ -5,7 +5,7 @@ import { browserError } from './errors.js';
5
5
  /** The channel protocol version this SDK speaks. */
6
6
  export const VERSION = 1;
7
7
  /** The SDK's name in hello: node/<package version>. */
8
- export const SDK = 'node/0.15.2';
8
+ export const SDK = 'node/0.17.0';
9
9
  const methodName = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
10
10
  const internal = { code: 'internal', message: 'internal error' };
11
11
  /** The App: its methods, served on one channel per call. */
@@ -33,16 +33,39 @@ export class App {
33
33
  const hello = await channel.recv();
34
34
  if (hello?.t !== 'hello' || !Array.isArray(hello.versions) || !hello.versions.includes(VERSION))
35
35
  return;
36
- await channel.send({ t: 'hello', version: VERSION, sdk: SDK, methods: this.describe().methods });
37
- const call = await channel.recv();
38
- if (call?.t !== 'call')
36
+ const batch = hello.batch === 1;
37
+ await channel.send({ t: 'hello', version: VERSION, sdk: SDK, methods: this.describe().methods, ...(batch ? { batch: 1 } : {}) });
38
+ const request = await channel.recv();
39
+ if (!request)
39
40
  return;
40
- const id = call.id;
41
- const state = new Call(channel, call.ctx);
42
- void follow(channel, id, state);
43
- const result = await this.#answer(id, call, state.ctx);
44
- state.close();
45
- await channel.write(result);
41
+ if (batch && (request.t !== 'batch' || request.v !== 1 || !Array.isArray(request.calls) || request.calls.length < 1 || request.calls.length > 8))
42
+ return;
43
+ const calls = batch ? request.calls : [request];
44
+ const batchStartedAt = Date.now();
45
+ const states = new Map();
46
+ void (async () => {
47
+ for (let f = await channel.recv(); f; f = await channel.recv()) {
48
+ if (f.t === 'svc_result')
49
+ for (const state of states.values())
50
+ state.answer(f);
51
+ else if (f.t === 'cancel')
52
+ for (const state of states.values())
53
+ state.cancel(f.reason === 'deadline' ? 'deadline' : 'canceled');
54
+ }
55
+ for (const state of states.values())
56
+ state.close();
57
+ })();
58
+ for (let i = 0; i < calls.length; i++) {
59
+ const call = calls[i];
60
+ if (call.t !== 'call')
61
+ return;
62
+ const id = call.id;
63
+ const state = new Call(channel, call.ctx, batch ? i : undefined, batch ? batchStartedAt : undefined);
64
+ states.set(id, state);
65
+ const result = await this.#answer(id, call, state.ctx);
66
+ state.close();
67
+ await channel.write(result);
68
+ }
46
69
  }
47
70
  /** What --describe prints: the App's methods, for the package build. */
48
71
  describe() {
@@ -109,13 +132,3 @@ function takeStdout() {
109
132
  stdout.on('error', () => { });
110
133
  return new Writable({ write: (chunk, encoding, done) => write(chunk, encoding, done) });
111
134
  }
112
- // Reads the frames that arrive while the handler runs.
113
- async function follow(channel, id, call) {
114
- for (let f = await channel.recv(); f; f = await channel.recv()) {
115
- if (f.t === 'svc_result')
116
- call.answer(f);
117
- else if (f.t === 'cancel' && f.id === id && (f.reason === 'deadline' || f.reason === 'canceled'))
118
- call.cancel(f.reason);
119
- }
120
- call.close();
121
- }
package/dist/context.d.ts CHANGED
@@ -41,7 +41,7 @@ export interface Context {
41
41
  export declare class Call {
42
42
  #private;
43
43
  readonly ctx: Context;
44
- constructor(channel: Channel, c: CallContext);
44
+ constructor(channel: Channel, c: CallContext, batchId?: number, startedAt?: number);
45
45
  answer(frame: Received): void;
46
46
  cancel(reason: CancelReason): void;
47
47
  close(): void;
package/dist/context.js CHANGED
@@ -7,7 +7,9 @@ export class Call {
7
7
  #abort = new AbortController();
8
8
  #pending = new Map();
9
9
  #next = 0;
10
- constructor(channel, c) {
10
+ #batchId;
11
+ constructor(channel, c, batchId, startedAt) {
12
+ this.#batchId = batchId;
11
13
  this.#channel = channel;
12
14
  const signal = this.#abort.signal;
13
15
  this.ctx = {
@@ -15,7 +17,7 @@ export class Call {
15
17
  admin: c.admin,
16
18
  locale: c.locale,
17
19
  requestId: c.request_id,
18
- deadline: Date.now() + c.deadline_ms,
20
+ deadline: (startedAt ?? Date.now()) + c.deadline_ms,
19
21
  deadlineMs: c.deadline_ms,
20
22
  signal,
21
23
  get cancelled() {
@@ -29,10 +31,10 @@ export class Call {
29
31
  return Promise.reject(new TypeError(`invalid service name: ${op}`));
30
32
  if (this.#abort.signal.aborted)
31
33
  return Promise.reject(closed(op));
32
- const id = `s${++this.#next}`;
34
+ const id = `${this.#batchId === undefined ? "" : `b${this.#batchId}-`}s${++this.#next}`;
33
35
  return new Promise((resolve, reject) => {
34
36
  // send throws for params JSON cannot encode; the answer comes later.
35
- void this.#channel.send({ t: 'svc', id, op, params });
37
+ void this.#channel.send({ t: 'svc', id, op, params, ...(this.#batchId === undefined ? {} : { batch_id: this.#batchId }) });
36
38
  this.#pending.set(id, { op, resolve, reject });
37
39
  });
38
40
  }
package/dist/frames.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type Frame = RuntimeHello | AppHello | Call | Cancel | Svc | SvcResult | Result;
1
+ export type Frame = RuntimeHello | AppHello | Call | Cancel | Svc | SvcResult | Result | Batch;
2
2
  export type Method = string;
3
3
  export type Id = string;
4
4
  export interface RuntimeHello {
@@ -7,12 +7,14 @@ export interface RuntimeHello {
7
7
  * @minItems 1
8
8
  */
9
9
  versions: [number, ...number[]];
10
+ batch?: 1;
10
11
  }
11
12
  export interface AppHello {
12
13
  t: "hello";
13
14
  version: number;
14
15
  sdk: string;
15
16
  methods: Method[];
17
+ batch?: 1;
16
18
  }
17
19
  export interface Call {
18
20
  t: "call";
@@ -38,6 +40,7 @@ export interface Svc {
38
40
  id: Id;
39
41
  op: string;
40
42
  params: unknown;
43
+ batch_id?: number;
41
44
  }
42
45
  export interface SvcResult {
43
46
  t: "svc_result";
@@ -61,3 +64,12 @@ export interface ResultError {
61
64
  code: string;
62
65
  message: string;
63
66
  }
67
+ export interface Batch {
68
+ t: "batch";
69
+ v: 1;
70
+ /**
71
+ * @minItems 1
72
+ * @maxItems 8
73
+ */
74
+ calls: [Call] | [Call, Call] | [Call, Call, Call] | [Call, Call, Call, Call] | [Call, Call, Call, Call, Call] | [Call, Call, Call, Call, Call, Call] | [Call, Call, Call, Call, Call, Call, Call] | [Call, Call, Call, Call, Call, Call, Call, Call];
75
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/app",
3
- "version": "0.15.2",
3
+ "version": "0.17.0",
4
4
  "description": "Node SDK for Synology Package Land Apps: the backend methods behind the synopkgland Go runtime (channel spec/bridge/v1)",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/synopackageland/syno-pkg-land-sdk/tree/main/node#readme",