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

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 (72) 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-server-protocol.d.ts +2 -4
  9. package/lib/data/data-server-protocol.d.ts.map +1 -1
  10. package/lib/data/events.d.ts +11 -5
  11. package/lib/data/events.d.ts.map +1 -1
  12. package/lib/data/requests.d.ts +4 -4
  13. package/lib/data/requests.d.ts.map +1 -1
  14. package/lib/errors.d.ts +31 -25
  15. package/lib/errors.d.ts.map +1 -1
  16. package/lib/errors.js +58 -23
  17. package/lib/errors.js.map +1 -1
  18. package/lib/index.d.ts +1 -0
  19. package/lib/index.d.ts.map +1 -1
  20. package/lib/index.js +1 -0
  21. package/lib/index.js.map +1 -1
  22. package/lib/messages/index.d.ts +2 -2
  23. package/lib/messages/index.d.ts.map +1 -1
  24. package/lib/messages/index.js +3 -2
  25. package/lib/messages/index.js.map +1 -1
  26. package/lib/model-service/base-version.d.ts +64 -0
  27. package/lib/model-service/base-version.d.ts.map +1 -0
  28. package/lib/model-service/{based-on.js → base-version.js} +21 -15
  29. package/lib/model-service/base-version.js.map +1 -0
  30. package/lib/model-service/index.d.ts +1 -1
  31. package/lib/model-service/index.d.ts.map +1 -1
  32. package/lib/model-service/index.js +2 -2
  33. package/lib/model-service/index.js.map +1 -1
  34. package/lib/patch-merge.d.ts +11 -9
  35. package/lib/patch-merge.d.ts.map +1 -1
  36. package/lib/patch-merge.js +2 -2
  37. package/lib/patch-merge.js.map +1 -1
  38. package/lib/reconcile-write.d.ts +65 -0
  39. package/lib/reconcile-write.d.ts.map +1 -0
  40. package/lib/reconcile-write.js +67 -0
  41. package/lib/reconcile-write.js.map +1 -0
  42. package/lib/rpc/create-rpc-proxy.d.ts +3 -0
  43. package/lib/rpc/create-rpc-proxy.d.ts.map +1 -1
  44. package/lib/rpc/create-rpc-proxy.js +6 -1
  45. package/lib/rpc/create-rpc-proxy.js.map +1 -1
  46. package/lib/transfer-document.d.ts +59 -61
  47. package/lib/transfer-document.d.ts.map +1 -1
  48. package/lib/transfer-document.js +15 -20
  49. package/lib/transfer-document.js.map +1 -1
  50. package/lib/util.d.ts +1 -1
  51. package/lib/util.js +1 -1
  52. package/package.json +1 -1
  53. package/src/client/data-connection.ts +2 -2
  54. package/src/client/data-port.ts +1 -1
  55. package/src/client/data-session.ts +89 -51
  56. package/src/data/data-server-protocol.ts +2 -4
  57. package/src/data/events.ts +11 -5
  58. package/src/data/requests.ts +4 -4
  59. package/src/errors.ts +71 -31
  60. package/src/index.ts +1 -0
  61. package/src/messages/index.ts +2 -2
  62. package/src/model-service/base-version.ts +72 -0
  63. package/src/model-service/index.ts +2 -2
  64. package/src/patch-merge.ts +11 -9
  65. package/src/reconcile-write.ts +124 -0
  66. package/src/rpc/create-rpc-proxy.ts +6 -1
  67. package/src/transfer-document.ts +70 -65
  68. package/src/util.ts +1 -1
  69. package/lib/model-service/based-on.d.ts +0 -57
  70. package/lib/model-service/based-on.d.ts.map +0 -1
  71. package/lib/model-service/based-on.js.map +0 -1
  72. package/src/model-service/based-on.ts +0 -63
@@ -9,71 +9,80 @@
9
9
 
10
10
  import type { TransferDiagnostic } from './transfer-diagnostic';
11
11
  import type { TransferElement } from './transfer-element';
