@superbee/core 0.1.6 → 0.2.0-pre.10

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 (248) hide show
  1. package/README.md +127 -0
  2. package/dist/artifact-contract.d.ts +90 -0
  3. package/dist/artifact-contract.d.ts.map +1 -0
  4. package/dist/artifact-contract.js +205 -0
  5. package/dist/artifact-contract.js.map +1 -0
  6. package/dist/backend.d.ts +16 -1
  7. package/dist/backend.d.ts.map +1 -1
  8. package/dist/backend.js +112 -47
  9. package/dist/backend.js.map +1 -1
  10. package/dist/bundle-ops.d.ts +42 -0
  11. package/dist/bundle-ops.d.ts.map +1 -0
  12. package/dist/bundle-ops.js +88 -0
  13. package/dist/bundle-ops.js.map +1 -0
  14. package/dist/bundle.d.ts +10 -28
  15. package/dist/bundle.d.ts.map +1 -1
  16. package/dist/bundle.js +12 -69
  17. package/dist/bundle.js.map +1 -1
  18. package/dist/document-codec.d.ts +6 -1
  19. package/dist/document-codec.d.ts.map +1 -1
  20. package/dist/document-codec.js +8 -4
  21. package/dist/document-codec.js.map +1 -1
  22. package/dist/document-field-actions.d.ts +107 -0
  23. package/dist/document-field-actions.d.ts.map +1 -0
  24. package/dist/document-field-actions.js +302 -0
  25. package/dist/document-field-actions.js.map +1 -0
  26. package/dist/document-mutation.d.ts +61 -3
  27. package/dist/document-mutation.d.ts.map +1 -1
  28. package/dist/document-mutation.js +116 -25
  29. package/dist/document-mutation.js.map +1 -1
  30. package/dist/document-write-policy.d.ts +0 -1
  31. package/dist/document-write-policy.d.ts.map +1 -1
  32. package/dist/document-write-policy.js +7 -4
  33. package/dist/document-write-policy.js.map +1 -1
  34. package/dist/engine.d.ts +3 -1
  35. package/dist/engine.d.ts.map +1 -1
  36. package/dist/engine.js +6 -4
  37. package/dist/engine.js.map +1 -1
  38. package/dist/file-journaled-backend-internal.d.ts +19 -0
  39. package/dist/file-journaled-backend-internal.d.ts.map +1 -0
  40. package/dist/file-journaled-backend-internal.js +10 -0
  41. package/dist/file-journaled-backend-internal.js.map +1 -0
  42. package/dist/file-journaled-backend.d.ts +162 -0
  43. package/dist/file-journaled-backend.d.ts.map +1 -0
  44. package/dist/file-journaled-backend.js +1113 -0
  45. package/dist/file-journaled-backend.js.map +1 -0
  46. package/dist/filesystem-host.d.ts +15 -0
  47. package/dist/filesystem-host.d.ts.map +1 -0
  48. package/dist/filesystem-host.js +20 -0
  49. package/dist/filesystem-host.js.map +1 -0
  50. package/dist/filesystem-identity.d.ts +86 -4
  51. package/dist/filesystem-identity.d.ts.map +1 -1
  52. package/dist/filesystem-identity.js +338 -102
  53. package/dist/filesystem-identity.js.map +1 -1
  54. package/dist/filesystem-lock.d.ts +16 -3
  55. package/dist/filesystem-lock.d.ts.map +1 -1
  56. package/dist/filesystem-lock.js +323 -77
  57. package/dist/filesystem-lock.js.map +1 -1
  58. package/dist/filesystem-push-role.d.ts +77 -0
  59. package/dist/filesystem-push-role.d.ts.map +1 -0
  60. package/dist/filesystem-push-role.js +165 -0
  61. package/dist/filesystem-push-role.js.map +1 -0
  62. package/dist/filesystem.d.ts +17 -0
  63. package/dist/filesystem.d.ts.map +1 -0
  64. package/dist/filesystem.js +18 -0
  65. package/dist/filesystem.js.map +1 -0
  66. package/dist/freshness.d.ts +10 -7
  67. package/dist/freshness.d.ts.map +1 -1
  68. package/dist/freshness.js +14 -11
  69. package/dist/freshness.js.map +1 -1
  70. package/dist/frontmatter-contract.d.ts +2 -2
  71. package/dist/frontmatter-contract.js +2 -2
  72. package/dist/frontmatter.d.ts +11 -1
  73. package/dist/frontmatter.d.ts.map +1 -1
  74. package/dist/frontmatter.js +20 -7
  75. package/dist/frontmatter.js.map +1 -1
  76. package/dist/governed-body-write.d.ts +106 -0
  77. package/dist/governed-body-write.d.ts.map +1 -0
  78. package/dist/governed-body-write.js +251 -0
  79. package/dist/governed-body-write.js.map +1 -0
  80. package/dist/heads-digest.d.ts +32 -0
  81. package/dist/heads-digest.d.ts.map +1 -0
  82. package/dist/heads-digest.js +37 -0
  83. package/dist/heads-digest.js.map +1 -0
  84. package/dist/host-policy-snapshot.d.ts +9 -0
  85. package/dist/host-policy-snapshot.d.ts.map +1 -0
  86. package/dist/host-policy-snapshot.js +33 -0
  87. package/dist/host-policy-snapshot.js.map +1 -0
  88. package/dist/host-text.d.ts +31 -0
  89. package/dist/host-text.d.ts.map +1 -0
  90. package/dist/host-text.js +77 -0
  91. package/dist/host-text.js.map +1 -0
  92. package/dist/hosted-transport/answer-rows.d.ts +216 -0
  93. package/dist/hosted-transport/answer-rows.d.ts.map +1 -0
  94. package/dist/hosted-transport/answer-rows.js +396 -0
  95. package/dist/hosted-transport/answer-rows.js.map +1 -0
  96. package/dist/hosted-transport/carrier.d.ts +142 -0
  97. package/dist/hosted-transport/carrier.d.ts.map +1 -0
  98. package/dist/hosted-transport/carrier.js +241 -0
  99. package/dist/hosted-transport/carrier.js.map +1 -0
  100. package/dist/hosted-transport/history-pages.d.ts +55 -0
  101. package/dist/hosted-transport/history-pages.d.ts.map +1 -0
  102. package/dist/hosted-transport/history-pages.js +69 -0
  103. package/dist/hosted-transport/history-pages.js.map +1 -0
  104. package/dist/hosted-transport/index.d.ts +18 -0
  105. package/dist/hosted-transport/index.d.ts.map +1 -0
  106. package/dist/hosted-transport/index.js +18 -0
  107. package/dist/hosted-transport/index.js.map +1 -0
  108. package/dist/hosted-transport/operations.d.ts +85 -0
  109. package/dist/hosted-transport/operations.d.ts.map +1 -0
  110. package/dist/hosted-transport/operations.js +128 -0
  111. package/dist/hosted-transport/operations.js.map +1 -0
  112. package/dist/hosted-transport/paged-reads.d.ts +87 -0
  113. package/dist/hosted-transport/paged-reads.d.ts.map +1 -0
  114. package/dist/hosted-transport/paged-reads.js +238 -0
  115. package/dist/hosted-transport/paged-reads.js.map +1 -0
  116. package/dist/hosted-transport/read-adapter.d.ts +250 -0
  117. package/dist/hosted-transport/read-adapter.d.ts.map +1 -0
  118. package/dist/hosted-transport/read-adapter.js +557 -0
  119. package/dist/hosted-transport/read-adapter.js.map +1 -0
  120. package/dist/hosted-transport/root-write.d.ts +114 -0
  121. package/dist/hosted-transport/root-write.d.ts.map +1 -0
  122. package/dist/hosted-transport/root-write.js +171 -0
  123. package/dist/hosted-transport/root-write.js.map +1 -0
  124. package/dist/hosted-transport/whole-document-transport.d.ts +174 -0
  125. package/dist/hosted-transport/whole-document-transport.d.ts.map +1 -0
  126. package/dist/hosted-transport/whole-document-transport.js +411 -0
  127. package/dist/hosted-transport/whole-document-transport.js.map +1 -0
  128. package/dist/index.d.ts +29 -8
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +26 -6
  131. package/dist/index.js.map +1 -1
  132. package/dist/indexeddb-backend.d.ts +197 -0
  133. package/dist/indexeddb-backend.d.ts.map +1 -0
  134. package/dist/indexeddb-backend.js +1183 -0
  135. package/dist/indexeddb-backend.js.map +1 -0
  136. package/dist/journaled-backend.d.ts +467 -0
  137. package/dist/journaled-backend.d.ts.map +1 -0
  138. package/dist/journaled-backend.js +389 -0
  139. package/dist/journaled-backend.js.map +1 -0
  140. package/dist/kind-coverage.d.ts +99 -0
  141. package/dist/kind-coverage.d.ts.map +1 -0
  142. package/dist/kind-coverage.js +186 -0
  143. package/dist/kind-coverage.js.map +1 -0
  144. package/dist/kind-field-mutation.d.ts +15 -0
  145. package/dist/kind-field-mutation.d.ts.map +1 -0
  146. package/dist/kind-field-mutation.js +160 -0
  147. package/dist/kind-field-mutation.js.map +1 -0
  148. package/dist/kinds.d.ts +31 -1
  149. package/dist/kinds.d.ts.map +1 -1
  150. package/dist/kinds.js +86 -17
  151. package/dist/kinds.js.map +1 -1
  152. package/dist/links.d.ts +1 -1
  153. package/dist/links.d.ts.map +1 -1
  154. package/dist/links.js +5 -10
  155. package/dist/links.js.map +1 -1
  156. package/dist/managed-document-metadata.d.ts +2 -0
  157. package/dist/managed-document-metadata.d.ts.map +1 -0
  158. package/dist/managed-document-metadata.js +9 -0
  159. package/dist/managed-document-metadata.js.map +1 -0
  160. package/dist/meaningful-change-time.d.ts +6 -6
  161. package/dist/meaningful-change-time.d.ts.map +1 -1
  162. package/dist/meaningful-change-time.js +12 -13
  163. package/dist/meaningful-change-time.js.map +1 -1
  164. package/dist/memory-backend.d.ts +2 -2
  165. package/dist/memory-backend.d.ts.map +1 -1
  166. package/dist/memory-backend.js +32 -16
  167. package/dist/memory-backend.js.map +1 -1
  168. package/dist/mutation.d.ts.map +1 -1
  169. package/dist/mutation.js +2 -2
  170. package/dist/mutation.js.map +1 -1
  171. package/dist/page.d.ts.map +1 -1
  172. package/dist/page.js +2 -1
  173. package/dist/page.js.map +1 -1
  174. package/dist/paths.d.ts +6 -2
  175. package/dist/paths.d.ts.map +1 -1
  176. package/dist/paths.js +35 -33
  177. package/dist/paths.js.map +1 -1
  178. package/dist/platform.d.ts +239 -0
  179. package/dist/platform.d.ts.map +1 -0
  180. package/dist/platform.js +82 -0
  181. package/dist/platform.js.map +1 -0
  182. package/dist/portable-export.d.ts +119 -0
  183. package/dist/portable-export.d.ts.map +1 -0
  184. package/dist/portable-export.js +501 -0
  185. package/dist/portable-export.js.map +1 -0
  186. package/dist/publication-filesystem.d.ts +1 -1
  187. package/dist/publication-filesystem.d.ts.map +1 -1
  188. package/dist/publication-filesystem.js +1 -1
  189. package/dist/publication-filesystem.js.map +1 -1
  190. package/dist/query-filter.d.ts +7 -2
  191. package/dist/query-filter.d.ts.map +1 -1
  192. package/dist/query-filter.js +24 -1
  193. package/dist/query-filter.js.map +1 -1
  194. package/dist/query-order.d.ts +22 -0
  195. package/dist/query-order.d.ts.map +1 -0
  196. package/dist/query-order.js +42 -0
  197. package/dist/query-order.js.map +1 -0
  198. package/dist/recipes.d.ts +3 -1
  199. package/dist/recipes.d.ts.map +1 -1
  200. package/dist/recipes.js +4 -4
  201. package/dist/recipes.js.map +1 -1
  202. package/dist/remote-backend.d.ts +114 -22
  203. package/dist/remote-backend.d.ts.map +1 -1
  204. package/dist/remote-backend.js +285 -44
  205. package/dist/remote-backend.js.map +1 -1
  206. package/dist/remote-document-codec.d.ts +15 -0
  207. package/dist/remote-document-codec.d.ts.map +1 -0
  208. package/dist/remote-document-codec.js +103 -0
  209. package/dist/remote-document-codec.js.map +1 -0
  210. package/dist/remote-error.d.ts +38 -0
  211. package/dist/remote-error.d.ts.map +1 -0
  212. package/dist/remote-error.js +59 -0
  213. package/dist/remote-error.js.map +1 -0
  214. package/dist/remote-operations.d.ts +63 -0
  215. package/dist/remote-operations.d.ts.map +1 -0
  216. package/dist/remote-operations.js +125 -0
  217. package/dist/remote-operations.js.map +1 -0
  218. package/dist/remote-parsers.d.ts +86 -0
  219. package/dist/remote-parsers.d.ts.map +1 -0
  220. package/dist/remote-parsers.js +181 -0
  221. package/dist/remote-parsers.js.map +1 -0
  222. package/dist/remote.d.ts +7 -2
  223. package/dist/remote.d.ts.map +1 -1
  224. package/dist/remote.js +6 -1
  225. package/dist/remote.js.map +1 -1
  226. package/dist/sha256.d.ts +21 -0
  227. package/dist/sha256.d.ts.map +1 -0
  228. package/dist/sha256.js +114 -0
  229. package/dist/sha256.js.map +1 -0
  230. package/dist/storage.d.ts +8 -2
  231. package/dist/storage.d.ts.map +1 -1
  232. package/dist/storage.js +11 -2
  233. package/dist/storage.js.map +1 -1
  234. package/dist/types.d.ts +40 -5
  235. package/dist/types.d.ts.map +1 -1
  236. package/dist/uncertain-write.d.ts +178 -0
  237. package/dist/uncertain-write.d.ts.map +1 -0
  238. package/dist/uncertain-write.js +198 -0
  239. package/dist/uncertain-write.js.map +1 -0
  240. package/dist/version-transport.d.ts +7 -0
  241. package/dist/version-transport.d.ts.map +1 -1
  242. package/dist/version-transport.js +9 -0
  243. package/dist/version-transport.js.map +1 -1
  244. package/dist/versioning.d.ts +18 -7
  245. package/dist/versioning.d.ts.map +1 -1
  246. package/dist/versioning.js +23 -14
  247. package/dist/versioning.js.map +1 -1
  248. package/package.json +75 -4
