@opengeni/sdk 0.48.0 → 0.57.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.
Files changed (122) hide show
  1. package/README.md +147 -1
  2. package/dist/artifact-client.d.ts +20 -2
  3. package/dist/artifacts.d.ts +4 -0
  4. package/dist/artifacts.js +11 -0
  5. package/dist/artifacts.js.map +1 -0
  6. package/dist/{chunk-V5F253OG.js → chunk-GU6JF75T.js} +6 -3
  7. package/dist/chunk-GU6JF75T.js.map +1 -0
  8. package/dist/chunk-HJ3HHUT5.js +222 -0
  9. package/dist/chunk-HJ3HHUT5.js.map +1 -0
  10. package/dist/{chunk-KIHGPM7H.js → chunk-JDLDDRNE.js} +20 -14
  11. package/dist/chunk-JDLDDRNE.js.map +1 -0
  12. package/dist/chunk-JRPFWI6T.js +128 -0
  13. package/dist/chunk-JRPFWI6T.js.map +1 -0
  14. package/dist/{chunk-MBGBLVUV.js → chunk-PKQ377ED.js} +1725 -555
  15. package/dist/chunk-PKQ377ED.js.map +1 -0
  16. package/dist/chunk-ST5DDKJP.js +987 -0
  17. package/dist/chunk-ST5DDKJP.js.map +1 -0
  18. package/dist/chunk-VWE2QIVO.js +1162 -0
  19. package/dist/chunk-VWE2QIVO.js.map +1 -0
  20. package/dist/chunk-VYCTL62C.js +230 -0
  21. package/dist/chunk-VYCTL62C.js.map +1 -0
  22. package/dist/{chunk-DXDL7EEW.js → chunk-YKZME56C.js} +197 -113
  23. package/dist/chunk-YKZME56C.js.map +1 -0
  24. package/dist/client.d.ts +240 -16
  25. package/dist/codex-realtime-controller.d.ts +9 -6
  26. package/dist/codex-realtime-controller.js +2 -2
  27. package/dist/codex-realtime-lifecycle.d.ts +1 -1
  28. package/dist/codex-realtime-v3.d.ts +3 -3
  29. package/dist/codex-realtime.d.ts +1 -1
  30. package/dist/company-profile.d.ts +100 -0
  31. package/dist/core.d.ts +5 -5
  32. package/dist/core.js +10 -5
  33. package/dist/desktop.d.ts +9 -1
  34. package/dist/editable-artifact-resources.d.ts +96 -0
  35. package/dist/editable-artifacts/browser-session.d.ts +35 -0
  36. package/dist/editable-artifacts/controller.d.ts +60 -0
  37. package/dist/editable-artifacts/errors.d.ts +23 -0
  38. package/dist/editable-artifacts/http-live-transport.d.ts +46 -0
  39. package/dist/editable-artifacts/index.d.ts +19 -0
  40. package/dist/editable-artifacts/pool.d.ts +15 -0
  41. package/dist/editable-artifacts/session.d.ts +96 -0
  42. package/dist/editable-artifacts/storage.d.ts +72 -0
  43. package/dist/editable-artifacts/types.d.ts +416 -0
  44. package/dist/editable-artifacts/worker/browser-client.d.ts +80 -0
  45. package/dist/editable-artifacts/worker/browser-entry.d.ts +6 -0
  46. package/dist/editable-artifacts/worker/kernel-adapter.d.ts +45 -0
  47. package/dist/editable-artifacts/worker/rpc-protocol.d.ts +92 -0
  48. package/dist/editable-artifacts/worker/runtime.d.ts +66 -0
  49. package/dist/editable-artifacts/worker/wire-codec.d.ts +52 -0
  50. package/dist/editable-artifacts-worker.d.ts +8 -0
  51. package/dist/editable-artifacts-worker.js +1708 -0
  52. package/dist/editable-artifacts-worker.js.map +1 -0
  53. package/dist/editable-artifacts.d.ts +6 -0
  54. package/dist/editable-artifacts.js +5954 -0
  55. package/dist/editable-artifacts.js.map +1 -0
  56. package/dist/gateway-realtime-transport.d.ts +2 -1
  57. package/dist/gateway-realtime-transport.js +5 -3
  58. package/dist/index.d.ts +45 -35
  59. package/dist/index.js +134 -64
  60. package/dist/index.js.map +1 -1
  61. package/dist/interaction-revision-stream.d.ts +11 -0
  62. package/dist/interaction.d.ts +1484 -0
  63. package/dist/interaction.js +81 -0
  64. package/dist/interaction.js.map +1 -0
  65. package/dist/memory-slack-client.d.ts +13 -0
  66. package/dist/memory-slack-delivery.d.ts +99 -0
  67. package/dist/memory-slack.d.ts +4 -0
  68. package/dist/memory-slack.js +44 -0
  69. package/dist/memory-slack.js.map +1 -0
  70. package/dist/proxy.d.ts +3 -3
  71. package/dist/realtime.d.ts +14 -14
  72. package/dist/realtime.js +11 -7
  73. package/dist/realtime.js.map +1 -1
  74. package/dist/retained-artifacts.d.ts +12 -0
  75. package/dist/stream.d.ts +1 -1
  76. package/dist/terminal.d.ts +1 -1
  77. package/dist/types.d.ts +1347 -34
  78. package/dist/workspace-control-stream.d.ts +2 -2
  79. package/dist/workspace-live-stream.d.ts +14 -0
  80. package/dist/workspace-state.d.ts +1 -1
  81. package/package.json +29 -3
  82. package/src/artifact-client.ts +129 -0
  83. package/src/artifacts.ts +17 -0
  84. package/src/client.ts +3022 -910
  85. package/src/codex-realtime-controller.ts +25 -7
  86. package/src/codex-realtime-lifecycle.ts +1 -0
  87. package/src/company-profile.ts +92 -0
  88. package/src/desktop.ts +11 -1
  89. package/src/editable-artifact-resources.ts +119 -0
  90. package/src/editable-artifacts/browser-session.ts +176 -0
  91. package/src/editable-artifacts/controller.ts +2632 -0
  92. package/src/editable-artifacts/errors.ts +52 -0
  93. package/src/editable-artifacts/http-live-transport.ts +1449 -0
  94. package/src/editable-artifacts/index.ts +175 -0
  95. package/src/editable-artifacts/pool.ts +81 -0
  96. package/src/editable-artifacts/session.ts +778 -0
  97. package/src/editable-artifacts/storage.ts +1718 -0
  98. package/src/editable-artifacts/types.ts +499 -0
  99. package/src/editable-artifacts/worker/browser-client.ts +1167 -0
  100. package/src/editable-artifacts/worker/browser-entry.ts +57 -0
  101. package/src/editable-artifacts/worker/kernel-adapter.ts +1039 -0
  102. package/src/editable-artifacts/worker/rpc-protocol.ts +622 -0
  103. package/src/editable-artifacts/worker/runtime.ts +1204 -0
  104. package/src/editable-artifacts/worker/wire-codec.ts +852 -0
  105. package/src/editable-artifacts-worker.ts +8 -0
  106. package/src/editable-artifacts.ts +6 -0
  107. package/src/errors.ts +16 -2
  108. package/src/gateway-realtime-transport.ts +252 -109
  109. package/src/index.ts +215 -1
  110. package/src/interaction-revision-stream.ts +117 -0
  111. package/src/interaction.ts +2874 -0
  112. package/src/memory-slack-client.ts +71 -0
  113. package/src/memory-slack-delivery.ts +128 -0
  114. package/src/memory-slack.ts +19 -0
  115. package/src/realtime.ts +14 -4
  116. package/src/retained-artifacts.ts +326 -0
  117. package/src/types.ts +1636 -52
  118. package/src/workspace-live-stream.ts +137 -0
  119. package/dist/chunk-DXDL7EEW.js.map +0 -1
  120. package/dist/chunk-KIHGPM7H.js.map +0 -1
  121. package/dist/chunk-MBGBLVUV.js.map +0 -1
  122. package/dist/chunk-V5F253OG.js.map +0 -1
