@hydranium/protocol 1.0.0-next.187 → 1.0.0-next.189

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 (78) hide show
  1. package/lib/client/data-connection.js +2 -2
  2. package/lib/client/data-connection.js.map +1 -1
  3. package/lib/client/data-port.d.ts +1 -1
  4. package/lib/client/data-session.d.ts +39 -21
  5. package/lib/client/data-session.d.ts.map +1 -1
  6. package/lib/client/data-session.js +69 -43
  7. package/lib/client/data-session.js.map +1 -1
  8. package/lib/data/data-protocol-methods.d.ts.map +1 -1
  9. package/lib/data/data-protocol-methods.js.map +1 -1
  10. package/lib/data/data-server-protocol.d.ts +10 -9
  11. package/lib/data/data-server-protocol.d.ts.map +1 -1
  12. package/lib/data/events.d.ts +11 -5
  13. package/lib/data/events.d.ts.map +1 -1
  14. package/lib/data/requests.d.ts +4 -4
  15. package/lib/data/requests.d.ts.map +1 -1
  16. package/lib/errors.d.ts +31 -25
  17. package/lib/errors.d.ts.map +1 -1
  18. package/lib/errors.js +58 -23
  19. package/lib/errors.js.map +1 -1
  20. package/lib/index.d.ts +1 -0
  21. package/lib/index.d.ts.map +1 -1
  22. package/lib/index.js +1 -0
  23. package/lib/index.js.map +1 -1
  24. package/lib/messages/index.d.ts +2 -2
  25. package/lib/messages/index.d.ts.map +1 -1
  26. package/lib/messages/index.js +3 -2
  27. package/lib/messages/index.js.map +1 -1
  28. package/lib/model-service/base-version.d.ts +64 -0
  29. package/lib/model-service/base-version.d.ts.map +1 -0
  30. package/lib/model-service/{based-on.js → base-version.js} +21 -15
  31. package/lib/model-service/base-version.js.map +1 -0
  32. package/lib/model-service/index.d.ts +1 -1
  33. package/lib/model-service/index.d.ts.map +1 -1
  34. package/lib/model-service/index.js +2 -2
  35. package/lib/model-service/index.js.map +1 -1
  36. package/lib/model-service/reference-candidate.d.ts +5 -3
  37. package/lib/model-service/reference-candidate.d.ts.map +1 -1
  38. package/lib/patch-merge.d.ts +11 -9
  39. package/lib/patch-merge.d.ts.map +1 -1
  40. package/lib/patch-merge.js +2 -2
  41. package/lib/patch-merge.js.map +1 -1
  42. package/lib/reconcile-write.d.ts +65 -0
  43. package/lib/reconcile-write.d.ts.map +1 -0
  44. package/lib/reconcile-write.js +67 -0
  45. package/lib/reconcile-write.js.map +1 -0
  46. package/lib/rpc/create-rpc-proxy.d.ts +3 -0
  47. package/lib/rpc/create-rpc-proxy.d.ts.map +1 -1
  48. package/lib/rpc/create-rpc-proxy.js +6 -1
  49. package/lib/rpc/create-rpc-proxy.js.map +1 -1
  50. package/lib/transfer-document.d.ts +59 -61
  51. package/lib/transfer-document.d.ts.map +1 -1
  52. package/lib/transfer-document.js +15 -20
  53. package/lib/transfer-document.js.map +1 -1
  54. package/lib/util.d.ts +1 -1
  55. package/lib/util.js +1 -1
  56. package/package.json +1 -1
  57. package/src/client/data-connection.ts +2 -2
  58. package/src/client/data-port.ts +1 -1
  59. package/src/client/data-session.ts +89 -51
  60. package/src/data/data-protocol-methods.ts +1 -1
  61. package/src/data/data-server-protocol.ts +12 -9
  62. package/src/data/events.ts +11 -5
  63. package/src/data/requests.ts +4 -4
  64. package/src/errors.ts +71 -31
  65. package/src/index.ts +1 -0
  66. package/src/messages/index.ts +2 -2
  67. package/src/model-service/base-version.ts +72 -0
  68. package/src/model-service/index.ts +2 -2
  69. package/src/model-service/reference-candidate.ts +5 -3
  70. package/src/patch-merge.ts +11 -9
  71. package/src/reconcile-write.ts +124 -0
  72. package/src/rpc/create-rpc-proxy.ts +6 -1
  73. package/src/transfer-document.ts +70 -65
  74. package/src/util.ts +1 -1
  75. package/lib/model-service/based-on.d.ts +0 -57
  76. package/lib/model-service/based-on.d.ts.map +0 -1
  77. package/lib/model-service/based-on.js.map +0 -1
  78. package/src/model-service/based-on.ts +0 -63
@@ -8,66 +8,73 @@
8
8
  ********************************************************************************/
9
9
  import type { TransferDiagnostic } from './transfer-diagnostic';
10
10
  import type { TransferElement } from './transfer-element';
