@superbee/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/backend.d.ts +77 -0
  2. package/dist/backend.d.ts.map +1 -0
  3. package/dist/backend.js +380 -0
  4. package/dist/backend.js.map +1 -0
  5. package/dist/bundle.d.ts +38 -0
  6. package/dist/bundle.d.ts.map +1 -0
  7. package/dist/bundle.js +107 -0
  8. package/dist/bundle.js.map +1 -0
  9. package/dist/content-type.d.ts +72 -0
  10. package/dist/content-type.d.ts.map +1 -0
  11. package/dist/content-type.js +149 -0
  12. package/dist/content-type.js.map +1 -0
  13. package/dist/document-mutation.d.ts +83 -0
  14. package/dist/document-mutation.d.ts.map +1 -0
  15. package/dist/document-mutation.js +319 -0
  16. package/dist/document-mutation.js.map +1 -0
  17. package/dist/document-precondition.d.ts +62 -0
  18. package/dist/document-precondition.d.ts.map +1 -0
  19. package/dist/document-precondition.js +134 -0
  20. package/dist/document-precondition.js.map +1 -0
  21. package/dist/document-write-policy.d.ts +42 -0
  22. package/dist/document-write-policy.d.ts.map +1 -0
  23. package/dist/document-write-policy.js +134 -0
  24. package/dist/document-write-policy.js.map +1 -0
  25. package/dist/engine.d.ts +48 -0
  26. package/dist/engine.d.ts.map +1 -0
  27. package/dist/engine.js +209 -0
  28. package/dist/engine.js.map +1 -0
  29. package/dist/errors.d.ts +36 -0
  30. package/dist/errors.d.ts.map +1 -0
  31. package/dist/errors.js +50 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/filesystem-identity.d.ts +208 -0
  34. package/dist/filesystem-identity.d.ts.map +1 -0
  35. package/dist/filesystem-identity.js +675 -0
  36. package/dist/filesystem-identity.js.map +1 -0
  37. package/dist/filesystem-lock.d.ts +65 -0
  38. package/dist/filesystem-lock.d.ts.map +1 -0
  39. package/dist/filesystem-lock.js +460 -0
  40. package/dist/filesystem-lock.js.map +1 -0
  41. package/dist/freshness.d.ts +31 -0
  42. package/dist/freshness.d.ts.map +1 -0
  43. package/dist/freshness.js +98 -0
  44. package/dist/freshness.js.map +1 -0
  45. package/dist/frontmatter-contract.d.ts +17 -0
  46. package/dist/frontmatter-contract.d.ts.map +1 -0
  47. package/dist/frontmatter-contract.js +29 -0
  48. package/dist/frontmatter-contract.js.map +1 -0
  49. package/dist/frontmatter.d.ts +30 -0
  50. package/dist/frontmatter.d.ts.map +1 -0
  51. package/dist/frontmatter.js +115 -0
  52. package/dist/frontmatter.js.map +1 -0
  53. package/dist/index-marker.d.ts +3 -0
  54. package/dist/index-marker.d.ts.map +1 -0
  55. package/dist/index-marker.js +3 -0
  56. package/dist/index-marker.js.map +1 -0
  57. package/dist/index-projection.d.ts +79 -0
  58. package/dist/index-projection.d.ts.map +1 -0
  59. package/dist/index-projection.js +245 -0
  60. package/dist/index-projection.js.map +1 -0
  61. package/dist/index.d.ts +68 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +110 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/kinds-load.d.ts +13 -0
  66. package/dist/kinds-load.d.ts.map +1 -0
  67. package/dist/kinds-load.js +34 -0
  68. package/dist/kinds-load.js.map +1 -0
  69. package/dist/kinds.d.ts +241 -0
  70. package/dist/kinds.d.ts.map +1 -0
  71. package/dist/kinds.js +1001 -0
  72. package/dist/kinds.js.map +1 -0
  73. package/dist/links.d.ts +62 -0
  74. package/dist/links.d.ts.map +1 -0
  75. package/dist/links.js +203 -0
  76. package/dist/links.js.map +1 -0
  77. package/dist/meaningful-change-time.d.ts +11 -0
  78. package/dist/meaningful-change-time.d.ts.map +1 -0
  79. package/dist/meaningful-change-time.js +16 -0
  80. package/dist/meaningful-change-time.js.map +1 -0
  81. package/dist/memory-backend.d.ts +55 -0
  82. package/dist/memory-backend.d.ts.map +1 -0
  83. package/dist/memory-backend.js +214 -0
  84. package/dist/memory-backend.js.map +1 -0
  85. package/dist/mutation-attribution.d.ts +19 -0
  86. package/dist/mutation-attribution.d.ts.map +1 -0
  87. package/dist/mutation-attribution.js +34 -0
  88. package/dist/mutation-attribution.js.map +1 -0
  89. package/dist/mutation.d.ts +89 -0
  90. package/dist/mutation.d.ts.map +1 -0
  91. package/dist/mutation.js +94 -0
  92. package/dist/mutation.js.map +1 -0
  93. package/dist/page.d.ts +87 -0
  94. package/dist/page.d.ts.map +1 -0
  95. package/dist/page.js +151 -0
  96. package/dist/page.js.map +1 -0
  97. package/dist/paths.d.ts +78 -0
  98. package/dist/paths.d.ts.map +1 -0
  99. package/dist/paths.js +148 -0
  100. package/dist/paths.js.map +1 -0
  101. package/dist/portable-frontmatter.d.ts +8 -0
  102. package/dist/portable-frontmatter.d.ts.map +1 -0
  103. package/dist/portable-frontmatter.js +35 -0
  104. package/dist/portable-frontmatter.js.map +1 -0
  105. package/dist/publication-filesystem.d.ts +6 -0
  106. package/dist/publication-filesystem.d.ts.map +1 -0
  107. package/dist/publication-filesystem.js +6 -0
  108. package/dist/publication-filesystem.js.map +1 -0
  109. package/dist/query-filter.d.ts +12 -0
  110. package/dist/query-filter.d.ts.map +1 -0
  111. package/dist/query-filter.js +29 -0
  112. package/dist/query-filter.js.map +1 -0
  113. package/dist/query-selection.d.ts +25 -0
  114. package/dist/query-selection.d.ts.map +1 -0
  115. package/dist/query-selection.js +48 -0
  116. package/dist/query-selection.js.map +1 -0
  117. package/dist/remote-backend.d.ts +152 -0
  118. package/dist/remote-backend.d.ts.map +1 -0
  119. package/dist/remote-backend.js +505 -0
  120. package/dist/remote-backend.js.map +1 -0
  121. package/dist/remote.d.ts +4 -0
  122. package/dist/remote.d.ts.map +1 -0
  123. package/dist/remote.js +3 -0
  124. package/dist/remote.js.map +1 -0
  125. package/dist/storage.d.ts +7 -0
  126. package/dist/storage.d.ts.map +1 -0
  127. package/dist/storage.js +6 -0
  128. package/dist/storage.js.map +1 -0
  129. package/dist/types.d.ts +438 -0
  130. package/dist/types.d.ts.map +1 -0
  131. package/dist/types.js +12 -0
  132. package/dist/types.js.map +1 -0
  133. package/dist/validation.d.ts +22 -0
  134. package/dist/validation.d.ts.map +1 -0
  135. package/dist/validation.js +12 -0
  136. package/dist/validation.js.map +1 -0
  137. package/dist/version-transport.d.ts +13 -0
  138. package/dist/version-transport.d.ts.map +1 -0
  139. package/dist/version-transport.js +26 -0
  140. package/dist/version-transport.js.map +1 -0
  141. package/dist/versioning.d.ts +48 -0
  142. package/dist/versioning.d.ts.map +1 -0
  143. package/dist/versioning.js +62 -0
  144. package/dist/versioning.js.map +1 -0
  145. package/package.json +84 -0
