@happyvertical/smrt-chat 0.42.6 → 0.42.7

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,740 @@
1
+ import { DATA_SURFACE_IDENTIFIER_MAX_LENGTH, DATA_SURFACE_MAX_REQUEST_BYTES } from "@happyvertical/smrt-ui/data-surface";
2
+ //#region src/data-surface-normalizer.ts
3
+ var MAX_QUERY_LIMIT = 1e3;
4
+ var MAX_JSON_DEPTH = 16;
5
+ var MAX_JSON_CONTAINER_ITEMS = 1e3;
6
+ var PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
7
+ "__proto__",
8
+ "constructor",
9
+ "prototype"
10
+ ]);
11
+ var FORBIDDEN_BOUNDARY_KEYS = /* @__PURE__ */ new Set([
12
+ "tenant",
13
+ "tenantid",
14
+ "principalid",
15
+ "principal",
16
+ "actorid",
17
+ "auth",
18
+ "authtoken",
19
+ "authorization",
20
+ "authorizationtoken",
21
+ "authorizationheader",
22
+ "authentication",
23
+ "authenticationtoken",
24
+ "accesstoken",
25
+ "token",
26
+ "bearer",
27
+ "bearertoken",
28
+ "apikey",
29
+ "sessiontoken",
30
+ "credential",
31
+ "credentials",
32
+ "sql",
33
+ "rawsql",
34
+ "rawquery",
35
+ "where"
36
+ ]);
37
+ var KINDS = /* @__PURE__ */ new Set([
38
+ "table",
39
+ "list",
40
+ "report",
41
+ "custom"
42
+ ]);
43
+ var SENSITIVITIES = /* @__PURE__ */ new Set([
44
+ "public",
45
+ "personal",
46
+ "sensitive",
47
+ "secret"
48
+ ]);
49
+ var CAPABILITIES = /* @__PURE__ */ new Set([
50
+ "read",
51
+ "search",
52
+ "filter",
53
+ "sort",
54
+ "project"
55
+ ]);
56
+ var QUERY_MODES = /* @__PURE__ */ new Set([
57
+ "rows",
58
+ "count",
59
+ "facets"
60
+ ]);
61
+ var SELECTION_SCOPES = /* @__PURE__ */ new Set([
62
+ "current-page",
63
+ "explicit-ids",
64
+ "all-matching"
65
+ ]);
66
+ function plainObject(value, label) {
67
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype) throw new TypeError(`${label} must be a plain object`);
68
+ return value;
69
+ }
70
+ function exactKeys(value, allowed, label) {
71
+ const accepted = new Set(allowed);
72
+ for (const key of Object.keys(value)) if (!accepted.has(key)) throw new TypeError(`${label} contains unsupported field: ${key}`);
73
+ }
74
+ function stringValue(value, label) {
75
+ if (typeof value !== "string" || value.length === 0) throw new TypeError(`${label} must be a non-empty string`);
76
+ return value;
77
+ }
78
+ function identifierValue(value, label) {
79
+ const result = stringValue(value, label);
80
+ if (result.length > DATA_SURFACE_IDENTIFIER_MAX_LENGTH) throw new TypeError(`${label} is too long`);
81
+ return result;
82
+ }
83
+ function positiveInteger(value, label) {
84
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new TypeError(`${label} must be a positive safe integer`);
85
+ return value;
86
+ }
87
+ function revisionNumber(value) {
88
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new TypeError("DataSurface revision must be a non-negative integer");
89
+ return value;
90
+ }
91
+ function addBytes(budget, bytes) {
92
+ if (!budget) return;
93
+ if (bytes > budget.limit - budget.used) throw new TypeError(`DataSurface envelope cannot exceed ${budget.limit} UTF-8 bytes`);
94
+ budget.used += bytes;
95
+ }
96
+ function jsonStringByteLength(value, budget) {
97
+ let bytes = 2;
98
+ for (let index = 0; index < value.length; index += 1) {
99
+ const codeUnit = value.charCodeAt(index);
100
+ if (codeUnit === 34 || codeUnit === 92) bytes += 2;
101
+ else if (codeUnit === 8 || codeUnit === 9 || codeUnit === 10 || codeUnit === 12 || codeUnit === 13) bytes += 2;
102
+ else if (codeUnit <= 31) bytes += 6;
103
+ else if (codeUnit >= 55296 && codeUnit <= 56319) {
104
+ const next = value.charCodeAt(index + 1);
105
+ if (next >= 56320 && next <= 57343) {
106
+ bytes += 4;
107
+ index += 1;
108
+ } else bytes += 6;
109
+ } else if (codeUnit >= 56320 && codeUnit <= 57343) bytes += 6;
110
+ else if (codeUnit <= 127) bytes += 1;
111
+ else if (codeUnit <= 2047) bytes += 2;
112
+ else bytes += 3;
113
+ if (budget && bytes > budget.limit - budget.used) throw new TypeError(`DataSurface envelope cannot exceed ${budget.limit} UTF-8 bytes`);
114
+ }
115
+ addBytes(budget, bytes);
116
+ return bytes;
117
+ }
118
+ function canonicalJson(value, ancestors = /* @__PURE__ */ new Set(), depth = 0, budget) {
119
+ if (depth > MAX_JSON_DEPTH) throw new TypeError(`DataSurface values cannot exceed ${MAX_JSON_DEPTH} levels`);
120
+ if (value === null) {
121
+ addBytes(budget, 4);
122
+ return null;
123
+ }
124
+ if (typeof value === "string") {
125
+ jsonStringByteLength(value, budget);
126
+ return value;
127
+ }
128
+ if (typeof value === "boolean") {
129
+ addBytes(budget, value ? 4 : 5);
130
+ return value;
131
+ }
132
+ if (typeof value === "number") {
133
+ if (!Number.isFinite(value)) throw new TypeError("DataSurface values cannot contain non-finite numbers");
134
+ const normalized = value === 0 ? 0 : value;
135
+ addBytes(budget, String(normalized).length);
136
+ return normalized;
137
+ }
138
+ if (!value || typeof value !== "object") throw new TypeError("DataSurface values must be JSON-safe plain data");
139
+ if (ancestors.has(value)) throw new TypeError("DataSurface values cannot be circular");
140
+ ancestors.add(value);
141
+ try {
142
+ if (Array.isArray(value)) {
143
+ if (value.length > MAX_JSON_CONTAINER_ITEMS) throw new TypeError("DataSurface arrays contain too many items");
144
+ addBytes(budget, 1);
145
+ const clone2 = [];
146
+ for (const [index, entry] of value.entries()) {
147
+ if (index > 0) addBytes(budget, 1);
148
+ clone2.push(canonicalJson(entry, ancestors, depth + 1, budget));
149
+ }
150
+ addBytes(budget, 1);
151
+ return clone2;
152
+ }
153
+ const object = plainObject(value, "DataSurface value");
154
+ const clone = {};
155
+ const keys = Object.keys(object).sort();
156
+ if (keys.length > MAX_JSON_CONTAINER_ITEMS) throw new TypeError("DataSurface objects contain too many keys");
157
+ addBytes(budget, 1);
158
+ for (const [index, key] of keys.entries()) {
159
+ if (index > 0) addBytes(budget, 1);
160
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) throw new TypeError(`DataSurface values cannot contain prototype key: ${key}`);
161
+ jsonStringByteLength(key, budget);
162
+ addBytes(budget, 1);
163
+ clone[key] = canonicalJson(object[key], ancestors, depth + 1, budget);
164
+ }
165
+ addBytes(budget, 1);
166
+ return clone;
167
+ } finally {
168
+ ancestors.delete(value);
169
+ }
170
+ }
171
+ function assertRequestByteLimit(value) {
172
+ canonicalJson(value, /* @__PURE__ */ new Set(), 0, {
173
+ used: 0,
174
+ limit: DATA_SURFACE_MAX_REQUEST_BYTES
175
+ });
176
+ }
177
+ function assertDataSurfaceEnvelope(value) {
178
+ assertRequestByteLimit(value);
179
+ }
180
+ function boundarySafe(value) {
181
+ const clone = canonicalJson(value);
182
+ const inspect = (entry) => {
183
+ if (Array.isArray(entry)) for (const item of entry) inspect(item);
184
+ else if (entry && typeof entry === "object") for (const [key, item] of Object.entries(entry)) {
185
+ if (FORBIDDEN_BOUNDARY_KEYS.has(key.replaceAll(/[-_]/g, "").toLowerCase())) throw new TypeError(`DataSurface boundary field is forbidden: ${key}`);
186
+ inspect(item);
187
+ }
188
+ };
189
+ inspect(clone);
190
+ return clone;
191
+ }
192
+ function boundarySafeObject(value) {
193
+ const clone = boundarySafe(value);
194
+ if (!clone || Array.isArray(clone) || typeof clone !== "object") throw new TypeError("DataSurface snapshot state must be an object");
195
+ return clone;
196
+ }
197
+ function normalizeIdentity(value) {
198
+ const object = plainObject(value, "DataSurface identity");
199
+ exactKeys(object, [
200
+ "surfaceId",
201
+ "kind",
202
+ "subject"
203
+ ], "DataSurface identity");
204
+ const kind = stringValue(object.kind, "DataSurface kind");
205
+ if (!KINDS.has(kind)) throw new TypeError(`Unsupported DataSurface kind: ${kind}`);
206
+ let subject;
207
+ if (object.subject !== void 0) {
208
+ const source = plainObject(object.subject, "DataSurface subject");
209
+ exactKeys(source, [
210
+ "type",
211
+ "id",
212
+ "label"
213
+ ], "DataSurface subject");
214
+ subject = {
215
+ type: identifierValue(source.type, "DataSurface subject type"),
216
+ id: identifierValue(source.id, "DataSurface subject id"),
217
+ ...source.label === void 0 ? {} : { label: stringValue(source.label, "DataSurface subject label") }
218
+ };
219
+ }
220
+ return {
221
+ surfaceId: identifierValue(object.surfaceId, "DataSurface surface id"),
222
+ kind,
223
+ ...subject ? { subject } : {}
224
+ };
225
+ }
226
+ function normalizeStringArray(value, label, sort = false) {
227
+ if (!Array.isArray(value) || value.length > MAX_JSON_CONTAINER_ITEMS) throw new TypeError(`${label} must be a bounded array`);
228
+ const values = value.map((entry) => stringValue(entry, label));
229
+ if (new Set(values).size !== values.length) throw new TypeError(`${label} cannot contain duplicates`);
230
+ return sort ? values.sort() : values;
231
+ }
232
+ function normalizeIdentifierArray(value, label, sort = false) {
233
+ return normalizeStringArray(value, label, sort).map((entry) => identifierValue(entry, label));
234
+ }
235
+ function normalizeSensitivity(value, label) {
236
+ if (value === void 0) return void 0;
237
+ const sensitivity = stringValue(value, label);
238
+ if (!SENSITIVITIES.has(sensitivity)) throw new TypeError(`Unsupported DataSurface sensitivity: ${sensitivity}`);
239
+ return sensitivity;
240
+ }
241
+ function normalizeSelection(value) {
242
+ const object = plainObject(value, "DataSurface selection");
243
+ const scope = stringValue(object.scope, "DataSurface selection scope");
244
+ if (!SELECTION_SCOPES.has(scope)) throw new TypeError(`Unsupported DataSurface selection scope: ${scope}`);
245
+ if (scope === "current-page") {
246
+ exactKeys(object, ["scope"], "DataSurface current-page selection");
247
+ return { scope };
248
+ }
249
+ if (scope === "explicit-ids") {
250
+ exactKeys(object, ["scope", "rowIds"], "DataSurface explicit selection");
251
+ if (!Array.isArray(object.rowIds) || object.rowIds.length > MAX_JSON_CONTAINER_ITEMS) throw new TypeError("DataSurface explicit selection rowIds must be bounded");
252
+ const rowIds = /* @__PURE__ */ new Map();
253
+ for (const rowId of object.rowIds) {
254
+ if (typeof rowId !== "string" && (typeof rowId !== "number" || !Number.isFinite(rowId))) throw new TypeError("DataSurface row ids must be finite strings or numbers");
255
+ const normalized = typeof rowId === "string" ? identifierValue(rowId, "DataSurface row id") : rowId === 0 ? 0 : rowId;
256
+ rowIds.set(`${typeof normalized}:${String(normalized)}`, normalized);
257
+ }
258
+ return {
259
+ scope,
260
+ rowIds: [...rowIds.values()].sort((left, right) => {
261
+ if (typeof left !== typeof right) return typeof left === "number" ? -1 : 1;
262
+ if (typeof left === "number" && typeof right === "number") return left - right;
263
+ return left < right ? -1 : left > right ? 1 : 0;
264
+ })
265
+ };
266
+ }
267
+ exactKeys(object, ["scope", "queryFingerprint"], "DataSurface all-matching selection");
268
+ return {
269
+ scope,
270
+ queryFingerprint: identifierValue(object.queryFingerprint, "DataSurface query fingerprint")
271
+ };
272
+ }
273
+ function normalizeDataSurfaceDescriptor(value) {
274
+ const object = plainObject(value, "DataSurface descriptor");
275
+ exactKeys(object, [
276
+ "version",
277
+ "identity",
278
+ "schemaVersion",
279
+ "label",
280
+ "description",
281
+ "rowKey",
282
+ "columns",
283
+ "query",
284
+ "controls",
285
+ "actions",
286
+ "limits"
287
+ ], "DataSurface descriptor");
288
+ if (object.version !== 1) throw new TypeError("Unsupported DataSurface descriptor version");
289
+ if (!Array.isArray(object.columns) || object.columns.length > MAX_JSON_CONTAINER_ITEMS) throw new TypeError("DataSurface descriptor columns must be bounded");
290
+ const columns = object.columns.map((value2) => {
291
+ const column = plainObject(value2, "DataSurface column");
292
+ exactKeys(column, [
293
+ "id",
294
+ "label",
295
+ "description",
296
+ "sensitivity",
297
+ "capabilities"
298
+ ], "DataSurface column");
299
+ const capabilities = normalizeStringArray(column.capabilities, "DataSurface column capabilities", true);
300
+ if (capabilities.some((capability) => !CAPABILITIES.has(capability))) throw new TypeError("Unsupported DataSurface column capability");
301
+ return {
302
+ id: identifierValue(column.id, "DataSurface column id"),
303
+ label: stringValue(column.label, "DataSurface column label"),
304
+ ...column.description === void 0 ? {} : { description: stringValue(column.description, "DataSurface column description") },
305
+ ...column.sensitivity === void 0 ? {} : { sensitivity: normalizeSensitivity(column.sensitivity, "DataSurface column sensitivity") },
306
+ capabilities
307
+ };
308
+ });
309
+ if (new Set(columns.map((column) => column.id)).size !== columns.length) throw new TypeError("DataSurface descriptor column ids must be unique");
310
+ const query = plainObject(object.query, "DataSurface query capabilities");
311
+ exactKeys(query, ["modes", "projectableColumnIds"], "DataSurface query capabilities");
312
+ const modes = normalizeStringArray(query.modes, "DataSurface query modes", true);
313
+ if (modes.some((mode) => !QUERY_MODES.has(mode))) throw new TypeError("Unsupported DataSurface query mode");
314
+ const projectableColumnIds = normalizeIdentifierArray(query.projectableColumnIds, "DataSurface projectable column ids", true);
315
+ const knownColumns = new Set(columns.map((column) => column.id));
316
+ if (projectableColumnIds.some((columnId) => !knownColumns.has(columnId))) throw new TypeError("Unknown projectable DataSurface column");
317
+ if (!Array.isArray(object.controls) || object.controls.length > MAX_JSON_CONTAINER_ITEMS) throw new TypeError("DataSurface controls must be bounded");
318
+ const controls = object.controls.map((value2) => {
319
+ const control = plainObject(value2, "DataSurface control");
320
+ exactKeys(control, [
321
+ "id",
322
+ "label",
323
+ "description"
324
+ ], "DataSurface control");
325
+ return {
326
+ id: identifierValue(control.id, "DataSurface control id"),
327
+ label: stringValue(control.label, "DataSurface control label"),
328
+ ...control.description === void 0 ? {} : { description: stringValue(control.description, "DataSurface control description") }
329
+ };
330
+ });
331
+ if (new Set(controls.map((control) => control.id)).size !== controls.length) throw new TypeError("DataSurface control ids must be unique");
332
+ if (!Array.isArray(object.actions) || object.actions.length > MAX_JSON_CONTAINER_ITEMS) throw new TypeError("DataSurface actions must be bounded");
333
+ const actions = object.actions.map((value2) => {
334
+ const action = plainObject(value2, "DataSurface action");
335
+ exactKeys(action, [
336
+ "id",
337
+ "label",
338
+ "description",
339
+ "sensitivity",
340
+ "selectionScopes",
341
+ "requiresConfirmation"
342
+ ], "DataSurface action");
343
+ if (action.requiresConfirmation !== void 0 && typeof action.requiresConfirmation !== "boolean") throw new TypeError("DataSurface action requiresConfirmation must be boolean");
344
+ const selectionScopes = normalizeStringArray(action.selectionScopes, "DataSurface action selection scopes", true);
345
+ if (selectionScopes.some((scope) => !SELECTION_SCOPES.has(scope))) throw new TypeError("Unsupported DataSurface action selection scope");
346
+ return {
347
+ id: identifierValue(action.id, "DataSurface action id"),
348
+ label: stringValue(action.label, "DataSurface action label"),
349
+ ...action.description === void 0 ? {} : { description: stringValue(action.description, "DataSurface action description") },
350
+ ...action.sensitivity === void 0 ? {} : { sensitivity: normalizeSensitivity(action.sensitivity, "DataSurface action sensitivity") },
351
+ selectionScopes,
352
+ ...action.requiresConfirmation === void 0 ? {} : { requiresConfirmation: action.requiresConfirmation }
353
+ };
354
+ });
355
+ if (new Set(actions.map((action) => action.id)).size !== actions.length) throw new TypeError("DataSurface action ids must be unique");
356
+ const limits = plainObject(object.limits, "DataSurface limits");
357
+ exactKeys(limits, [
358
+ "maxQueryRows",
359
+ "maxQueryBytes",
360
+ "maxSelectionSize"
361
+ ], "DataSurface limits");
362
+ const maxQueryRows = positiveInteger(limits.maxQueryRows, "DataSurface maxQueryRows");
363
+ if (maxQueryRows > MAX_QUERY_LIMIT) throw new TypeError("DataSurface maxQueryRows exceeds its limit");
364
+ const rowKey = identifierValue(object.rowKey, "DataSurface row key");
365
+ if (!knownColumns.has(rowKey)) throw new TypeError("DataSurface rowKey must name a declared column");
366
+ return {
367
+ version: 1,
368
+ identity: normalizeIdentity(object.identity),
369
+ schemaVersion: positiveInteger(object.schemaVersion, "DataSurface schema version"),
370
+ label: stringValue(object.label, "DataSurface label"),
371
+ ...object.description === void 0 ? {} : { description: stringValue(object.description, "DataSurface description") },
372
+ rowKey,
373
+ columns,
374
+ query: {
375
+ modes,
376
+ projectableColumnIds
377
+ },
378
+ controls,
379
+ actions,
380
+ limits: {
381
+ maxQueryRows,
382
+ maxQueryBytes: positiveInteger(limits.maxQueryBytes, "DataSurface maxQueryBytes"),
383
+ maxSelectionSize: positiveInteger(limits.maxSelectionSize, "DataSurface maxSelectionSize")
384
+ }
385
+ };
386
+ }
387
+ function normalizeDataSurfaceSnapshot(value) {
388
+ const object = plainObject(value, "DataSurface snapshot");
389
+ exactKeys(object, [
390
+ "version",
391
+ "descriptor",
392
+ "revision",
393
+ "state",
394
+ "selection"
395
+ ], "DataSurface snapshot");
396
+ if (object.version !== 1) throw new TypeError("Unsupported DataSurface snapshot version");
397
+ return {
398
+ version: 1,
399
+ descriptor: normalizeDataSurfaceDescriptor(object.descriptor),
400
+ revision: revisionNumber(object.revision),
401
+ state: boundarySafeObject(object.state),
402
+ selection: object.selection === null || object.selection === void 0 ? null : normalizeSelection(object.selection)
403
+ };
404
+ }
405
+ function normalizeDataSurfaceVisibleCommand(value) {
406
+ const object = plainObject(value, "DataSurface visible command");
407
+ exactKeys(object, [
408
+ "version",
409
+ "commandId",
410
+ "identity",
411
+ "expectedRevision",
412
+ "controlId",
413
+ "payload"
414
+ ], "DataSurface visible command");
415
+ if (object.version !== 1) throw new TypeError("Unsupported DataSurface visible command version");
416
+ const normalized = {
417
+ version: 1,
418
+ commandId: identifierValue(object.commandId, "DataSurface command id"),
419
+ identity: normalizeIdentity(object.identity),
420
+ expectedRevision: revisionNumber(object.expectedRevision),
421
+ controlId: identifierValue(object.controlId, "DataSurface control id"),
422
+ ...object.payload === void 0 ? {} : { payload: boundarySafe(object.payload) }
423
+ };
424
+ assertRequestByteLimit(value);
425
+ assertRequestByteLimit(normalized);
426
+ return normalized;
427
+ }
428
+ //#endregion
429
+ //#region src/data-surface-bridge.ts
430
+ var DATA_SURFACE_BRIDGE_VERSION = 1;
431
+ var DEFAULT_DATA_SURFACE_BRIDGE_TTL_MS = 3e4;
432
+ function isRecord(value) {
433
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
434
+ }
435
+ function isString(value) {
436
+ return typeof value === "string" && value.length > 0 && value.length <= DATA_SURFACE_IDENTIFIER_MAX_LENGTH;
437
+ }
438
+ function isDisplayString(value) {
439
+ return typeof value === "string" && value.length > 0;
440
+ }
441
+ function isPeer(value) {
442
+ return isRecord(value) && isString(value.sessionId) && isString(value.source);
443
+ }
444
+ function isFiniteInteger(value) {
445
+ return typeof value === "number" && Number.isSafeInteger(value);
446
+ }
447
+ function isFailureReason(value) {
448
+ return value === "not_found" || value === "unsupported" || value === "stale_revision" || value === "idempotency_conflict" || value === "denied" || value === "execution_failed" || value === "non_monotonic_revision" || value === "expired" || value === "timeout" || value === "disconnected" || value === "invalid_request" || value === "source_mismatch" || value === "session_mismatch" || value === "replay_capacity_exceeded";
449
+ }
450
+ function isCommandResultReason(value) {
451
+ return value === "not_found" || value === "unsupported" || value === "stale_revision" || value === "idempotency_conflict" || value === "denied" || value === "execution_failed" || value === "non_monotonic_revision";
452
+ }
453
+ function identityOf(value) {
454
+ if (!isRecord(value) || !isString(value.surfaceId) || !isString(value.kind)) return void 0;
455
+ if (value.kind !== "table" && value.kind !== "list" && value.kind !== "report" && value.kind !== "custom") return void 0;
456
+ let subject;
457
+ if (value.subject !== void 0) {
458
+ if (!isRecord(value.subject) || !isString(value.subject.type) || !isString(value.subject.id) || value.subject.label !== void 0 && !isDisplayString(value.subject.label)) return;
459
+ subject = {
460
+ type: value.subject.type,
461
+ id: value.subject.id,
462
+ ...value.subject.label === void 0 ? {} : { label: value.subject.label }
463
+ };
464
+ }
465
+ return {
466
+ surfaceId: value.surfaceId,
467
+ kind: value.kind,
468
+ ...subject ? { subject } : {}
469
+ };
470
+ }
471
+ function identitySignature(identity) {
472
+ return JSON.stringify({
473
+ surfaceId: identity.surfaceId,
474
+ kind: identity.kind,
475
+ ...identity.subject ? { subject: {
476
+ type: identity.subject.type,
477
+ id: identity.subject.id
478
+ } } : {}
479
+ });
480
+ }
481
+ function snapshotOf(value) {
482
+ try {
483
+ assertDataSurfaceEnvelope(value);
484
+ return normalizeDataSurfaceSnapshot(value);
485
+ } catch {
486
+ return;
487
+ }
488
+ }
489
+ function isAck(value) {
490
+ return Boolean(isRecord(value) && value.type === "data-surface.ack" && value.version === 1 && isString(value.commandId) && isString(value.sessionId) && isString(value.source) && typeof value.expiresAt === "number" && Number.isFinite(value.expiresAt) && identityOf(value.identity) && typeof value.expectedRevision === "number" && Number.isSafeInteger(value.expectedRevision) && value.expectedRevision >= 0 && typeof value.ok === "boolean");
491
+ }
492
+ function isEvent(value) {
493
+ return Boolean(isRecord(value) && value.type === "data-surface.event" && value.version === 1 && isString(value.sessionId) && isString(value.source) && typeof value.sequence === "number" && Number.isSafeInteger(value.sequence) && value.sequence > 0 && identityOf(value.identity) && typeof value.revision === "number" && Number.isSafeInteger(value.revision) && value.revision >= 0 && (value.event === "registered" || value.event === "unregistered" || value.event === "command"));
494
+ }
495
+ function resultOf(value) {
496
+ if (!isRecord(value) || typeof value.ok !== "boolean") return void 0;
497
+ const allowed = /* @__PURE__ */ new Set([
498
+ "ok",
499
+ "commandId",
500
+ "identity",
501
+ "revision",
502
+ "snapshot",
503
+ "reason"
504
+ ]);
505
+ if (Object.keys(value).some((key) => !allowed.has(key))) return void 0;
506
+ if (!isString(value.commandId)) return void 0;
507
+ const identity = identityOf(value.identity);
508
+ if (!identity) return void 0;
509
+ if (value.revision !== void 0 && (!isFiniteInteger(value.revision) || value.revision < 0)) return;
510
+ if (value.ok) {
511
+ if (value.reason !== void 0) return void 0;
512
+ } else if (!isCommandResultReason(value.reason)) return;
513
+ let snapshot;
514
+ if (value.snapshot !== void 0) {
515
+ snapshot = snapshotOf(value.snapshot);
516
+ if (!snapshot) return void 0;
517
+ if (identitySignature(snapshot.descriptor.identity) !== identitySignature(identity) || value.revision !== void 0 && snapshot.revision !== value.revision) return;
518
+ }
519
+ return {
520
+ ok: value.ok,
521
+ commandId: value.commandId,
522
+ identity,
523
+ ...value.revision === void 0 ? {} : { revision: value.revision },
524
+ ...snapshot === void 0 ? {} : { snapshot },
525
+ ...value.reason === void 0 ? {} : { reason: value.reason }
526
+ };
527
+ }
528
+ function eventOf(value) {
529
+ if (!isEvent(value)) return void 0;
530
+ try {
531
+ assertDataSurfaceEnvelope(value);
532
+ const identity = identityOf(value.identity);
533
+ if (!identity) return void 0;
534
+ let command;
535
+ if (value.command !== void 0) command = normalizeDataSurfaceVisibleCommand(value.command);
536
+ const result = value.result === void 0 ? void 0 : resultOf(value.result);
537
+ if (value.result !== void 0 && result === void 0) return void 0;
538
+ if (value.event === "command" && (!command || !result)) return void 0;
539
+ if (value.event !== "command" && (command || result)) return void 0;
540
+ if (value.event === "command" && (result?.revision === void 0 || value.revision !== result.revision || result.ok && result.revision < (command?.expectedRevision ?? 0))) return;
541
+ if (command && identitySignature(command.identity) !== identitySignature(identity) || result && (identitySignature(result.identity) !== identitySignature(identity) || command !== void 0 && result.commandId !== command.commandId)) return;
542
+ return {
543
+ type: "data-surface.event",
544
+ version: 1,
545
+ sessionId: value.sessionId,
546
+ source: value.source,
547
+ sequence: value.sequence,
548
+ identity,
549
+ revision: value.revision,
550
+ ...command === void 0 ? {} : { command },
551
+ ...result === void 0 ? {} : { result },
552
+ event: value.event
553
+ };
554
+ } catch {
555
+ return;
556
+ }
557
+ }
558
+ function fallbackAck(command, sessionId, source, reason, expiresAt) {
559
+ return {
560
+ type: "data-surface.ack",
561
+ version: 1,
562
+ commandId: command.commandId,
563
+ sessionId,
564
+ source,
565
+ expiresAt,
566
+ identity: command.identity,
567
+ expectedRevision: command.expectedRevision,
568
+ ok: false,
569
+ reason
570
+ };
571
+ }
572
+ function commandSignature(command) {
573
+ return JSON.stringify(command);
574
+ }
575
+ function createDataSurfaceCommandBridge(options) {
576
+ if (!isString(options.sessionId) || !isString(options.source) || !isString(options.peerSource)) throw new TypeError("DataSurface bridge session/source ids are required");
577
+ const now = options.now ?? (() => Date.now());
578
+ const ttlMs = options.ttlMs ?? 3e4;
579
+ const timeoutMs = options.timeoutMs ?? ttlMs;
580
+ if (!Number.isSafeInteger(ttlMs) || ttlMs <= 0 || !Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > ttlMs) throw new RangeError("Invalid DataSurface bridge bounds");
581
+ const pending = /* @__PURE__ */ new Map();
582
+ const inflight = /* @__PURE__ */ new Map();
583
+ const listeners = /* @__PURE__ */ new Set();
584
+ let state = "connected";
585
+ let disposed = false;
586
+ let lastSequence = 0;
587
+ const rejectPending = (reason) => {
588
+ for (const [commandId, item] of pending) {
589
+ clearTimeout(item.timer);
590
+ pending.delete(commandId);
591
+ inflight.delete(commandId);
592
+ item.resolve(fallbackAck(item.command, options.sessionId, options.peerSource, reason, item.expiresAt));
593
+ }
594
+ };
595
+ const validateAck = (value, item) => {
596
+ const request = item.request;
597
+ if (value.commandId !== request.commandId || value.sessionId !== request.sessionId || value.source !== options.peerSource || value.expiresAt !== request.expiresAt || value.expectedRevision !== request.expectedRevision || identitySignature(value.identity) !== identitySignature(request.identity)) return;
598
+ if (value.revision !== void 0 && (!isFiniteInteger(value.revision) || value.revision < 0)) return;
599
+ if (value.ok) {
600
+ if (value.reason !== void 0 || value.revision === void 0 || value.snapshot === void 0 || value.ok && value.revision < request.expectedRevision) return;
601
+ } else if (!isFailureReason(value.reason)) return;
602
+ let snapshot;
603
+ if (value.snapshot !== void 0) {
604
+ snapshot = snapshotOf(value.snapshot);
605
+ if (snapshot === void 0) return void 0;
606
+ if (identitySignature(snapshot.descriptor.identity) !== identitySignature(request.identity) || value.ok && snapshot.revision < request.expectedRevision || value.revision !== void 0 && snapshot.revision !== value.revision) return;
607
+ }
608
+ return {
609
+ type: "data-surface.ack",
610
+ version: 1,
611
+ commandId: request.commandId,
612
+ sessionId: request.sessionId,
613
+ source: options.peerSource,
614
+ expiresAt: request.expiresAt,
615
+ identity: request.identity,
616
+ expectedRevision: request.expectedRevision,
617
+ ok: value.ok,
618
+ ...value.revision === void 0 ? {} : { revision: value.revision },
619
+ ...snapshot === void 0 ? {} : { snapshot },
620
+ ...value.reason === void 0 ? {} : { reason: value.reason }
621
+ };
622
+ };
623
+ const receive = (value, peer) => {
624
+ if (disposed || !isPeer(peer) || peer.sessionId !== options.sessionId || peer.source !== options.peerSource) return;
625
+ if (isAck(value)) {
626
+ if (value.sessionId !== options.sessionId || value.source !== options.peerSource) return;
627
+ const item = pending.get(value.commandId);
628
+ if (!item) return;
629
+ const validated = validateAck(value, item);
630
+ if (!validated) return;
631
+ pending.delete(value.commandId);
632
+ inflight.delete(value.commandId);
633
+ clearTimeout(item.timer);
634
+ if (value.expiresAt <= now()) item.resolve(fallbackAck(item.command, options.sessionId, options.peerSource, "expired", item.expiresAt));
635
+ else item.resolve(validated);
636
+ return;
637
+ }
638
+ const event = eventOf(value);
639
+ if (!event) return;
640
+ if (event.sessionId !== options.sessionId || event.source !== options.peerSource || event.sequence <= lastSequence) return;
641
+ lastSequence = event.sequence;
642
+ for (const listener of listeners) listener(event);
643
+ };
644
+ const unsubscribeTransport = options.transport.subscribe(receive);
645
+ const unsubscribeStatus = options.transport.subscribeStatus?.((next) => {
646
+ state = next;
647
+ if (next === "disconnected") rejectPending("disconnected");
648
+ });
649
+ const send = async (command) => {
650
+ let normalized;
651
+ try {
652
+ normalized = normalizeDataSurfaceVisibleCommand(command);
653
+ } catch {
654
+ return fallbackAck(command, options.sessionId, options.peerSource, "invalid_request", now());
655
+ }
656
+ if (disposed || state !== "connected") return fallbackAck(normalized, options.sessionId, options.peerSource, "disconnected", now());
657
+ let allowed = false;
658
+ try {
659
+ allowed = await options.authorize(normalized);
660
+ } catch {
661
+ allowed = false;
662
+ }
663
+ if (disposed || state !== "connected") return fallbackAck(normalized, options.sessionId, options.peerSource, "disconnected", now());
664
+ if (!allowed) return fallbackAck(normalized, options.sessionId, options.peerSource, "denied", now());
665
+ const signature = commandSignature(normalized);
666
+ const existing = inflight.get(normalized.commandId);
667
+ if (existing) return existing.signature === signature ? existing.promise : fallbackAck(normalized, options.sessionId, options.peerSource, "idempotency_conflict", now());
668
+ const expiresAt = now() + ttlMs;
669
+ const promise = new Promise((resolve) => {
670
+ const timer = setTimeout(() => {
671
+ if (!pending.delete(normalized.commandId)) return;
672
+ inflight.delete(normalized.commandId);
673
+ resolve(fallbackAck(normalized, options.sessionId, options.peerSource, "timeout", expiresAt));
674
+ }, timeoutMs);
675
+ const request = {
676
+ type: "data-surface.command",
677
+ version: 1,
678
+ commandId: normalized.commandId,
679
+ sessionId: options.sessionId,
680
+ source: options.source,
681
+ expiresAt,
682
+ identity: normalized.identity,
683
+ expectedRevision: normalized.expectedRevision,
684
+ controlId: normalized.controlId,
685
+ ...normalized.payload === void 0 ? {} : { payload: normalized.payload }
686
+ };
687
+ pending.set(normalized.commandId, {
688
+ command: normalized,
689
+ request,
690
+ expiresAt,
691
+ resolve,
692
+ timer
693
+ });
694
+ const handleSendFailure = () => {
695
+ if (!pending.delete(normalized.commandId)) return;
696
+ inflight.delete(normalized.commandId);
697
+ clearTimeout(timer);
698
+ resolve(fallbackAck(normalized, options.sessionId, options.peerSource, "disconnected", expiresAt));
699
+ };
700
+ try {
701
+ Promise.resolve(options.transport.send(request)).catch(handleSendFailure);
702
+ } catch {
703
+ handleSendFailure();
704
+ }
705
+ });
706
+ inflight.set(normalized.commandId, {
707
+ signature,
708
+ promise
709
+ });
710
+ if (!pending.has(normalized.commandId)) inflight.delete(normalized.commandId);
711
+ return promise;
712
+ };
713
+ return {
714
+ sessionId: options.sessionId,
715
+ source: options.source,
716
+ get state() {
717
+ return state;
718
+ },
719
+ send,
720
+ subscribe(listener) {
721
+ listeners.add(listener);
722
+ return () => listeners.delete(listener);
723
+ },
724
+ dispose() {
725
+ if (disposed) return;
726
+ disposed = true;
727
+ unsubscribeTransport();
728
+ unsubscribeStatus?.();
729
+ rejectPending("disconnected");
730
+ inflight.clear();
731
+ listeners.clear();
732
+ }
733
+ };
734
+ }
735
+ var createServerDataSurfaceBridge = createDataSurfaceCommandBridge;
736
+ var createDataSurfaceBridge = createDataSurfaceCommandBridge;
737
+ //#endregion
738
+ export { createServerDataSurfaceBridge as a, createDataSurfaceCommandBridge as i, DEFAULT_DATA_SURFACE_BRIDGE_TTL_MS as n, createDataSurfaceBridge as r, DATA_SURFACE_BRIDGE_VERSION as t };
739
+
740
+ //# sourceMappingURL=data-surface-bridge-BJomMxjS.js.map