11
- import { type SnapshotVersion } from './model-service/based-on';
11
+ import { type ModelVersion, type TextVersion } from './model-service/base-version';
12
+ /** The model a data server built for a document, encoded for the wire. */
13
+ export interface TransferModelSnapshot<TRoot extends TransferElement, TDiagnostic = TransferDiagnostic> {
14
+ root: TRoot;
15
+ /** Absent until the document is validated; `[]` once it is and nothing was found. */
16
+ diagnostics?: TDiagnostic[];
17
+ /**
18
+ * The version of the text `root` was parsed from. A caller that mutates the
19
+ * model sends this back as the `baseVersion` of its write, and the server
20
+ * rejects the write with `ConflictError` once its text has moved past it.
21
+ */
22
+ version: ModelVersion;
23
+ /**
24
+ * The data server's fingerprint of `root` and `diagnostics`, or under its
25
+ * `'text-diagnostics'` strategy of the text `root` was parsed from and
26
+ * `diagnostics`.
27
+ * Independent of `version`; an equal hash also means equally validated.
28
+ */
29
+ hash: string;
30
+ }
31
+ /** The text a data server held for a document when it sent it. */
32
+ export interface TextState {
33
+ /**
34
+ * The server's version of that text. A `TransferModelSnapshot.version` below it
35
+ * means the model was parsed from older text, and an update at this version
36
+ * or a later one follows.
37
+ */
38
+ version: TextVersion;
39
+ /**
40
+ * What `textHash` makes of the text alone: equal texts hash equal whatever
41
+ * their version or dirty state, across a revert or a server restart that
42
+ * numbers versions afresh.
43
+ */
44
+ hash: string;
45
+ /**
46
+ * Whether the text differs from the file, as the server last knew the file.
47
+ * A later change arrives as `onDocumentDirtyChanged` to a client watching
48
+ * the document.
49
+ */
50
+ dirty: boolean;
51
+ }
12
52
  /**
13
53
  * Wire envelope exchanged between the model-server's data-server head and
14
- * its clients. Carries a transfer-shaped root — never an AST root — plus
15
- * the document's diagnostics in the adopter's wire-diagnostic shape.
54
+ * its clients: the server's model of a document and the text it holds for it,
55
+ * each absent where the server has none.
16
56
  *
17
57
  * The constraint `TTransfer extends TransferElement` pins the type system
18
58
  * to the wire data shape (no `$container` cycles, references as strings;
19
- * see {@link TransferElement}). The paired server-internal envelope is
20
- * `AstDocument<TAst extends AstNode, TDiagnostic>` in
21
- * `@hydranium/core` — same structural shape, different generic
22
- * constraint, used at the AST layer before the `TransferEncoder`
23
- * translation produces a {@link TransferDocument}.
59
+ * see {@link TransferElement}).
24
60
  */
25
61
  export interface TransferDocument<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic> {
26
62
  uri: string;
27
63
  /**
28
- * The document version this snapshot was taken at — sourced from the
29
- * server's text-document version counter. A caller that mutates the document
30
- * subsequently sends this straight back as `TransferUpdateDocumentArgs.basedOn`, and
31
- * the server rejects a stale-based update with `ConflictError`.
32
- *
33
- * See `@hydranium/protocol#errors` for the conflict-detection contract.
34
- */
35
- version: SnapshotVersion;
36
- /**
37
- * Absent when the document does not exist — the server answers an unknown URI
38
- * with a shaped envelope rather than an error, so absence is an ordinary
39
- * branch and not a catch. Declared required it reads as a guarantee, and
40
- * every `root.x` compiles into a throw.
41
- */
42
- root?: TTransfer;
43
- diagnostics: TDiagnostic[];
44
- /**
45
- * Whether the server holds text for this document that its file does not,
46
- * as the server last knew the file: set on every document a data server
47
- * answers or sends that the server holds, and absent otherwise. A change
48
- * after this snapshot arrives as `onDocumentDirtyChanged` to a client
49
- * watching the document.
50
- */
51
- dirty?: boolean;
52
- /**
53
- * What `textHash` makes of the text the server holds for this document
54
- * when it sends it: set on every document a data server answers or sends
55
- * that the server holds, and absent otherwise. Equal texts hash equal,
56
- * whatever the version, so a client compares the text two snapshots hold,
57
- * across a revert or a server restart that numbers versions afresh.
58
- *
59
- * Of the text alone. The data server's de-duplication fingerprint hashes the
60
- * text or the transfer root together with the diagnostics, and changes with
61
- * a diagnostic where this does not.
64
+ * Absent when the document does not exist: the server answers an unknown
65
+ * URI with a shaped envelope rather than an error, so absence is an
66
+ * ordinary branch and not a catch.
62
67
  */
63
- textHash?: string;
68
+ model?: TransferModelSnapshot<TTransfer, TDiagnostic>;
69
+ /** Absent when the server holds no text for the document. */
70
+ text?: TextState;
64
71
  }
65
72
  /**
66
- * A {@link TransferDocument} the server actually had, so `root` is present.
73
+ * A {@link TransferDocument} the server actually had, so `model` is present.
67
74
  * Narrowed to by {@link TransferDocument.isLoaded} / {@link TransferDocument.assertLoaded}.
68
75
  */
69
76
  export type LoadedTransferDocument<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic> = TransferDocument<TTransfer, TDiagnostic> & {
70
- root: TTransfer;
77
+ model: TransferModelSnapshot<TTransfer, TDiagnostic>;
71
78
  };
