@xyo-network/dapp-kit-port 3.0.1 → 3.1.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.
@@ -2,11 +2,14 @@
2
2
  import {
3
3
  canonicalizeIJson,
4
4
  PortAttachmentGrantZod,
5
+ PortFrameSchemaVersionZod,
6
+ portStreamResumeNextSequence,
7
+ portStreamTerminalMatches,
5
8
  validatePortFrame
6
9
  } from "@xyo-network/dapp-kit";
7
10
 
8
11
  // src/applicationPortModel.ts
9
- import { PortFrameSchema, PortFrameZod } from "@xyo-network/dapp-kit";
12
+ import { PortFrameAnyVersionZod, PortFrameSchema } from "@xyo-network/dapp-kit";
10
13
  var ApplicationPortProxyErrorCode = {
11
14
  cancelled: "port.cancelled",
12
15
  detached: "port.detached",
@@ -58,7 +61,7 @@ function applicationPortOperationGrant(grant, portId, operationId) {
58
61
  function applicationPortFrameBase(grant, frame) {
59
62
  return {
60
63
  schema: PortFrameSchema,
61
- schemaVersion: 1,
64
+ schemaVersion: frame.schemaVersion ?? 1,
62
65
  dappId: grant.dappId,
63
66
  protocolVersion: grant.protocolVersion,
64
67
  planId: grant.planId,
@@ -72,7 +75,7 @@ function applicationPortFrameBase(grant, frame) {
72
75
  };
73
76
  }
74
77
  function parseApplicationPortFrame(value) {
75
- return PortFrameZod.parse(value);
78
+ return PortFrameAnyVersionZod.parse(value);
76
79
  }
77
80
  function applicationPortErrorFrame(grant, frame, error) {
78
81
  return parseApplicationPortFrame({
@@ -87,13 +90,21 @@ var ApplicationPortStreamInstance = class {
87
90
  requestId;
88
91
  streamId;
89
92
  client;
93
+ openingProgress;
90
94
  state;
91
95
  constructor(client, state, streamId) {
92
96
  this.client = client;
97
+ this.openingProgress = Object.freeze({
98
+ nextSequence: state.lastAcceptedSequence + 1,
99
+ ...state.resumeCursor === void 0 ? {} : { resumeCursor: state.resumeCursor }
100
+ });
93
101
  this.requestId = state.frame.requestId;
94
102
  this.state = state;
95
103
  this.streamId = streamId;
96
104
  }
105
+ get initialProgress() {
106
+ return this.openingProgress;
107
+ }
97
108
  [Symbol.asyncIterator]() {
98
109
  return this;
99
110
  }
@@ -119,7 +130,11 @@ var ApplicationPortClientInstance = class {
119
130
  options;
120
131
  requests = /* @__PURE__ */ new Map();
121
132
  constructor(options) {
122
- this.options = { ...options, grant: PortAttachmentGrantZod.parse(options.grant) };
133
+ this.options = {
134
+ ...options,
135
+ frameSchemaVersion: PortFrameSchemaVersionZod.parse(options.frameSchemaVersion ?? 1),
136
+ grant: PortAttachmentGrantZod.parse(options.grant)
137
+ };
123
138
  this.listener = (message) => this.receive(message);
124
139
  options.endpoint.addMessageListener(this.listener);
125
140
  options.endpoint.start?.();
@@ -188,17 +203,21 @@ var ApplicationPortClientInstance = class {
188
203
  );
189
204
  }
190
205
  this.verifyCredit(options.credit, 0, grant);
206
+ const version = this.options.frameSchemaVersion ?? 1;
207
+ if (version === 1 && (options.lastAcceptedSequence === void 0 || options.nextSequence !== void 0) || version === 2 && (options.nextSequence === void 0 || options.lastAcceptedSequence !== void 0)) {
208
+ throw new ApplicationPortProxyError(ApplicationPortProxyErrorCode.streamStateInvalid, "Resume progress does not match the selected frame schema version");
209
+ }
191
210
  const frame = parseApplicationPortFrame({
192
211
  ...this.newFrameBase(options),
193
212
  frameKind: "stream-resume",
194
213
  streamId: options.streamId,
195
- lastAcceptedSequence: options.lastAcceptedSequence,
214
+ ...version === 2 ? { nextSequence: options.nextSequence } : { lastAcceptedSequence: options.lastAcceptedSequence },
196
215
  resumeCursor: options.resumeCursor,
197
216
  credit: options.credit
198
217
  });
199
218
  const state = this.streamState(frame, grant, options.credit);
200
219
  state.availableCredit = options.credit;
201
- state.lastAcceptedSequence = options.lastAcceptedSequence;
220
+ state.lastAcceptedSequence = portStreamResumeNextSequence(frame) - 1;
202
221
  state.resumeCursor = options.resumeCursor;
203
222
  state.streamId = options.streamId;
204
223
  this.requests.set(frame.requestId, state);
@@ -269,12 +288,13 @@ var ApplicationPortClientInstance = class {
269
288
  }
270
289
  return applicationPortFrameBase(this.options.grant, {
271
290
  ...options,
291
+ schemaVersion: this.options.frameSchemaVersion,
272
292
  requestId: this.options.nextRequestId()
273
293
  });
274
294
  }
275
295
  openStream(state, frame) {
276
296
  const grant = state.grant;
277
- const resumeMismatch = state.lastAcceptedSequence > 0 && frame.nextSequence !== state.lastAcceptedSequence + 1;
297
+ const resumeMismatch = state.frame.frameKind === "stream-resume" && frame.nextSequence !== state.lastAcceptedSequence + 1;
278
298
  if (state.isOpen || frame.resumable !== grant.resumable || state.streamId !== void 0 && state.streamId !== frame.streamId || resumeMismatch) {
279
299
  this.fail(state, new ApplicationPortProxyError(
280
300
  ApplicationPortProxyErrorCode.streamStateInvalid,
@@ -299,6 +319,7 @@ var ApplicationPortClientInstance = class {
299
319
  if (this.isClosed) return;
300
320
  const result = validatePortFrame(value, {
301
321
  authenticatedPrincipalId: this.options.authenticatedPrincipalId,
322
+ frameSchemaVersion: this.options.frameSchemaVersion,
302
323
  grant: this.options.grant,
303
324
  now: this.options.now()
304
325
  });
@@ -437,7 +458,7 @@ var ApplicationPortClientInstance = class {
437
458
  };
438
459
  }
439
460
  terminateStream(state, frame) {
440
- if (frame.lastSequence !== Math.max(0, state.lastAcceptedSequence)) {
461
+ if (!portStreamTerminalMatches(frame, state.lastAcceptedSequence + 1)) {
441
462
  this.fail(state, new ApplicationPortProxyError(
442
463
  ApplicationPortProxyErrorCode.streamStateInvalid,
443
464
  "Stream terminal sequence does not match accepted items"
@@ -464,29 +485,31 @@ function createApplicationPortClient(options) {
464
485
  // src/applicationPortHost.ts
465
486
  import {
466
487
  PortAttachmentGrantZod as PortAttachmentGrantZod2,
488
+ PortFrameSchemaVersionZod as PortFrameSchemaVersionZod2,
489
+ PortStreamProgressUnsupportedCode,
490
+ portStreamResumeNextSequence as portStreamResumeNextSequence2,
491
+ portStreamTerminalProgress,
467
492
  validatePortFrame as validatePortFrame2
468
493
  } from "@xyo-network/dapp-kit";
469
- function registeredOperations(options) {
494
+
495
+ // src/applicationPortOperations.ts
496
+ function registeredApplicationPortOperations(attachment, values) {
470
497
  const operations2 = /* @__PURE__ */ new Map();
471
- for (const operation of options.operations) {
498
+ for (const operation of values) {
472
499
  const key2 = applicationPortOperationKey(operation.portId, operation.operationId);
473
- const grant = applicationPortOperationGrant(options.grant, operation.portId, operation.operationId);
500
+ const grant = applicationPortOperationGrant(attachment, operation.portId, operation.operationId);
474
501
  if (operations2.has(key2)) {
475
- throw new ApplicationPortProxyError(
476
- ApplicationPortProxyErrorCode.duplicateRequest,
477
- `Application-port operation is registered more than once: ${operation.portId}.${operation.operationId}`
478
- );
502
+ throw new ApplicationPortProxyError(ApplicationPortProxyErrorCode.duplicateRequest, `Application-port operation is registered more than once: ${operation.portId}.${operation.operationId}`);
479
503
  }
480
504
  if (grant?.streaming !== operation.streaming) {
481
- throw new ApplicationPortProxyError(
482
- ApplicationPortProxyErrorCode.operationKindMismatch,
483
- `Registered operation conflicts with attachment grant: ${operation.portId}.${operation.operationId}`
484
- );
505
+ throw new ApplicationPortProxyError(ApplicationPortProxyErrorCode.operationKindMismatch, `Registered operation conflicts with attachment grant: ${operation.portId}.${operation.operationId}`);
485
506
  }
486
507
  operations2.set(key2, operation);
487
508
  }
488
509
  return operations2;
489
510
  }
511
+
512
+ // src/applicationPortHost.ts
490
513
  var ApplicationPortHostInstance = class {
491
514
  cleanupErrors = [];
492
515
  closePromise;
@@ -497,8 +520,12 @@ var ApplicationPortHostInstance = class {
497
520
  ownedRequests = /* @__PURE__ */ new Set();
498
521
  requests = /* @__PURE__ */ new Map();
499
522
  constructor(options) {
500
- this.options = { ...options, grant: PortAttachmentGrantZod2.parse(options.grant) };
501
- this.operations = registeredOperations(this.options);
523
+ this.options = {
524
+ ...options,
525
+ frameSchemaVersion: PortFrameSchemaVersionZod2.parse(options.frameSchemaVersion ?? 1),
526
+ grant: PortAttachmentGrantZod2.parse(options.grant)
527
+ };
528
+ this.operations = registeredApplicationPortOperations(this.options.grant, this.options.operations);
502
529
  this.listener = (message) => void this.receive(message);
503
530
  options.endpoint.addMessageListener(this.listener);
504
531
  options.endpoint.start?.();
@@ -630,7 +657,7 @@ var ApplicationPortHostInstance = class {
630
657
  isCancelled: false,
631
658
  isTerminal: false,
632
659
  kind: "stream",
633
- lastSequence: frame.frameKind === "stream-resume" ? frame.lastAcceptedSequence : -1,
660
+ lastSequence: frame.frameKind === "stream-resume" ? portStreamResumeNextSequence2(frame) - 1 : -1,
634
661
  ...frame.frameKind === "stream-resume" ? { resumeCursor: frame.resumeCursor } : {}
635
662
  };
636
663
  this.requests.set(frame.requestId, state);
@@ -667,6 +694,7 @@ var ApplicationPortHostInstance = class {
667
694
  if (this.isClosed) return;
668
695
  const result = validatePortFrame2(value, {
669
696
  authenticatedPrincipalId: this.options.authenticatedPrincipalId,
697
+ frameSchemaVersion: this.options.frameSchemaVersion,
670
698
  grant: this.options.grant,
671
699
  now: this.options.now()
672
700
  });
@@ -783,18 +811,25 @@ var ApplicationPortHostInstance = class {
783
811
  const read = await source.next(state.controller.signal);
784
812
  if (state.isCancelled || this.isClosed) return false;
785
813
  if (read.done) {
814
+ if (state.frame.schemaVersion === 1 && state.lastSequence < 0) {
815
+ this.terminateStreamWithError(state, void 0, PortStreamProgressUnsupportedCode);
816
+ return false;
817
+ }
786
818
  state.isTerminal = true;
787
819
  this.post(parseApplicationPortFrame({
788
820
  ...applicationPortFrameBase(this.options.grant, state.frame),
789
821
  frameKind: "stream-close",
790
822
  streamId: source.streamId,
791
- lastSequence: Math.max(0, state.lastSequence),
823
+ ...portStreamTerminalProgress(state.frame.schemaVersion, state.lastSequence + 1),
792
824
  ...read.resumeCursor === void 0 ? {} : { resumeCursor: read.resumeCursor }
793
825
  }));
794
826
  this.requests.delete(state.frame.requestId);
795
827
  this.ownedRequests.delete(state);
796
828
  return false;
797
829
  }
830
+ if (!Number.isSafeInteger(state.lastSequence + 2)) {
831
+ throw new ApplicationPortProxyError(ApplicationPortProxyErrorCode.streamStateInvalid, "Stream successor exceeds safe integer progress");
832
+ }
798
833
  if (source.resumable && read.value.resumeCursor === void 0) {
799
834
  throw new ApplicationPortProxyError(
800
835
  ApplicationPortProxyErrorCode.streamStateInvalid,
@@ -831,7 +866,8 @@ var ApplicationPortHostInstance = class {
831
866
  }
832
867
  return await operation.resume({
833
868
  ...context,
834
- lastAcceptedSequence: frame.lastAcceptedSequence,
869
+ nextSequence: portStreamResumeNextSequence2(frame),
870
+ ...portStreamResumeNextSequence2(frame) > 0 ? { lastAcceptedSequence: portStreamResumeNextSequence2(frame) - 1 } : {},
835
871
  resumeCursor: frame.resumeCursor,
836
872
  streamId: frame.streamId
837
873
  });
@@ -855,11 +891,12 @@ var ApplicationPortHostInstance = class {
855
891
  if (source === void 0 || state.isTerminal || this.isClosed) return;
856
892
  state.isTerminal = true;
857
893
  const error = code === void 0 ? this.mapError(state.frame, cause) : { code, retriable: false };
858
- this.post(parseApplicationPortFrame({
894
+ if (state.frame.schemaVersion === 1 && state.lastSequence < 0) this.postError(state.frame, PortStreamProgressUnsupportedCode);
895
+ else this.post(parseApplicationPortFrame({
859
896
  ...applicationPortFrameBase(this.options.grant, state.frame),
860
897
  frameKind: "stream-error",
861
898
  streamId: source.streamId,
862
- lastSequence: Math.max(0, state.lastSequence),
899
+ ...portStreamTerminalProgress(state.frame.schemaVersion, state.lastSequence + 1),
863
900
  ...state.resumeCursor === void 0 ? {} : { resumeCursor: state.resumeCursor },
864
901
  error
865
902
  }));
@@ -869,8 +906,8 @@ var ApplicationPortHostInstance = class {
869
906
  void this.cleanupStream(state, ApplicationPortProxyErrorCode.hostFailure);
870
907
  }
871
908
  verifyStreamSource(source, frame, grant) {
872
- const resumeMismatch = frame.frameKind === "stream-resume" && (source.streamId !== frame.streamId || source.nextSequence !== frame.lastAcceptedSequence + 1);
873
- if (source.resumable !== grant.resumable || source.resumable && source.resumeCursor === void 0 || resumeMismatch) {
909
+ const resumeMismatch = frame.frameKind === "stream-resume" && (source.streamId !== frame.streamId || source.nextSequence !== portStreamResumeNextSequence2(frame));
910
+ if (!Number.isSafeInteger(source.nextSequence) || source.nextSequence < 0 || source.resumable !== grant.resumable || source.resumable && source.resumeCursor === void 0 || resumeMismatch) {
874
911
  throw new ApplicationPortProxyError(
875
912
  ApplicationPortProxyErrorCode.streamStateInvalid,
876
913
  "Stream source conflicts with its grant or resume request"
@@ -888,7 +925,7 @@ import {
888
925
  IdentifierZod,
889
926
  parseStrictJsonText,
890
927
  PortAttachmentGrantZod as PortAttachmentGrantZod3,
891
- PortFrameZod as PortFrameZod2
928
+ PortFrameAnyVersionZod as PortFrameAnyVersionZod2
892
929
  } from "@xyo-network/dapp-kit";
893
930
  import { asSchema } from "@xyo-network/sdk";
894
931
  import { z } from "zod";
@@ -900,6 +937,15 @@ var ApplicationPortTransportHelloZod = z.strictObject({
900
937
  maximumFrameBytes: z.number().int().positive().max(16 * 1024 * 1024),
901
938
  grant: PortAttachmentGrantZod3
902
939
  });
940
+ var ApplicationPortTransportHelloV2Zod = z.strictObject({
941
+ ...ApplicationPortTransportHelloZod.shape,
942
+ schemaVersion: z.literal(2),
943
+ frameSchemaVersion: z.literal(2)
944
+ });
945
+ var ApplicationPortTransportHelloAnyVersionZod = z.discriminatedUnion("schemaVersion", [
946
+ ApplicationPortTransportHelloZod,
947
+ ApplicationPortTransportHelloV2Zod
948
+ ]);
903
949
  var ApplicationPortTransportErrorZod = z.strictObject({
904
950
  schema: z.literal(ApplicationPortTransportSchema),
905
951
  schemaVersion: z.literal(1),
@@ -908,8 +954,8 @@ var ApplicationPortTransportErrorZod = z.strictObject({
908
954
  retriable: z.boolean()
909
955
  });
910
956
  var ApplicationPortWireMessageZod = z.union([
911
- PortFrameZod2,
912
- ApplicationPortTransportHelloZod,
957
+ PortFrameAnyVersionZod2,
958
+ ApplicationPortTransportHelloAnyVersionZod,
913
959
  ApplicationPortTransportErrorZod
914
960
  ]);
915
961
  var ApplicationPortWireErrorCode = {
@@ -981,6 +1027,7 @@ function serializeApplicationPortWireMessage(message, maximumFrameBytes) {
981
1027
  import {
982
1028
  DappEngineSystemReferenceZod,
983
1029
  PortAttachmentGrantZod as PortAttachmentGrantZod4,
1030
+ PortFrameSchemaVersionZod as PortFrameSchemaVersionZod3,
984
1031
  validateDappEngineSystemReference
985
1032
  } from "@xyo-network/dapp-kit";
986
1033
 
@@ -1147,6 +1194,7 @@ function operations(bindings, grant, readTarget, admitRecovery) {
1147
1194
  });
1148
1195
  }
1149
1196
  async function createDappEngineApplicationPortHost(options) {
1197
+ const frameSchemaVersion = PortFrameSchemaVersionZod3.parse(options.frameSchemaVersion ?? 1);
1150
1198
  const reference = DappEngineSystemReferenceZod.parse(options.reference);
1151
1199
  const grant = PortAttachmentGrantZod4.parse(options.grant);
1152
1200
  const readStatus = options.engine.status.bind(options.engine);
@@ -1161,6 +1209,7 @@ async function createDappEngineApplicationPortHost(options) {
1161
1209
  const host = createApplicationPortHost({
1162
1210
  authenticatedPrincipalId: options.authenticatedPrincipalId,
1163
1211
  endpoint: deferred.endpoint,
1212
+ frameSchemaVersion,
1164
1213
  grant,
1165
1214
  now: options.now,
1166
1215
  onProtocolError: options.onProtocolError,
@@ -1205,7 +1254,7 @@ async function createDappEngineApplicationPortHost(options) {
1205
1254
  }
1206
1255
 
1207
1256
  // src/webSocketApplicationPortClient.ts
1208
- import { PortFrameZod as PortFrameZod3 } from "@xyo-network/dapp-kit";
1257
+ import { PortFrameAnyVersionZod as PortFrameAnyVersionZod3, PortFrameSchemaVersionZod as PortFrameSchemaVersionZod4 } from "@xyo-network/dapp-kit";
1209
1258
  var WebSocketApplicationPortClientErrorCode = {
1210
1259
  aborted: "port.websocket-aborted",
1211
1260
  closed: "port.websocket-closed",
@@ -1294,7 +1343,7 @@ var BrowserWebSocketApplicationPortEndpoint = class {
1294
1343
  "application-port WebSocket is not open"
1295
1344
  );
1296
1345
  }
1297
- const frame = PortFrameZod3.parse(message);
1346
+ const frame = PortFrameAnyVersionZod3.parse(message);
1298
1347
  this.socket.send(serializeApplicationPortWireMessage(frame, this.maximumFrameBytes));
1299
1348
  }
1300
1349
  receive = (event) => {
@@ -1325,6 +1374,8 @@ async function connectWebSocketApplicationPort(options) {
1325
1374
  "application-port connection was aborted before launch"
1326
1375
  );
1327
1376
  }
1377
+ const acceptedVersions = new Set((options.acceptedFrameSchemaVersions ?? [1, 2]).map((version) => PortFrameSchemaVersionZod4.parse(version)));
1378
+ if (acceptedVersions.size === 0) throw new TypeError("at least one application-port frame schema version must be accepted");
1328
1379
  const maximumFrameBytes = positiveInteger(
1329
1380
  options.maximumFrameBytes ?? 256 * 1024,
1330
1381
  "maximum frame bytes",
@@ -1375,7 +1426,8 @@ async function connectWebSocketApplicationPort(options) {
1375
1426
  ));
1376
1427
  return;
1377
1428
  }
1378
- const parsed = ApplicationPortTransportHelloZod.parse(message);
1429
+ const parsed = ApplicationPortTransportHelloAnyVersionZod.parse(message);
1430
+ if (!acceptedVersions.has(parsed.schemaVersion)) throw new TypeError("server selected an unsupported application-port frame schema version");
1379
1431
  assertExpectedGrant(parsed.grant, options.expected, options.now());
1380
1432
  cleanup();
1381
1433
  resolve(parsed);
@@ -1409,6 +1461,7 @@ async function connectWebSocketApplicationPort(options) {
1409
1461
  authenticatedPrincipalId: hello.grant.principalId,
1410
1462
  endpoint,
1411
1463
  grant: hello.grant,
1464
+ frameSchemaVersion: hello.schemaVersion,
1412
1465
  nextRequestId: options.nextRequestId,
1413
1466
  now: options.now,
1414
1467
  onProtocolError: options.onProtocolError
@@ -1421,6 +1474,7 @@ async function connectWebSocketApplicationPort(options) {
1421
1474
  return {
1422
1475
  client,
1423
1476
  grant: hello.grant,
1477
+ frameSchemaVersion: hello.schemaVersion,
1424
1478
  whenClosed: closed.promise,
1425
1479
  close: () => {
1426
1480
  if (isClosed) return;
@@ -1441,6 +1495,8 @@ export {
1441
1495
  ApplicationPortProxyErrorCode,
1442
1496
  ApplicationPortRemoteError,
1443
1497
  ApplicationPortTransportErrorZod,
1498
+ ApplicationPortTransportHelloAnyVersionZod,
1499
+ ApplicationPortTransportHelloV2Zod,
1444
1500
  ApplicationPortTransportHelloZod,
1445
1501
  ApplicationPortTransportSchema,
1446
1502
  ApplicationPortWireError,