12
- import { asSnapshotVersion, type SnapshotVersion } from './model-service/based-on';
12
+ import { asModelVersion, type ModelVersion, type TextVersion } from './model-service/base-version';
13
+
14
+ /** The model a data server built for a document, encoded for the wire. */
15
+ export interface TransferModelSnapshot<TRoot extends TransferElement, TDiagnostic = TransferDiagnostic> {
16
+ root: TRoot;
17
+ /** Absent until the document is validated; `[]` once it is and nothing was found. */
18
+ diagnostics?: TDiagnostic[];
19
+ /**
20
+ * The version of the text `root` was parsed from. A caller that mutates the
21
+ * model sends this back as the `baseVersion` of its write, and the server
22
+ * rejects the write with `ConflictError` once its text has moved past it.
23
+ */
24
+ version: ModelVersion;
25
+ /**
26
+ * The data server's fingerprint of `root` and `diagnostics`, or under its
27
+ * `'text-diagnostics'` strategy of the text `root` was parsed from and
28
+ * `diagnostics`.
29
+ * Independent of `version`; an equal hash also means equally validated.
30
+ */
31
+ hash: string;
32
+ }
33
+
34
+ /** The text a data server held for a document when it sent it. */
35
+ export interface TextState {
36
+ /**
37
+ * The server's version of that text. A `TransferModelSnapshot.version` below it
38
+ * means the model was parsed from older text, and an update at this version
39
+ * or a later one follows.
40
+ */
41
+ version: TextVersion;
42
+ /**
43
+ * What `textHash` makes of the text alone: equal texts hash equal whatever
44
+ * their version or dirty state, across a revert or a server restart that
45
+ * numbers versions afresh.
46
+ */
47
+ hash: string;
48
+ /**
49
+ * Whether the text differs from the file, as the server last knew the file.
50
+ * A later change arrives as `onDocumentDirtyChanged` to a client watching
51
+ * the document.
52
+ */
53
+ dirty: boolean;
54
+ }
13
55
 
14
56
  /**
15
57
  * Wire envelope exchanged between the model-server's data-server head and
16
- * its clients. Carries a transfer-shaped root — never an AST root — plus
17
- * the document's diagnostics in the adopter's wire-diagnostic shape.
58
+ * its clients: the server's model of a document and the text it holds for it,
59
+ * each absent where the server has none.
18
60
  *
19
61
  * The constraint `TTransfer extends TransferElement` pins the type system
20
62
  * to the wire data shape (no `$container` cycles, references as strings;
21
- * see {@link TransferElement}). The paired server-internal envelope is
22
- * `AstDocument<TAst extends AstNode, TDiagnostic>` in
23
- * `@hydranium/core` — same structural shape, different generic
24
- * constraint, used at the AST layer before the `TransferEncoder`
25
- * translation produces a {@link TransferDocument}.
63
+ * see {@link TransferElement}).
26
64
  */
27
65
  export interface TransferDocument<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic> {
28
66
  uri: string;
29
67
  /**
30
- * The document version this snapshot was taken at — sourced from the
31
- * server's text-document version counter. A caller that mutates the document
32
- * subsequently sends this straight back as `TransferUpdateDocumentArgs.basedOn`, and
33
- * the server rejects a stale-based update with `ConflictError`.
34
- *
35
- * See `@hydranium/protocol#errors` for the conflict-detection contract.
36
- */
37
- version: SnapshotVersion;
38
- /**
39
- * Absent when the document does not exist — the server answers an unknown URI
40
- * with a shaped envelope rather than an error, so absence is an ordinary
41
- * branch and not a catch. Declared required it reads as a guarantee, and
42
- * every `root.x` compiles into a throw.
43
- */
44
- root?: TTransfer;
45
- diagnostics: TDiagnostic[];
46
- /**
47
- * Whether the server holds text for this document that its file does not,
48
- * as the server last knew the file: set on every document a data server
49
- * answers or sends that the server holds, and absent otherwise. A change
50
- * after this snapshot arrives as `onDocumentDirtyChanged` to a client
51
- * watching the document.
68
+ * Absent when the document does not exist: the server answers an unknown
69
+ * URI with a shaped envelope rather than an error, so absence is an
70
+ * ordinary branch and not a catch.
52
71
  */
53
- dirty?: boolean;
54
- /**
55
- * What `textHash` makes of the text the server holds for this document
56
- * when it sends it: set on every document a data server answers or sends
57
- * that the server holds, and absent otherwise. Equal texts hash equal,
58
- * whatever the version, so a client compares the text two snapshots hold,
59
- * across a revert or a server restart that numbers versions afresh.
60
- *
61
- * Of the text alone. The data server's de-duplication fingerprint hashes the
62
- * text or the transfer root together with the diagnostics, and changes with
63
- * a diagnostic where this does not.
64
- */
65
- textHash?: string;
72
+ model?: TransferModelSnapshot<TTransfer, TDiagnostic>;
73
+ /** Absent when the server holds no text for the document. */
74
+ text?: TextState;
66
75
  }
67
76
 
68
77
  /**
69
- * A {@link TransferDocument} the server actually had, so `root` is present.
78
+ * A {@link TransferDocument} the server actually had, so `model` is present.
70
79
  * Narrowed to by {@link TransferDocument.isLoaded} / {@link TransferDocument.assertLoaded}.
71
80
  */
72
81
  export type LoadedTransferDocument<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic> = TransferDocument<
73
82
  TTransfer,
74
83
  TDiagnostic
75
84
  > & {
76
- root: TTransfer;
85
+ model: TransferModelSnapshot<TTransfer, TDiagnostic>;
77
86
  };