package/dist/engine.js ADDED
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Runtime-neutral Superbee engine operations.
3
+ *
4
+ * Every operation receives its {@link StorageBackend} explicitly. Node filesystem defaults live
5
+ * only in the legacy root adapter (`bundle.ts`); Worker and browser consumers import this module.
6
+ */
7
+ import { normalizeV01DocumentForWrite, normalizeV02DocumentForWrite, } from "./document-write-policy.js";
8
+ import { InvalidInputError } from "./errors.js";
9
+ import { isUsableTimestamp, MalformedDocumentError } from "./frontmatter-contract.js";
10
+ import { parseLinksFromDoc } from "./links.js";
11
+ import { assertSafeConceptId, isReservedFile, pathFromConceptId } from "./paths.js";
12
+ import { parseLeadingFrontmatter } from "./portable-frontmatter.js";
13
+ import { matchesFilter } from "./query-filter.js";
14
+ export { MalformedDocumentError } from "./frontmatter-contract.js";
15
+ /** Read the bundle-root OKF edition through an explicit backend. */
16
+ export async function readBundleOkfVersion(backend) {
17
+ const index = await backend.readReserved("", "index.md");
18
+ if (!index)
19
+ return undefined;
20
+ const value = parseLeadingFrontmatter(index.content, "index.md").okf_version;
21
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
22
+ }
23
+ function assertWritableConceptDocument(doc) {
24
+ assertSafeConceptId(doc.id);
25
+ const rel = pathFromConceptId(doc.id);
26
+ if (isReservedFile(rel)) {
27
+ throw new InvalidInputError(`'${doc.id}' maps to a reserved file (${rel}); use the index/log accessors, not writeDoc.`);
28
+ }
29
+ const type = doc.frontmatter?.type;
30
+ if (typeof type !== "string" || type.trim() === "") {
31
+ throw new InvalidInputError(`OKF conformance: frontmatter.type is required and must be non-empty (concept '${doc.id}').`);
32
+ }
33
+ return type;
34
+ }
35
+ async function persistDocForEdition(backend, doc, type, okfVersion, options) {
36
+ let saved;
37
+ if (okfVersion === "0.1") {
38
+ const existingTimestamp = doc.frontmatter.timestamp;
39
+ const timestamp = isUsableTimestamp(existingTimestamp)
40
+ ? { preserveExisting: true, existingTimestamp }
41
+ : { preserveExisting: false, fallbackTimestamp: new Date().toISOString() };
42
+ saved = normalizeV01DocumentForWrite(doc, type, timestamp);
43
+ }
44
+ else {
45
+ saved = normalizeV02DocumentForWrite(doc, type);
46
+ }
47
+ const version = await backend.write(doc.id, saved, options);
48
+ return { doc: saved, version };
49
+ }
50
+ /** Validate, normalize for the backend's bundle edition, and atomically persist a document. */
51
+ export async function writeDocVersioned(backend, doc, options) {
52
+ const type = assertWritableConceptDocument(doc);
53
+ const okfVersion = (await readBundleOkfVersion(backend)) ?? "0.1";
54
+ return persistDocForEdition(backend, doc, type, okfVersion, options);
55
+ }
56
+ /** Edition-pinned sibling for mutation policy after it has read the root once. */
57
+ export async function writeDocVersionedForEdition(backend, doc, okfVersion, options) {
58
+ return persistDocForEdition(backend, doc, assertWritableConceptDocument(doc), okfVersion, options);
59
+ }
60
+ /** Persist a document while keeping the historical document-only result. */
61
+ export async function writeDoc(backend, doc, options) {
62
+ return (await writeDocVersioned(backend, doc, options)).doc;
63
+ }
64
+ function assertReadableConceptId(id) {
65
+ assertSafeConceptId(id);
66
+ const rel = pathFromConceptId(id);
67
+ if (isReservedFile(rel)) {
68
+ throw new InvalidInputError(`'${id}' is a reserved file (index.md / log.md), not a concept document.`);
69
+ }
70
+ }
71
+ export async function readDocVersioned(backend, id) {
72
+ assertReadableConceptId(id);
73
+ return backend.read(id);
74
+ }
75
+ export async function readDoc(backend, id) {
76
+ return (await readDocVersioned(backend, id)).doc;
77
+ }
78
+ export async function existsDoc(backend, id) {
79
+ assertReadableConceptId(id);
80
+ return backend.exists(id);
81
+ }
82
+ export async function docVersions(backend, id) {
83
+ assertReadableConceptId(id);
84
+ return backend.versions(id);
85
+ }
86
+ export async function deleteDoc(backend, id, options) {
87
+ assertSafeConceptId(id);
88
+ const rel = pathFromConceptId(id);
89
+ if (isReservedFile(rel)) {
90
+ throw new InvalidInputError(`'${id}' is a reserved file (${rel}); reserved files cannot be deleted.`);
91
+ }
92
+ return backend.delete(id, options);
93
+ }
94
+ function isEnoent(err) {
95
+ return typeof err === "object" && err !== null && err.code === "ENOENT";
96
+ }
97
+ async function readManyExisting(backend, ids, onMalformed) {
98
+ try {
99
+ return await backend.readMany(ids);
100
+ }
101
+ catch (err) {
102
+ const malformed = err instanceof MalformedDocumentError;
103
+ if (!isEnoent(err) && !(malformed && onMalformed))
104
+ throw err;
105
+ const out = [];
106
+ for (const id of ids) {
107
+ try {
108
+ out.push(await backend.read(id));
109
+ }
110
+ catch (candidate) {
111
+ if (isEnoent(candidate))
112
+ continue;
113
+ if (candidate instanceof MalformedDocumentError && onMalformed) {
114
+ onMalformed({ id, reason: candidate.detail });
115
+ continue;
116
+ }
117
+ throw candidate;
118
+ }
119
+ }
120
+ return out;
121
+ }
122
+ }
123
+ async function scanMatching(backend, filter, onSkip) {
124
+ const ids = await backend.list(filter.prefix);
125
+ const results = [];
126
+ for (const result of await readManyExisting(backend, ids, onSkip)) {
127
+ if (matchesFilter(result.doc, filter))
128
+ results.push(result);
129
+ }
130
+ results.sort((a, b) => a.doc.id.localeCompare(b.doc.id));
131
+ return results;
132
+ }
133
+ export async function query(backend, filter = {}, options = {}) {
134
+ return (await scanMatching(backend, filter, options.onSkip)).map((result) => result.doc);
135
+ }
136
+ export const list = query;
137
+ export async function queryHeads(backend, filter = {}, options = {}) {
138
+ if (backend.queryHeads) {
139
+ const rows = (await backend.queryHeads(filter)).filter((row) => matchesFilter(row, filter));
140
+ rows.sort((a, b) => a.id.localeCompare(b.id));
141
+ return rows;
142
+ }
143
+ return (await scanMatching(backend, filter, options.onSkip)).map(({ doc, version }) => ({
144
+ id: doc.id,
145
+ frontmatter: doc.frontmatter,
146
+ version,
147
+ }));
148
+ }
149
+ export function parseLinks(doc) {
150
+ return parseLinksFromDoc(doc);
151
+ }
152
+ function normalizeEdgeSelector(raw) {
153
+ if (raw.endsWith("/")) {
154
+ const stem = raw.slice(0, -1);
155
+ assertSafeConceptId(stem);
156
+ return `${stem}/`;
157
+ }
158
+ assertSafeConceptId(raw);
159
+ return raw;
160
+ }
161
+ function matchesEdgeSelector(value, selectors) {
162
+ if (selectors === undefined)
163
+ return true;
164
+ return selectors.some((selector) => selector.endsWith("/") ? value.startsWith(selector) : value === selector);
165
+ }
166
+ function toSelectorList(value) {
167
+ if (value === undefined)
168
+ return undefined;
169
+ return Array.isArray(value) ? value : [value];
170
+ }
171
+ export async function queryEdges(backend, filter = {}) {
172
+ const fromSelectors = toSelectorList(filter.from)?.map(normalizeEdgeSelector);
173
+ const toSelectors = toSelectorList(filter.to)?.map(normalizeEdgeSelector);
174
+ const edges = [];
175
+ for (const doc of await query(backend)) {
176
+ for (const link of parseLinksFromDoc(doc)) {
177
+ if (!matchesEdgeSelector(link.from, fromSelectors))
178
+ continue;
179
+ if (!matchesEdgeSelector(link.to, toSelectors))
180
+ continue;
181
+ if (filter.text !== undefined && link.text !== filter.text)
182
+ continue;
183
+ edges.push(link);
184
+ }
185
+ }
186
+ edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || a.text.localeCompare(b.text));
187
+ return edges;
188
+ }
189
+ export async function backlinks(backend, target) {
190
+ if (target.endsWith("/"))
191
+ return [];
192
+ return queryEdges(backend, { to: target });
193
+ }
194
+ export async function readBlob(backend, key) {
195
+ return backend.readBlob(key);
196
+ }
197
+ export async function writeBlob(backend, key, bytes, contentType, options) {
198
+ return backend.writeBlob(key, bytes, contentType, options);
199
+ }
200
+ export async function existsBlob(backend, key) {
201
+ return backend.existsBlob(key);
202
+ }
203
+ export async function listBlobs(backend, prefix) {
204
+ return backend.listBlobs(prefix);
205
+ }
206
+ export async function deleteBlob(backend, key, options) {
207
+ return backend.deleteBlob(key, options);
208
+ }
209
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Typed rejection for caller-supplied input the engine refuses: unsafe/reserved concept ids,
3
+ * invalid blob keys, missing/empty `frontmatter.type`, malformed option values, and (through
4
+ * {@link FilesystemIdentityAliasError}) an id whose spelling names an existing filesystem entry
5
+ * only by alias. Most rejections happen before storage is touched; the alias verdict needs one
6
+ * inspection of storage but is still "fix your input". Distinct from a runtime failure (I/O,
7
+ * transport, backend) so a consumer boundary — e.g. the CLI's `classifyBundleError` — can map
8
+ * "fix your input" (USAGE, exit 2) separately from "retry/report a bug" (RUNTIME, exit 1) without
9
+ * prose-matching messages.
10
+ */
11
+ export declare class InvalidInputError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ /**
15
+ * The requested bundle-relative path resolves, on a case- or normalization-insensitive
16
+ * filesystem, to a directory entry spelled differently at `segment`. Every operation refuses it
17
+ * so two distinct canonical ids can never read, compare-and-swap, or delete one physical file.
18
+ */
19
+ export declare class FilesystemIdentityAliasError extends InvalidInputError {
20
+ readonly rel: string;
21
+ readonly segment: string;
22
+ constructor(rel: string, segment: string);
23
+ }
24
+ /**
25
+ * A filesystem operation could not bind its result to one stable on-disk entry: an observation
26
+ * saw replacement, or a Windows replacement sharing violation made the current write attempt
27
+ * unsafe to continue. A retryable runtime condition; the owning read/decide/CAS loop must restart
28
+ * the complete attempt rather than retrying the filesystem operation in place.
29
+ */
30
+ export declare class ConcurrentReplacementError extends Error {
31
+ readonly rel: string;
32
+ /** Optional bounded backoff hint for a transient host condition; ordinary observation races use zero. */
33
+ readonly retryAfterMs: number;
34
+ constructor(rel: string, attempts: number, retryAfterMs?: number);
35
+ }
36
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,iBAAiB;IACjE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CASzC;AAED;;;;;GAKG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yGAAyG;IACzG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,SAAI;CAM5D"}
package/dist/errors.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Typed rejection for caller-supplied input the engine refuses: unsafe/reserved concept ids,
3
+ * invalid blob keys, missing/empty `frontmatter.type`, malformed option values, and (through
4
+ * {@link FilesystemIdentityAliasError}) an id whose spelling names an existing filesystem entry
5
+ * only by alias. Most rejections happen before storage is touched; the alias verdict needs one
6
+ * inspection of storage but is still "fix your input". Distinct from a runtime failure (I/O,
7
+ * transport, backend) so a consumer boundary — e.g. the CLI's `classifyBundleError` — can map
8
+ * "fix your input" (USAGE, exit 2) separately from "retry/report a bug" (RUNTIME, exit 1) without
9
+ * prose-matching messages.
10
+ */
11
+ export class InvalidInputError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "InvalidInputError";
15
+ }
16
+ }
17
+ /**
18
+ * The requested bundle-relative path resolves, on a case- or normalization-insensitive
19
+ * filesystem, to a directory entry spelled differently at `segment`. Every operation refuses it
20
+ * so two distinct canonical ids can never read, compare-and-swap, or delete one physical file.
21
+ */
22
+ export class FilesystemIdentityAliasError extends InvalidInputError {
23
+ rel;
24
+ segment;
25
+ constructor(rel, segment) {
26
+ super(`Path '${rel}' does not match the exact spelling of an existing filesystem entry at segment ` +
27
+ `'${segment}'; ids that differ only by case or Unicode normalization cannot share one file.`);
28
+ this.name = "FilesystemIdentityAliasError";
29
+ this.rel = rel;
30
+ this.segment = segment;
31
+ }
32
+ }
33
+ /**
34
+ * A filesystem operation could not bind its result to one stable on-disk entry: an observation
35
+ * saw replacement, or a Windows replacement sharing violation made the current write attempt
36
+ * unsafe to continue. A retryable runtime condition; the owning read/decide/CAS loop must restart
37
+ * the complete attempt rather than retrying the filesystem operation in place.
38
+ */
39
+ export class ConcurrentReplacementError extends Error {
40
+ rel;
41
+ /** Optional bounded backoff hint for a transient host condition; ordinary observation races use zero. */
42
+ retryAfterMs;
43
+ constructor(rel, attempts, retryAfterMs = 0) {
44
+ super(`Path '${rel}' could not be bound to one stable entry during ${attempts} filesystem attempt(s); retry.`);
45
+ this.name = "ConcurrentReplacementError";
46
+ this.rel = rel;
47
+ this.retryAfterMs = retryAfterMs;
48
+ }
49
+ }
50
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,4BAA6B,SAAQ,iBAAiB;IACxD,GAAG,CAAS;IACZ,OAAO,CAAS;IAEzB,YAAY,GAAW,EAAE,OAAe;QACtC,KAAK,CACH,SAAS,GAAG,iFAAiF;YAC3F,IAAI,OAAO,iFAAiF,CAC/F,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,8BAA8B,CAAC;QAC3C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IAC1C,GAAG,CAAS;IACrB,yGAAyG;IAChG,YAAY,CAAS;IAE9B,YAAY,GAAW,EAAE,QAAgB,EAAE,YAAY,GAAG,CAAC;QACzD,KAAK,CAAC,SAAS,GAAG,mDAAmD,QAAQ,gCAAgC,CAAC,CAAC;QAC/G,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Exact filesystem identity for the filesystem adapter.
3
+ *
4
+ * A bundle-relative path `rel` names ONE physical entry only when every segment's on-disk
5
+ * spelling equals the requested spelling. On a case- or normalization-insensitive filesystem
6
+ * (case-insensitive APFS, NTFS, ext4 casefold, normalization-insensitive APFS) two distinct
7
+ * canonical ids would otherwise reach one file, so reads, compare-and-swap, and deletes would
8
+ * alias. This module is the single owner of that decision:
9
+ *
10
+ * - Observations record every `rel` segment's inode, verify each segment's spelling by listing
11
+ * before and after use, confirm any would-be alias by probing the requested spelling against
12
+ * the recorded inode, and hold the read handle open through the post-walk so the bytes stay
13
+ * bound to a live inode. They hold no lock and write nothing, so an absent bundle root stays
14
+ * absent. A symbolic link observed by any probe is refused; a link swapped in and out
15
+ * entirely between two probes of one observation can be traversed by the operating system
16
+ * during a listing or open, but its bytes can never be returned as EXACT, because the leaf
17
+ * witness at the post-walk must match the entry the requested path reaches.
18
+ * - Mutations hold one cross-process lock keyed by a pure fold of the root and `rel`, decide
19
+ * inside it, create directories one segment at a time with a spelling check, and publish a
20
+ * first-created leaf with `link` so the host's own equivalence refuses a second spelling the
21
+ * fold did not equate; a first-creation race cannot succeed with the wrong spelling and cannot
22
+ * silently replace the winner.
23
+ *
24
+ * An observation's linearization point is the post-walk that validated the bytes: what it returns
25
+ * is a state of THIS identity at that instant, never a foreign one and never bytes straddling two
26
+ * states. A read concurrent with a legitimate same-spelling replacement may therefore return the
27
+ * old value or the new one, both real; a caller that needs one state across several reads uses
28
+ * versions and compare-and-swap, as mutations already do. Sustained interference is bounded
29
+ * rather than resolved: after MAX_RESTARTS the observation refuses with
30
+ * {@link ConcurrentReplacementError} instead of attributing bytes to a state it cannot name.
31
+ * This rests on published bytes being immutable per inode — every write publishes a complete temp
32
+ * file with `link` or `rename` and never edits a live one — since the bytes are read BEFORE the
33
+ * post-walk. A non-core writer editing a published file in place is the case it excludes.
34
+ *
35
+ * Exactness applies to `rel` segments only. The bundle root is the declared storage context:
36
+ * a root (tail) segment that already exists as a directory under any spelling is accepted,
37
+ * and the root's spelling is folded into the lock key rather than verified.
38
+ *
39
+ * Every filesystem call goes through a {@link FilesystemIdentityPort} passed to the protocol
40
+ * functions. Production binds {@link nodeFilesystemIdentityPort} once, as a module constant;
41
+ * nothing on a backend instance can substitute it. Normalizing stores that rewrite names on
42
+ * write (legacy HFS+) are unsupported: an id whose NFD form differs from its own form is written
43
+ * and then refused as an alias on every later observation, never silently aliased.
44
+ */
45
+ import { InvalidInputError } from "./errors.js";
46
+ export type EntryKind = "file" | "directory" | "symlink" | "other";
47
+ /** The `(dev, ino)` pair that binds a name to a physical entry for one observation. */
48
+ export interface EntryWitness {
49
+ dev: number;
50
+ ino: number;
51
+ }
52
+ export interface ProbeResult extends EntryWitness {
53
+ kind: EntryKind;
54
+ }
55
+ /** A listing entry: spelling and kind only. Listings never carry inodes; witnesses come from `probe`. */
56
+ export interface ListedEntry {
57
+ name: string;
58
+ kind: EntryKind;
59
+ }
60
+ /** An opaque open-file token owned by the port; the protocol never inspects it. */
61
+ export type PortHandle = object;
62
+ export interface OpenedFile extends EntryWitness {
63
+ handle: PortHandle;
64
+ }
65
+ export interface IdentityDescriptor {
66
+ root: string;
67
+ rel: string;
68
+ }
69
+ /**
70
+ * The complete filesystem surface the protocol needs. `probe` is `lstat` and never follows a
71
+ * symlink; `mkdir` is deliberately NON-recursive: exact directory creation is decided one
72
+ * segment at a time against the parent listing.
73
+ */
74
+ export interface FilesystemIdentityPort {
75
+ /** Host semantics used by the protocol for narrowly platform-specific filesystem recovery. */
76
+ readonly platform?: NodeJS.Platform;
77
+ /** `lstat`; `null` when nothing is at the path (ENOENT or ENOTDIR). */
78
+ probe(target: string): Promise<ProbeResult | null>;
79
+ /** Directory listing, names and kinds only; `null` when the directory is absent. */
80
+ entries(dir: string): Promise<ListedEntry[] | null>;
81
+ /** Open for reading and `fstat` the handle; throws the filesystem error otherwise. */
82
+ open(target: string): Promise<OpenedFile>;
83
+ readAll(handle: PortHandle): Promise<Buffer>;
84
+ close(handle: PortHandle): Promise<void>;
85
+ stat(target: string): Promise<{
86
+ mtime: Date;
87
+ }>;
88
+ mkdir(dir: string): Promise<"created" | "exists">;
89
+ writeTemp(dir: string, name: string, bytes: Uint8Array): Promise<void>;
90
+ /**
91
+ * Hard-link `from` to `to` without replacing anything: `exists` when ANY entry the host equates
92
+ * with `to` is present; `unsupported` when the filesystem has no hard links (EPERM, ENOTSUP,
93
+ * EOPNOTSUPP, EXDEV); other failures throw.
94
+ */
95
+ link(from: string, to: string): Promise<"linked" | "exists" | "unsupported">;
96
+ rename(from: string, to: string): Promise<void>;
97
+ unlink(target: string): Promise<void>;
98
+ claim(key: string, identity: IdentityDescriptor): Promise<() => Promise<void>>;
99
+ }
100
+ /**
101
+ * A symbolic link at a `rel` segment. The walk never follows links: a link's own inode is not the
102
+ * inode of what opening it would reach, so no exactness verdict is possible. Refused as caller
103
+ * input (the layout is unsupported) rather than reported as a runtime failure.
104
+ */
105
+ export declare class FilesystemSymlinkEntryError extends InvalidInputError {
106
+ readonly rel: string;
107
+ readonly segment: string;
108
+ constructor(rel: string, segment: string);
109
+ }
110
+ /**
111
+ * A regular file where a directory is needed (or the reverse) at an exactly spelled segment.
112
+ * A runtime state of the bundle, not a caller-input error, so it is not an `InvalidInputError`.
113
+ */
114
+ export declare class FilesystemShapeMismatchError extends Error {
115
+ readonly rel: string;
116
+ readonly segment: string;
117
+ constructor(rel: string, segment: string);
118
+ }
119
+ /**
120
+ * NFKD, then Unicode FULL case folding applied as lower-upper-lower (locale-independent). A plain
121
+ * lower-case fold misses the pairs a case-insensitive host equates through full case mapping
122
+ * (ß/ss, ẞ/SS, final sigma ς/σ, iota-subscript ᾈ/ἀι and its family): two such spellings would get
123
+ * different keys, both first-creation writers would realize ABSENT, and the second rename would
124
+ * silently replace the first document. The round trip through upper case expands every such
125
+ * pair to a common form, so the fold is a superset of every supported host's equivalence;
126
+ * over-folding (dotless ı with i) only adds serialization, never removes exclusion.
127
+ */
128
+ export declare function foldSegment(segment: string): string;
129
+ /**
130
+ * The lock key for one logical identity `(root, rel)`: a digest of the fully folded physical
131
+ * root path and the folded `rel`. The WHOLE existing ancestor chain is resolved and folded, so
132
+ * the key is identical whether it is computed before or after any part of the root is created,
133
+ * and identical for every case or normalization spelling of any segment. It depends on nothing
134
+ * else: not the environment, the clock, the process, or the host name.
135
+ */
136
+ export declare function identityKey(root: string, rel: string): Promise<string>;
137
+ export type ConfirmVerdict = "continue" | "absent" | "aliased" | "restart";
138
+ /**
139
+ * The confirmed-witness rule for one `rel` segment: a fresh probe of the requested spelling is
140
+ * compared with the inode recorded for that segment earlier in the same observation, whether or
141
+ * not the exact spelling is present in the parent listing. A listing on its own is never a
142
+ * verdict: an entry can be REPLACED under its own spelling (a non-core actor swapping a whole
143
+ * directory) without the listing changing at all, and that must restart the observation rather
144
+ * than let records from two different states pass as one EXACT read (I6). ALIASED requires the
145
+ * probe to reach the recorded inode while the exact spelling is absent; a different inode is
146
+ * ambiguous (replaced under either spelling, or deleted and recreated) and restarts; a
147
+ * same-spelling replacement is NOT an alias.
148
+ */
149
+ export declare function confirmAlias(listingHasExact: boolean, probe: ProbeResult | null, recorded: EntryWitness): ConfirmVerdict;
150
+ export type LeafVerdict = "exact" | "replaced" | "aliased" | "absent";
151
+ export interface LeafSnapshot {
152
+ /** The exact spelling is present in the fresh listing of the parent. */
153
+ listed: boolean;
154
+ /** `probe(parent/leaf)` taken after that listing. */
155
+ probe: ProbeResult | null;
156
+ /** The inode the observation is bound to: the open handle's, or the recorded leaf for handle-less observations. */
157
+ handle: EntryWitness;
158
+ }
159
+ /**
160
+ * Post-use verdict for the leaf, over the six cells of (listed, probe): the bytes came from the
161
+ * handle's inode, which must still be what the requested spelling reaches, under its own name.
162
+ */
163
+ export declare function classifyLeaf(snapshot: LeafSnapshot): LeafVerdict;
164
+ export type MkdirVerdict = "created" | "exact" | "aliased" | "shape-mismatch";
165
+ /**
166
+ * Verdict for one non-recursive `mkdir` outcome against the parent listing. A `rel` segment
167
+ * that already exists is exact only under the requested spelling and only as a directory; a
168
+ * tail (root) segment that already exists as a directory is accepted under any spelling.
169
+ */
170
+ export declare function classifyMkdir(outcome: "created" | "exists", listing: ListedEntry[], segment: string, isTail: boolean): MkdirVerdict;
171
+ export type Observation<T> = {
172
+ state: "exact";
173
+ value: T;
174
+ } | {
175
+ state: "absent";
176
+ };
177
+ /**
178
+ * Observe `(root, rel)` without a lock and without writing anything, reading through one open
179
+ * handle. Every segment is probed and recorded (O-PROBE), verified by listing with inode
180
+ * confirmation (O-VERIFY), opened and read, then re-verified with fresh listings while the handle
181
+ * is still open so its inode cannot be recycled (O-POSTVERIFY). Every ambiguous outcome restarts
182
+ * from O-PROBE, bounded; the handle is closed before any restart opens another.
183
+ */
184
+ export declare function observeExact<T>(port: FilesystemIdentityPort, root: string, rel: string, read: (handle: PortHandle, target: string) => Promise<T | null>): Promise<Observation<T>>;
185
+ /**
186
+ * Handle-less observation of presence: the same walks, with the recorded leaf inode standing in
187
+ * for an open handle. Yields the leaf's kind and witness as verified by the post-walk.
188
+ */
189
+ export declare function probeExact(port: FilesystemIdentityPort, root: string, rel: string): Promise<Observation<ProbeResult>>;
190
+ export interface MutationContext {
191
+ /** Realized state of the exact path while the identity lock is held. */
192
+ readonly state: "exact" | "absent";
193
+ /** Current bytes at the exact path, `null` when absent; the compare-and-swap basis. */
194
+ current(): Promise<Buffer | null>;
195
+ /** Create the missing directory segments exactly, then atomically replace the leaf. */
196
+ replace(bytes: Uint8Array): Promise<void>;
197
+ remove(): Promise<void>;
198
+ }
199
+ /**
200
+ * Mutate `(root, rel)` inside its identity lock: claim the key, realize the exact state, and let
201
+ * `body` decide (compare-and-swap, delete-of-absent) before anything is written. An alias at
202
+ * any segment, a conflict, or an early return leaves the bundle untouched; the bundle root and
203
+ * missing directories are created only by `replace`. The lock is released on every path.
204
+ */
205
+ export declare function mutateExact<T>(port: FilesystemIdentityPort, root: string, rel: string, body: (context: MutationContext) => Promise<T>): Promise<T>;
206
+ /** The one production binding: `node:fs`, the temp-file convention, and the identity lock. */
207
+ export declare const nodeFilesystemIdentityPort: FilesystemIdentityPort;
208
+ //# sourceMappingURL=filesystem-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem-identity.d.ts","sourceRoot":"","sources":["../src/filesystem-identity.ts"],"names":[],"mappings":"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"}