72
79
  export declare namespace TransferDocument {
73
80
  /**
@@ -76,27 +83,18 @@ export declare namespace TransferDocument {
76
83
  */
77
84
  function isLoaded<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(document: TransferDocument<TTransfer, TDiagnostic>): document is LoadedTransferDocument<TTransfer, TDiagnostic>;
78
85
  /**
79
- * The same document with `root` no longer optional, or a throw. For call
86
+ * The same document with `model` no longer optional, or a throw. For call
80
87
  * sites where absence is a bug rather than a branch; the whole envelope is
81
- * returned because a caller needing `root` usually needs `uri` or `version`
88
+ * returned because a caller needing `model` usually needs `uri` or `text`
82
89
  * with it.
83
90
  */
84
91
  function assertLoaded<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(document: TransferDocument<TTransfer, TDiagnostic>): LoadedTransferDocument<TTransfer, TDiagnostic>;
92
+ /** The envelope for a document the server does not have. */
93
+ function absent<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(uri: string): TransferDocument<TTransfer, TDiagnostic>;
85
94
  /**
86
- * The envelope for a document the server does not have — `root` absent, and
87
- * the version a read of a URI the store never saw reports.
88
- *
89
- * Exists so the absent branch is built the same way the present one is: both
90
- * doors mark the version as coming from a read, and neither asks a caller to
91
- * do it.
92
- */
93
- function absent<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(uri: string, diagnostics?: TDiagnostic[]): TransferDocument<TTransfer, TDiagnostic>;
94
- /**
95
- * Construct a {@link TransferDocument} envelope. `diagnostics` defaults
96
- * to `[]` so test fixtures, fake protocol implementations, and the
97
- * "no diagnostics yet" code paths don't need to repeat the empty array
98
- * at every call site.
95
+ * Construct a {@link TransferDocument} envelope with a model, its version
96
+ * marked as coming from a read. `diagnostics` absent means not validated.
99
97
  */
100
- function create<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(uri: string, version: number, root: TTransfer, diagnostics?: TDiagnostic[]): TransferDocument<TTransfer, TDiagnostic>;
98
+ function create<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(uri: string, version: number, root: TTransfer, hash: string, diagnostics?: TDiagnostic[], text?: TextState): TransferDocument<TTransfer, TDiagnostic>;
101
99
  }
102
100
  //# sourceMappingURL=transfer-document.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transfer-document.d.ts","sourceRoot":"","sources":["../src/transfer-document.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEnF;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB;IAClG,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,OAAO,EAAE,eAAe,CAAC;IACzB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,IAAI,gBAAgB,CACvH,SAAS,EACT,WAAW,CACb,GAAG;IACD,IAAI,EAAE,SAAS,CAAC;CAClB,CAAC;AAEF,yBAAiB,gBAAgB,CAAC;IAC/B;;;OAGG;IACH,SAAgB,QAAQ,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,EACzF,QAAQ,EAAE,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,GAClD,QAAQ,IAAI,sBAAsB,CAAC,SAAS,EAAE,WAAW,CAAC,CAE5D;IAED;;;;;OAKG;IACH,SAAgB,YAAY,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,EAC7F,QAAQ,EAAE,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,GAClD,sBAAsB,CAAC,SAAS,EAAE,WAAW,CAAC,CAKhD;IAED;;;;;;;OAOG;IACH,SAAgB,MAAM,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,EACvF,GAAG,EAAE,MAAM,EACX,WAAW,GAAE,WAAW,EAAO,GAC/B,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,CAE1C;IAED;;;;;OAKG;IACH,SAAgB,MAAM,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,EACvF,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,EACf,WAAW,GAAE,WAAW,EAAO,GAC/B,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,CAE1C;CACH"}