78
87
 
79
88
  export namespace TransferDocument {
@@ -84,13 +93,13 @@ export namespace TransferDocument {
84
93
  export function isLoaded<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(
85
94
  document: TransferDocument<TTransfer, TDiagnostic>
86
95
  ): document is LoadedTransferDocument<TTransfer, TDiagnostic> {
87
- return document.root !== undefined;
96
+ return document.model !== undefined;
88
97
  }
89
98
 
90
99
  /**
91
- * The same document with `root` no longer optional, or a throw. For call
100
+ * The same document with `model` no longer optional, or a throw. For call
92
101
  * sites where absence is a bug rather than a branch; the whole envelope is
93
- * returned because a caller needing `root` usually needs `uri` or `version`
102
+ * returned because a caller needing `model` usually needs `uri` or `text`
94
103
  * with it.
95
104
  */
96
105
  export function assertLoaded<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(
@@ -102,33 +111,29 @@ export namespace TransferDocument {
102
111
  return document;
103
112
  }
104
113
 
105
- /**
106
- * The envelope for a document the server does not have — `root` absent, and
107
- * the version a read of a URI the store never saw reports.
108
- *
109
- * Exists so the absent branch is built the same way the present one is: both
110
- * doors mark the version as coming from a read, and neither asks a caller to
111
- * do it.
112
- */
114
+ /** The envelope for a document the server does not have. */
113
115
  export function absent<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(
114
- uri: string,
115
- diagnostics: TDiagnostic[] = []
116
+ uri: string
116
117
  ): TransferDocument<TTransfer, TDiagnostic> {
117
- return { uri, version: asSnapshotVersion(0), root: undefined, diagnostics };
118
+ return { uri };
118
119
  }
119
120
 
120
121
  /**
121
- * Construct a {@link TransferDocument} envelope. `diagnostics` defaults
122
- * to `[]` so test fixtures, fake protocol implementations, and the
123
- * "no diagnostics yet" code paths don't need to repeat the empty array
124
- * at every call site.
122
+ * Construct a {@link TransferDocument} envelope with a model, its version
123
+ * marked as coming from a read. `diagnostics` absent means not validated.
125
124
  */
126
125
  export function create<TTransfer extends TransferElement, TDiagnostic = TransferDiagnostic>(
127
126
  uri: string,
128
127
  version: number,
129
128
  root: TTransfer,
130
- diagnostics: TDiagnostic[] = []
129
+ hash: string,
130
+ diagnostics?: TDiagnostic[],
131
+ text?: TextState
131
132
  ): TransferDocument<TTransfer, TDiagnostic> {
132
- return { uri, version: asSnapshotVersion(version), root, diagnostics };
133
+ return {
134
+ uri,
135
+ model: { root, version: asModelVersion(version), hash, ...(diagnostics === undefined ? {} : { diagnostics }) },
136
+ ...(text === undefined ? {} : { text })
137
+ };
133
138
  }
134
139
  }
package/src/util.ts CHANGED
@@ -251,7 +251,7 @@ export function getAt<T = unknown>(obj: unknown, path: string[]): T | undefined
251
251
 
252
252
  /**
253
253
  * A stable, non-cryptographic digest of `text`, equal on every host for equal
254
- * text: the `TransferDocument.textHash` a data server stamps, so a client can
254
+ * text: the `TextState.hash` a data server stamps, so a client can
255
255
  * hash text of its own to compare. An array is read as its elements joined, so
256
256
  * a large text is hashed in parts without concatenating it. It tells whether a
257
257
  * text changed, not who could forge one.
@@ -1,57 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the MIT License which is available in the project root.
6
- *
7
- * SPDX-License-Identifier: MIT
8
- ********************************************************************************/
9
- declare const snapshotMarker: unique symbol;
10
- /**
11
- * A text-document version that came out of a SNAPSHOT read: a document
12
- * envelope's `version`, which cannot move once the envelope exists.
13
- *
14
- * A plain number at runtime, usable anywhere a number is, and it travels the
15
- * wire as one. The marker exists only so the compiler can tell it apart from a
16
- * version read off a LIVE document, which is the same number type and is the
17
- * defect the conflict gate exists to prevent: read at write time it is whatever
18
- * the server is at now, which is the number the gate is about to compare it
19
- * against, so the gate passes unconditionally and a concurrent edit is
20
- * overwritten with nothing logged.
21
- */
22
- export type SnapshotVersion = number & {
23
- readonly [snapshotMarker]: true;
24
- };
25
- /**
26
- * What a write declares it was based on: a version from a snapshot read, or
27
- * `'anything'`.
28
- *
29
- * `'anything'` is a precondition that always holds — the write accepts whatever
30
- * the server currently has, and therefore overwrites it. It is the honest
31
- * answer wherever the write was authored against no particular server version,
32
- * and a lie anywhere a reader handed the writer a document. The field carrying
33
- * it is required, so an ungated write is a word someone chose rather than a
34
- * field someone forgot.
35
- */
36
- export type BasedOn = SnapshotVersion | 'anything';
37
- /**
38
- * Whether `basedOn` names a version, and so arms the gate.
39
- *
40
- * Branch on this rather than on `basedOn !== 'anything'`, which reads as "not
41
- * based on anything" — the opposite of what the branch tests.
42
- */
43
- export declare function isSnapshotVersion(basedOn: BasedOn): basedOn is SnapshotVersion;
44
- /**
45
- * Mark a version as having come from a snapshot read.
46
- *
47
- * **For the envelope constructors, not for callers.** Every door that builds a
48
- * document envelope applies it on the way in, so anything a read returns
49
- * already carries it and a writer never needs this. A caller reaching for it is
50
- * asserting a provenance the compiler was about to deny — the read-late defect
51
- * written out where a reviewer can see it.
52
- */
53
- export declare function asSnapshotVersion(version: number): SnapshotVersion;
54
- /** A snapshot version no write can match, so a write based on it conflicts. */
55
- export declare const NO_MATCHING_VERSION: SnapshotVersion;
56
- export {};
57
- //# sourceMappingURL=based-on.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"based-on.d.ts","sourceRoot":"","sources":["../../src/model-service/based-on.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAE5C;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,MAAM,MAAM,OAAO,GAAG,eAAe,GAAG,UAAU,CAAC;AAEnD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,eAAe,CAE9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAElE;AAED,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,EAAE,eAAuC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"based-on.js","sourceRoot":"","sources":["../../src/model-service/based-on.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;AA+BlF;;;;;GAKG;AACH,2BAAkC,OAAgB;IAC/C,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC;AACtC,CAAC;AAED;;;;;;;;GAQG;AACH,2BAAkC,OAAe;IAC9C,OAAO,OAA0B,CAAC;AACrC,CAAC;AAED,+EAA+E;AAClE,QAAA,mBAAmB,GAAoB,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -1,63 +0,0 @@
1
- /********************************************************************************
2
- * Copyright (c) 2026 EclipseSource and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the MIT License which is available in the project root.
6
- *
7
- * SPDX-License-Identifier: MIT
8
- ********************************************************************************/
9
-
10
- declare const snapshotMarker: unique symbol;
11
-
12
- /**
13
- * A text-document version that came out of a SNAPSHOT read: a document
14
- * envelope's `version`, which cannot move once the envelope exists.
15
- *
16
- * A plain number at runtime, usable anywhere a number is, and it travels the
17
- * wire as one. The marker exists only so the compiler can tell it apart from a
18
- * version read off a LIVE document, which is the same number type and is the
19
- * defect the conflict gate exists to prevent: read at write time it is whatever
20
- * the server is at now, which is the number the gate is about to compare it
21
- * against, so the gate passes unconditionally and a concurrent edit is
22
- * overwritten with nothing logged.
23
- */
24
- export type SnapshotVersion = number & { readonly [snapshotMarker]: true };
25
-
26
- /**
27
- * What a write declares it was based on: a version from a snapshot read, or
28
- * `'anything'`.
29
- *
30
- * `'anything'` is a precondition that always holds — the write accepts whatever
31
- * the server currently has, and therefore overwrites it. It is the honest
32
- * answer wherever the write was authored against no particular server version,
33
- * and a lie anywhere a reader handed the writer a document. The field carrying
34
- * it is required, so an ungated write is a word someone chose rather than a
35
- * field someone forgot.
36
- */
37
- export type BasedOn = SnapshotVersion | 'anything';
38
-
39
- /**
40
- * Whether `basedOn` names a version, and so arms the gate.
41
- *
42
- * Branch on this rather than on `basedOn !== 'anything'`, which reads as "not
43
- * based on anything" — the opposite of what the branch tests.
44
- */
45
- export function isSnapshotVersion(basedOn: BasedOn): basedOn is SnapshotVersion {
46
- return typeof basedOn === 'number';
47
- }
48
-
49
- /**
50
- * Mark a version as having come from a snapshot read.
51
- *
52
- * **For the envelope constructors, not for callers.** Every door that builds a
53
- * document envelope applies it on the way in, so anything a read returns
54
- * already carries it and a writer never needs this. A caller reaching for it is
55
- * asserting a provenance the compiler was about to deny — the read-late defect
56
- * written out where a reviewer can see it.
57
- */
58
- export function asSnapshotVersion(version: number): SnapshotVersion {
59
- return version as SnapshotVersion;
60
- }
61
-
62
- /** A snapshot version no write can match, so a write based on it conflicts. */
63
- export const NO_MATCHING_VERSION: SnapshotVersion = asSnapshotVersion(-1);