@@ -0,0 +1,778 @@
1
+ import {
2
+ SPREADSHEET_ARTIFACT_COMMAND_VERSION,
3
+ editableArtifactStableId,
4
+ encodeSpreadsheetArtifactCommandBatch,
5
+ spreadsheetSheetId,
6
+ type SpreadsheetArtifactCommandBatch,
7
+ type SpreadsheetSheetPrecondition,
8
+ } from "@opengeni/contracts/editable-artifacts";
9
+ import {
10
+ DOCUMENT_ARTIFACT_COMMAND_VERSION,
11
+ encodeDocumentArtifactCommandBatch,
12
+ type DocumentArtifactCommandBatch,
13
+ type DocumentArtifactParagraphStyle,
14
+ type DocumentArtifactStoryTarget,
15
+ type DocumentArtifactTextRun,
16
+ } from "@opengeni/contracts/document-artifact-commands";
17
+ import {
18
+ PRESENTATION_ARTIFACT_COMMAND_VERSION,
19
+ encodePresentationArtifactCommandBatch,
20
+ type PresentationArtifactCommandBatch,
21
+ type PresentationArtifactFill,
22
+ } from "@opengeni/contracts/presentation-artifact-commands";
23
+ import {
24
+ createEditableArtifactSyncController,
25
+ type CreateEditableArtifactSyncControllerOptions,
26
+ type EditableArtifactQueueCommandsInput,
27
+ type EditableArtifactSyncController,
28
+ } from "./controller";
29
+ import { IndexedDbEditableArtifactStorage } from "./storage";
30
+ import type {
31
+ EditableArtifactPendingTransaction,
32
+ EditableArtifactModality,
33
+ EditableDocumentProjection,
34
+ EditableDocumentQuery,
35
+ EditablePresentationProjection,
36
+ EditablePresentationQuery,
37
+ EditablePresentationEditorSlideProjection,
38
+ EditablePresentationEditorSlideQuery,
39
+ EditablePresentationSlideCatalogProjection,
40
+ EditablePresentationSlideCatalogQuery,
41
+ EditableArtifactSyncListener,
42
+ EditableArtifactSyncView,
43
+ EditableArtifactWorkerKernel,
44
+ EditableSpreadsheetMetadataProjection,
45
+ EditableSpreadsheetMetadataQuery,
46
+ EditableSpreadsheetViewportProjection,
47
+ EditableSpreadsheetViewportQuery,
48
+ } from "./types";
49
+
50
+ const DEFAULT_VIEWPORT_MAX_CELLS = 262_144;
51
+ const DEFAULT_VIEWPORT_MAX_BYTES = 8 * 1024 * 1024;
52
+ const MAX_VIEWPORT_AREA = 1_048_576;
53
+ const UINT32_MAX = 0xffff_ffff;
54
+
55
+ export type CreateEditableArtifactSessionOptions = Omit<
56
+ CreateEditableArtifactSyncControllerOptions,
57
+ "kernel" | "storage"
58
+ > &
59
+ Readonly<{
60
+ /** Dedicated Worker client; canonical artifact state never enters the UI thread. */
61
+ worker: EditableArtifactWorkerKernel;
62
+ /** Defaults to the browser IndexedDB WAL/verified-replica store. */
63
+ storage?: CreateEditableArtifactSyncControllerOptions["storage"];
64
+ /** Close/dispose the Worker when this session closes. Defaults to true. */
65
+ ownsWorker?: boolean;
66
+ }>;
67
+
68
+ export type EditableSpreadsheetViewportListener = (
69
+ projection: EditableSpreadsheetViewportProjection,
70
+ ) => void;
71
+
72
+ export type EditableSpreadsheetViewportSubscriptionOptions = Readonly<{
73
+ onError?: (error: Error) => void;
74
+ }>;
75
+
76
+ export type EditableSpreadsheetMetadataListener = (
77
+ projection: EditableSpreadsheetMetadataProjection,
78
+ ) => void;
79
+
80
+ export type ApplySpreadsheetCommandsOptions = Readonly<{
81
+ selectiveUndoTargets?: readonly string[];
82
+ clientTransactionId?: string;
83
+ }>;
84
+
85
+ export type ApplySerializedArtifactCommandsOptions = Readonly<{
86
+ clientTransactionId?: string;
87
+ }>;
88
+
89
+ export type CreateSpreadsheetSheetInput = Readonly<{
90
+ name: string;
91
+ after?: SpreadsheetSheetPrecondition | null;
92
+ /** Optional stable id for a host retry; normally allocated by the SDK. */
93
+ sheetId?: string;
94
+ clientTransactionId?: string;
95
+ }>;
96
+
97
+ export type CreateSpreadsheetSheetResult = Readonly<{
98
+ sheetId: string;
99
+ pending: EditableArtifactPendingTransaction;
100
+ }>;
101
+
102
+ export type CreateDocumentParagraphInput = Readonly<{
103
+ target?: DocumentArtifactStoryTarget;
104
+ runs?: readonly DocumentArtifactTextRun[];
105
+ style?: DocumentArtifactParagraphStyle;
106
+ /** Optional stable id for a host retry; normally allocated by the SDK. */
107
+ paragraphId?: string;
108
+ clientTransactionId?: string;
109
+ }>;
110
+
111
+ export type CreateDocumentParagraphResult = Readonly<{
112
+ paragraphId: string;
113
+ pending: EditableArtifactPendingTransaction;
114
+ }>;
115
+
116
+ export type CreatePresentationSlideInput = Readonly<{
117
+ index: number;
118
+ title?: string;
119
+ layoutId?: string | null;
120
+ background?: PresentationArtifactFill;
121
+ /** Optional stable id for a host retry; normally allocated by the SDK. */
122
+ slideId?: string;
123
+ clientTransactionId?: string;
124
+ }>;
125
+
126
+ export type CreatePresentationSlideResult = Readonly<{
127
+ slideId: string;
128
+ pending: EditableArtifactPendingTransaction;
129
+ }>;
130
+
131
+ /**
132
+ * Public browser editing session. It owns sync, WAL recovery, live replay, and
133
+ * one Worker-resident speculative kernel. The main thread sees only bounded
134
+ * immutable projections and authored command receipts.
135
+ */
136
+ export interface EditableArtifactSession {
137
+ readonly artifactId: string;
138
+ readonly modality: EditableArtifactModality;
139
+ start(): void;
140
+ whenReady(): Promise<void>;
141
+ close(): Promise<void>;
142
+ getView(): EditableArtifactSyncView;
143
+ subscribe(listener: EditableArtifactSyncListener): () => void;
144
+ queueCommands(
145
+ input: EditableArtifactQueueCommandsInput,
146
+ ): Promise<EditableArtifactPendingTransaction>;
147
+ /** Pure OGASC encoding followed by the ordinary Worker/WAL authoring path. */
148
+ applySpreadsheetCommands(
149
+ batch: SpreadsheetArtifactCommandBatch,
150
+ options?: ApplySpreadsheetCommandsOptions,
151
+ ): Promise<EditableArtifactPendingTransaction>;
152
+ applyDocumentCommands(
153
+ batch: DocumentArtifactCommandBatch,
154
+ options?: ApplySerializedArtifactCommandsOptions,
155
+ ): Promise<EditableArtifactPendingTransaction>;
156
+ applyPresentationCommands(
157
+ batch: PresentationArtifactCommandBatch,
158
+ options?: ApplySerializedArtifactCommandsOptions,
159
+ ): Promise<EditableArtifactPendingTransaction>;
160
+ /** Allocates a collision-resistant offline sheet id and submits one OGASC create. */
161
+ createSpreadsheetSheet(input: CreateSpreadsheetSheetInput): Promise<CreateSpreadsheetSheetResult>;
162
+ /** Allocates a collision-resistant id in the document namespace and appends one paragraph. */
163
+ createDocumentParagraph(
164
+ input?: CreateDocumentParagraphInput,
165
+ ): Promise<CreateDocumentParagraphResult>;
166
+ /** Allocates a collision-resistant stable id and inserts one slide. */
167
+ createPresentationSlide(
168
+ input: CreatePresentationSlideInput,
169
+ ): Promise<CreatePresentationSlideResult>;
170
+ querySpreadsheetViewport(
171
+ query: EditableSpreadsheetViewportQuery,
172
+ ): Promise<EditableSpreadsheetViewportProjection>;
173
+ subscribeSpreadsheetViewport(
174
+ query: EditableSpreadsheetViewportQuery,
175
+ listener: EditableSpreadsheetViewportListener,
176
+ options?: EditableSpreadsheetViewportSubscriptionOptions,
177
+ ): () => void;
178
+ querySpreadsheetMetadata(
179
+ query?: EditableSpreadsheetMetadataQuery,
180
+ ): Promise<EditableSpreadsheetMetadataProjection>;
181
+ subscribeSpreadsheetMetadata(
182
+ query: EditableSpreadsheetMetadataQuery,
183
+ listener: EditableSpreadsheetMetadataListener,
184
+ options?: EditableSpreadsheetViewportSubscriptionOptions,
185
+ ): () => void;
186
+ queryDocument(query: EditableDocumentQuery): Promise<EditableDocumentProjection>;
187
+ queryPresentation(query: EditablePresentationQuery): Promise<EditablePresentationProjection>;
188
+ queryPresentationSlideCatalog(
189
+ query: EditablePresentationSlideCatalogQuery,
190
+ ): Promise<EditablePresentationSlideCatalogProjection>;
191
+ queryPresentationEditorSlide(
192
+ query: EditablePresentationEditorSlideQuery,
193
+ ): Promise<EditablePresentationEditorSlideProjection>;
194
+ }
195
+
196
+ /** Creates and starts one independently durable browser editing session. */
197
+ export function createEditableArtifactSession(
198
+ options: CreateEditableArtifactSessionOptions,
199
+ ): EditableArtifactSession {
200
+ const storage = options.storage ?? new IndexedDbEditableArtifactStorage();
201
+ const controller = createEditableArtifactSyncController({
202
+ ...options,
203
+ storage,
204
+ kernel: options.worker,
205
+ });
206
+ const session = new EditableArtifactSessionImpl(
207
+ controller,
208
+ options.worker,
209
+ options.ownsWorker ?? true,
210
+ );
211
+ session.start();
212
+ return session;
213
+ }
214
+
215
+ type ProjectionChannel = {
216
+ readonly query: EditableSpreadsheetViewportQuery;
217
+ readonly listeners: Map<
218
+ EditableSpreadsheetViewportListener,
219
+ EditableSpreadsheetViewportSubscriptionOptions
220
+ >;
221
+ generation: number;
222
+ running: boolean;
223
+ requested: boolean;
224
+ last: EditableSpreadsheetViewportProjection | null;
225
+ };
226
+
227
+ type MetadataChannel = {
228
+ readonly query: Required<EditableSpreadsheetMetadataQuery>;
229
+ readonly listeners: Map<
230
+ EditableSpreadsheetMetadataListener,
231
+ EditableSpreadsheetViewportSubscriptionOptions
232
+ >;
233
+ generation: number;
234
+ running: boolean;
235
+ requested: boolean;
236
+ last: EditableSpreadsheetMetadataProjection | null;
237
+ };
238
+
239
+ class EditableArtifactSessionImpl implements EditableArtifactSession {
240
+ readonly artifactId: string;
241
+ readonly modality: EditableArtifactModality;
242
+
243
+ private readonly channels = new Map<string, ProjectionChannel>();
244
+ private readonly metadataChannels = new Map<string, MetadataChannel>();
245
+ private releaseController: (() => void) | null = null;
246
+ private closed = false;
247
+ private lastProjectionInvalidator = "";
248
+
249
+ constructor(
250
+ private readonly controller: EditableArtifactSyncController,
251
+ private readonly worker: EditableArtifactWorkerKernel,
252
+ private readonly ownsWorker: boolean,
253
+ ) {
254
+ this.artifactId = controller.artifactId;
255
+ this.modality = controller.modality;
256
+ }
257
+
258
+ start(): void {
259
+ this.requireOpen();
260
+ if (!this.releaseController) {
261
+ this.releaseController = this.controller.subscribe((view) => {
262
+ const invalidator = projectionInvalidator(view);
263
+ if (invalidator !== this.lastProjectionInvalidator) {
264
+ this.lastProjectionInvalidator = invalidator;
265
+ for (const channel of this.channels.values()) this.requestChannel(channel);
266
+ for (const channel of this.metadataChannels.values())
267
+ this.requestMetadataChannel(channel);
268
+ }
269
+ });
270
+ }
271
+ this.controller.start();
272
+ }
273
+
274
+ async whenReady(): Promise<void> {
275
+ this.requireOpen();
276
+ this.start();
277
+ await this.controller.whenLive();
278
+ }
279
+
280
+ async close(): Promise<void> {
281
+ if (this.closed) return;
282
+ this.closed = true;
283
+ this.releaseController?.();
284
+ this.releaseController = null;
285
+ this.channels.clear();
286
+ this.metadataChannels.clear();
287
+ await this.controller.close();
288
+ if (this.ownsWorker) {
289
+ const candidate = this.worker as EditableArtifactWorkerKernel & {
290
+ dispose?: () => Promise<void>;
291
+ };
292
+ await candidate.dispose?.();
293
+ }
294
+ }
295
+
296
+ getView(): EditableArtifactSyncView {
297
+ return this.controller.getView();
298
+ }
299
+
300
+ subscribe(listener: EditableArtifactSyncListener): () => void {
301
+ this.requireOpen();
302
+ return this.controller.subscribe(listener);
303
+ }
304
+
305
+ async queueCommands(
306
+ input: EditableArtifactQueueCommandsInput,
307
+ ): Promise<EditableArtifactPendingTransaction> {
308
+ this.requireOpen();
309
+ return await this.controller.queueCommands(input);
310
+ }
311
+
312
+ async applySpreadsheetCommands(
313
+ batch: SpreadsheetArtifactCommandBatch,
314
+ options: ApplySpreadsheetCommandsOptions = {},
315
+ ): Promise<EditableArtifactPendingTransaction> {
316
+ this.requireOpen();
317
+ this.requireModality("spreadsheet");
318
+ const commandBytes = encodeSpreadsheetArtifactCommandBatch(batch);
319
+ return await this.queueCommands({
320
+ commandBytes,
321
+ ...(options.selectiveUndoTargets === undefined
322
+ ? {}
323
+ : { selectiveUndoTargets: options.selectiveUndoTargets }),
324
+ ...(options.clientTransactionId === undefined
325
+ ? {}
326
+ : { clientTransactionId: options.clientTransactionId }),
327
+ });
328
+ }
329
+
330
+ async applyDocumentCommands(
331
+ batch: DocumentArtifactCommandBatch,
332
+ options: ApplySerializedArtifactCommandsOptions = {},
333
+ ): Promise<EditableArtifactPendingTransaction> {
334
+ this.requireOpen();
335
+ this.requireModality("document");
336
+ return await this.queueCommands({
337
+ commandBytes: encodeDocumentArtifactCommandBatch(batch),
338
+ ...(options.clientTransactionId === undefined
339
+ ? {}
340
+ : { clientTransactionId: options.clientTransactionId }),
341
+ });
342
+ }
343
+
344
+ async applyPresentationCommands(
345
+ batch: PresentationArtifactCommandBatch,
346
+ options: ApplySerializedArtifactCommandsOptions = {},
347
+ ): Promise<EditableArtifactPendingTransaction> {
348
+ this.requireOpen();
349
+ this.requireModality("presentation");
350
+ return await this.queueCommands({
351
+ commandBytes: encodePresentationArtifactCommandBatch(batch),
352
+ ...(options.clientTransactionId === undefined
353
+ ? {}
354
+ : { clientTransactionId: options.clientTransactionId }),
355
+ });
356
+ }
357
+
358
+ async createSpreadsheetSheet(
359
+ input: CreateSpreadsheetSheetInput,
360
+ ): Promise<CreateSpreadsheetSheetResult> {
361
+ this.requireOpen();
362
+ const sheetId = spreadsheetSheetId(input.sheetId ?? randomStableId());
363
+ const pending = await this.applySpreadsheetCommands(
364
+ {
365
+ version: SPREADSHEET_ARTIFACT_COMMAND_VERSION,
366
+ commands: [
367
+ {
368
+ kind: "sheet.create",
369
+ sheetId,
370
+ name: input.name,
371
+ after: input.after ?? null,
372
+ },
373
+ ],
374
+ },
375
+ input.clientTransactionId === undefined
376
+ ? {}
377
+ : { clientTransactionId: input.clientTransactionId },
378
+ );
379
+ return Object.freeze({ sheetId, pending });
380
+ }
381
+
382
+ async createDocumentParagraph(
383
+ input: CreateDocumentParagraphInput = {},
384
+ ): Promise<CreateDocumentParagraphResult> {
385
+ this.requireOpen();
386
+ this.requireModality("document");
387
+ const summary = await this.queryDocument({ kind: "summary" });
388
+ const facts = summary.items[0];
389
+ if (
390
+ summary.items.length !== 1 ||
391
+ facts?.kind !== "summary" ||
392
+ summary.nextCursor !== null ||
393
+ summary.truncated
394
+ ) {
395
+ throw new Error("Document summary projection is malformed");
396
+ }
397
+ const paragraphId = input.paragraphId ?? randomDocumentId("p", facts.idNamespace);
398
+ const pending = await this.applyDocumentCommands(
399
+ {
400
+ version: DOCUMENT_ARTIFACT_COMMAND_VERSION,
401
+ commands: [
402
+ {
403
+ kind: "paragraph.add",
404
+ target: input.target ?? { kind: "body" },
405
+ id: paragraphId,
406
+ runs: input.runs ?? [],
407
+ style: input.style ?? {},
408
+ },
409
+ ],
410
+ },
411
+ input.clientTransactionId === undefined
412
+ ? {}
413
+ : { clientTransactionId: input.clientTransactionId },
414
+ );
415
+ return Object.freeze({ paragraphId, pending });
416
+ }
417
+
418
+ async createPresentationSlide(
419
+ input: CreatePresentationSlideInput,
420
+ ): Promise<CreatePresentationSlideResult> {
421
+ this.requireOpen();
422
+ this.requireModality("presentation");
423
+ const slideId = editableArtifactStableId(input.slideId ?? randomStableId());
424
+ const pending = await this.applyPresentationCommands(
425
+ {
426
+ version: PRESENTATION_ARTIFACT_COMMAND_VERSION,
427
+ commands: [
428
+ {
429
+ kind: "slide.create",
430
+ id: slideId,
431
+ index: input.index,
432
+ title: input.title ?? "",
433
+ layoutId: input.layoutId ?? null,
434
+ background: input.background ?? { kind: "solid", color: 0xffffffff },
435
+ },
436
+ ],
437
+ },
438
+ input.clientTransactionId === undefined
439
+ ? {}
440
+ : { clientTransactionId: input.clientTransactionId },
441
+ );
442
+ return Object.freeze({ slideId, pending });
443
+ }
444
+
445
+ async querySpreadsheetViewport(
446
+ query: EditableSpreadsheetViewportQuery,
447
+ ): Promise<EditableSpreadsheetViewportProjection> {
448
+ this.requireOpen();
449
+ this.requireModality("spreadsheet");
450
+ const normalized = normalizeViewportQuery(query);
451
+ await this.whenReady();
452
+ this.requireOpen();
453
+ return await this.worker.querySpreadsheetViewport(normalized);
454
+ }
455
+
456
+ subscribeSpreadsheetViewport(
457
+ query: EditableSpreadsheetViewportQuery,
458
+ listener: EditableSpreadsheetViewportListener,
459
+ options: EditableSpreadsheetViewportSubscriptionOptions = {},
460
+ ): () => void {
461
+ this.requireOpen();
462
+ this.requireModality("spreadsheet");
463
+ const normalized = normalizeViewportQuery(query);
464
+ const key = viewportQueryKey(normalized);
465
+ let channel = this.channels.get(key);
466
+ if (!channel) {
467
+ channel = {
468
+ query: normalized,
469
+ listeners: new Map(),
470
+ generation: 0,
471
+ running: false,
472
+ requested: false,
473
+ last: null,
474
+ };
475
+ this.channels.set(key, channel);
476
+ }
477
+ channel.listeners.set(listener, options);
478
+ if (channel.last) listener(channel.last);
479
+ else this.requestChannel(channel);
480
+ return () => {
481
+ const current = this.channels.get(key);
482
+ if (!current) return;
483
+ current.listeners.delete(listener);
484
+ if (current.listeners.size === 0) {
485
+ current.generation += 1;
486
+ this.channels.delete(key);
487
+ }
488
+ };
489
+ }
490
+
491
+ async querySpreadsheetMetadata(
492
+ query: EditableSpreadsheetMetadataQuery = {},
493
+ ): Promise<EditableSpreadsheetMetadataProjection> {
494
+ this.requireOpen();
495
+ this.requireModality("spreadsheet");
496
+ const normalized = normalizeMetadataQuery(query);
497
+ await this.whenReady();
498
+ this.requireOpen();
499
+ return await this.worker.querySpreadsheetMetadata(normalized);
500
+ }
501
+
502
+ subscribeSpreadsheetMetadata(
503
+ query: EditableSpreadsheetMetadataQuery,
504
+ listener: EditableSpreadsheetMetadataListener,
505
+ options: EditableSpreadsheetViewportSubscriptionOptions = {},
506
+ ): () => void {
507
+ this.requireOpen();
508
+ this.requireModality("spreadsheet");
509
+ const normalized = normalizeMetadataQuery(query);
510
+ const key = `${normalized.maxSheets}:${normalized.maxBytes}`;
511
+ let channel = this.metadataChannels.get(key);
512
+ if (!channel) {
513
+ channel = {
514
+ query: normalized,
515
+ listeners: new Map(),
516
+ generation: 0,
517
+ running: false,
518
+ requested: false,
519
+ last: null,
520
+ };
521
+ this.metadataChannels.set(key, channel);
522
+ }
523
+ channel.listeners.set(listener, options);
524
+ if (channel.last) listener(channel.last);
525
+ else this.requestMetadataChannel(channel);
526
+ return () => {
527
+ const current = this.metadataChannels.get(key);
528
+ if (!current) return;
529
+ current.listeners.delete(listener);
530
+ if (current.listeners.size === 0) {
531
+ current.generation += 1;
532
+ this.metadataChannels.delete(key);
533
+ }
534
+ };
535
+ }
536
+
537
+ async queryDocument(query: EditableDocumentQuery): Promise<EditableDocumentProjection> {
538
+ this.requireOpen();
539
+ this.requireModality("document");
540
+ await this.whenReady();
541
+ this.requireOpen();
542
+ return await this.worker.queryDocument(query);
543
+ }
544
+
545
+ async queryPresentation(
546
+ query: EditablePresentationQuery,
547
+ ): Promise<EditablePresentationProjection> {
548
+ this.requireOpen();
549
+ this.requireModality("presentation");
550
+ await this.whenReady();
551
+ this.requireOpen();
552
+ return await this.worker.queryPresentation(query);
553
+ }
554
+
555
+ async queryPresentationSlideCatalog(
556
+ query: EditablePresentationSlideCatalogQuery,
557
+ ): Promise<EditablePresentationSlideCatalogProjection> {
558
+ this.requireOpen();
559
+ this.requireModality("presentation");
560
+ await this.whenReady();
561
+ this.requireOpen();
562
+ const response = await this.worker.queryPresentation(query);
563
+ if (
564
+ response.kind !== "slide-catalog" ||
565
+ response.startSlide !== query.startSlide ||
566
+ response.slides.length > query.maxSlides ||
567
+ response.projectedTextBytes > query.maxTextBytes
568
+ ) {
569
+ throw new TypeError("presentation slide catalog response is malformed");
570
+ }
571
+ return response;
572
+ }
573
+
574
+ async queryPresentationEditorSlide(
575
+ query: EditablePresentationEditorSlideQuery,
576
+ ): Promise<EditablePresentationEditorSlideProjection> {
577
+ this.requireOpen();
578
+ this.requireModality("presentation");
579
+ await this.whenReady();
580
+ this.requireOpen();
581
+ const response = await this.worker.queryPresentation(query);
582
+ if (
583
+ response.kind !== "editor-slide" ||
584
+ response.slide.id !== query.slideId ||
585
+ response.nodes.length > query.maxNodes ||
586
+ response.projectedTextBytes > query.maxTextBytes
587
+ ) {
588
+ throw new TypeError("presentation editor slide response is malformed");
589
+ }
590
+ return response;
591
+ }
592
+
593
+ private requestChannel(channel: ProjectionChannel): void {
594
+ if (this.closed || channel.listeners.size === 0) return;
595
+ channel.requested = true;
596
+ if (channel.running) return;
597
+ channel.running = true;
598
+ void this.runChannel(channel);
599
+ }
600
+
601
+ private async runChannel(channel: ProjectionChannel): Promise<void> {
602
+ try {
603
+ while (!this.closed && channel.listeners.size > 0 && channel.requested) {
604
+ channel.requested = false;
605
+ const generation = ++channel.generation;
606
+ try {
607
+ const projection = await this.querySpreadsheetViewport(channel.query);
608
+ if (this.closed || generation !== channel.generation || channel.listeners.size === 0) {
609
+ continue;
610
+ }
611
+ channel.last = projection;
612
+ for (const listener of channel.listeners.keys()) listener(projection);
613
+ } catch (error) {
614
+ if (this.closed || generation !== channel.generation) continue;
615
+ const failure = asError(error);
616
+ for (const options of channel.listeners.values()) options.onError?.(failure);
617
+ }
618
+ }
619
+ } finally {
620
+ channel.running = false;
621
+ if (channel.requested && !this.closed && channel.listeners.size > 0) {
622
+ this.requestChannel(channel);
623
+ }
624
+ }
625
+ }
626
+
627
+ private requestMetadataChannel(channel: MetadataChannel): void {
628
+ if (this.closed || channel.listeners.size === 0) return;
629
+ channel.requested = true;
630
+ if (channel.running) return;
631
+ channel.running = true;
632
+ void this.runMetadataChannel(channel);
633
+ }
634
+
635
+ private async runMetadataChannel(channel: MetadataChannel): Promise<void> {
636
+ try {
637
+ while (!this.closed && channel.listeners.size > 0 && channel.requested) {
638
+ channel.requested = false;
639
+ const generation = ++channel.generation;
640
+ try {
641
+ const projection = await this.querySpreadsheetMetadata(channel.query);
642
+ if (this.closed || generation !== channel.generation || channel.listeners.size === 0) {
643
+ continue;
644
+ }
645
+ channel.last = projection;
646
+ for (const listener of channel.listeners.keys()) listener(projection);
647
+ } catch (error) {
648
+ if (this.closed || generation !== channel.generation) continue;
649
+ const failure = asError(error);
650
+ for (const options of channel.listeners.values()) options.onError?.(failure);
651
+ }
652
+ }
653
+ } finally {
654
+ channel.running = false;
655
+ if (channel.requested && !this.closed && channel.listeners.size > 0) {
656
+ this.requestMetadataChannel(channel);
657
+ }
658
+ }
659
+ }
660
+
661
+ private requireOpen(): void {
662
+ if (this.closed) throw new Error("editable artifact session is closed");
663
+ }
664
+
665
+ private requireModality(expected: EditableArtifactModality): void {
666
+ if (this.modality !== expected) {
667
+ throw new TypeError(`${expected} operation requires a ${expected} artifact session`);
668
+ }
669
+ }
670
+ }
671
+
672
+ function normalizeViewportQuery(
673
+ input: EditableSpreadsheetViewportQuery,
674
+ ): EditableSpreadsheetViewportQuery {
675
+ const sheetId = stableId(input.sheetId, "sheetId");
676
+ const startRow = uint32(input.startRow, "startRow");
677
+ const startColumn = uint32(input.startColumn, "startColumn");
678
+ const rowCount = positiveUint32(input.rowCount, "rowCount");
679
+ const columnCount = positiveUint32(input.columnCount, "columnCount");
680
+ if (startRow + rowCount - 1 > UINT32_MAX || startColumn + columnCount - 1 > UINT32_MAX) {
681
+ throw new RangeError("spreadsheet viewport exceeds the uint32 coordinate space");
682
+ }
683
+ const area = rowCount * columnCount;
684
+ if (!Number.isSafeInteger(area) || area > MAX_VIEWPORT_AREA) {
685
+ throw new RangeError(`spreadsheet viewport area exceeds ${MAX_VIEWPORT_AREA} cells`);
686
+ }
687
+ const maxCells = positiveUint32(input.maxCells ?? DEFAULT_VIEWPORT_MAX_CELLS, "maxCells");
688
+ const maxBytes = positiveUint32(input.maxBytes ?? DEFAULT_VIEWPORT_MAX_BYTES, "maxBytes");
689
+ if (maxCells > DEFAULT_VIEWPORT_MAX_CELLS) {
690
+ throw new RangeError(`maxCells exceeds ${DEFAULT_VIEWPORT_MAX_CELLS}`);
691
+ }
692
+ if (maxBytes > DEFAULT_VIEWPORT_MAX_BYTES) {
693
+ throw new RangeError(`maxBytes exceeds ${DEFAULT_VIEWPORT_MAX_BYTES}`);
694
+ }
695
+ return Object.freeze({
696
+ sheetId,
697
+ startRow,
698
+ startColumn,
699
+ rowCount,
700
+ columnCount,
701
+ maxCells,
702
+ maxBytes,
703
+ });
704
+ }
705
+
706
+ function viewportQueryKey(query: EditableSpreadsheetViewportQuery): string {
707
+ return `${query.sheetId}:${query.startRow}:${query.startColumn}:${query.rowCount}:${query.columnCount}:${query.maxCells}:${query.maxBytes}`;
708
+ }
709
+
710
+ function normalizeMetadataQuery(
711
+ input: EditableSpreadsheetMetadataQuery,
712
+ ): Required<EditableSpreadsheetMetadataQuery> {
713
+ const maxSheets = positiveUint32(input.maxSheets ?? 4_096, "maxSheets");
714
+ const maxBytes = positiveUint32(input.maxBytes ?? DEFAULT_VIEWPORT_MAX_BYTES, "maxBytes");
715
+ if (maxSheets > 262_144) throw new RangeError("maxSheets exceeds 262144");
716
+ if (maxBytes > DEFAULT_VIEWPORT_MAX_BYTES) {
717
+ throw new RangeError(`maxBytes exceeds ${DEFAULT_VIEWPORT_MAX_BYTES}`);
718
+ }
719
+ return Object.freeze({ maxSheets, maxBytes });
720
+ }
721
+
722
+ function projectionInvalidator(view: EditableArtifactSyncView): string {
723
+ return `${view.state}:${view.cursor}:${view.headSequence}:${view.pendingTransactions}:${view.blockedPending.map((entry) => `${entry.clientTransactionId}:${entry.code}`).join(",")}`;
724
+ }
725
+
726
+ function stableId(value: unknown, label: string): string {
727
+ if (typeof value !== "string" || !/^[0-9a-f]{32}$/u.test(value) || /^0+$/u.test(value)) {
728
+ throw new TypeError(`${label} must be nonzero fixed-width lowercase hexadecimal text`);
729
+ }
730
+ return value;
731
+ }
732
+
733
+ function uint32(value: unknown, label: string): number {
734
+ if (!Number.isInteger(value) || (value as number) < 0 || (value as number) > UINT32_MAX) {
735
+ throw new TypeError(`${label} must be a uint32`);
736
+ }
737
+ return value as number;
738
+ }
739
+
740
+ function positiveUint32(value: unknown, label: string): number {
741
+ const output = uint32(value, label);
742
+ if (output === 0) throw new TypeError(`${label} must be positive`);
743
+ return output;
744
+ }
745
+
746
+ function asError(error: unknown): Error {
747
+ return error instanceof Error ? error : new Error("editable artifact projection failed");
748
+ }
749
+
750
+ function randomStableId(): string {
751
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
752
+ // Sheet object ids require nonzero allocator namespace and counter halves.
753
+ if (bytes.subarray(0, 8).every((value) => value === 0)) bytes[0] = 1;
754
+ if (bytes.subarray(8).every((value) => value === 0)) bytes[8] = 1;
755
+ let value = 0n;
756
+ for (let index = 0; index < bytes.length; index += 1) {
757
+ value |= BigInt(bytes[index]!) << BigInt(index * 8);
758
+ }
759
+ return value.toString(16).padStart(32, "0");
760
+ }
761
+
762
+ function randomDocumentId(prefix: "p", namespace: bigint): string {
763
+ if (namespace < 0n || namespace > 0xffff_ffff_ffff_ffffn) {
764
+ throw new Error("Document id namespace is malformed");
765
+ }
766
+ const maximumCounter = BigInt(Number.MAX_SAFE_INTEGER) - 1n;
767
+ for (;;) {
768
+ const bytes = crypto.getRandomValues(new Uint8Array(7));
769
+ bytes[0] = bytes[0]! & 0x1f;
770
+ let counter = 0n;
771
+ for (const byte of bytes) counter = (counter << 8n) | BigInt(byte);
772
+ if (counter > 0n && counter <= maximumCounter) {
773
+ return `${prefix}/${namespace.toString(16).padStart(16, "0")}${counter
774
+ .toString(16)
775
+ .padStart(16, "0")}`;
776
+ }
777
+ }
778
+ }