@@ -0,0 +1,178 @@
1
+ /**
2
+ * The shared uncertain-write primitive: durable request identity, unknown-outcome handling, and
3
+ * acknowledgement reconciliation for a write whose delivery to a shared authority may be lost.
4
+ *
5
+ * A network write has three answers, not two: the authority applied it, refused it, or the
6
+ * client cannot tell (a dropped response, a timeout, a transport error). Retrying a write in the
7
+ * third case is only safe when the authority can recognize the retry as the same request, so
8
+ * every write carries a {@link OperationIntent.requestId} minted once when the intent is first
9
+ * recorded and never reminted, and every transport offers {@link OperationTransport.lookup} to
10
+ * fetch the authority's recorded outcome for that id.
11
+ *
12
+ * This module is owned by core so the browser-local sync component and bounded CLI remote
13
+ * operations consume one primitive rather than each defining unknown-outcome handling. It
14
+ * imports no Node builtin and bundles for the browser. The only injected time source is the
15
+ * `sleep` used for the submission deadline and the delay between lookups; there is no
16
+ * injectable clock, and a transport's own latency is real.
17
+ */
18
+ import type { Version } from "./types.js";
19
+ /**
20
+ * The operation an intent performs: a whole-document write, or `document.delete` (the document
21
+ * leaves at exactly the intent's base). The union is open.
22
+ */
23
+ export type OperationKind = "document.write" | "document.delete" | (string & {});
24
+ /**
25
+ * The journal states of an intent. `unknown` is the primitive's own classification of an
26
+ * exhausted attempt: the authority may or may not hold the write, and a later attempt begins
27
+ * with a lookup rather than a submission.
28
+ */
29
+ export type OperationState = "pending" | "in_flight" | "acknowledged" | "conflict" | "refused" | "unknown";
30
+ /** A recorded local write awaiting a shared outcome. */
31
+ export interface OperationIntent {
32
+ /** Durable request identity: minted once at record time, reused verbatim on every retry. */
33
+ requestId: string;
34
+ kind: OperationKind;
35
+ /** The target the operation applies to; a concept id for `document.write`. */
36
+ target: string;
37
+ /**
38
+ * The tombstone a create acknowledges it re-creates: the id's latest deletion, as the
39
+ * authority named it. Set only by an explicit decision to re-create a deleted document (the
40
+ * caller's own observed delete, or `keep` on a "deleted remotely" conflict), and only on a
41
+ * create recorded after that decision; never inferred by a transport. An authority whose latest
42
+ * deletion of the id is another one refuses the create, and the refusal comes back as a
43
+ * conflict.
44
+ */
45
+ recreates?: Version;
46
+ /** The shared version the local edit was made against, or `null` for a create. */
47
+ base: Version | null;
48
+ /** The content-addressed version of `content`. */
49
+ local: Version;
50
+ /** The serialized document: the comparison content reconciliation needs later. */
51
+ content: string;
52
+ createdAt: string;
53
+ /**
54
+ * Submissions made or durably claimed so far. A non-zero count means the authority may already
55
+ * hold the write, so delivery begins with a lookup rather than a submission; a journal records
56
+ * the count before a submission leaves, never only after its outcome is known.
57
+ */
58
+ attempts: number;
59
+ state: OperationState;
60
+ }
61
+ /** The authority's answer for one request identity. */
62
+ export type Outcome = {
63
+ kind: "committed";
64
+ version: Version;
65
+ }
66
+ /**
67
+ * The shared head moved. `actual` is the version the authority serves now, or `null` when it
68
+ * serves none ("deleted remotely"). With `actual: null`, `tombstone` names the deletion the
69
+ * authority reported, when it reported one: the acknowledgement a deliberate re-create sends.
70
+ * It is never a version any read serves, so it is never a base or a remote version.
71
+ */
72
+ | {
73
+ kind: "conflict";
74
+ actual: Version | null;
75
+ tombstone?: Version;
76
+ } | {
77
+ kind: "refused";
78
+ code: string;
79
+ message: string;
80
+ } | {
81
+ kind: "unknown";
82
+ };
83
+ /** A transport to a shared authority that records outcomes by request identity. */
84
+ export interface OperationTransport {
85
+ /** Deliver the intent once; a repeated `requestId` must return the recorded outcome unchanged. */
86
+ submit(intent: OperationIntent, options?: {
87
+ signal?: AbortSignal;
88
+ }): Promise<Outcome>;
89
+ /** The recorded outcome for `requestId`, or `null` when the authority never recorded it. */
90
+ lookup(requestId: string): Promise<Outcome | null>;
91
+ }
92
+ /** Wait `ms`, resolving early and harmlessly when `signal` aborts. Injected by tests. */
93
+ export type Sleep = (ms: number, signal?: AbortSignal) => Promise<void>;
94
+ export interface UncertainWriteOptions {
95
+ /** Exact-content writes may settle matching conflicts; governed writes require recorded evidence. */
96
+ settlement?: "exact-content" | "recorded-only";
97
+ /** How long one submission may take before its outcome is treated as unknown. */
98
+ deadlineMs?: number;
99
+ /** Lookups attempted after an unknown submission before giving up (default 3). */
100
+ maxLookups?: number;
101
+ /** Delay between lookups (default 250 ms; the injected `sleep` decides what that means). */
102
+ lookupDelayMs?: number;
103
+ /** Submissions permitted in one call, counting the first (default 2). */
104
+ maxSubmissions?: number;
105
+ /** Timer; defaults to a `setTimeout` sleep. */
106
+ sleep?: Sleep;
107
+ }
108
+ export interface UncertainWriteResult {
109
+ /** The intent with `attempts` and `state` advanced to reflect this call. */
110
+ intent: OperationIntent;
111
+ outcome: Outcome;
112
+ /** How many times `lookup` was called in this call. */
113
+ lookups: number;
114
+ }
115
+ /**
116
+ * Refusal codes that pause shared operations rather than say the content is wrong: the caller's
117
+ * authorization is gone, or a hosted sync quota (`REQUEST_CAPACITY_*`, one per scope the host
118
+ * names) is spent. Nothing is lost; the refused intents are requeued when the store resumes.
119
+ */
120
+ export declare const AUTHORIZATION_REFUSAL_CODES: ReadonlySet<string>;
121
+ /** True when a refusal reports lost permission, which pauses further shared operations. */
122
+ export declare function isAuthorizationRefusal(outcome: Outcome): boolean;
123
+ /** The journal state an outcome settles an intent into. */
124
+ export declare function stateForOutcome(outcome: Outcome): OperationState;
125
+ /** The longest request identity the wire accepts as `Idempotency-Key`. */
126
+ export declare const REQUEST_ID_MAX_LENGTH = 128;
127
+ /**
128
+ * True when `value` is a request identity the wire accepts: 1 to {@link REQUEST_ID_MAX_LENGTH}
129
+ * printable ASCII characters with no space, and neither `.` nor `..`, which URL normalization
130
+ * folds away so the lookup route could never name them. One rule, checked by the client before
131
+ * a header is built and by the reference router before a key is claimed, so the two sides cannot
132
+ * disagree about which keys identify a write. A minted UUID always satisfies it.
133
+ */
134
+ export declare function isRequestIdentity(value: string): boolean;
135
+ /** A fresh request identity. UUIDs come from the platform's `crypto` in both Node and browsers. */
136
+ export declare function mintRequestId(): string;
137
+ /**
138
+ * The outcome as it applies to `intent`. A conflict whose `actual` is the intent's own `local`
139
+ * version means the shared head already holds exactly this content, so the write is committed
140
+ * at that version. This is the wire's post-expiry compare-and-swap property: a committed write
141
+ * whose acknowledgement was lost and whose recorded outcome expired from the authority's
142
+ * retention window is resubmitted with its original `base`, which no longer matches, and the
143
+ * authority answers a conflict against the version the client itself committed. If the head
144
+ * has meanwhile returned to `base` exactly, the premise matches and the resubmission is applied
145
+ * again instead (see {@link performUncertainWrite}). The mapping depends only on the outcome and
146
+ * the intent, so the primitive owns it and every consumer settles such a write as acknowledged
147
+ * rather than presenting it as a concurrent edit. Every other conflict, including one against
148
+ * an absent head (`actual: null`), is returned unchanged.
149
+ */
150
+ export declare function settleAgainstIntent(outcome: Outcome, intent: OperationIntent): Outcome;
151
+ /**
152
+ * Deliver `intent` and settle it against the authority's recorded outcome.
153
+ *
154
+ * Flow: when the intent has never been submitted, submit it; when it has (`attempts > 0`), or
155
+ * when a submission's outcome is unknown, look the request identity up. A positive lookup
156
+ * settles the intent. A `null` lookup is taken as proof that the authority never recorded the
157
+ * request, so a resubmission with the same `requestId` is the first delivery and is allowed, up
158
+ * to `maxSubmissions`. A lookup that fails leaves the outcome unknown and the intent for a later
159
+ * call.
160
+ *
161
+ * No resubmission happens without a `null` lookup because a blind retry after an unknown
162
+ * outcome is unsound: on an authority without request identity the retry applies the write
163
+ * twice, and even on one with it the retry can only ever learn what the lookup already knew.
164
+ * The lookup is what turns "unknown" into a fact before any second delivery.
165
+ *
166
+ * That fact is only as good as the transport's `null`. An authority that has expired or lost a
167
+ * record answers as if it never recorded it, and the reference wire transport returns `null`
168
+ * then. The resubmission is guarded only by its `base`, and versions are content hashes: if the
169
+ * write had landed and a third party has since restored `base` exactly, the premise matches, the
170
+ * write is applied a second time over that revert, and the outcome is `committed`. The hosted
171
+ * transport never answers `null` for an absent record once the intent is older than the host's
172
+ * stated retention window, less a skew margin, so it does not resubmit there.
173
+ *
174
+ * By default every outcome passes through {@link settleAgainstIntent}, so a conflict naming the
175
+ * intent's own version is committed. `recorded-only` preserves the recorded outcome instead.
176
+ */
177
+ export declare function performUncertainWrite(transport: OperationTransport, intent: OperationIntent, options?: UncertainWriteOptions): Promise<UncertainWriteResult>;
178
+ //# sourceMappingURL=uncertain-write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uncertain-write.d.ts","sourceRoot":"","sources":["../src/uncertain-write.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,198 @@
1
+ /**
2
+ * The shared uncertain-write primitive: durable request identity, unknown-outcome handling, and
3
+ * acknowledgement reconciliation for a write whose delivery to a shared authority may be lost.
4
+ *
5
+ * A network write has three answers, not two: the authority applied it, refused it, or the
6
+ * client cannot tell (a dropped response, a timeout, a transport error). Retrying a write in the
7
+ * third case is only safe when the authority can recognize the retry as the same request, so
8
+ * every write carries a {@link OperationIntent.requestId} minted once when the intent is first
9
+ * recorded and never reminted, and every transport offers {@link OperationTransport.lookup} to
10
+ * fetch the authority's recorded outcome for that id.
11
+ *
12
+ * This module is owned by core so the browser-local sync component and bounded CLI remote
13
+ * operations consume one primitive rather than each defining unknown-outcome handling. It
14
+ * imports no Node builtin and bundles for the browser. The only injected time source is the
15
+ * `sleep` used for the submission deadline and the delay between lookups; there is no
16
+ * injectable clock, and a transport's own latency is real.
17
+ */
18
+ /**
19
+ * Refusal codes that pause shared operations rather than say the content is wrong: the caller's
20
+ * authorization is gone, or a hosted sync quota (`REQUEST_CAPACITY_*`, one per scope the host
21
+ * names) is spent. Nothing is lost; the refused intents are requeued when the store resumes.
22
+ */
23
+ export const AUTHORIZATION_REFUSAL_CODES = new Set([
24
+ "AUTH_REQUIRED",
25
+ "FORBIDDEN",
26
+ "UNAUTHORIZED",
27
+ "PERMISSION_DENIED",
28
+ "REQUEST_CAPACITY_PRINCIPAL",
29
+ "REQUEST_CAPACITY_BUNDLE",
30
+ ]);
31
+ /** True when a refusal reports lost permission, which pauses further shared operations. */
32
+ export function isAuthorizationRefusal(outcome) {
33
+ return outcome.kind === "refused" && AUTHORIZATION_REFUSAL_CODES.has(outcome.code);
34
+ }
35
+ /** The journal state an outcome settles an intent into. */
36
+ export function stateForOutcome(outcome) {
37
+ switch (outcome.kind) {
38
+ case "committed":
39
+ return "acknowledged";
40
+ case "conflict":
41
+ return "conflict";
42
+ case "refused":
43
+ return "refused";
44
+ case "unknown":
45
+ return "unknown";
46
+ }
47
+ }
48
+ /** The longest request identity the wire accepts as `Idempotency-Key`. */
49
+ export const REQUEST_ID_MAX_LENGTH = 128;
50
+ /**
51
+ * True when `value` is a request identity the wire accepts: 1 to {@link REQUEST_ID_MAX_LENGTH}
52
+ * printable ASCII characters with no space, and neither `.` nor `..`, which URL normalization
53
+ * folds away so the lookup route could never name them. One rule, checked by the client before
54
+ * a header is built and by the reference router before a key is claimed, so the two sides cannot
55
+ * disagree about which keys identify a write. A minted UUID always satisfies it.
56
+ */
57
+ export function isRequestIdentity(value) {
58
+ if (value === "." || value === "..")
59
+ return false;
60
+ return value.length >= 1 && value.length <= REQUEST_ID_MAX_LENGTH && /^[!-~]+$/.test(value);
61
+ }
62
+ /** A fresh request identity. UUIDs come from the platform's `crypto` in both Node and browsers. */
63
+ export function mintRequestId() {
64
+ const cryptoApi = globalThis.crypto;
65
+ if (cryptoApi && typeof cryptoApi.randomUUID === "function")
66
+ return cryptoApi.randomUUID();
67
+ throw new Error("mintRequestId requires crypto.randomUUID in this host.");
68
+ }
69
+ const defaultSleep = (ms, signal) => new Promise((resolve) => {
70
+ if (signal?.aborted) {
71
+ resolve();
72
+ return;
73
+ }
74
+ const timer = setTimeout(() => {
75
+ signal?.removeEventListener("abort", onAbort);
76
+ resolve();
77
+ }, ms);
78
+ function onAbort() {
79
+ clearTimeout(timer);
80
+ resolve();
81
+ }
82
+ signal?.addEventListener("abort", onAbort, { once: true });
83
+ });
84
+ const UNKNOWN = { kind: "unknown" };
85
+ /**
86
+ * The outcome as it applies to `intent`. A conflict whose `actual` is the intent's own `local`
87
+ * version means the shared head already holds exactly this content, so the write is committed
88
+ * at that version. This is the wire's post-expiry compare-and-swap property: a committed write
89
+ * whose acknowledgement was lost and whose recorded outcome expired from the authority's
90
+ * retention window is resubmitted with its original `base`, which no longer matches, and the
91
+ * authority answers a conflict against the version the client itself committed. If the head
92
+ * has meanwhile returned to `base` exactly, the premise matches and the resubmission is applied
93
+ * again instead (see {@link performUncertainWrite}). The mapping depends only on the outcome and
94
+ * the intent, so the primitive owns it and every consumer settles such a write as acknowledged
95
+ * rather than presenting it as a concurrent edit. Every other conflict, including one against
96
+ * an absent head (`actual: null`), is returned unchanged.
97
+ */
98
+ export function settleAgainstIntent(outcome, intent) {
99
+ if (outcome.kind === "conflict" && outcome.actual !== null && outcome.actual === intent.local) {
100
+ return { kind: "committed", version: outcome.actual };
101
+ }
102
+ return outcome;
103
+ }
104
+ /**
105
+ * One submission under a deadline. A transport rejection or a deadline overrun both yield
106
+ * `unknown`: the request may have reached the authority, so nothing here decides otherwise.
107
+ */
108
+ async function submitOnce(transport, intent, deadlineMs, sleep) {
109
+ const controller = new AbortController();
110
+ const attempt = transport.submit(intent, { signal: controller.signal }).then((outcome) => ({ settled: true, outcome }), () => ({ settled: true, outcome: UNKNOWN }));
111
+ if (deadlineMs === undefined)
112
+ return (await attempt).outcome;
113
+ const timerControl = new AbortController();
114
+ const deadline = sleep(deadlineMs, timerControl.signal).then(() => ({ settled: false }));
115
+ const first = await Promise.race([attempt, deadline]);
116
+ if (first.settled) {
117
+ timerControl.abort();
118
+ return first.outcome;
119
+ }
120
+ controller.abort();
121
+ return UNKNOWN;
122
+ }
123
+ /**
124
+ * Deliver `intent` and settle it against the authority's recorded outcome.
125
+ *
126
+ * Flow: when the intent has never been submitted, submit it; when it has (`attempts > 0`), or
127
+ * when a submission's outcome is unknown, look the request identity up. A positive lookup
128
+ * settles the intent. A `null` lookup is taken as proof that the authority never recorded the
129
+ * request, so a resubmission with the same `requestId` is the first delivery and is allowed, up
130
+ * to `maxSubmissions`. A lookup that fails leaves the outcome unknown and the intent for a later
131
+ * call.
132
+ *
133
+ * No resubmission happens without a `null` lookup because a blind retry after an unknown
134
+ * outcome is unsound: on an authority without request identity the retry applies the write
135
+ * twice, and even on one with it the retry can only ever learn what the lookup already knew.
136
+ * The lookup is what turns "unknown" into a fact before any second delivery.
137
+ *
138
+ * That fact is only as good as the transport's `null`. An authority that has expired or lost a
139
+ * record answers as if it never recorded it, and the reference wire transport returns `null`
140
+ * then. The resubmission is guarded only by its `base`, and versions are content hashes: if the
141
+ * write had landed and a third party has since restored `base` exactly, the premise matches, the
142
+ * write is applied a second time over that revert, and the outcome is `committed`. The hosted
143
+ * transport never answers `null` for an absent record once the intent is older than the host's
144
+ * stated retention window, less a skew margin, so it does not resubmit there.
145
+ *
146
+ * By default every outcome passes through {@link settleAgainstIntent}, so a conflict naming the
147
+ * intent's own version is committed. `recorded-only` preserves the recorded outcome instead.
148
+ */
149
+ export async function performUncertainWrite(transport, intent, options = {}) {
150
+ if (options.settlement !== undefined && options.settlement !== "exact-content" && options.settlement !== "recorded-only") {
151
+ throw new Error("Unknown uncertain-write settlement mode.");
152
+ }
153
+ const sleep = options.sleep ?? defaultSleep;
154
+ const maxLookups = Math.max(1, options.maxLookups ?? 3);
155
+ const maxSubmissions = Math.max(1, options.maxSubmissions ?? 2);
156
+ const lookupDelayMs = options.lookupDelayMs ?? 250;
157
+ let attempts = intent.attempts;
158
+ let submissions = 0;
159
+ let lookups = 0;
160
+ const finish = (raw) => {
161
+ const outcome = options.settlement === "recorded-only" ? raw : settleAgainstIntent(raw, intent);
162
+ return { intent: { ...intent, attempts, state: stateForOutcome(outcome) }, outcome, lookups };
163
+ };
164
+ // A previously submitted intent starts at the lookup, never at a submission.
165
+ let needSubmission = attempts === 0;
166
+ for (;;) {
167
+ if (needSubmission) {
168
+ if (submissions >= maxSubmissions)
169
+ return finish(UNKNOWN);
170
+ submissions += 1;
171
+ attempts += 1;
172
+ const outcome = await submitOnce(transport, { ...intent, attempts, state: "in_flight" }, options.deadlineMs, sleep);
173
+ if (outcome.kind !== "unknown")
174
+ return finish(outcome);
175
+ }
176
+ needSubmission = false;
177
+ let recorded;
178
+ for (let round = 0; round < maxLookups; round++) {
179
+ if (round > 0)
180
+ await sleep(lookupDelayMs);
181
+ lookups += 1;
182
+ try {
183
+ recorded = await transport.lookup(intent.requestId);
184
+ }
185
+ catch {
186
+ recorded = undefined;
187
+ continue;
188
+ }
189
+ break;
190
+ }
191
+ if (recorded === undefined)
192
+ return finish(UNKNOWN);
193
+ if (recorded !== null)
194
+ return finish(recorded);
195
+ needSubmission = true;
196
+ }
197
+ }
198
+ //# sourceMappingURL=uncertain-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uncertain-write.js","sourceRoot":"","sources":["../src/uncertain-write.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,12 @@
1
1
  /** Runtime-neutral version transport primitives shared by storage adapters and HTTP clients. */
