mindoodb-app-sdk 0.0.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.
@@ -0,0 +1,801 @@
1
+ // src/testing/index.ts
2
+ var PROTOCOL = "mindoodb-app-bridge";
3
+ function createBridgeErrorPayload(error, fallbackCode = "bridge-error") {
4
+ if (error && typeof error === "object" && "message" in error) {
5
+ const namedError = error;
6
+ const code = typeof namedError.name === "string" && namedError.name ? namedError.name : fallbackCode;
7
+ const message = typeof namedError.message === "string" ? namedError.message : String(error);
8
+ return { code, message };
9
+ }
10
+ return { code: fallbackCode, message: String(error) };
11
+ }
12
+ function cloneBytes(chunk) {
13
+ return new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
14
+ }
15
+ function mergeLaunchContext(current, patch) {
16
+ if (!patch) {
17
+ return {
18
+ ...current,
19
+ theme: { ...current.theme },
20
+ viewport: current.viewport ? { ...current.viewport } : null,
21
+ user: { ...current.user },
22
+ launchParameters: { ...current.launchParameters },
23
+ views: current.views.map((view) => ({
24
+ ...view,
25
+ sources: view.sources.map((source) => ({ ...source })),
26
+ columns: view.columns.map((column) => ({
27
+ ...column,
28
+ expression: column.expression.mode === "field" ? { ...column.expression } : { mode: "formula", expression: structuredClone(column.expression.expression) }
29
+ })),
30
+ filter: view.filter.mode === "rules" ? {
31
+ mode: "rules",
32
+ match: view.filter.match,
33
+ rules: view.filter.rules.map((rule) => ({ ...rule }))
34
+ } : {
35
+ mode: "formula",
36
+ expression: structuredClone(view.filter.expression)
37
+ }
38
+ }))
39
+ };
40
+ }
41
+ return {
42
+ ...current,
43
+ ...patch,
44
+ theme: patch.theme ? { ...current.theme, ...patch.theme } : { ...current.theme },
45
+ viewport: patch.viewport === void 0 ? current.viewport ? { ...current.viewport } : null : patch.viewport ? { ...patch.viewport } : null,
46
+ user: patch.user ? { ...current.user, ...patch.user } : { ...current.user },
47
+ launchParameters: patch.launchParameters ? { ...current.launchParameters, ...patch.launchParameters } : { ...current.launchParameters },
48
+ views: patch.views ? patch.views.map((view) => ({
49
+ ...view,
50
+ sources: view.sources.map((source) => ({ ...source })),
51
+ columns: view.columns.map((column) => ({
52
+ ...column,
53
+ expression: column.expression.mode === "field" ? { ...column.expression } : { mode: "formula", expression: structuredClone(column.expression.expression) }
54
+ })),
55
+ filter: view.filter.mode === "rules" ? {
56
+ mode: "rules",
57
+ match: view.filter.match,
58
+ rules: view.filter.rules.map((rule) => ({ ...rule }))
59
+ } : {
60
+ mode: "formula",
61
+ expression: structuredClone(view.filter.expression)
62
+ }
63
+ })) : current.views.map((view) => ({
64
+ ...view,
65
+ sources: view.sources.map((source) => ({ ...source })),
66
+ columns: view.columns.map((column) => ({
67
+ ...column,
68
+ expression: column.expression.mode === "field" ? { ...column.expression } : { mode: "formula", expression: structuredClone(column.expression.expression) }
69
+ })),
70
+ filter: view.filter.mode === "rules" ? {
71
+ mode: "rules",
72
+ match: view.filter.match,
73
+ rules: view.filter.rules.map((rule) => ({ ...rule }))
74
+ } : {
75
+ mode: "formula",
76
+ expression: structuredClone(view.filter.expression)
77
+ }
78
+ }))
79
+ };
80
+ }
81
+ function createDefaultLaunchContext(patch) {
82
+ const base = {
83
+ appId: "sample-app",
84
+ appInstanceId: "sample-app-instance",
85
+ appVersion: "1.0.0",
86
+ launchId: "test-launch",
87
+ runtime: "iframe",
88
+ theme: {
89
+ mode: "dark",
90
+ preset: "mindoo"
91
+ },
92
+ viewport: {
93
+ width: 1024,
94
+ height: 768
95
+ },
96
+ user: {
97
+ id: "user-1",
98
+ username: "Test User"
99
+ },
100
+ launchParameters: {},
101
+ views: []
102
+ };
103
+ return mergeLaunchContext(base, patch);
104
+ }
105
+ function createDefaultReadableAttachmentStream(chunks = []) {
106
+ const queue = chunks.map((chunk) => cloneBytes(chunk));
107
+ let closed = false;
108
+ return {
109
+ async read() {
110
+ if (closed) {
111
+ return null;
112
+ }
113
+ const next = queue.shift();
114
+ if (!next) {
115
+ closed = true;
116
+ return null;
117
+ }
118
+ return next;
119
+ },
120
+ async close() {
121
+ closed = true;
122
+ queue.splice(0);
123
+ }
124
+ };
125
+ }
126
+ function createDefaultWritableAttachmentStream() {
127
+ let closed = false;
128
+ return {
129
+ async write() {
130
+ if (closed) {
131
+ throw new Error("The attachment write stream is already closed.");
132
+ }
133
+ },
134
+ async close() {
135
+ closed = true;
136
+ },
137
+ async abort() {
138
+ closed = true;
139
+ }
140
+ };
141
+ }
142
+ function createDefaultViewHandle() {
143
+ const definition = {
144
+ title: "Mock View",
145
+ columns: []
146
+ };
147
+ const expansion = {
148
+ mode: "collapsed",
149
+ ids: []
150
+ };
151
+ const emptyPage = {
152
+ rows: [],
153
+ nextPosition: null,
154
+ hasMore: false
155
+ };
156
+ return {
157
+ async getDefinition() {
158
+ return definition;
159
+ },
160
+ async refresh() {
161
+ },
162
+ async page(_input) {
163
+ return emptyPage;
164
+ },
165
+ async getExpansionState() {
166
+ return expansion;
167
+ },
168
+ async setExpansionState(state) {
169
+ return state;
170
+ },
171
+ async expand(rowKey) {
172
+ return {
173
+ mode: "collapsed",
174
+ ids: [rowKey]
175
+ };
176
+ },
177
+ async collapse(_rowKey) {
178
+ return expansion;
179
+ },
180
+ async expandAll() {
181
+ return {
182
+ mode: "expanded",
183
+ ids: []
184
+ };
185
+ },
186
+ async collapseAll() {
187
+ return expansion;
188
+ },
189
+ async getRow(_rowKey) {
190
+ return null;
191
+ },
192
+ async getCategory(_input) {
193
+ return null;
194
+ },
195
+ async pageCategory(_categoryKey, _input) {
196
+ return emptyPage;
197
+ },
198
+ async listCategoryDocumentIds(_categoryKey) {
199
+ return [];
200
+ },
201
+ async dispose() {
202
+ }
203
+ };
204
+ }
205
+ function createDatabaseHandle(definition) {
206
+ let createCounter = 0;
207
+ const defaultViewFactory = async () => createDefaultViewHandle();
208
+ const defaultDocuments = {
209
+ async list(_query) {
210
+ return {
211
+ items: [],
212
+ nextCursor: null
213
+ };
214
+ },
215
+ async get(_docId) {
216
+ return null;
217
+ },
218
+ async create(input) {
219
+ createCounter += 1;
220
+ return {
221
+ id: `doc-${createCounter}`,
222
+ data: { ...input.data },
223
+ attachments: []
224
+ };
225
+ },
226
+ async update(docId, patch) {
227
+ return {
228
+ id: docId,
229
+ data: { ...patch.data },
230
+ attachments: []
231
+ };
232
+ },
233
+ async delete(_docId) {
234
+ return { ok: true };
235
+ },
236
+ async listHistory(_docId) {
237
+ return [];
238
+ },
239
+ async getAtTimestamp(docId, timestamp) {
240
+ return {
241
+ id: docId,
242
+ timestamp,
243
+ state: "missing",
244
+ data: null
245
+ };
246
+ }
247
+ };
248
+ const defaultViews = {
249
+ async create(_definition) {
250
+ return await defaultViewFactory();
251
+ },
252
+ async open(_viewId) {
253
+ return await defaultViewFactory();
254
+ }
255
+ };
256
+ const defaultAttachments = {
257
+ async list(_docId) {
258
+ return [];
259
+ },
260
+ async remove(_docId, _attachmentName) {
261
+ return { ok: true };
262
+ },
263
+ async openReadStream(_docId, _attachmentName) {
264
+ return createDefaultReadableAttachmentStream();
265
+ },
266
+ async openWriteStream(_docId, _attachmentName, _contentType) {
267
+ return createDefaultWritableAttachmentStream();
268
+ }
269
+ };
270
+ const methods = definition.methods ?? {};
271
+ return {
272
+ async info() {
273
+ return { ...definition.info, capabilities: [...definition.info.capabilities] };
274
+ },
275
+ documents: {
276
+ ...defaultDocuments,
277
+ ...methods.documents
278
+ },
279
+ views: {
280
+ ...defaultViews,
281
+ ...methods.views
282
+ },
283
+ attachments: {
284
+ ...defaultAttachments,
285
+ ...methods.attachments
286
+ }
287
+ };
288
+ }
289
+ function createMockSessionState(options = {}) {
290
+ let launchContext = createDefaultLaunchContext(options.launchContext);
291
+ const themeListeners = /* @__PURE__ */ new Set();
292
+ const viewportListeners = /* @__PURE__ */ new Set();
293
+ const databaseHandles = /* @__PURE__ */ new Map();
294
+ let databaseInfos = [];
295
+ const setDatabases = (definitions) => {
296
+ databaseHandles.clear();
297
+ databaseInfos = definitions.map((definition) => ({
298
+ ...definition.info,
299
+ capabilities: [...definition.info.capabilities]
300
+ }));
301
+ for (const definition of definitions) {
302
+ databaseHandles.set(definition.info.id, createDatabaseHandle(definition));
303
+ }
304
+ };
305
+ setDatabases(options.databases ?? []);
306
+ const session = {
307
+ async getLaunchContext() {
308
+ return mergeLaunchContext(launchContext);
309
+ },
310
+ async listDatabases() {
311
+ return databaseInfos.map((entry) => ({
312
+ ...entry,
313
+ capabilities: [...entry.capabilities]
314
+ }));
315
+ },
316
+ async openDatabase(databaseId) {
317
+ const database = databaseHandles.get(databaseId);
318
+ if (!database) {
319
+ throw new Error(`Unknown test database: ${databaseId}`);
320
+ }
321
+ return database;
322
+ },
323
+ onThemeChange(listener) {
324
+ themeListeners.add(listener);
325
+ return () => {
326
+ themeListeners.delete(listener);
327
+ };
328
+ },
329
+ onViewportChange(listener) {
330
+ viewportListeners.add(listener);
331
+ return () => {
332
+ viewportListeners.delete(listener);
333
+ };
334
+ },
335
+ async disconnect() {
336
+ await options.onDisconnect?.();
337
+ }
338
+ };
339
+ const bridge = {
340
+ async connect() {
341
+ return session;
342
+ }
343
+ };
344
+ return {
345
+ getLaunchContext() {
346
+ return mergeLaunchContext(launchContext);
347
+ },
348
+ setLaunchContext(patch) {
349
+ launchContext = mergeLaunchContext(launchContext, patch);
350
+ return mergeLaunchContext(launchContext);
351
+ },
352
+ listDatabaseInfos() {
353
+ return databaseInfos.map((entry) => ({
354
+ ...entry,
355
+ capabilities: [...entry.capabilities]
356
+ }));
357
+ },
358
+ setDatabases,
359
+ getDatabase(databaseId) {
360
+ const database = databaseHandles.get(databaseId);
361
+ if (!database) {
362
+ throw new Error(`Unknown test database: ${databaseId}`);
363
+ }
364
+ return database;
365
+ },
366
+ bridge,
367
+ session,
368
+ emitThemeChange(theme) {
369
+ launchContext = mergeLaunchContext(launchContext, { theme });
370
+ themeListeners.forEach((listener) => listener({ ...launchContext.theme }));
371
+ },
372
+ emitViewportChange(viewport) {
373
+ launchContext = mergeLaunchContext(launchContext, { viewport });
374
+ viewportListeners.forEach((listener) => listener({ ...viewport }));
375
+ }
376
+ };
377
+ }
378
+ function createMockMindooDBAppSession(options = {}) {
379
+ const state = createMockSessionState(options);
380
+ return {
381
+ bridge: state.bridge,
382
+ session: state.session,
383
+ getLaunchContext: state.getLaunchContext,
384
+ setLaunchContext: state.setLaunchContext,
385
+ listDatabases: state.listDatabaseInfos,
386
+ setDatabases: state.setDatabases,
387
+ emitThemeChange: state.emitThemeChange,
388
+ emitViewportChange: state.emitViewportChange
389
+ };
390
+ }
391
+ function createMockMindooDBAppBridge(options = {}) {
392
+ return createMockMindooDBAppSession(options);
393
+ }
394
+ function createFakeBridgeHost(options = {}) {
395
+ const state = createMockSessionState(options);
396
+ const customRequestHandlers = new Map(Object.entries(options.requestHandlers ?? {}));
397
+ const requests = [];
398
+ const connectedPorts = /* @__PURE__ */ new Set();
399
+ const viewSessions = /* @__PURE__ */ new Map();
400
+ const readStreams = /* @__PURE__ */ new Map();
401
+ const writeStreams = /* @__PURE__ */ new Map();
402
+ let viewCounter = 0;
403
+ let streamCounter = 0;
404
+ let previousWindowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
405
+ let previousWindowValue = typeof window === "undefined" ? void 0 : window;
406
+ let installed = false;
407
+ const hostWindow = {
408
+ postMessage(message, _targetOrigin, transfer) {
409
+ const payload = message;
410
+ if (!payload || payload.protocol !== PROTOCOL || payload.type !== "mindoodb-app:connect") {
411
+ return;
412
+ }
413
+ if (payload.launchId !== state.getLaunchContext().launchId) {
414
+ return;
415
+ }
416
+ const port = transfer?.[0];
417
+ if (!port) {
418
+ throw new Error("Expected the bridge connection to transfer a MessagePort.");
419
+ }
420
+ connectedPorts.add(port);
421
+ port.addEventListener("message", (event) => {
422
+ void handlePortMessage(port, event.data);
423
+ });
424
+ port.start();
425
+ const connected = {
426
+ protocol: PROTOCOL,
427
+ type: "mindoodb-app:connected"
428
+ };
429
+ port.postMessage(connected);
430
+ }
431
+ };
432
+ function getWindowSearch() {
433
+ return `?mindoodbAppLaunchId=${encodeURIComponent(state.getLaunchContext().launchId)}`;
434
+ }
435
+ function restoreWindow() {
436
+ if (!installed) {
437
+ return;
438
+ }
439
+ connectedPorts.forEach((port) => {
440
+ port.close();
441
+ });
442
+ connectedPorts.clear();
443
+ if (previousWindowDescriptor) {
444
+ Object.defineProperty(globalThis, "window", previousWindowDescriptor);
445
+ } else if (previousWindowValue !== void 0) {
446
+ Object.defineProperty(globalThis, "window", {
447
+ value: previousWindowValue,
448
+ configurable: true,
449
+ writable: true
450
+ });
451
+ } else {
452
+ Reflect.deleteProperty(globalThis, "window");
453
+ }
454
+ installed = false;
455
+ }
456
+ function install() {
457
+ if (installed) {
458
+ return;
459
+ }
460
+ previousWindowDescriptor = Object.getOwnPropertyDescriptor(globalThis, "window");
461
+ previousWindowValue = typeof window === "undefined" ? void 0 : window;
462
+ const baseWindow = typeof window === "undefined" ? {} : window;
463
+ const nextWindow = Object.create(baseWindow);
464
+ Object.defineProperties(nextWindow, {
465
+ parent: {
466
+ value: options.windowMode === "opener" ? baseWindow : hostWindow,
467
+ configurable: true
468
+ },
469
+ opener: {
470
+ value: options.windowMode === "opener" ? hostWindow : null,
471
+ configurable: true
472
+ },
473
+ location: {
474
+ value: {
475
+ search: getWindowSearch()
476
+ },
477
+ configurable: true
478
+ },
479
+ setTimeout: {
480
+ value: typeof window !== "undefined" && typeof window.setTimeout === "function" ? window.setTimeout.bind(window) : setTimeout,
481
+ configurable: true
482
+ },
483
+ clearTimeout: {
484
+ value: typeof window !== "undefined" && typeof window.clearTimeout === "function" ? window.clearTimeout.bind(window) : clearTimeout,
485
+ configurable: true
486
+ }
487
+ });
488
+ Object.defineProperty(globalThis, "window", {
489
+ value: nextWindow,
490
+ configurable: true,
491
+ writable: true
492
+ });
493
+ installed = true;
494
+ }
495
+ async function resolveViewHandle(databaseId, viewId) {
496
+ const key = `${databaseId}::${viewId}`;
497
+ const existing = viewSessions.get(key);
498
+ if (existing) {
499
+ return existing;
500
+ }
501
+ const database = state.getDatabase(databaseId);
502
+ const handle = await database.views.open(viewId);
503
+ viewSessions.set(key, handle);
504
+ return handle;
505
+ }
506
+ function postRpcSuccess(port, requestId, result) {
507
+ port.postMessage({
508
+ protocol: PROTOCOL,
509
+ kind: "success",
510
+ id: requestId,
511
+ result
512
+ });
513
+ }
514
+ function postRpcError(port, requestId, error) {
515
+ port.postMessage({
516
+ protocol: PROTOCOL,
517
+ kind: "error",
518
+ id: requestId,
519
+ error
520
+ });
521
+ }
522
+ function postStreamAck(port, streamId) {
523
+ port.postMessage({
524
+ protocol: PROTOCOL,
525
+ kind: "stream-ack",
526
+ streamId
527
+ });
528
+ }
529
+ function postStreamError(port, streamId, error) {
530
+ port.postMessage({
531
+ protocol: PROTOCOL,
532
+ kind: "stream-error",
533
+ streamId,
534
+ error
535
+ });
536
+ }
537
+ async function handleBuiltinRequest(request) {
538
+ const params = request.params;
539
+ switch (request.method) {
540
+ case "session.getLaunchContext":
541
+ return state.getLaunchContext();
542
+ case "session.listDatabases":
543
+ return state.listDatabaseInfos();
544
+ case "session.openDatabase":
545
+ state.getDatabase(String(params.databaseId));
546
+ return { ok: true };
547
+ case "session.disconnect":
548
+ await state.session.disconnect();
549
+ return { ok: true };
550
+ case "documents.list":
551
+ return await state.getDatabase(String(params.databaseId)).documents.list(params.query);
552
+ case "documents.get":
553
+ return await state.getDatabase(String(params.databaseId)).documents.get(String(params.docId));
554
+ case "documents.create":
555
+ return await state.getDatabase(String(params.databaseId)).documents.create(params.input);
556
+ case "documents.update":
557
+ return await state.getDatabase(String(params.databaseId)).documents.update(
558
+ String(params.docId),
559
+ params.patch
560
+ );
561
+ case "documents.delete":
562
+ return await state.getDatabase(String(params.databaseId)).documents.delete(String(params.docId));
563
+ case "documents.history.list":
564
+ return await state.getDatabase(String(params.databaseId)).documents.listHistory(String(params.docId));
565
+ case "documents.history.getAtTimestamp":
566
+ return await state.getDatabase(String(params.databaseId)).documents.getAtTimestamp(
567
+ String(params.docId),
568
+ Number(params.timestamp)
569
+ );
570
+ case "attachments.list":
571
+ return await state.getDatabase(String(params.databaseId)).attachments.list(String(params.docId));
572
+ case "attachments.remove":
573
+ return await state.getDatabase(String(params.databaseId)).attachments.remove(
574
+ String(params.docId),
575
+ String(params.attachmentName)
576
+ );
577
+ case "attachments.openReadStream": {
578
+ const stream = await state.getDatabase(String(params.databaseId)).attachments.openReadStream(
579
+ String(params.docId),
580
+ String(params.attachmentName)
581
+ );
582
+ const streamId = `read-${streamCounter += 1}`;
583
+ readStreams.set(streamId, stream);
584
+ return { streamId };
585
+ }
586
+ case "attachments.openWriteStream": {
587
+ const stream = await state.getDatabase(String(params.databaseId)).attachments.openWriteStream(
588
+ String(params.docId),
589
+ String(params.attachmentName),
590
+ typeof params.contentType === "string" ? params.contentType : void 0
591
+ );
592
+ const streamId = `write-${streamCounter += 1}`;
593
+ writeStreams.set(streamId, stream);
594
+ return { streamId };
595
+ }
596
+ case "views.create": {
597
+ const handle = await state.getDatabase(String(params.databaseId)).views.create(params.definition);
598
+ const viewId = `view-${viewCounter += 1}`;
599
+ viewSessions.set(`${String(params.databaseId)}::${viewId}`, handle);
600
+ return { viewId };
601
+ }
602
+ case "views.getDefinition":
603
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).getDefinition();
604
+ case "views.refresh":
605
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).refresh();
606
+ case "views.page":
607
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).page(
608
+ params.request
609
+ );
610
+ case "views.expansion.get":
611
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).getExpansionState();
612
+ case "views.expansion.set":
613
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).setExpansionState(
614
+ params.expansion
615
+ );
616
+ case "views.expansion.expand":
617
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).expand(String(params.rowKey));
618
+ case "views.expansion.collapse":
619
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).collapse(String(params.rowKey));
620
+ case "views.expansion.expandAll":
621
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).expandAll();
622
+ case "views.expansion.collapseAll":
623
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).collapseAll();
624
+ case "views.row.get":
625
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).getRow(String(params.rowKey));
626
+ case "views.category.get":
627
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).getCategory(
628
+ params.lookup
629
+ );
630
+ case "views.category.page":
631
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).pageCategory(
632
+ String(params.categoryKey),
633
+ params.request
634
+ );
635
+ case "views.category.documentIds":
636
+ return await (await resolveViewHandle(String(params.databaseId), String(params.viewId))).listCategoryDocumentIds(
637
+ String(params.categoryKey)
638
+ );
639
+ case "views.dispose": {
640
+ const key = `${String(params.databaseId)}::${String(params.viewId)}`;
641
+ const handle = await resolveViewHandle(String(params.databaseId), String(params.viewId));
642
+ await handle.dispose();
643
+ viewSessions.delete(key);
644
+ return void 0;
645
+ }
646
+ default:
647
+ return void 0;
648
+ }
649
+ }
650
+ async function handleStreamMessage(port, message) {
651
+ if (message.kind === "stream-read") {
652
+ const stream = readStreams.get(message.streamId);
653
+ if (!stream) {
654
+ throw Object.assign(new Error(`Unknown read stream: ${message.streamId}`), { name: "stream-not-found" });
655
+ }
656
+ const chunk = await stream.read();
657
+ port.postMessage({
658
+ protocol: PROTOCOL,
659
+ kind: "stream-chunk",
660
+ streamId: message.streamId,
661
+ chunk: chunk ? cloneBytes(chunk).buffer : void 0,
662
+ done: !chunk
663
+ });
664
+ if (!chunk) {
665
+ readStreams.delete(message.streamId);
666
+ }
667
+ return true;
668
+ }
669
+ if (message.kind === "stream-write") {
670
+ const stream = writeStreams.get(message.streamId);
671
+ if (!stream) {
672
+ throw Object.assign(new Error(`Unknown write stream: ${message.streamId}`), { name: "stream-not-found" });
673
+ }
674
+ await stream.write(new Uint8Array(message.chunk));
675
+ postStreamAck(port, message.streamId);
676
+ return true;
677
+ }
678
+ if (message.kind === "stream-close") {
679
+ const readStream = readStreams.get(message.streamId);
680
+ if (readStream) {
681
+ await readStream.close();
682
+ readStreams.delete(message.streamId);
683
+ postStreamAck(port, message.streamId);
684
+ return true;
685
+ }
686
+ const writeStream = writeStreams.get(message.streamId);
687
+ if (writeStream) {
688
+ await writeStream.close();
689
+ writeStreams.delete(message.streamId);
690
+ postStreamAck(port, message.streamId);
691
+ return true;
692
+ }
693
+ throw Object.assign(new Error(`Unknown stream: ${message.streamId}`), { name: "stream-not-found" });
694
+ }
695
+ if (message.kind === "stream-abort") {
696
+ const stream = writeStreams.get(message.streamId);
697
+ if (!stream) {
698
+ throw Object.assign(new Error(`Unknown write stream: ${message.streamId}`), { name: "stream-not-found" });
699
+ }
700
+ await stream.abort();
701
+ writeStreams.delete(message.streamId);
702
+ postStreamAck(port, message.streamId);
703
+ return true;
704
+ }
705
+ return false;
706
+ }
707
+ async function handlePortMessage(port, rawMessage) {
708
+ const message = rawMessage;
709
+ if (!message || message.protocol !== PROTOCOL) {
710
+ return;
711
+ }
712
+ if (message.kind === "request") {
713
+ requests.push(message);
714
+ try {
715
+ const customHandler = customRequestHandlers.get(message.method);
716
+ if (customHandler) {
717
+ const result = await customHandler(message.params, { host: controller, request: message, port });
718
+ postRpcSuccess(port, message.id, result);
719
+ return;
720
+ }
721
+ const builtinResult = await handleBuiltinRequest(message);
722
+ if (builtinResult === void 0 && message.method !== "views.dispose") {
723
+ postRpcError(port, message.id, {
724
+ code: "unsupported-method",
725
+ message: `No fake bridge handler is configured for ${message.method}.`
726
+ });
727
+ return;
728
+ }
729
+ postRpcSuccess(port, message.id, builtinResult);
730
+ } catch (error) {
731
+ postRpcError(port, message.id, createBridgeErrorPayload(error));
732
+ }
733
+ return;
734
+ }
735
+ try {
736
+ const handledByCustom = await options.onPortMessage?.(message, { host: controller, port });
737
+ if (handledByCustom) {
738
+ return;
739
+ }
740
+ const handled = await handleStreamMessage(port, message);
741
+ if (!handled) {
742
+ throw Object.assign(new Error(`No fake bridge port handler is configured for ${message.kind}.`), {
743
+ name: "unsupported-message"
744
+ });
745
+ }
746
+ } catch (error) {
747
+ const streamId = "streamId" in message ? String(message.streamId) : "unknown";
748
+ postStreamError(port, streamId, createBridgeErrorPayload(error));
749
+ }
750
+ }
751
+ const controller = {
752
+ bridge: state.bridge,
753
+ session: state.session,
754
+ get launchId() {
755
+ return state.getLaunchContext().launchId;
756
+ },
757
+ get requests() {
758
+ return requests;
759
+ },
760
+ install,
761
+ dispose() {
762
+ restoreWindow();
763
+ viewSessions.clear();
764
+ readStreams.clear();
765
+ writeStreams.clear();
766
+ },
767
+ emitThemeChange(theme) {
768
+ state.emitThemeChange(theme);
769
+ const payload = {
770
+ protocol: PROTOCOL,
771
+ kind: "theme-changed",
772
+ theme
773
+ };
774
+ connectedPorts.forEach((port) => port.postMessage(payload));
775
+ },
776
+ emitViewportChange(viewport) {
777
+ state.emitViewportChange(viewport);
778
+ const payload = {
779
+ protocol: PROTOCOL,
780
+ kind: "viewport-changed",
781
+ viewport
782
+ };
783
+ connectedPorts.forEach((port) => port.postMessage(payload));
784
+ },
785
+ postPortMessage(message, transfer = []) {
786
+ connectedPorts.forEach((port) => port.postMessage(message, transfer));
787
+ },
788
+ setRequestHandler(method, handler) {
789
+ customRequestHandlers.set(method, handler);
790
+ },
791
+ clearRequestHandler(method) {
792
+ customRequestHandlers.delete(method);
793
+ }
794
+ };
795
+ return controller;
796
+ }
797
+ export {
798
+ createFakeBridgeHost,
799
+ createMockMindooDBAppBridge,
800
+ createMockMindooDBAppSession
801
+ };