1
+ {"version":3,"file":"transfer-document.d.ts","sourceRoot":"","sources":["../src/transfer-document.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAkB,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEnG,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB,CAAC,KAAK,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB;IACnG,IAAI,EAAE,KAAK,CAAC;IACZ,qFAAqF;IACrF,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5B;;;;OAIG;IACH,OAAO,EAAE,YAAY,CAAC;IACtB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;CACf;AAED,kEAAkE;AAClE,MAAM,WAAW,SAAS;IACvB;;;;OAIG;IACH,OAAO,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,KAAK,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB;IAClG,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,KAAK,CAAC,EAAE,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACtD,6DAA6D;IAC7D,IAAI,CAAC,EAAE,SAAS,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,IAAI,gBAAgB,CACvH,SAAS,EACT,WAAW,CACb,GAAG;IACD,KAAK,EAAE,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;CACvD,CAAC;AAEF,yBAAiB,gBAAgB,CAAC;IAC/B;;;OAGG;IACH,SAAgB,QAAQ,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,EACzF,QAAQ,EAAE,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,GAClD,QAAQ,IAAI,sBAAsB,CAAC,SAAS,EAAE,WAAW,CAAC,CAE5D;IAED;;;;;OAKG;IACH,SAAgB,YAAY,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,EAC7F,QAAQ,EAAE,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,GAClD,sBAAsB,CAAC,SAAS,EAAE,WAAW,CAAC,CAKhD;IAED,4DAA4D;IAC5D,SAAgB,MAAM,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,EACvF,GAAG,EAAE,MAAM,GACX,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,CAE1C;IAED;;;OAGG;IACH,SAAgB,MAAM,CAAC,SAAS,SAAS,eAAe,EAAE,WAAW,GAAG,kBAAkB,EACvF,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,WAAW,EAAE,EAC3B,IAAI,CAAC,EAAE,SAAS,GAChB,gBAAgB,CAAC,SAAS,EAAE,WAAW,CAAC,CAM1C;CACH"}
@@ -9,7 +9,7 @@
9
9
  ********************************************************************************/
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.TransferDocument = void 0;
12
- const based_on_1 = require("./model-service/based-on");
12
+ const base_version_1 = require("./model-service/base-version");
13
13
  var TransferDocument;
14
14
  (function (TransferDocument) {
15
15
  /**
@@ -17,13 +17,13 @@ var TransferDocument;
17
17
  * ordinary state — a view bound to a file that may not exist yet.
18
18
  */
19
19
  function isLoaded(document) {
20
- return document.root !== undefined;
20
+ return document.model !== undefined;
21
21
  }
22
22
  TransferDocument.isLoaded = isLoaded;
23
23
  /**
24
- * The same document with `root` no longer optional, or a throw. For call
24
+ * The same document with `model` no longer optional, or a throw. For call
25
25
  * sites where absence is a bug rather than a branch; the whole envelope is
26
- * returned because a caller needing `root` usually needs `uri` or `version`
26
+ * returned because a caller needing `model` usually needs `uri` or `text`
27
27
  * with it.
28
28
  */
29
29
  function assertLoaded(document) {
@@ -33,26 +33,21 @@ var TransferDocument;
33
33
  return document;
34
34
  }
35
35
  TransferDocument.assertLoaded = assertLoaded;
36
- /**
37
- * The envelope for a document the server does not have — `root` absent, and
38
- * the version a read of a URI the store never saw reports.
39
- *
40
- * Exists so the absent branch is built the same way the present one is: both
41
- * doors mark the version as coming from a read, and neither asks a caller to
42
- * do it.
43
- */
44
- function absent(uri, diagnostics = []) {
45
- return { uri, version: (0, based_on_1.asSnapshotVersion)(0), root: undefined, diagnostics };
36
+ /** The envelope for a document the server does not have. */
37
+ function absent(uri) {
38
+ return { uri };
46
39
  }
47
40
  TransferDocument.absent = absent;
48
41
  /**
49
- * Construct a {@link TransferDocument} envelope. `diagnostics` defaults
50
- * to `[]` so test fixtures, fake protocol implementations, and the
51
- * "no diagnostics yet" code paths don't need to repeat the empty array
52
- * at every call site.
42
+ * Construct a {@link TransferDocument} envelope with a model, its version
43
+ * marked as coming from a read. `diagnostics` absent means not validated.
53
44
  */
54
- function create(uri, version, root, diagnostics = []) {
55
- return { uri, version: (0, based_on_1.asSnapshotVersion)(version), root, diagnostics };
45
+ function create(uri, version, root, hash, diagnostics, text) {
46
+ return {
47
+ uri,
48
+ model: { root, version: (0, base_version_1.asModelVersion)(version), hash, ...(diagnostics === undefined ? {} : { diagnostics }) },
49
+ ...(text === undefined ? {} : { text })
50
+ };
56
51
  }
57
52
  TransferDocument.create = create;
58
53
  })(TransferDocument || (exports.TransferDocument = TransferDocument = {}));
@@ -1 +1 @@
1
- {"version":3,"file":"transfer-document.js","sourceRoot":"","sources":["../src/transfer-document.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAIlF,uDAAmF;AAmEnF,IAAiB,gBAAgB,CAuDhC;AAvDD,WAAiB,gBAAgB;IAC9B;;;OAGG;IACH,SAAgB,QAAQ,CACrB,QAAkD;QAElD,OAAO,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC;IACtC,CAAC;IAJe,iBAAA,QAAQ,WAIvB,CAAA;IAED;;;;;OAKG;IACH,SAAgB,YAAY,CACzB,QAAkD;QAElD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,kBAAkB,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,QAAQ,CAAC;IACnB,CAAC;IAPe,iBAAA,YAAY,eAO3B,CAAA;IAED;;;;;;;OAOG;IACH,SAAgB,MAAM,CACnB,GAAW,EACX,WAAW,GAAkB,EAAE;QAE/B,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAA,4BAAiB,EAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;IAC/E,CAAC;IALe,iBAAA,MAAM,SAKrB,CAAA;IAED;;;;;OAKG;IACH,SAAgB,MAAM,CACnB,GAAW,EACX,OAAe,EACf,IAAe,EACf,WAAW,GAAkB,EAAE;QAE/B,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAA,4BAAiB,EAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC1E,CAAC;IAPe,iBAAA,MAAM,SAOrB,CAAA;AACJ,CAAC,EAvDgB,gBAAgB,aAAhB,gBAAgB,GAAhB,gBAAgB,QAuDhC"}
1
+ {"version":3,"file":"transfer-document.js","sourceRoot":"","sources":["../src/transfer-document.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAIlF,+DAAmG;AA4EnG,IAAiB,gBAAgB,CAmDhC;AAnDD,WAAiB,gBAAgB;IAC9B;;;OAGG;IACH,SAAgB,QAAQ,CACrB,QAAkD;QAElD,OAAO,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC;IACvC,CAAC;IAJe,iBAAA,QAAQ,WAIvB,CAAA;IAED;;;;;OAKG;IACH,SAAgB,YAAY,CACzB,QAAkD;QAElD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,kBAAkB,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,QAAQ,CAAC;IACnB,CAAC;IAPe,iBAAA,YAAY,eAO3B,CAAA;IAED,4DAA4D;IAC5D,SAAgB,MAAM,CACnB,GAAW;QAEX,OAAO,EAAE,GAAG,EAAE,CAAC;IAClB,CAAC;IAJe,iBAAA,MAAM,SAIrB,CAAA;IAED;;;OAGG;IACH,SAAgB,MAAM,CACnB,GAAW,EACX,OAAe,EACf,IAAe,EACf,IAAY,EACZ,WAA2B,EAC3B,IAAgB;QAEhB,OAAO;YACJ,GAAG;YACH,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAA,6BAAc,EAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE;YAC9G,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;SACzC,CAAC;IACL,CAAC;IAbe,iBAAA,MAAM,SAarB,CAAA;AACJ,CAAC,EAnDgB,gBAAgB,aAAhB,gBAAgB,GAAhB,gBAAgB,QAmDhC"}
package/lib/util.d.ts CHANGED
@@ -146,7 +146,7 @@ export declare function unreachable(input: never): never;
146
146
  export declare function getAt<T = unknown>(obj: unknown, path: string[]): T | undefined;
147
147
  /**
148
148
  * A stable, non-cryptographic digest of `text`, equal on every host for equal
149
- * text: the `TransferDocument.textHash` a data server stamps, so a client can
149
+ * text: the `TextState.hash` a data server stamps, so a client can
150
150
  * hash text of its own to compare. An array is read as its elements joined, so
151
151
  * a large text is hashed in parts without concatenating it. It tells whether a
152
152
  * text changed, not who could forge one.
package/lib/util.js CHANGED
@@ -211,7 +211,7 @@ function getAt(obj, path) {
211
211
  }
212
212
  /**
213
213
  * A stable, non-cryptographic digest of `text`, equal on every host for equal
214
- * text: the `TransferDocument.textHash` a data server stamps, so a client can
214
+ * text: the `TextState.hash` a data server stamps, so a client can
215
215
  * hash text of its own to compare. An array is read as its elements joined, so
216
216
  * a large text is hashed in parts without concatenating it. It tells whether a
217
217
  * text changed, not who could forge one.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hydranium/protocol",
3
- "version": "1.0.0-next.187",
3
+ "version": "1.0.0-next.189",
4
4
  "description": "Generic, language-agnostic types, constants, and pure utilities for the hydranium framework.",
5
5
  "keywords": [
6
6
  "hydranium",
@@ -172,7 +172,7 @@ export class DataConnection<
172
172
  connected: () => this.connected(),
173
173
  reportError: (error, reported) => this.reportError(error, reported),
174
174
  restoreDirty: event => {
175
- if (this.dirtyStates.get(event.uri) !== event.dirty) {
175
+ if (this.dirtyStates.get(event.uri) !== (event.text?.dirty ?? false)) {
176
176
  this.deliverDirty(event);
177
177
  }
178
178
  },
@@ -234,7 +234,7 @@ export class DataConnection<
234
234
 
235
235
  /** Record `event` in {@link dirtyStates} and hand it to the client. */
236
236
  protected deliverDirty(event: TransferDocumentDirtyChangedEvent): void {
237
- this.dirtyStates.set(event.uri, event.dirty);
237
+ this.dirtyStates.set(event.uri, event.text?.dirty ?? false);
238
238
  const client = this.client as unknown as Partial<Pick<DataClientProtocol<TTransfer>, 'onDocumentDirtyChanged'>>;
239
239
  client.onDocumentDirtyChanged?.(event);
240
240
  }
@@ -27,7 +27,7 @@ import type { RpcConnectionLifecycle } from './rpc-connection';
27
27
  * `DataServerProtocol` surface, so wrapping it would re-derive the framework's
28
28
  * pass-throughs in a second place and lose the `as const satisfies keyof`
29
29
  * method allowlists, which cannot drift. Everything a form or a tree actually
30
- * does — the wire contract, the open/watch/update/close sequence, `basedOn`
30
+ * does — the wire contract, the open/watch/update/close sequence, `baseVersion`
31
31
  * conflict handling, echo filtering by `sourceClientId` — is host-invariant and
32
32
  * lives above this interface. What varies between hosts is exactly the
33
33
  * members below.
@@ -11,12 +11,12 @@ import { type Disposable, Emitter, type Event } from 'vscode-jsonrpc';
11
11
  import { type Clock, SystemClock } from '../clock';
12
12
  import type { DataServerProtocol, DiagnosticOf, TransferDocumentDirtyChangedEvent, TransferDocumentUpdatedEvent } from '../data';
13
13
  import { isConflictError, SessionClosedError } from '../errors';
14
- import { type BasedOn, isSnapshotVersion, type SnapshotVersion } from '../model-service/based-on';
14
+ import { type BaseVersion, isModelVersion, type ModelVersion, UNRECORDED_VERSION } from '../model-service/base-version';
15
15
  import { type ResolvedMessage, defineMessage, describeError, resolve } from '../messages/primitives';
16
16
  import type { OpenModelArgs } from '../model-server';
17
17
  import type { MaybePromise } from '../util';
18
18
  import type { RpcProxy } from '../rpc';
19
- import type { TransferDocument } from '../transfer-document';
19
+ import type { TextState, TransferDocument } from '../transfer-document';
20
20
  import type { TransferElement } from '../transfer-element';
21
21
 
22
22
  /**
@@ -29,6 +29,12 @@ export const DATA_SESSION_RESTORE_FAILED = defineMessage(
29
29
  'Could not restore {uri} after reconnecting to the data server: {detail}'
30
30
  );
31
31
 
32
+ /** A write or open of `{uri}` that the data server answered without a model. */
33
+ export const DATA_SESSION_ANSWER_WITHOUT_MODEL = defineMessage(
34
+ 'hydranium/protocol/data-session-answer-without-model',
35
+ 'The data server answered a write of {uri} without a model; the session counts that answer as older than every version.'
36
+ );
37
+
32
38
  /**
33
39
  * A session re-opened documents after its connection dropped and cannot put
34
40
  * back what it wrote to them since their last save: another client changed
@@ -110,14 +116,18 @@ export interface DataSessionUnsavedWrite<
110
116
  TServer extends DataServerProtocol<TTransfer, DiagnosticOf<TServer>> = DataServerProtocol<TTransfer>
111
117
  > {
112
118
  /**
113
- * The {@link TransferDocument.textHash} of the text the first of these
114
- * writes was based on. `undefined` when the session could not tell, for a
115
- * write based on `'anything'` or on a version it could not read back, and
116
- * such a write is never sent again: the session cannot tell another
117
- * client's text from the text it wrote over.
119
+ * The `text.hash` of the text the first of these writes was based on.
120
+ * `undefined` when the session could not tell, for a write based on
121
+ * `'any'` or on a version it could not read back, and such a write is
122
+ * never sent again: the session cannot tell another client's text from the
123
+ * text it wrote over.
118
124
  */
119
125
  readonly baseHash: string | undefined;
120
- readonly answer: Pick<DataSessionDocument<TTransfer, TServer>, 'version' | 'textHash'>;
126
+ /**
127
+ * The model version the last of these writes was answered with, which
128
+ * orders its answers, and the `text.hash` of that answer.
129
+ */
130
+ readonly answer: { readonly version: ModelVersion; readonly hash?: string };
121
131
  /**
122
132
  * The call that made the last write, and for an `updateDocuments` call this
123
133
  * URI's index in it. Records whose `updates` is the same object were
@@ -251,18 +261,18 @@ export class DataSession<
251
261
  /** Per URI written since its last save, what {@link restore} needs to write it again. */
252
262
  protected readonly unsavedWrites = new Map<string, DataSessionUnsavedWrite<TTransfer, TServer>>();
253
263
  /**
254
- * Per URI this session has open, the version and text hash of the last
255
- * document one of its own calls was answered with, which spares the read a
256
- * first unsaved write otherwise makes for its base; see {@link baseHashOf}.
264
+ * Per URI this session has open, the text of the last document one of its
265
+ * own calls was answered with, which spares the read a first unsaved write
266
+ * otherwise makes for its base; see {@link baseHashOf}.
257
267
  */
258
- protected readonly lastAnswers = new Map<string, Pick<DataSessionDocument<TTransfer, TServer>, 'version' | 'textHash'>>();
268
+ protected readonly lastAnswers = new Map<string, TextState>();
259
269
  /**
260
- * Per URI this session has open, the version its last save of it was
270
+ * Per URI this session has open, the model version its last save of it was
261
271
  * answered with; see {@link recordWrite}. Only a save sets it: an open
262
272
  * answers at the version of a write still in flight as well, and that
263
273
  * write is not saved.
264
274
  */
265
- protected readonly savedVersions = new Map<string, SnapshotVersion>();
275
+ protected readonly savedVersions = new Map<string, ModelVersion>();
266
276
  /** Per URI, this session's calls still in flight on it, which a close waits for. */
267
277
  protected readonly inFlight = new Map<string, Set<Promise<unknown>>>();
268
278
  /** This session's saves still in flight, which a host's exit waits for; see {@link hasSavesInFlight}. */
@@ -347,8 +357,8 @@ export class DataSession<
347
357
  * view misreads as a concurrent third-party write, losing whatever the user
348
358
  * had typed.
349
359
  *
350
- * Note that the returned snapshot's empty `diagnostics` does not mean
351
- * valid: `open` settles at the integrity landmark, not at validation.
360
+ * Note that the returned snapshot's model usually has no `diagnostics`:
361
+ * `open` settles at the integrity landmark, not at validation.
352
362
  * Validity arrives asynchronously on `onDocumentUpdated`, or synchronously
353
363
  * from `getModelDocument({ includeDiagnostics: true })`.
354
364
  */
@@ -389,7 +399,7 @@ export class DataSession<
389
399
  }
390
400
  this.openUris.add(uri);
391
401
  this.serverUris.set(uri, document.uri);
392
- this.lastAnswers.set(uri, { version: document.version, textHash: document.textHash });
402
+ this.recordText(uri, document);
393
403
  // Keyed as the server keys its notifications, which may not be how the
394
404
  // caller spelled the URI.
395
405
  this.host.forgetDirty?.(document.uri);
@@ -444,7 +454,7 @@ export class DataSession<
444
454
  return this.track([args.uri], async () => {
445
455
  const server = await this.connected();
446
456
  const update = { ...args };
447
- const baseHash = await this.baseHashOf(server, args.uri, args.basedOn);
457
+ const baseHash = await this.baseHashOf(server, args.uri, args.baseVersion);
448
458
  const document = await server.updateModelDocument({ ...args, clientId: this.clientId });
449
459
  this.recordWrite(args.uri, document, baseHash, { update });
450
460
  return document;
@@ -464,7 +474,7 @@ export class DataSession<
464
474
  // records together.
465
475
  const updates = { ...args, updates: args.updates.map(update => ({ ...update })) };
466
476
  const server = await this.connected();
467
- const baseHashes = await Promise.all(args.updates.map(update => this.baseHashOf(server, update.uri, update.basedOn)));
477
+ const baseHashes = await Promise.all(args.updates.map(update => this.baseHashOf(server, update.uri, update.baseVersion)));
468
478
  const documents = await server.updateModelDocuments({ ...args, clientId: this.clientId });
469
479
  args.updates.forEach((update, index) => this.recordWrite(update.uri, documents[index], baseHashes[index], { updates, index }));
470
480
  return documents;
@@ -483,11 +493,12 @@ export class DataSession<
483
493
  if (this.unsavedWrites.get(args.uri) === unsaved) {
484
494
  this.unsavedWrites.delete(args.uri);
485
495
  }
486
- this.lastAnswers.set(args.uri, { version: document.version, textHash: document.textHash });
496
+ const answer = this.answerOf(args.uri, document);
497
+ this.recordText(args.uri, document);
487
498
  // The higher of two saves' answers, which may arrive out of order.
488
499
  const saved = this.savedVersions.get(args.uri);
489
- if (saved === undefined || document.version > saved) {
490
- this.savedVersions.set(args.uri, document.version);
500
+ if (saved === undefined || answer.version > saved) {
501
+ this.savedVersions.set(args.uri, answer.version);
491
502
  }
492
503
  return document;
493
504
  });
@@ -620,7 +631,7 @@ export class DataSession<
620
631
  * can still be reported lost that way.
621
632
  */
622
633
  protected forgetSavedWrite(event: TransferDocumentDirtyChangedEvent): void {
623
- if (event.dirty) {
634
+ if (event.text?.dirty) {
624
635
  return;
625
636
  }
626
637
  // The flip names the server's key, which is not always the caller's
@@ -675,7 +686,7 @@ export class DataSession<
675
686
  * what it wrote since their last save where the re-open lost it, and tell
676
687
  * the host which of them lost it for good.
677
688
  *
678
- * Decided per document by the re-opened {@link TransferDocument.textHash},
689
+ * Decided per document by the re-opened document's `text.hash`,
679
690
  * in {@link restoreOutcome}. Versions cannot decide a write: a revert moves
680
691
  * the version on and a restarted server numbers afresh, so the version the
681
692
  * last write was answered with never matches where a write is needed, and
@@ -717,7 +728,7 @@ export class DataSession<
717
728
  await server.watchModelDocument({ uri, clientId: this.clientId });
718
729
  if (this.openUris.has(uri)) {
719
730
  this.serverUris.set(uri, document.uri);
720
- this.lastAnswers.set(uri, { version: document.version, textHash: document.textHash });
731
+ this.recordText(uri, document);
721
732
  }
722
733
  reopened.set(uri, document);
723
734
  } catch (error: unknown) {
@@ -742,12 +753,12 @@ export class DataSession<
742
753
  if (!this.openUris.has(uri)) {
743
754
  continue;
744
755
  }
745
- // A failed read leaves the client's dirty state as it was; the
746
- // document is restored all the same.
756
+ // A failed read, or one without text, leaves the client's dirty
757
+ // state as it was; the document is restored all the same.
747
758
  const current = await server.getModelDocument({ uri }).catch(() => undefined);
748
- if (current?.dirty !== undefined) {
759
+ if (current?.text !== undefined) {
749
760
  try {
750
- this.host.restoreDirty?.({ uri: current.uri, dirty: current.dirty });
761
+ this.host.restoreDirty?.({ uri: current.uri, text: current.text });
751
762
  } catch {
752
763
  // The client's listener failed, not the restore: the server
753
764
  // has the document open and watched, so it stays restored.
@@ -765,20 +776,21 @@ export class DataSession<
765
776
  * `write` (`'kept'`), holds the text the write started from, so the write
766
777
  * can be sent again (`'resend'`), or holds something else (`'lost'`).
767
778
  *
768
- * A document without a text hash comes from a server that sends none, and
769
- * counts as kept only at the version the write was answered with.
779
+ * A document without `text` comes from a server that sends none, and
780
+ * counts as kept only at the version the write was answered with. One
781
+ * without a model has no version to send the write again on.
770
782
  */
771
783
  protected restoreOutcome(
772
784
  write: DataSessionUnsavedWrite<TTransfer, TServer>,
773
785
  document: DataSessionDocument<TTransfer, TServer>
774
786
  ): 'kept' | 'resend' | 'lost' {
775
- if (document.textHash === undefined) {
776
- return document.version === write.answer.version ? 'kept' : 'lost';
787
+ if (document.text === undefined) {
788
+ return document.model?.version === write.answer.version ? 'kept' : 'lost';
777
789
  }
778
- if (document.textHash === write.answer.textHash) {
790
+ if (document.text.hash === write.answer.hash) {
779
791
  return 'kept';
780
792
  }
781
- return write.baseHash !== undefined && document.textHash === write.baseHash ? 'resend' : 'lost';
793
+ return document.model && write.baseHash !== undefined && document.text.hash === write.baseHash ? 'resend' : 'lost';
782
794
  }
783
795
 
784
796
  /**
@@ -808,7 +820,7 @@ export class DataSession<
808
820
  // on from the re-opened version, not from the one the record holds.
809
821
  for (const { uri, write, document, outcome } of outcomes) {
810
822
  if (outcome === 'kept' && document) {
811
- this.unsavedWrites.set(uri, { ...write, answer: { version: document.version, textHash: document.textHash } });
823
+ this.unsavedWrites.set(uri, { ...write, answer: this.answerOf(uri, document) });
812
824
  }
813
825
  }
814
826
  // A document that could not be re-opened is forgotten, and was reported
@@ -823,7 +835,7 @@ export class DataSession<
823
835
  // In the order of the call's updates, which is the order of its answers.
824
836
  const resend = outcomes
825
837
  .flatMap(({ uri, write, document, outcome }) =>
826
- outcome === 'resend' && document ? [{ uri, write, basedOn: document.version }] : []
838
+ outcome === 'resend' && document?.model ? [{ uri, write, baseVersion: document.model.version }] : []
827
839
  )
828
840
  .sort((left, right) => indexOf(left.write) - indexOf(right.write));
829
841
  if (resend.length === 0) {
@@ -833,15 +845,15 @@ export class DataSession<
833
845
  try {
834
846
  const { call } = resend[0].write;
835
847
  if ('update' in call) {
836
- documents = [await server.updateModelDocument({ ...call.update, basedOn: resend[0].basedOn, clientId: this.clientId })];
848
+ documents = [await server.updateModelDocument({ ...call.update, baseVersion: resend[0].baseVersion, clientId: this.clientId })];
837
849
  } else {
838
- const basedOn = new Map(resend.map(member => [indexOf(member.write), member.basedOn]));
850
+ const baseVersion = new Map(resend.map(member => [indexOf(member.write), member.baseVersion]));
839
851
  documents = await server.updateModelDocuments({
840
852
  ...call.updates,
841
853
  clientId: this.clientId,
842
854
  updates: call.updates.updates.flatMap((update, index) => {
843
- const version = basedOn.get(index);
844
- return version === undefined ? [] : [{ ...update, basedOn: version }];
855
+ const version = baseVersion.get(index);
856
+ return version === undefined ? [] : [{ ...update, baseVersion: version }];
845
857
  })
846
858
  });
847
859
  }
@@ -849,13 +861,13 @@ export class DataSession<
849
861
  if (isConflictError(error)) {
850
862
  return lose();
851
863
  }
852
- for (const { uri, write, basedOn } of resend) {
864
+ for (const { uri, write, baseVersion } of resend) {
853
865
  this.host.reportError?.(error, resolve(DATA_SESSION_RESTORE_FAILED, { uri, detail: describeError(error) }));
854
866
  // Kept for the next restore, and numbered from the re-opened
855
867
  // version, as the kept records are: a record numbered by a server
856
868
  // that has since restarted would have later answers ignored.
857
869
  if (current(uri, write)) {
858
- this.unsavedWrites.set(uri, { ...write, answer: { ...write.answer, version: basedOn } });
870
+ this.unsavedWrites.set(uri, { ...write, answer: { ...write.answer, version: baseVersion } });
859
871
  }
860
872
  }
861
873
  return [];
@@ -872,7 +884,7 @@ export class DataSession<
872
884
  }
873
885
 
874
886
  /**
875
- * The text hash of the version `basedOn` names for `uri`, for a write that
887
+ * The text hash of the version `baseVersion` names for `uri`, for a write that
876
888
  * starts the URI's record in {@link unsavedWrites}; `undefined` for any
877
889
  * other write, whose record keeps the base it has.
878
890
  *
@@ -881,16 +893,16 @@ export class DataSession<
881
893
  * changes only with its version, so the text at a version is the text the
882
894
  * write, once it passes the gate, was applied to.
883
895
  */
884
- protected async baseHashOf(server: RpcProxy<TServer>, uri: string, basedOn: BasedOn): Promise<string | undefined> {
885
- if (this.unsavedWrites.has(uri) || !isSnapshotVersion(basedOn)) {
896
+ protected async baseHashOf(server: RpcProxy<TServer>, uri: string, baseVersion: BaseVersion): Promise<string | undefined> {
897
+ if (this.unsavedWrites.has(uri) || !isModelVersion(baseVersion)) {
886
898
  return undefined;
887
899
  }
888
900
  const known = this.lastAnswers.get(uri);
889
- if (known?.version === basedOn) {
890
- return known.textHash;
901
+ if (known?.version === baseVersion) {
902
+ return known.hash;
891
903
  }
892
904
  const read = await server.getModelDocument({ uri }).catch(() => undefined);
893
- return read?.version === basedOn ? read.textHash : undefined;
905
+ return read?.text?.version === baseVersion ? read.text.hash : undefined;
894
906
  }
895
907
 
896
908
  /**
@@ -910,7 +922,7 @@ export class DataSession<
910
922
  baseHash: string | undefined,
911
923
  call: DataSessionUnsavedWrite<TTransfer, TServer>['call']
912
924
  ): void {
913
- const answer = { version: document.version, textHash: document.textHash };
925
+ const answer = this.answerOf(uri, document);
914
926
  const kept = this.unsavedWrites.get(uri);
915
927
  if (kept && answer.version < kept.answer.version) {
916
928
  return;
@@ -920,7 +932,33 @@ export class DataSession<
920
932
  return;
921
933
  }
922
934
  this.unsavedWrites.set(uri, { baseHash: kept ? kept.baseHash : baseHash, answer, call });
923
- this.lastAnswers.set(uri, answer);
935
+ this.recordText(uri, document);
936
+ }
937
+
938
+ /**
939
+ * What {@link DataSessionUnsavedWrite.answer} keeps of `document`. Every write
940
+ * and open answers with a model; one without is reported and counted older
941
+ * than every version, so it never replaces a real answer.
942
+ */
943
+ protected answerOf(
944
+ uri: string,
945
+ document: DataSessionDocument<TTransfer, TServer>
946
+ ): DataSessionUnsavedWrite<TTransfer, TServer>['answer'] {
947
+ if (!document.model) {
948
+ const reported = resolve(DATA_SESSION_ANSWER_WITHOUT_MODEL, { uri });
949
+ this.host.reportError?.(new Error(reported.text), reported);
950
+ return { version: UNRECORDED_VERSION, hash: document.text?.hash };
951
+ }
952
+ return { version: document.model.version, hash: document.text?.hash };
953
+ }
954
+
955
+ /** Keep `document`'s text in {@link lastAnswers}, or forget it for a server that sends none. */
956
+ protected recordText(uri: string, document: DataSessionDocument<TTransfer, TServer>): void {
957
+ if (document.text) {
958
+ this.lastAnswers.set(uri, document.text);
959
+ } else {
960
+ this.lastAnswers.delete(uri);
961
+ }
924
962
  }
925
963
 
926
964
  /** Run `call`, counted as in flight on each of `uris` until it settles. */
@@ -60,7 +60,7 @@ export const REFERENCE_SERVER_PROTOCOL_METHODS = [
60
60
  'findReferenceCandidates',
61
61
  'resolveReference',
62
62
  'findNextName'
63
- ] as const satisfies ReadonlyArray<keyof ReferenceServerProtocol<TransferElement> & string>;
63
+ ] as const satisfies ReadonlyArray<keyof ReferenceServerProtocol & string>;
64
64
 
65
65
  /** Notification-method names on {@link DocumentClientProtocol}. */
66
66
  export const DOCUMENT_CLIENT_PROTOCOL_METHODS = [