@superdoc/sdk 2.26.0 → 2.27.0-next.4

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/index.cjs CHANGED
@@ -152,6 +152,9 @@ class SuperDocClient {
152
152
  async connect() {
153
153
  await this.runtime.connect();
154
154
  }
155
+ subscribe(options = {}) {
156
+ return this.runtime.subscribe(options);
157
+ }
155
158
  /**
156
159
  * Open a document and return a bound document handle.
157
160
  *
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type BoundDocApi, type DocCloseBoundParams, type DocCloseResult, type DocFormatApplyBoundParams, type DocFormatApplyResult, type DocOpenParams as GeneratedDocOpenParams, type DocOpenResult, type DocSaveBoundParams, type DocSaveResult } from './generated/client.js';
2
2
  import { SuperDocRuntime, type SuperDocClientOptions, type DocOpenOptions, type InvokeOptions, type OperationSpec, type ReplaceFileOptions, type ReplaceFileResult, type ReplaceFileSource, type RuntimeInvoker } from './runtime/process.js';
3
+ import type { HostNotifications, SubscribeOptions } from './runtime/notifications.js';
3
4
  /**
4
5
  * Wraps a raw runtime and injects a fixed sessionId into every invoke call.
5
6
  * Implements RuntimeInvoker so generated code can use it directly.
@@ -73,6 +74,7 @@ export declare class SuperDocClient {
73
74
  private readonly pendingSessionIds;
74
75
  constructor(options?: SuperDocClientOptions);
75
76
  connect(): Promise<void>;
77
+ subscribe(options?: SubscribeOptions): HostNotifications;
76
78
  /**
77
79
  * Open a document and return a bound document handle.
78
80
  *
@@ -104,6 +106,7 @@ export type { AgentToolkit, CreateAgentToolkitInput } from './tools.js';
104
106
  export { defineAction } from './actions/define.js';
105
107
  export type { ActionSpec, ActionStep, DefineActionInput, JSONSchemaObject } from './actions/define.js';
106
108
  export { SuperDocCliError } from './runtime/errors.js';
109
+ export type { HostNotification, HostNotifications, SubscribeOptions } from './runtime/notifications.js';
107
110
  export { runTrackedComparisonReview } from './tracked-review-route.js';
108
111
  export type { TrackedReviewSession, TrackedComparisonReviewInput } from './tracked-review-route.js';
109
112
  export type { CollaborationAuth, DocOpenOptions, InvokeOptions, OperationSpec, OperationParamSpec, RuntimeInvoker, SuperDocClientOptions, } from './runtime/process.js';
package/dist/index.js CHANGED
@@ -140,6 +140,9 @@ export class SuperDocClient {
140
140
  async connect() {
141
141
  await this.runtime.connect();
142
142
  }
143
+ subscribe(options = {}) {
144
+ return this.runtime.subscribe(options);
145
+ }
143
146
  /**
144
147
  * Open a document and return a bound document handle.
145
148
  *
@@ -4,6 +4,7 @@ var node_child_process = require('node:child_process');
4
4
  var transportCommon = require('./transport-common.cjs');
5
5
  var errors = require('./errors.cjs');
6
6
  var stateDirectory = require('./state-directory.cjs');
7
+ var notifications = require('./notifications.cjs');
7
8
  var documentEvidence = require('./document-evidence.cjs');
8
9
  var contract = require('../generated/contract.cjs');
9
10
  var sdkPolicy = require('./sdk-policy.cjs');
@@ -11,6 +12,7 @@ var sdkPolicy = require('./sdk-policy.cjs');
11
12
  const HOST_PROTOCOL_VERSION = sdkPolicy.SDK_POLICY.protocolVersion;
12
13
  const CLI_HOST_REQUIRED_FEATURES = sdkPolicy.SDK_POLICY.cliRequiredFeatures;
13
14
  const COLLABORATION_AUTH_PER_OPEN_FEATURE = sdkPolicy.SDK_POLICY.features.collaborationAuthPerOpen;
15
+ const IDENTITY_CLAIM_PER_OPEN_FEATURE = sdkPolicy.SDK_POLICY.features.identityClaimPerOpen;
14
16
  const HOST_REQUEST_TIMEOUT_FEATURE = sdkPolicy.SDK_POLICY.features.requestTimeout;
15
17
  const CHANGE_MODES = ['direct', 'tracked'];
16
18
  const FORWARD_HOST_STDERR = typeof process !== 'undefined' && typeof process.env?.SUPERDOC_DEBUG_TEXT_REWRITE === 'string'
@@ -125,6 +127,7 @@ class HostTransport {
125
127
  activeCalls = new Set();
126
128
  hostFeatures = new Set();
127
129
  cliLocalSessions = new Set();
130
+ notifications = new notifications.HostNotificationHub();
128
131
  constructor(options) {
129
132
  this.stateDirectory = new stateDirectory.SdkStateDirectory(options.stateDir);
130
133
  this.hostBin = options.hostBin;
@@ -146,6 +149,9 @@ class HostTransport {
146
149
  async connect() {
147
150
  await this.runWhileActive(() => this.ensureConnected());
148
151
  }
152
+ subscribe(options = {}) {
153
+ return this.notifications.subscribe(options);
154
+ }
149
155
  supportsDocumentFacts(sessionId) {
150
156
  return this.usesCliDocumentFacts(sessionId);
151
157
  }
@@ -171,14 +177,18 @@ class HostTransport {
171
177
  async dispose() {
172
178
  if (this.disposePromise)
173
179
  return this.disposePromise;
174
- if (this.terminalDisposalError)
180
+ if (this.terminalDisposalError) {
181
+ this.notifications.close();
175
182
  throw this.terminalDisposalError;
183
+ }
176
184
  if (!this.child &&
177
185
  !this.connecting &&
178
186
  this.activeCalls.size === 0 &&
179
187
  !this.stateDirectory.ownsRoot &&
180
- this.stoppedChildren.size === 0)
188
+ this.stoppedChildren.size === 0) {
189
+ this.notifications.close();
181
190
  return;
191
+ }
182
192
  this.stopping = true;
183
193
  const disposal = this.disposeAfterActiveCalls();
184
194
  this.disposePromise = disposal;
@@ -192,6 +202,14 @@ class HostTransport {
192
202
  }
193
203
  }
194
204
  async disposeAfterActiveCalls() {
205
+ try {
206
+ await this.shutdownAfterActiveCalls();
207
+ }
208
+ finally {
209
+ this.notifications.close();
210
+ }
211
+ }
212
+ async shutdownAfterActiveCalls() {
195
213
  await Promise.allSettled(Array.from(this.activeCalls));
196
214
  if (this.terminalDisposalError)
197
215
  throw this.terminalDisposalError;
@@ -244,6 +262,12 @@ class HostTransport {
244
262
  });
245
263
  }
246
264
  }
265
+ const identityClaim = options.identityClaim === undefined ? undefined : transportCommon.normalizeIdentityClaim(options.identityClaim);
266
+ if (identityClaim !== undefined && operation.operationId !== 'doc.open') {
267
+ throw new errors.SuperDocCliError('identityClaim is supported only for doc.open requests.', {
268
+ code: 'INVALID_ARGUMENT',
269
+ });
270
+ }
247
271
  await this.ensureConnected();
248
272
  if (collaborationAuth !== undefined && !this.hostFeatures.has(COLLABORATION_AUTH_PER_OPEN_FEATURE)) {
249
273
  throw new errors.SuperDocCliError('The connected CLI host does not support per-open collaboration authentication.', {
@@ -251,6 +275,12 @@ class HostTransport {
251
275
  details: { feature: COLLABORATION_AUTH_PER_OPEN_FEATURE },
252
276
  });
253
277
  }
278
+ if (identityClaim !== undefined && !this.hostFeatures.has(IDENTITY_CLAIM_PER_OPEN_FEATURE)) {
279
+ throw new errors.SuperDocCliError('The connected CLI host does not support a per-open identity claim.', {
280
+ code: 'CAPABILITY_UNSUPPORTED',
281
+ details: { feature: IDENTITY_CLAIM_PER_OPEN_FEATURE },
282
+ });
283
+ }
254
284
  const sessionId = typeof params.sessionId === 'string' ? params.sessionId : undefined;
255
285
  const supportsRequestTimeoutMetadata = this.hostFeatures.has(HOST_REQUEST_TIMEOUT_FEATURE);
256
286
  const cliHostRequestTimeoutMs = this.requestTimeoutMs ?? HOST_DEFAULT_REQUEST_TIMEOUT_MS;
@@ -288,6 +318,7 @@ class HostTransport {
288
318
  }),
289
319
  ...(includeRevision ? { includeRevision: true } : {}),
290
320
  ...(collaborationAuth === undefined ? {} : { collaborationAuth }),
321
+ ...(identityClaim === undefined ? {} : { identityClaim }),
291
322
  }, watchdogTimeout, {
292
323
  requestTimeoutMs: supportsRequestTimeoutMetadata ? options.timeoutMs : undefined,
293
324
  });
@@ -511,8 +542,8 @@ class HostTransport {
511
542
  if (record.jsonrpc !== '2.0') {
512
543
  return;
513
544
  }
514
- // Notification (no id) — reserved for future eventing
515
545
  if ('method' in record && !('id' in record)) {
546
+ this.notifications.publish(record);
516
547
  return;
517
548
  }
518
549
  const idRaw = record.id;
@@ -616,6 +647,7 @@ class HostTransport {
616
647
  }
617
648
  handleDisconnect(error) {
618
649
  this.cleanupProcess(error);
650
+ this.notifications.disconnect(error);
619
651
  }
620
652
  cleanupProcess(error) {
621
653
  this.hostFeatures.clear();
@@ -644,6 +676,7 @@ class HostTransport {
644
676
 
645
677
  exports.COLLABORATION_AUTH_PER_OPEN_FEATURE = COLLABORATION_AUTH_PER_OPEN_FEATURE;
646
678
  exports.HostTransport = HostTransport;
679
+ exports.IDENTITY_CLAIM_PER_OPEN_FEATURE = IDENTITY_CLAIM_PER_OPEN_FEATURE;
647
680
  exports.buildHostSpawnArgs = buildHostSpawnArgs;
648
681
  exports.mapCliInvocationResult = mapCliInvocationResult;
649
682
  exports.mapDocumentReplaceFileResult = mapDocumentReplaceFileResult;
@@ -1,5 +1,7 @@
1
1
  import { type OperationSpec, type SuperDocClientOptions, type TransportInvokeOptions } from './transport-common.js';
2
+ import { type HostNotifications, type SubscribeOptions } from './notifications.js';
2
3
  export declare const COLLABORATION_AUTH_PER_OPEN_FEATURE: string;
4
+ export declare const IDENTITY_CLAIM_PER_OPEN_FEATURE: string;
3
5
  export declare function mapCliInvocationResult(operation: OperationSpec, value: unknown): unknown;
4
6
  export declare function mapDocumentReplaceFileResult(response: unknown, sessionId: string): Record<string, unknown>;
5
7
  /**
@@ -53,15 +55,18 @@ export declare class HostTransport {
53
55
  private readonly activeCalls;
54
56
  private readonly hostFeatures;
55
57
  private readonly cliLocalSessions;
58
+ private readonly notifications;
56
59
  constructor(options: {
57
60
  hostBin: string;
58
61
  } & SuperDocClientOptions);
59
62
  connect(): Promise<void>;
63
+ subscribe(options?: SubscribeOptions): HostNotifications;
60
64
  supportsDocumentFacts(sessionId: string): boolean;
61
65
  usesCliDocumentFacts(sessionId: string): boolean;
62
66
  readDocumentRevision(sessionId: string): Promise<string | undefined>;
63
67
  dispose(): Promise<void>;
64
68
  private disposeAfterActiveCalls;
69
+ private shutdownAfterActiveCalls;
65
70
  invoke<TData = unknown>(operation: OperationSpec, params?: Record<string, unknown>, options?: TransportInvokeOptions): Promise<TData>;
66
71
  replaceFile(sessionId: string, path: string, options?: TransportInvokeOptions): Promise<Record<string, unknown>>;
67
72
  private invokeWhileActive;
@@ -1,13 +1,15 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { buildOperationArgv, captureMutationPlanAuthor, buildStructuredReadInput, normalizeCollaborationAuth, resolveInvocation, } from './transport-common.js';
2
+ import { buildOperationArgv, captureMutationPlanAuthor, buildStructuredReadInput, normalizeCollaborationAuth, normalizeIdentityClaim, resolveInvocation, } from './transport-common.js';
3
3
  import { SuperDocCliError } from './errors.js';
4
4
  import { SdkStateDirectory } from './state-directory.js';
5
+ import { HostNotificationHub } from './notifications.js';
5
6
  import { registerMutationRevision } from './document-evidence.js';
6
7
  import { CONTRACT } from '../generated/contract.js';
7
8
  import { SDK_POLICY } from './sdk-policy.js';
8
9
  const HOST_PROTOCOL_VERSION = SDK_POLICY.protocolVersion;
9
10
  const CLI_HOST_REQUIRED_FEATURES = SDK_POLICY.cliRequiredFeatures;
10
11
  export const COLLABORATION_AUTH_PER_OPEN_FEATURE = SDK_POLICY.features.collaborationAuthPerOpen;
12
+ export const IDENTITY_CLAIM_PER_OPEN_FEATURE = SDK_POLICY.features.identityClaimPerOpen;
11
13
  const HOST_REQUEST_TIMEOUT_FEATURE = SDK_POLICY.features.requestTimeout;
12
14
  const CHANGE_MODES = ['direct', 'tracked'];
13
15
  const FORWARD_HOST_STDERR = typeof process !== 'undefined' && typeof process.env?.SUPERDOC_DEBUG_TEXT_REWRITE === 'string'
@@ -122,6 +124,7 @@ export class HostTransport {
122
124
  activeCalls = new Set();
123
125
  hostFeatures = new Set();
124
126
  cliLocalSessions = new Set();
127
+ notifications = new HostNotificationHub();
125
128
  constructor(options) {
126
129
  this.stateDirectory = new SdkStateDirectory(options.stateDir);
127
130
  this.hostBin = options.hostBin;
@@ -143,6 +146,9 @@ export class HostTransport {
143
146
  async connect() {
144
147
  await this.runWhileActive(() => this.ensureConnected());
145
148
  }
149
+ subscribe(options = {}) {
150
+ return this.notifications.subscribe(options);
151
+ }
146
152
  supportsDocumentFacts(sessionId) {
147
153
  return this.usesCliDocumentFacts(sessionId);
148
154
  }
@@ -168,14 +174,18 @@ export class HostTransport {
168
174
  async dispose() {
169
175
  if (this.disposePromise)
170
176
  return this.disposePromise;
171
- if (this.terminalDisposalError)
177
+ if (this.terminalDisposalError) {
178
+ this.notifications.close();
172
179
  throw this.terminalDisposalError;
180
+ }
173
181
  if (!this.child &&
174
182
  !this.connecting &&
175
183
  this.activeCalls.size === 0 &&
176
184
  !this.stateDirectory.ownsRoot &&
177
- this.stoppedChildren.size === 0)
185
+ this.stoppedChildren.size === 0) {
186
+ this.notifications.close();
178
187
  return;
188
+ }
179
189
  this.stopping = true;
180
190
  const disposal = this.disposeAfterActiveCalls();
181
191
  this.disposePromise = disposal;
@@ -189,6 +199,14 @@ export class HostTransport {
189
199
  }
190
200
  }
191
201
  async disposeAfterActiveCalls() {
202
+ try {
203
+ await this.shutdownAfterActiveCalls();
204
+ }
205
+ finally {
206
+ this.notifications.close();
207
+ }
208
+ }
209
+ async shutdownAfterActiveCalls() {
192
210
  await Promise.allSettled(Array.from(this.activeCalls));
193
211
  if (this.terminalDisposalError)
194
212
  throw this.terminalDisposalError;
@@ -241,6 +259,12 @@ export class HostTransport {
241
259
  });
242
260
  }
243
261
  }
262
+ const identityClaim = options.identityClaim === undefined ? undefined : normalizeIdentityClaim(options.identityClaim);
263
+ if (identityClaim !== undefined && operation.operationId !== 'doc.open') {
264
+ throw new SuperDocCliError('identityClaim is supported only for doc.open requests.', {
265
+ code: 'INVALID_ARGUMENT',
266
+ });
267
+ }
244
268
  await this.ensureConnected();
245
269
  if (collaborationAuth !== undefined && !this.hostFeatures.has(COLLABORATION_AUTH_PER_OPEN_FEATURE)) {
246
270
  throw new SuperDocCliError('The connected CLI host does not support per-open collaboration authentication.', {
@@ -248,6 +272,12 @@ export class HostTransport {
248
272
  details: { feature: COLLABORATION_AUTH_PER_OPEN_FEATURE },
249
273
  });
250
274
  }
275
+ if (identityClaim !== undefined && !this.hostFeatures.has(IDENTITY_CLAIM_PER_OPEN_FEATURE)) {
276
+ throw new SuperDocCliError('The connected CLI host does not support a per-open identity claim.', {
277
+ code: 'CAPABILITY_UNSUPPORTED',
278
+ details: { feature: IDENTITY_CLAIM_PER_OPEN_FEATURE },
279
+ });
280
+ }
251
281
  const sessionId = typeof params.sessionId === 'string' ? params.sessionId : undefined;
252
282
  const supportsRequestTimeoutMetadata = this.hostFeatures.has(HOST_REQUEST_TIMEOUT_FEATURE);
253
283
  const cliHostRequestTimeoutMs = this.requestTimeoutMs ?? HOST_DEFAULT_REQUEST_TIMEOUT_MS;
@@ -285,6 +315,7 @@ export class HostTransport {
285
315
  }),
286
316
  ...(includeRevision ? { includeRevision: true } : {}),
287
317
  ...(collaborationAuth === undefined ? {} : { collaborationAuth }),
318
+ ...(identityClaim === undefined ? {} : { identityClaim }),
288
319
  }, watchdogTimeout, {
289
320
  requestTimeoutMs: supportsRequestTimeoutMetadata ? options.timeoutMs : undefined,
290
321
  });
@@ -508,8 +539,8 @@ export class HostTransport {
508
539
  if (record.jsonrpc !== '2.0') {
509
540
  return;
510
541
  }
511
- // Notification (no id) — reserved for future eventing
512
542
  if ('method' in record && !('id' in record)) {
543
+ this.notifications.publish(record);
513
544
  return;
514
545
  }
515
546
  const idRaw = record.id;
@@ -613,6 +644,7 @@ export class HostTransport {
613
644
  }
614
645
  handleDisconnect(error) {
615
646
  this.cleanupProcess(error);
647
+ this.notifications.disconnect(error);
616
648
  }
617
649
  cleanupProcess(error) {
618
650
  this.hostFeatures.clear();
@@ -0,0 +1,124 @@
1
+ 'use strict';
2
+
3
+ var errors = require('./errors.cjs');
4
+
5
+ const NOTIFICATIONS_LAGGED = 'NOTIFICATIONS_LAGGED';
6
+ const DEFAULT_NOTIFICATION_CAPACITY = 64;
7
+ const LAGGED_MESSAGE = 'resynchronize document state before using notification-derived caches';
8
+ const RESYNC_HINT = 'Call changes.list with the cursor of the last change record you processed.';
9
+ class HostNotifications {
10
+ capacity;
11
+ detach;
12
+ queue = [];
13
+ waiters = [];
14
+ skipped = 0;
15
+ failure = null;
16
+ closed = false;
17
+ constructor(capacity, detach) {
18
+ this.capacity = capacity;
19
+ this.detach = detach;
20
+ }
21
+ next() {
22
+ if (this.skipped > 0) {
23
+ const skipped = this.skipped;
24
+ this.skipped = 0;
25
+ return Promise.reject(new errors.SuperDocCliError(LAGGED_MESSAGE, {
26
+ code: NOTIFICATIONS_LAGGED,
27
+ details: { skipped, resync: RESYNC_HINT },
28
+ }));
29
+ }
30
+ const notification = this.queue.shift();
31
+ if (notification)
32
+ return Promise.resolve({ done: false, value: notification });
33
+ if (this.failure) {
34
+ const failure = this.failure;
35
+ this.failure = null;
36
+ return Promise.reject(failure);
37
+ }
38
+ if (this.closed)
39
+ return Promise.resolve({ done: true, value: undefined });
40
+ return new Promise((resolve, reject) => this.waiters.push({ resolve, reject }));
41
+ }
42
+ return() {
43
+ this.close();
44
+ return Promise.resolve({ done: true, value: undefined });
45
+ }
46
+ close() {
47
+ if (this.closed)
48
+ return;
49
+ this.closed = true;
50
+ this.queue.length = 0;
51
+ this.skipped = 0;
52
+ this.failure = null;
53
+ this.detach(this);
54
+ for (const waiter of this.waiters.splice(0))
55
+ waiter.resolve({ done: true, value: undefined });
56
+ }
57
+ [Symbol.asyncIterator]() {
58
+ return this;
59
+ }
60
+ get isClosed() {
61
+ return this.closed;
62
+ }
63
+ deliver(notification) {
64
+ if (this.closed)
65
+ return;
66
+ const waiter = this.waiters.shift();
67
+ if (waiter) {
68
+ waiter.resolve({ done: false, value: notification });
69
+ return;
70
+ }
71
+ if (this.queue.length >= this.capacity) {
72
+ this.queue.shift();
73
+ this.skipped += 1;
74
+ }
75
+ this.queue.push(notification);
76
+ }
77
+ fail(error) {
78
+ if (this.closed)
79
+ return;
80
+ const waiter = this.waiters.shift();
81
+ if (waiter) {
82
+ waiter.reject(error);
83
+ return;
84
+ }
85
+ this.failure = error;
86
+ }
87
+ }
88
+ class HostNotificationHub {
89
+ subscriptions = new Set();
90
+ subscribe(options = {}) {
91
+ const capacity = options.capacity ?? DEFAULT_NOTIFICATION_CAPACITY;
92
+ if (!Number.isSafeInteger(capacity) || capacity < 1) {
93
+ throw new errors.SuperDocCliError('Notification capacity must be a positive integer.', {
94
+ code: 'INVALID_ARGUMENT',
95
+ details: { capacity },
96
+ });
97
+ }
98
+ const subscription = new HostNotifications(capacity, (closed) => this.subscriptions.delete(closed));
99
+ this.subscriptions.add(subscription);
100
+ return subscription;
101
+ }
102
+ publish(frame) {
103
+ if (this.subscriptions.size === 0 || typeof frame.method !== 'string')
104
+ return;
105
+ const notification = frame.params === undefined
106
+ ? { jsonrpc: '2.0', method: frame.method }
107
+ : { jsonrpc: '2.0', method: frame.method, params: frame.params };
108
+ for (const subscription of this.subscriptions)
109
+ subscription.deliver(notification);
110
+ }
111
+ disconnect(error) {
112
+ for (const subscription of this.subscriptions)
113
+ subscription.fail(error);
114
+ }
115
+ close() {
116
+ for (const subscription of [...this.subscriptions])
117
+ subscription.close();
118
+ }
119
+ }
120
+
121
+ exports.DEFAULT_NOTIFICATION_CAPACITY = DEFAULT_NOTIFICATION_CAPACITY;
122
+ exports.HostNotificationHub = HostNotificationHub;
123
+ exports.HostNotifications = HostNotifications;
124
+ exports.NOTIFICATIONS_LAGGED = NOTIFICATIONS_LAGGED;
@@ -0,0 +1,35 @@
1
+ import { SuperDocCliError } from './errors.js';
2
+ export declare const NOTIFICATIONS_LAGGED = "NOTIFICATIONS_LAGGED";
3
+ export declare const DEFAULT_NOTIFICATION_CAPACITY = 64;
4
+ export interface HostNotification {
5
+ readonly jsonrpc: '2.0';
6
+ readonly method: string;
7
+ readonly params?: unknown;
8
+ }
9
+ export interface SubscribeOptions {
10
+ readonly capacity?: number;
11
+ }
12
+ export declare class HostNotifications implements AsyncIterableIterator<HostNotification> {
13
+ private readonly capacity;
14
+ private readonly detach;
15
+ private readonly queue;
16
+ private readonly waiters;
17
+ private skipped;
18
+ private failure;
19
+ private closed;
20
+ constructor(capacity: number, detach: (subscription: HostNotifications) => void);
21
+ next(): Promise<IteratorResult<HostNotification, undefined>>;
22
+ return(): Promise<IteratorResult<HostNotification, undefined>>;
23
+ close(): void;
24
+ [Symbol.asyncIterator](): this;
25
+ get isClosed(): boolean;
26
+ deliver(notification: HostNotification): void;
27
+ fail(error: SuperDocCliError): void;
28
+ }
29
+ export declare class HostNotificationHub {
30
+ private readonly subscriptions;
31
+ subscribe(options?: SubscribeOptions): HostNotifications;
32
+ publish(frame: Record<string, unknown>): void;
33
+ disconnect(error: SuperDocCliError): void;
34
+ close(): void;
35
+ }
@@ -0,0 +1,116 @@
1
+ import { SuperDocCliError } from './errors.js';
2
+ export const NOTIFICATIONS_LAGGED = 'NOTIFICATIONS_LAGGED';
3
+ export const DEFAULT_NOTIFICATION_CAPACITY = 64;
4
+ const LAGGED_MESSAGE = 'resynchronize document state before using notification-derived caches';
5
+ const RESYNC_HINT = 'Call changes.list with the cursor of the last change record you processed.';
6
+ export class HostNotifications {
7
+ capacity;
8
+ detach;
9
+ queue = [];
10
+ waiters = [];
11
+ skipped = 0;
12
+ failure = null;
13
+ closed = false;
14
+ constructor(capacity, detach) {
15
+ this.capacity = capacity;
16
+ this.detach = detach;
17
+ }
18
+ next() {
19
+ if (this.skipped > 0) {
20
+ const skipped = this.skipped;
21
+ this.skipped = 0;
22
+ return Promise.reject(new SuperDocCliError(LAGGED_MESSAGE, {
23
+ code: NOTIFICATIONS_LAGGED,
24
+ details: { skipped, resync: RESYNC_HINT },
25
+ }));
26
+ }
27
+ const notification = this.queue.shift();
28
+ if (notification)
29
+ return Promise.resolve({ done: false, value: notification });
30
+ if (this.failure) {
31
+ const failure = this.failure;
32
+ this.failure = null;
33
+ return Promise.reject(failure);
34
+ }
35
+ if (this.closed)
36
+ return Promise.resolve({ done: true, value: undefined });
37
+ return new Promise((resolve, reject) => this.waiters.push({ resolve, reject }));
38
+ }
39
+ return() {
40
+ this.close();
41
+ return Promise.resolve({ done: true, value: undefined });
42
+ }
43
+ close() {
44
+ if (this.closed)
45
+ return;
46
+ this.closed = true;
47
+ this.queue.length = 0;
48
+ this.skipped = 0;
49
+ this.failure = null;
50
+ this.detach(this);
51
+ for (const waiter of this.waiters.splice(0))
52
+ waiter.resolve({ done: true, value: undefined });
53
+ }
54
+ [Symbol.asyncIterator]() {
55
+ return this;
56
+ }
57
+ get isClosed() {
58
+ return this.closed;
59
+ }
60
+ deliver(notification) {
61
+ if (this.closed)
62
+ return;
63
+ const waiter = this.waiters.shift();
64
+ if (waiter) {
65
+ waiter.resolve({ done: false, value: notification });
66
+ return;
67
+ }
68
+ if (this.queue.length >= this.capacity) {
69
+ this.queue.shift();
70
+ this.skipped += 1;
71
+ }
72
+ this.queue.push(notification);
73
+ }
74
+ fail(error) {
75
+ if (this.closed)
76
+ return;
77
+ const waiter = this.waiters.shift();
78
+ if (waiter) {
79
+ waiter.reject(error);
80
+ return;
81
+ }
82
+ this.failure = error;
83
+ }
84
+ }
85
+ export class HostNotificationHub {
86
+ subscriptions = new Set();
87
+ subscribe(options = {}) {
88
+ const capacity = options.capacity ?? DEFAULT_NOTIFICATION_CAPACITY;
89
+ if (!Number.isSafeInteger(capacity) || capacity < 1) {
90
+ throw new SuperDocCliError('Notification capacity must be a positive integer.', {
91
+ code: 'INVALID_ARGUMENT',
92
+ details: { capacity },
93
+ });
94
+ }
95
+ const subscription = new HostNotifications(capacity, (closed) => this.subscriptions.delete(closed));
96
+ this.subscriptions.add(subscription);
97
+ return subscription;
98
+ }
99
+ publish(frame) {
100
+ if (this.subscriptions.size === 0 || typeof frame.method !== 'string')
101
+ return;
102
+ const notification = frame.params === undefined
103
+ ? { jsonrpc: '2.0', method: frame.method }
104
+ : { jsonrpc: '2.0', method: frame.method, params: frame.params };
105
+ for (const subscription of this.subscriptions)
106
+ subscription.deliver(notification);
107
+ }
108
+ disconnect(error) {
109
+ for (const subscription of this.subscriptions)
110
+ subscription.fail(error);
111
+ }
112
+ close() {
113
+ for (const subscription of [...this.subscriptions])
114
+ subscription.close();
115
+ }
116
+ }
@@ -16,7 +16,7 @@ function resolveRuntimeProcess(options = {}, embedded = {
16
16
  };
17
17
  }
18
18
  function toSafeInvokeTraceOptions(options) {
19
- const { collaborationAuth: _collaborationAuth, ...traceOptions } = options;
19
+ const { collaborationAuth: _collaborationAuth, identityClaim: _identityClaim, ...traceOptions } = options;
20
20
  return traceOptions;
21
21
  }
22
22
  class SuperDocRuntime {
@@ -31,6 +31,9 @@ class SuperDocRuntime {
31
31
  async connect() {
32
32
  await this.transport.connect();
33
33
  }
34
+ subscribe(options = {}) {
35
+ return this.transport.subscribe(options);
36
+ }
34
37
  supportsDocumentFacts(sessionId) {
35
38
  return this.transport.supportsDocumentFacts(sessionId);
36
39
  }
@@ -1,3 +1,4 @@
1
+ import type { HostNotifications, SubscribeOptions } from './notifications.js';
1
2
  import type { CollaborationAuth, DocumentRuntimeKind, DocOpenOptions, InvokeOptions, TransportInvokeOptions, OperationParamSpec, OperationSpec, RuntimeInvoker, SuperDocClientOptions } from './transport-common.js';
2
3
  type RuntimeProcessResolution = {
3
4
  hostBin: string;
@@ -24,6 +25,7 @@ export declare class SuperDocRuntime {
24
25
  private readonly transport;
25
26
  constructor(options?: SuperDocClientOptions);
26
27
  connect(): Promise<void>;
28
+ subscribe(options?: SubscribeOptions): HostNotifications;
27
29
  supportsDocumentFacts(sessionId: string): boolean;
28
30
  usesCliDocumentFacts(sessionId: string): boolean;
29
31
  readDocumentRevision(sessionId: string): Promise<string | undefined>;
@@ -13,7 +13,7 @@ export function resolveRuntimeProcess(options = {}, embedded = {
13
13
  };
14
14
  }
15
15
  export function toSafeInvokeTraceOptions(options) {
16
- const { collaborationAuth: _collaborationAuth, ...traceOptions } = options;
16
+ const { collaborationAuth: _collaborationAuth, identityClaim: _identityClaim, ...traceOptions } = options;
17
17
  return traceOptions;
18
18
  }
19
19
  export class SuperDocRuntime {
@@ -28,6 +28,9 @@ export class SuperDocRuntime {
28
28
  async connect() {
29
29
  await this.transport.connect();
30
30
  }
31
+ subscribe(options = {}) {
32
+ return this.transport.subscribe(options);
33
+ }
31
34
  supportsDocumentFacts(sessionId) {
32
35
  return this.transport.supportsDocumentFacts(sessionId);
33
36
  }
@@ -5,6 +5,7 @@ export declare const SDK_POLICY: {
5
5
  replaceFile: string;
6
6
  requestTimeout: string;
7
7
  collaborationAuthPerOpen: string;
8
+ identityClaimPerOpen: string;
8
9
  };
9
10
  operationParamAliases: Record<string, Readonly<Record<string, readonly string[]>>>;
10
11
  notifications: readonly string[];
@@ -3,6 +3,6 @@
3
3
  // AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
4
4
  // Source of truth: package.json. Regenerated on every SDK build so the
5
5
  // SDK retains its own version identity when bundled into another package.
6
- const SDK_VERSION = '2.26.0';
6
+ const SDK_VERSION = '2.27.0-next.4';
7
7
 
8
8
  exports.SDK_VERSION = SDK_VERSION;
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.26.0";
1
+ export declare const SDK_VERSION = "2.27.0-next.4";
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
2
2
  // Source of truth: package.json. Regenerated on every SDK build so the
3
3
  // SDK retains its own version identity when bundled into another package.
4
- export const SDK_VERSION = '2.26.0';
4
+ export const SDK_VERSION = '2.27.0-next.4';