2
2
  import type { Version } from "./types.js";
3
+ /**
4
+ * True only for a content-addressed {@link Version} token as every conforming backend mints it:
5
+ * `sha256:` followed by 64 lowercase hex characters. One rule, shared by the reference router
6
+ * (an identified delete's premise) and the View registry (an entry pin), so no adapter carries
7
+ * its own reading of what a version looks like.
8
+ */
9
+ export declare function isContentVersion(value: unknown): value is Version;
3
10
  /** Recover a bare version token from a quoted or weak HTTP ETag. */
4
11
  export declare function stripETagWrapper(raw: string): string;
5
12
  /** Typed optimistic-concurrency rejection raised by every conforming backend. */
@@ -1 +1 @@
1
- {"version":3,"file":"version-transport.d.ts","sourceRoot":"","sources":["../src/version-transport.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAEhG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED,iFAAiF;AACjF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,IAAI,qBAAqB;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;gBAEpB,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;CASzE"}
1
+ {"version":3,"file":"version-transport.d.ts","sourceRoot":"","sources":["../src/version-transport.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAEhG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAEjE;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED,iFAAiF;AACjF,qBAAa,eAAgB,SAAQ,KAAK;IACxC,SAAkB,IAAI,qBAAqB;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;gBAEpB,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;CASzE"}
@@ -1,4 +1,13 @@
1
1
  /** Runtime-neutral version transport primitives shared by storage adapters and HTTP clients. */
2
+ /**
3
+ * True only for a content-addressed {@link Version} token as every conforming backend mints it:
4
+ * `sha256:` followed by 64 lowercase hex characters. One rule, shared by the reference router
5
+ * (an identified delete's premise) and the View registry (an entry pin), so no adapter carries
6
+ * its own reading of what a version looks like.
7
+ */
8
+ export function isContentVersion(value) {
9
+ return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
10
+ }
2
11
  /** Recover a bare version token from a quoted or weak HTTP ETag. */
3
12
  export function stripETagWrapper(raw) {
4
13
  let value = raw.trim();
@@ -1 +1 @@
1
- {"version":3,"file":"version-transport.js","sourceRoot":"","sources":["../src/version-transport.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAIhG,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACtE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iFAAiF;AACjF,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACtB,IAAI,GAAG,iBAAiB,CAAC;IAClC,EAAE,CAAS;IACX,QAAQ,CAAiB;IACzB,MAAM,CAAiB;IAEhC,YAAY,EAAU,EAAE,QAAwB,EAAE,MAAsB;QACtE,KAAK,CACH,wBAAwB,EAAE,eAAe,QAAQ,IAAI,QAAQ,WAAW,MAAM,IAAI,MAAM,GAAG;YACzF,8DAA8D,CACjE,CAAC;QACF,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
1
+ {"version":3,"file":"version-transport.js","sourceRoot":"","sources":["../src/version-transport.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAIhG;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnD,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACtE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iFAAiF;AACjF,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACtB,IAAI,GAAG,iBAAiB,CAAC;IAClC,EAAE,CAAS;IACX,QAAQ,CAAiB;IACzB,MAAM,CAAiB;IAEhC,YAAY,EAAU,EAAE,QAAwB,EAAE,MAAsB;QACtE,KAAK,CACH,wBAAwB,EAAE,eAAe,QAAQ,IAAI,QAAQ,WAAW,MAAM,IAAI,MAAM,GAAG;YACzF,8DAA8D,CACjE,CAAC;QACF,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}
@@ -4,10 +4,15 @@
4
4
  * These make the seam's "hardest case" (content-addressed versions, compare-and-swap
5
5
  * writes, actor attribution) implementable uniformly across adapters:
6
6
  * - {@link contentVersion} defines the opaque {@link Version} token as a SHA-256 over
7
- * the canonically serialized document, so identical content yields the same token
8
- * REGARDLESS of backend. The filesystem adapter hashes the on-disk bytes; the
9
- * in-memory adapter hashes the same serialization — an engine-written document
10
- * therefore carries the same version token in either store.
7
+ * the canonically serialized document. A version token is a property of a backend,
8
+ * not of the content: the filesystem adapter hashes the on-disk bytes exactly as
9
+ * written, so a hand-authored file keeps its bytes and its token; the in-memory and
10
+ * wire adapters hash a re-serialization, which folds long strings, expands flow
11
+ * sequences and normalizes timestamps, so content Superbee did not author may carry a
12
+ * different token there. An engine-written document carries the same token in either
13
+ * store, because the engine wrote the canonical bytes. Bytes are the promise that
14
+ * travels between backends (decision of 2026-09-17, "bytes win"); no promise rests on
15
+ * cross-backend token equality.
11
16
  * - {@link blobVersion} is the SAME idea for opaque blob bytes, but hashes the raw
12
17
  * `Uint8Array` directly with no string/UTF-8 step — a doc-shaped hash would corrupt
13
18
  * binary content, so blobs get their own primitive rather than reusing `contentVersion`
@@ -17,10 +22,12 @@
17
22
  * the caller's `expectedVersion` no longer matches the backend's current version.
18
23
  *
19
24
  * Nothing here touches the filesystem — hashing and attribution are backend-neutral,
20
- * which is precisely why the memory adapter can reuse them.
25
+ * which is precisely why the memory adapter can reuse them. The digest itself lives in
26
+ * `sha256.ts`, a pure implementation with no `node:*` import, so this module and the memory
27
+ * adapter bundle for the browser and mint the same tokens there.
21
28
  */
22
29
  import type { OkfDocument, Version } from "./types.js";
23
- export { VersionConflict, stripETagWrapper } from "./version-transport.js";
30
+ export { VersionConflict, isContentVersion, stripETagWrapper } from "./version-transport.js";
24
31
  /** Lowercase hex SHA-256 of a UTF-8 string. The version tokens' underlying digest. */
25
32
  export declare function sha256Hex(input: string): string;
26
33
  /**
@@ -43,6 +50,10 @@ export declare function versionOfBytes(raw: string): Version;
43
50
  * of adapter, exactly as {@link contentVersion} is for documents.
44
51
  */
45
52
  export declare function blobVersion(bytes: Uint8Array): Version;
46
- /** A local identity for unattributed writes: `$USER`/`$USERNAME`/`$LOGNAME`, else `"local"`. */
53
+ /**
54
+ * A local identity for unattributed writes: `$USER`/`$USERNAME`/`$LOGNAME`, else `"local"`.
55
+ * A browser host has no `process` global, so the environment lookup is guarded and such a
56
+ * host attributes to `"local"`.
57
+ */
47
58
  export declare function defaultActor(): string;
48
59
  //# sourceMappingURL=versioning.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"versioning.d.ts","sourceRoot":"","sources":["../src/versioning.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE3E,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAExD;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAEtD;AAED,gGAAgG;AAChG,wBAAgB,YAAY,IAAI,MAAM,CAOrC"}
1
+ {"version":3,"file":"versioning.d.ts","sourceRoot":"","sources":["../src/versioning.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE7F,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAExD;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAGrC"}
@@ -4,10 +4,15 @@
4
4
  * These make the seam's "hardest case" (content-addressed versions, compare-and-swap
5
5
  * writes, actor attribution) implementable uniformly across adapters:
6
6
  * - {@link contentVersion} defines the opaque {@link Version} token as a SHA-256 over
7
- * the canonically serialized document, so identical content yields the same token
8
- * REGARDLESS of backend. The filesystem adapter hashes the on-disk bytes; the
9
- * in-memory adapter hashes the same serialization — an engine-written document
10
- * therefore carries the same version token in either store.
7
+ * the canonically serialized document. A version token is a property of a backend,
8
+ * not of the content: the filesystem adapter hashes the on-disk bytes exactly as
9
+ * written, so a hand-authored file keeps its bytes and its token; the in-memory and
10
+ * wire adapters hash a re-serialization, which folds long strings, expands flow
11
+ * sequences and normalizes timestamps, so content Superbee did not author may carry a
12
+ * different token there. An engine-written document carries the same token in either
13
+ * store, because the engine wrote the canonical bytes. Bytes are the promise that
14
+ * travels between backends (decision of 2026-09-17, "bytes win"); no promise rests on
15
+ * cross-backend token equality.
11
16
  * - {@link blobVersion} is the SAME idea for opaque blob bytes, but hashes the raw
12
17
  * `Uint8Array` directly with no string/UTF-8 step — a doc-shaped hash would corrupt
13
18
  * binary content, so blobs get their own primitive rather than reusing `contentVersion`
@@ -17,14 +22,16 @@
17
22
  * the caller's `expectedVersion` no longer matches the backend's current version.
18
23
  *
19
24
  * Nothing here touches the filesystem — hashing and attribution are backend-neutral,
20
- * which is precisely why the memory adapter can reuse them.
25
+ * which is precisely why the memory adapter can reuse them. The digest itself lives in
26
+ * `sha256.ts`, a pure implementation with no `node:*` import, so this module and the memory
27
+ * adapter bundle for the browser and mint the same tokens there.
21
28
  */
22
- import { createHash } from "node:crypto";
23
29
  import { stringifyDoc } from "./frontmatter.js";
24
- export { VersionConflict, stripETagWrapper } from "./version-transport.js";
30
+ import { sha256HexOfBytes, sha256HexOfUtf8 } from "./sha256.js";
31
+ export { VersionConflict, isContentVersion, stripETagWrapper } from "./version-transport.js";
25
32
  /** Lowercase hex SHA-256 of a UTF-8 string. The version tokens' underlying digest. */
26
33
  export function sha256Hex(input) {
27
- return createHash("sha256").update(input, "utf8").digest("hex");
34
+ return sha256HexOfUtf8(input);
28
35
  }
29
36
  /**
30
37
  * The content-addressed {@link Version} of a concept document: the SHA-256 of its
@@ -50,13 +57,15 @@ export function versionOfBytes(raw) {
50
57
  * of adapter, exactly as {@link contentVersion} is for documents.
51
58
  */
52
59
  export function blobVersion(bytes) {
53
- return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
60
+ return `sha256:${sha256HexOfBytes(bytes)}`;
54
61
  }
55
- /** A local identity for unattributed writes: `$USER`/`$USERNAME`/`$LOGNAME`, else `"local"`. */
62
+ /**
63
+ * A local identity for unattributed writes: `$USER`/`$USERNAME`/`$LOGNAME`, else `"local"`.
64
+ * A browser host has no `process` global, so the environment lookup is guarded and such a
65
+ * host attributes to `"local"`.
66
+ */
56
67
  export function defaultActor() {
57
- return (process.env.USER?.trim() ||
58
- process.env.USERNAME?.trim() ||
59
- process.env.LOGNAME?.trim() ||
60
- "local");
68
+ const env = typeof process !== "undefined" ? process.env : undefined;
69
+ return env?.USER?.trim() || env?.USERNAME?.trim() || env?.LOGNAME?.trim() || "local";
61
70
  }
62
71
  //# sourceMappingURL=versioning.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"versioning.js","sourceRoot":"","sources":["../src/versioning.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE3E,sFAAsF;AACtF,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,GAAgB;IAC7C,OAAO,UAAU,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,UAAU,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;AACpC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,KAAiB;IAC3C,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACtE,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,YAAY;IAC1B,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE;QACxB,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE;QAC5B,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE;QAC3B,OAAO,CACR,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"versioning.js","sourceRoot":"","sources":["../src/versioning.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE7F,sFAAsF;AACtF,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,GAAgB;IAC7C,OAAO,UAAU,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;AAC9E,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,UAAU,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;AACpC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,KAAiB;IAC3C,OAAO,UAAU,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,OAAO,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC;AACvF,CAAC"}