@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.
- package/dist/backend.d.ts +77 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +380 -0
- package/dist/backend.js.map +1 -0
- package/dist/bundle.d.ts +38 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +107 -0
- package/dist/bundle.js.map +1 -0
- package/dist/content-type.d.ts +72 -0
- package/dist/content-type.d.ts.map +1 -0
- package/dist/content-type.js +149 -0
- package/dist/content-type.js.map +1 -0
- package/dist/document-mutation.d.ts +83 -0
- package/dist/document-mutation.d.ts.map +1 -0
- package/dist/document-mutation.js +319 -0
- package/dist/document-mutation.js.map +1 -0
- package/dist/document-precondition.d.ts +62 -0
- package/dist/document-precondition.d.ts.map +1 -0
- package/dist/document-precondition.js +134 -0
- package/dist/document-precondition.js.map +1 -0
- package/dist/document-write-policy.d.ts +42 -0
- package/dist/document-write-policy.d.ts.map +1 -0
- package/dist/document-write-policy.js +134 -0
- package/dist/document-write-policy.js.map +1 -0
- package/dist/engine.d.ts +48 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +209 -0
- package/dist/engine.js.map +1 -0
- package/dist/errors.d.ts +36 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +50 -0
- package/dist/errors.js.map +1 -0
- package/dist/filesystem-identity.d.ts +208 -0
- package/dist/filesystem-identity.d.ts.map +1 -0
- package/dist/filesystem-identity.js +675 -0
- package/dist/filesystem-identity.js.map +1 -0
- package/dist/filesystem-lock.d.ts +65 -0
- package/dist/filesystem-lock.d.ts.map +1 -0
- package/dist/filesystem-lock.js +460 -0
- package/dist/filesystem-lock.js.map +1 -0
- package/dist/freshness.d.ts +31 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +98 -0
- package/dist/freshness.js.map +1 -0
- package/dist/frontmatter-contract.d.ts +17 -0
- package/dist/frontmatter-contract.d.ts.map +1 -0
- package/dist/frontmatter-contract.js +29 -0
- package/dist/frontmatter-contract.js.map +1 -0
- package/dist/frontmatter.d.ts +30 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +115 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/index-marker.d.ts +3 -0
- package/dist/index-marker.d.ts.map +1 -0
- package/dist/index-marker.js +3 -0
- package/dist/index-marker.js.map +1 -0
- package/dist/index-projection.d.ts +79 -0
- package/dist/index-projection.d.ts.map +1 -0
- package/dist/index-projection.js +245 -0
- package/dist/index-projection.js.map +1 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/kinds-load.d.ts +13 -0
- package/dist/kinds-load.d.ts.map +1 -0
- package/dist/kinds-load.js +34 -0
- package/dist/kinds-load.js.map +1 -0
- package/dist/kinds.d.ts +241 -0
- package/dist/kinds.d.ts.map +1 -0
- package/dist/kinds.js +1001 -0
- package/dist/kinds.js.map +1 -0
- package/dist/links.d.ts +62 -0
- package/dist/links.d.ts.map +1 -0
- package/dist/links.js +203 -0
- package/dist/links.js.map +1 -0
- package/dist/meaningful-change-time.d.ts +11 -0
- package/dist/meaningful-change-time.d.ts.map +1 -0
- package/dist/meaningful-change-time.js +16 -0
- package/dist/meaningful-change-time.js.map +1 -0
- package/dist/memory-backend.d.ts +55 -0
- package/dist/memory-backend.d.ts.map +1 -0
- package/dist/memory-backend.js +214 -0
- package/dist/memory-backend.js.map +1 -0
- package/dist/mutation-attribution.d.ts +19 -0
- package/dist/mutation-attribution.d.ts.map +1 -0
- package/dist/mutation-attribution.js +34 -0
- package/dist/mutation-attribution.js.map +1 -0
- package/dist/mutation.d.ts +89 -0
- package/dist/mutation.d.ts.map +1 -0
- package/dist/mutation.js +94 -0
- package/dist/mutation.js.map +1 -0
- package/dist/page.d.ts +87 -0
- package/dist/page.d.ts.map +1 -0
- package/dist/page.js +151 -0
- package/dist/page.js.map +1 -0
- package/dist/paths.d.ts +78 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +148 -0
- package/dist/paths.js.map +1 -0
- package/dist/portable-frontmatter.d.ts +8 -0
- package/dist/portable-frontmatter.d.ts.map +1 -0
- package/dist/portable-frontmatter.js +35 -0
- package/dist/portable-frontmatter.js.map +1 -0
- package/dist/publication-filesystem.d.ts +6 -0
- package/dist/publication-filesystem.d.ts.map +1 -0
- package/dist/publication-filesystem.js +6 -0
- package/dist/publication-filesystem.js.map +1 -0
- package/dist/query-filter.d.ts +12 -0
- package/dist/query-filter.d.ts.map +1 -0
- package/dist/query-filter.js +29 -0
- package/dist/query-filter.js.map +1 -0
- package/dist/query-selection.d.ts +25 -0
- package/dist/query-selection.d.ts.map +1 -0
- package/dist/query-selection.js +48 -0
- package/dist/query-selection.js.map +1 -0
- package/dist/remote-backend.d.ts +152 -0
- package/dist/remote-backend.d.ts.map +1 -0
- package/dist/remote-backend.js +505 -0
- package/dist/remote-backend.js.map +1 -0
- package/dist/remote.d.ts +4 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +3 -0
- package/dist/remote.js.map +1 -0
- package/dist/storage.d.ts +7 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +6 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +438 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +22 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +12 -0
- package/dist/validation.js.map +1 -0
- package/dist/version-transport.d.ts +13 -0
- package/dist/version-transport.d.ts.map +1 -0
- package/dist/version-transport.js +26 -0
- package/dist/version-transport.js.map +1 -0
- package/dist/versioning.d.ts +48 -0
- package/dist/versioning.d.ts.map +1 -0
- package/dist/versioning.js +62 -0
- package/dist/versioning.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared content_type inference — PORTED from holaxis-agentstate
|
|
3
|
+
* (`packages/schemas/src/content-type.ts`, dx-05 / `specs/zod-validation-layer-spec.md` §2.7).
|
|
4
|
+
*
|
|
5
|
+
* This is the single source for "infer a MIME `content_type` from a doc path extension." In the
|
|
6
|
+
* OKF store engine it is exposed for downstream consumers (the CLI's promote/pull commands,
|
|
7
|
+
* Page assets, and any `resource`-pointing frontmatter) rather than being stamped onto concept
|
|
8
|
+
* docs — OKF concept files are always markdown, and the format favors clean, spec-minimal
|
|
9
|
+
* frontmatter, so the engine does NOT inject a `content_type` key into concept documents.
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: this file has NO runtime `zod` import (only a type-only import of `ValidationWarning`,
|
|
12
|
+
* which the compiler erases), so it stays dependency-free.
|
|
13
|
+
*
|
|
14
|
+
* Design guardrails (unchanged from source):
|
|
15
|
+
* - Inference only FILLS a gap: an explicit caller-supplied `content_type` always wins.
|
|
16
|
+
* - For an unknown/ambiguous extension we keep the call site's existing default AND emit a warning —
|
|
17
|
+
* we never guess wildly. Genuinely ambiguous extensions (e.g. `.ts` = TypeScript vs MPEG-TS) are
|
|
18
|
+
* deliberately omitted from the table so they fall through to the fallback.
|
|
19
|
+
* - A path with NO extension infers nothing and warns nothing.
|
|
20
|
+
* - When the inferred type EQUALS the call site's existing default there is no behavior change, so no
|
|
21
|
+
* warning is emitted.
|
|
22
|
+
*/
|
|
23
|
+
import type { ValidationWarning } from "./validation.js";
|
|
24
|
+
export type { ValidationWarning } from "./validation.js";
|
|
25
|
+
/**
|
|
26
|
+
* Extension (lower-case, no dot) -> MIME content_type. Text-ish types carry `; charset=utf-8`.
|
|
27
|
+
* Binary types carry no charset. Deliberately conservative — ambiguous extensions are omitted.
|
|
28
|
+
*/
|
|
29
|
+
export declare const EXTENSION_CONTENT_TYPES: Readonly<Record<string, string>>;
|
|
30
|
+
/**
|
|
31
|
+
* Lower-cased extension (no dot) of the LAST path segment of `docKey`, or `undefined` when the final
|
|
32
|
+
* segment has no extension (no dot, or a leading-dot dotfile like `.gitignore`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function extensionOfDocKey(docKey: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Look up the inferred content_type for a `doc_key` extension, or `undefined` when the extension is
|
|
37
|
+
* absent or unrecognized. This is the low-level lookup; most callers want
|
|
38
|
+
* {@link inferContentTypeForNewBlob}, which also produces the warning.
|
|
39
|
+
*/
|
|
40
|
+
export declare function inferContentTypeFromDocKey(docKey: string): string | undefined;
|
|
41
|
+
/** Outcome of {@link inferContentTypeForNewBlob}. */
|
|
42
|
+
export type ContentTypeInference = {
|
|
43
|
+
/** The content_type to stamp on a brand-new blob (inferred type, or the call site's `fallback`). */
|
|
44
|
+
contentType: string;
|
|
45
|
+
/** True when an extension was recognized and produced `contentType`. */
|
|
46
|
+
inferred: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Non-fatal warning to surface, or `undefined` when there is nothing to report (no extension, or
|
|
49
|
+
* the inferred type equals `fallback` so behavior is unchanged).
|
|
50
|
+
*/
|
|
51
|
+
warning?: ValidationWarning;
|
|
52
|
+
};
|
|
53
|
+
/** Fallback content-type when a blob key has no extension, or an unrecognized one, and no explicit override was supplied. */
|
|
54
|
+
export declare const DEFAULT_BLOB_CONTENT_TYPE = "application/octet-stream";
|
|
55
|
+
/**
|
|
56
|
+
* THE ONE place a blob's content-type is resolved (`StorageBackend.readBlob`/`writeBlob`
|
|
57
|
+
* both route through this): an explicit `override` always wins; otherwise infer from
|
|
58
|
+
* `key`'s extension via {@link inferContentTypeFromDocKey}; otherwise
|
|
59
|
+
* {@link DEFAULT_BLOB_CONTENT_TYPE}. A trimmed-empty `override` is treated as absent
|
|
60
|
+
* (never resolves to `""`).
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveContentType(key: string, override?: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Decide the brand-new-blob content_type from a `doc_key` extension, FALLING BACK to the call site's
|
|
65
|
+
* existing default. Call this ONLY when the caller supplied no explicit content_type — explicit types
|
|
66
|
+
* always win and must short-circuit before reaching here.
|
|
67
|
+
*
|
|
68
|
+
* @param docKey the destination document key.
|
|
69
|
+
* @param fallback the call site's pre-existing brand-new-blob default.
|
|
70
|
+
*/
|
|
71
|
+
export declare function inferContentTypeForNewBlob(docKey: string, fallback: string): ContentTypeInference;
|
|
72
|
+
//# sourceMappingURL=content-type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAuCnE,CAAC;AAEH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOpE;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI7E;AAED,qDAAqD;AACrD,MAAM,MAAM,oBAAoB,GAAG;IACjC,oGAAoG;IACpG,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,QAAQ,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B,CAAC;AAEF,6HAA6H;AAC7H,eAAO,MAAM,yBAAyB,6BAA6B,CAAC;AAEpE;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAqCjG"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared content_type inference — PORTED from holaxis-agentstate
|
|
3
|
+
* (`packages/schemas/src/content-type.ts`, dx-05 / `specs/zod-validation-layer-spec.md` §2.7).
|
|
4
|
+
*
|
|
5
|
+
* This is the single source for "infer a MIME `content_type` from a doc path extension." In the
|
|
6
|
+
* OKF store engine it is exposed for downstream consumers (the CLI's promote/pull commands,
|
|
7
|
+
* Page assets, and any `resource`-pointing frontmatter) rather than being stamped onto concept
|
|
8
|
+
* docs — OKF concept files are always markdown, and the format favors clean, spec-minimal
|
|
9
|
+
* frontmatter, so the engine does NOT inject a `content_type` key into concept documents.
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: this file has NO runtime `zod` import (only a type-only import of `ValidationWarning`,
|
|
12
|
+
* which the compiler erases), so it stays dependency-free.
|
|
13
|
+
*
|
|
14
|
+
* Design guardrails (unchanged from source):
|
|
15
|
+
* - Inference only FILLS a gap: an explicit caller-supplied `content_type` always wins.
|
|
16
|
+
* - For an unknown/ambiguous extension we keep the call site's existing default AND emit a warning —
|
|
17
|
+
* we never guess wildly. Genuinely ambiguous extensions (e.g. `.ts` = TypeScript vs MPEG-TS) are
|
|
18
|
+
* deliberately omitted from the table so they fall through to the fallback.
|
|
19
|
+
* - A path with NO extension infers nothing and warns nothing.
|
|
20
|
+
* - When the inferred type EQUALS the call site's existing default there is no behavior change, so no
|
|
21
|
+
* warning is emitted.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Extension (lower-case, no dot) -> MIME content_type. Text-ish types carry `; charset=utf-8`.
|
|
25
|
+
* Binary types carry no charset. Deliberately conservative — ambiguous extensions are omitted.
|
|
26
|
+
*/
|
|
27
|
+
export const EXTENSION_CONTENT_TYPES = Object.freeze({
|
|
28
|
+
// Text / markup / data (searchable + text-readable)
|
|
29
|
+
html: "text/html; charset=utf-8",
|
|
30
|
+
htm: "text/html; charset=utf-8",
|
|
31
|
+
md: "text/markdown; charset=utf-8",
|
|
32
|
+
markdown: "text/markdown; charset=utf-8",
|
|
33
|
+
txt: "text/plain; charset=utf-8",
|
|
34
|
+
text: "text/plain; charset=utf-8",
|
|
35
|
+
log: "text/plain; charset=utf-8",
|
|
36
|
+
csv: "text/csv; charset=utf-8",
|
|
37
|
+
tsv: "text/tab-separated-values; charset=utf-8",
|
|
38
|
+
json: "application/json; charset=utf-8",
|
|
39
|
+
jsonl: "application/json; charset=utf-8",
|
|
40
|
+
ndjson: "application/json; charset=utf-8",
|
|
41
|
+
xml: "application/xml; charset=utf-8",
|
|
42
|
+
yaml: "application/yaml; charset=utf-8",
|
|
43
|
+
yml: "application/yaml; charset=utf-8",
|
|
44
|
+
toml: "application/toml; charset=utf-8",
|
|
45
|
+
css: "text/css; charset=utf-8",
|
|
46
|
+
js: "text/javascript; charset=utf-8",
|
|
47
|
+
mjs: "text/javascript; charset=utf-8",
|
|
48
|
+
cjs: "text/javascript; charset=utf-8",
|
|
49
|
+
// Images
|
|
50
|
+
png: "image/png",
|
|
51
|
+
jpg: "image/jpeg",
|
|
52
|
+
jpeg: "image/jpeg",
|
|
53
|
+
gif: "image/gif",
|
|
54
|
+
webp: "image/webp",
|
|
55
|
+
bmp: "image/bmp",
|
|
56
|
+
ico: "image/x-icon",
|
|
57
|
+
svg: "image/svg+xml",
|
|
58
|
+
// Documents / archives / binary
|
|
59
|
+
pdf: "application/pdf",
|
|
60
|
+
zip: "application/zip",
|
|
61
|
+
gz: "application/gzip",
|
|
62
|
+
tar: "application/x-tar",
|
|
63
|
+
wasm: "application/wasm",
|
|
64
|
+
woff: "font/woff",
|
|
65
|
+
woff2: "font/woff2",
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* Lower-cased extension (no dot) of the LAST path segment of `docKey`, or `undefined` when the final
|
|
69
|
+
* segment has no extension (no dot, or a leading-dot dotfile like `.gitignore`).
|
|
70
|
+
*/
|
|
71
|
+
export function extensionOfDocKey(docKey) {
|
|
72
|
+
const lastSegment = docKey.split("/").pop() ?? docKey;
|
|
73
|
+
const dot = lastSegment.lastIndexOf(".");
|
|
74
|
+
// No dot, or a leading-dot dotfile (".gitignore") — nothing to infer from.
|
|
75
|
+
if (dot <= 0)
|
|
76
|
+
return undefined;
|
|
77
|
+
const ext = lastSegment.slice(dot + 1).toLowerCase();
|
|
78
|
+
return ext.length > 0 ? ext : undefined;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Look up the inferred content_type for a `doc_key` extension, or `undefined` when the extension is
|
|
82
|
+
* absent or unrecognized. This is the low-level lookup; most callers want
|
|
83
|
+
* {@link inferContentTypeForNewBlob}, which also produces the warning.
|
|
84
|
+
*/
|
|
85
|
+
export function inferContentTypeFromDocKey(docKey) {
|
|
86
|
+
const ext = extensionOfDocKey(docKey);
|
|
87
|
+
if (!ext)
|
|
88
|
+
return undefined;
|
|
89
|
+
return EXTENSION_CONTENT_TYPES[ext];
|
|
90
|
+
}
|
|
91
|
+
/** Fallback content-type when a blob key has no extension, or an unrecognized one, and no explicit override was supplied. */
|
|
92
|
+
export const DEFAULT_BLOB_CONTENT_TYPE = "application/octet-stream";
|
|
93
|
+
/**
|
|
94
|
+
* THE ONE place a blob's content-type is resolved (`StorageBackend.readBlob`/`writeBlob`
|
|
95
|
+
* both route through this): an explicit `override` always wins; otherwise infer from
|
|
96
|
+
* `key`'s extension via {@link inferContentTypeFromDocKey}; otherwise
|
|
97
|
+
* {@link DEFAULT_BLOB_CONTENT_TYPE}. A trimmed-empty `override` is treated as absent
|
|
98
|
+
* (never resolves to `""`).
|
|
99
|
+
*/
|
|
100
|
+
export function resolveContentType(key, override) {
|
|
101
|
+
if (typeof override === "string" && override.trim() !== "")
|
|
102
|
+
return override;
|
|
103
|
+
return inferContentTypeFromDocKey(key) ?? DEFAULT_BLOB_CONTENT_TYPE;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Decide the brand-new-blob content_type from a `doc_key` extension, FALLING BACK to the call site's
|
|
107
|
+
* existing default. Call this ONLY when the caller supplied no explicit content_type — explicit types
|
|
108
|
+
* always win and must short-circuit before reaching here.
|
|
109
|
+
*
|
|
110
|
+
* @param docKey the destination document key.
|
|
111
|
+
* @param fallback the call site's pre-existing brand-new-blob default.
|
|
112
|
+
*/
|
|
113
|
+
export function inferContentTypeForNewBlob(docKey, fallback) {
|
|
114
|
+
const ext = extensionOfDocKey(docKey);
|
|
115
|
+
if (!ext) {
|
|
116
|
+
// No extension to reason about — preserve today's silent default.
|
|
117
|
+
return { contentType: fallback, inferred: false };
|
|
118
|
+
}
|
|
119
|
+
const mime = EXTENSION_CONTENT_TYPES[ext];
|
|
120
|
+
if (mime) {
|
|
121
|
+
if (mime === fallback) {
|
|
122
|
+
// Recognized, but identical to today's default — no behavior change, so stay quiet.
|
|
123
|
+
return { contentType: mime, inferred: true };
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
contentType: mime,
|
|
127
|
+
inferred: true,
|
|
128
|
+
warning: {
|
|
129
|
+
code: "CONTENT_TYPE_INFERRED",
|
|
130
|
+
message: `content_type not provided; inferred '${mime}' from the '.${ext}' doc_key extension.`,
|
|
131
|
+
field: "content_type",
|
|
132
|
+
severity: "info",
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Present-but-unrecognized extension: keep the existing default, but breadcrumb that we saw an
|
|
137
|
+
// extension we do not infer for (do not guess wildly).
|
|
138
|
+
return {
|
|
139
|
+
contentType: fallback,
|
|
140
|
+
inferred: false,
|
|
141
|
+
warning: {
|
|
142
|
+
code: "CONTENT_TYPE_INFER_FALLBACK",
|
|
143
|
+
message: `content_type not provided and the '.${ext}' doc_key extension is not recognized; defaulting to '${fallback}'.`,
|
|
144
|
+
field: "content_type",
|
|
145
|
+
severity: "info",
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=content-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-type.js","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-neutral document mutation policy.
|
|
3
|
+
*
|
|
4
|
+
* This is the document-specific layer above {@link versionedMutation}: create-only,
|
|
5
|
+
* overwrite, and patch postures share fresh-read/CAS coupling, kind validation,
|
|
6
|
+
* timestamp ordering, semantic no-op detection, attribution, and final receipts.
|
|
7
|
+
* Consumers own their transport and presentation errors; this module throws typed
|
|
8
|
+
* core failures and never imports CLI or browser concerns.
|
|
9
|
+
*/
|
|
10
|
+
import { InvalidInputError } from "./errors.js";
|
|
11
|
+
import type { FieldPrecondition } from "./document-precondition.js";
|
|
12
|
+
import type { KindRegistry } from "./kinds.js";
|
|
13
|
+
import type { ValidationWarning } from "./content-type.js";
|
|
14
|
+
import type { Bundle, ConceptId, Frontmatter, OkfDocument, Version } from "./types.js";
|
|
15
|
+
export type DocumentMutationMode = "create-only" | "overwrite" | "patch";
|
|
16
|
+
/** The frontmatter and body a caller wants persisted; the service supplies the id. */
|
|
17
|
+
export interface DocumentMutationCandidate {
|
|
18
|
+
frontmatter: Frontmatter;
|
|
19
|
+
body: string;
|
|
20
|
+
}
|
|
21
|
+
/** Root-derived bundle facts supplied by the mutation authority to candidate construction. */
|
|
22
|
+
export interface DocumentMutationContext {
|
|
23
|
+
okfVersion: "0.1" | "0.2";
|
|
24
|
+
}
|
|
25
|
+
/** Typed strict-kind rejection for trusted consumers to translate at their own boundary. */
|
|
26
|
+
export declare class KindConformanceError extends InvalidInputError {
|
|
27
|
+
readonly id: ConceptId;
|
|
28
|
+
readonly governs: string;
|
|
29
|
+
readonly violations: ValidationWarning[];
|
|
30
|
+
readonly okfVersion?: "0.1" | "0.2";
|
|
31
|
+
constructor(id: ConceptId, governs: string, violations: ValidationWarning[], okfVersion?: "0.1" | "0.2");
|
|
32
|
+
}
|
|
33
|
+
/** Typed missing-document result for patch callers that require an existing target. */
|
|
34
|
+
export declare class DocumentNotFoundError extends Error {
|
|
35
|
+
readonly id: ConceptId;
|
|
36
|
+
constructor(id: ConceptId);
|
|
37
|
+
}
|
|
38
|
+
export interface MutateDocumentOptions {
|
|
39
|
+
bundle: Bundle;
|
|
40
|
+
id: ConceptId;
|
|
41
|
+
mode: DocumentMutationMode;
|
|
42
|
+
/** Loaded once by the trusted caller; the service never performs registry discovery implicitly. */
|
|
43
|
+
registry: KindRegistry;
|
|
44
|
+
/** Reject rather than return a non-empty kind warning set. */
|
|
45
|
+
strict: boolean;
|
|
46
|
+
/** Recomputed against every fresh CAS attempt. */
|
|
47
|
+
buildCandidate: (existing: OkfDocument | undefined, context: DocumentMutationContext) => DocumentMutationCandidate | Promise<DocumentMutationCandidate>;
|
|
48
|
+
/** Patch only: require an existing target or allow an expect-absent create. */
|
|
49
|
+
onAbsent?: "fail" | "create";
|
|
50
|
+
/** Retry budget for overwrite and ordinary patch. */
|
|
51
|
+
maxAttempts?: number;
|
|
52
|
+
/** Include the legacy timestamp in semantic no-op comparison. */
|
|
53
|
+
compareTimestamp?: boolean;
|
|
54
|
+
/** Advisory backend-history attribution, applied only when a write occurs. */
|
|
55
|
+
actor?: string;
|
|
56
|
+
/** Also persist the advisory actor in the edition-appropriate frontmatter field after no-op detection. */
|
|
57
|
+
persistActor?: boolean;
|
|
58
|
+
/** Patch only: a caller-supplied token makes the operation a single-shot hard CAS. */
|
|
59
|
+
expectedVersion?: Version;
|
|
60
|
+
/**
|
|
61
|
+
* Read-based modes only: asserted facts about the observed document, ANDed and re-evaluated
|
|
62
|
+
* against EVERY attempt's fresh read before any candidate is built. An unsatisfied assertion is
|
|
63
|
+
* a terminal {@link PreconditionFailed} — never a retry — so the guard and the CAS write commit
|
|
64
|
+
* or refuse together.
|
|
65
|
+
*/
|
|
66
|
+
preconditions?: readonly FieldPrecondition[];
|
|
67
|
+
/** Test seam for edition-specific clocks; production defaults to the current ISO instant. */
|
|
68
|
+
now?: () => string;
|
|
69
|
+
/**
|
|
70
|
+
* Seed the v0.2 generation clock (`generated: {by, at}`) on creates that carry no usable
|
|
71
|
+
* time value (default true). Definition installers (recipe application) pass false so the
|
|
72
|
+
* installed definition's bytes stay comparable to the recipe source across reinstalls.
|
|
73
|
+
*/
|
|
74
|
+
seedGenerationClock?: boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface DocumentMutationResult {
|
|
77
|
+
doc: OkfDocument;
|
|
78
|
+
changed: boolean;
|
|
79
|
+
version: Version;
|
|
80
|
+
warnings: ValidationWarning[];
|
|
81
|
+
}
|
|
82
|
+
export declare function mutateDocument(opts: MutateDocumentOptions): Promise<DocumentMutationResult>;
|
|
83
|
+
//# sourceMappingURL=document-mutation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document-mutation.d.ts","sourceRoot":"","sources":["../src/document-mutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAkBhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EACV,MAAM,EACN,SAAS,EACT,WAAW,EACX,WAAW,EACX,OAAO,EACR,MAAM,YAAY,CAAC;AAIpB,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,WAAW,GAAG,OAAO,CAAC;AAEzE,sFAAsF;AACtF,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,WAAW,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8FAA8F;AAC9F,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,KAAK,GAAG,KAAK,CAAC;CAC3B;AAED,4FAA4F;AAC5F,qBAAa,oBAAqB,SAAQ,iBAAiB;IACzD,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,iBAAiB,EAAE,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;gBAGlC,EAAE,EAAE,SAAS,EACb,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,iBAAiB,EAAE,EAC/B,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK;CAS7B;AAED,uFAAuF;AACvF,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;gBAEX,EAAE,EAAE,SAAS;CAK1B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,SAAS,CAAC;IACd,IAAI,EAAE,oBAAoB,CAAC;IAC3B,mGAAmG;IACnG,QAAQ,EAAE,YAAY,CAAC;IACvB,8DAA8D;IAC9D,MAAM,EAAE,OAAO,CAAC;IAChB,kDAAkD;IAClD,cAAc,EAAE,CACd,QAAQ,EAAE,WAAW,GAAG,SAAS,EACjC,OAAO,EAAE,uBAAuB,KAC7B,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACpE,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC7B,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0GAA0G;IAC1G,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sFAAsF;IACtF,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C,6FAA6F;IAC7F,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,GAAG,EAAE,WAAW,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAgLD,wBAAsB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAmMjG"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-neutral document mutation policy.
|
|
3
|
+
*
|
|
4
|
+
* This is the document-specific layer above {@link versionedMutation}: create-only,
|
|
5
|
+
* overwrite, and patch postures share fresh-read/CAS coupling, kind validation,
|
|
6
|
+
* timestamp ordering, semantic no-op detection, attribution, and final receipts.
|
|
7
|
+
* Consumers own their transport and presentation errors; this module throws typed
|
|
8
|
+
* core failures and never imports CLI or browser concerns.
|
|
9
|
+
*/
|
|
10
|
+
import { InvalidInputError } from "./errors.js";
|
|
11
|
+
import { applyV02MutationMetadata } from "./document-write-policy.js";
|
|
12
|
+
import { assertFieldPreconditions } from "./document-precondition.js";
|
|
13
|
+
import { normalizeDocumentBodyForStorage } from "./frontmatter.js";
|
|
14
|
+
import { parseTimestamp } from "./freshness.js";
|
|
15
|
+
import { meaningfulChangeTimeValue } from "./meaningful-change-time.js";
|
|
16
|
+
import { persistMutationActor, SUPERBEE_UPDATED_BY_FIELD } from "./mutation-attribution.js";
|
|
17
|
+
import { defaultTimestampAndValidateAgainstRegistry, validateAgainstKind, } from "./kinds.js";
|
|
18
|
+
import { versionedMutation } from "./mutation.js";
|
|
19
|
+
import { VersionConflict } from "./versioning.js";
|
|
20
|
+
import { readBundleOkfVersion, readDocVersioned, writeDocVersionedForEdition, } from "./bundle.js";
|
|
21
|
+
const DEFAULT_MAX_ATTEMPTS = 5;
|
|
22
|
+
/** Typed strict-kind rejection for trusted consumers to translate at their own boundary. */
|
|
23
|
+
export class KindConformanceError extends InvalidInputError {
|
|
24
|
+
id;
|
|
25
|
+
governs;
|
|
26
|
+
violations;
|
|
27
|
+
okfVersion;
|
|
28
|
+
constructor(id, governs, violations, okfVersion) {
|
|
29
|
+
super(`'${id}' does not satisfy the '${governs}' kind: ${violations.map((warning) => warning.message).join("; ")}`);
|
|
30
|
+
this.name = "KindConformanceError";
|
|
31
|
+
this.id = id;
|
|
32
|
+
this.governs = governs;
|
|
33
|
+
this.violations = violations;
|
|
34
|
+
this.okfVersion = okfVersion;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Typed missing-document result for patch callers that require an existing target. */
|
|
38
|
+
export class DocumentNotFoundError extends Error {
|
|
39
|
+
id;
|
|
40
|
+
constructor(id) {
|
|
41
|
+
super(`no concept document at id '${id}'`);
|
|
42
|
+
this.name = "DocumentNotFoundError";
|
|
43
|
+
this.id = id;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Structural equality through plain objects and arrays, independent of object key order. */
|
|
47
|
+
function valuesEqual(a, b) {
|
|
48
|
+
if (a === b)
|
|
49
|
+
return true;
|
|
50
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
51
|
+
if (!Array.isArray(a) || !Array.isArray(b))
|
|
52
|
+
return false;
|
|
53
|
+
return a.length === b.length && a.every((value, index) => valuesEqual(value, b[index]));
|
|
54
|
+
}
|
|
55
|
+
if (a && b && typeof a === "object" && typeof b === "object") {
|
|
56
|
+
const aRecord = a;
|
|
57
|
+
const bRecord = b;
|
|
58
|
+
const aKeys = Object.keys(aRecord);
|
|
59
|
+
const bKeys = Object.keys(bRecord);
|
|
60
|
+
return aKeys.length === bKeys.length && aKeys.every((key) => valuesEqual(aRecord[key], bRecord[key]));
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
function withoutAutomaticMutationActor(frontmatter, okfVersion, kindRequiresActor) {
|
|
65
|
+
if (okfVersion === "0.1") {
|
|
66
|
+
const { actor: _actor, ...rest } = frontmatter;
|
|
67
|
+
return rest;
|
|
68
|
+
}
|
|
69
|
+
const { [SUPERBEE_UPDATED_BY_FIELD]: _updatedBy, ...rest } = frontmatter;
|
|
70
|
+
if (!kindRequiresActor)
|
|
71
|
+
return rest;
|
|
72
|
+
const { actor: _actor, ...withoutActor } = rest;
|
|
73
|
+
return withoutActor;
|
|
74
|
+
}
|
|
75
|
+
function withCanonicalComparableTimestamp(frontmatter) {
|
|
76
|
+
const timestamp = frontmatter.timestamp;
|
|
77
|
+
if (typeof timestamp !== "string")
|
|
78
|
+
return frontmatter;
|
|
79
|
+
const instant = Date.parse(timestamp);
|
|
80
|
+
if (Number.isNaN(instant))
|
|
81
|
+
return frontmatter;
|
|
82
|
+
const canonical = new Date(instant).toISOString();
|
|
83
|
+
return canonical === timestamp ? frontmatter : { ...frontmatter, timestamp: canonical };
|
|
84
|
+
}
|
|
85
|
+
function isNoopMutation(existing, candidate, compareTimestamp, okfVersion, ignoreAutomaticActor = false, kindRequiresActor = false) {
|
|
86
|
+
if (normalizeDocumentBodyForStorage(candidate.body)
|
|
87
|
+
!== normalizeDocumentBodyForStorage(existing.body)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const existingFrontmatterWithActorPolicy = ignoreAutomaticActor
|
|
91
|
+
? withoutAutomaticMutationActor(existing.frontmatter, okfVersion, kindRequiresActor)
|
|
92
|
+
: existing.frontmatter;
|
|
93
|
+
const candidateFrontmatterWithActorPolicy = ignoreAutomaticActor
|
|
94
|
+
? withoutAutomaticMutationActor(candidate.frontmatter, okfVersion, kindRequiresActor)
|
|
95
|
+
: candidate.frontmatter;
|
|
96
|
+
const existingFrontmatter = compareTimestamp
|
|
97
|
+
? withCanonicalComparableTimestamp(existingFrontmatterWithActorPolicy)
|
|
98
|
+
: existingFrontmatterWithActorPolicy;
|
|
99
|
+
const candidateFrontmatter = compareTimestamp
|
|
100
|
+
? withCanonicalComparableTimestamp(candidateFrontmatterWithActorPolicy)
|
|
101
|
+
: candidateFrontmatterWithActorPolicy;
|
|
102
|
+
if (okfVersion === "0.2") {
|
|
103
|
+
const withoutGeneratedAt = (frontmatter) => {
|
|
104
|
+
const generated = frontmatter.generated;
|
|
105
|
+
if (generated === null || typeof generated !== "object" || Array.isArray(generated)) {
|
|
106
|
+
return frontmatter;
|
|
107
|
+
}
|
|
108
|
+
const { at: _at, ...generatedRest } = generated;
|
|
109
|
+
return { ...frontmatter, generated: generatedRest };
|
|
110
|
+
};
|
|
111
|
+
return valuesEqual(withoutGeneratedAt(existingFrontmatter), withoutGeneratedAt(candidateFrontmatter));
|
|
112
|
+
}
|
|
113
|
+
if (compareTimestamp)
|
|
114
|
+
return valuesEqual(existingFrontmatter, candidateFrontmatter);
|
|
115
|
+
const { timestamp: _existingTimestamp, ...existingRest } = existingFrontmatter;
|
|
116
|
+
const { timestamp: _candidateTimestamp, ...candidateRest } = candidateFrontmatter;
|
|
117
|
+
return valuesEqual(existingRest, candidateRest);
|
|
118
|
+
}
|
|
119
|
+
function attributeCandidate(candidate, actor, persistActor, okfVersion, registry) {
|
|
120
|
+
if (!persistActor)
|
|
121
|
+
return candidate;
|
|
122
|
+
const kind = registry.kinds.get(String(candidate.frontmatter.type));
|
|
123
|
+
return {
|
|
124
|
+
...candidate,
|
|
125
|
+
frontmatter: persistMutationActor(candidate.frontmatter, {
|
|
126
|
+
actor,
|
|
127
|
+
okfVersion,
|
|
128
|
+
kindRequiresActor: kind?.fields.required.includes("actor") ?? false,
|
|
129
|
+
}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Validate `candidate` against its own (possibly retyped) governing kind, defaulting its
|
|
134
|
+
* timestamp first. Returns the kind alongside the warnings so overwrite's ratchet (below) can
|
|
135
|
+
* reuse this ONE validation pass instead of re-deriving the candidate's kind separately.
|
|
136
|
+
*/
|
|
137
|
+
function validateCandidate(id, candidate, registry, strict, okfVersion, now) {
|
|
138
|
+
const result = defaultTimestampAndValidateAgainstRegistry({ id, ...candidate }, registry, { okfVersion, now });
|
|
139
|
+
if (strict && result.kind && result.warnings.length > 0) {
|
|
140
|
+
throw new KindConformanceError(id, result.kind.governs, result.warnings, okfVersion);
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
function withV02Metadata(candidate, existing, okfVersion, registry, now, seedGenerationClock) {
|
|
145
|
+
if (okfVersion !== "0.2")
|
|
146
|
+
return candidate;
|
|
147
|
+
const kind = registry.kinds.get(String(candidate.frontmatter.type));
|
|
148
|
+
return applyV02MutationMetadata({
|
|
149
|
+
existing,
|
|
150
|
+
candidate,
|
|
151
|
+
meaningfulChangeAt: now(),
|
|
152
|
+
// Every v0.2 create gets one standard clock unless a usable time already exists (an explicit
|
|
153
|
+
// legacy `timestamp`, a declared `generated.at`) or its kind requires `timestamp` (that one
|
|
154
|
+
// clock is defaulted instead). Ungoverned types are the point: accumulation evidence for
|
|
155
|
+
// per-bundle modeling (designs/agent-proposed-kind-conventions-v1) must not depend on a kind
|
|
156
|
+
// already existing. Callers installing definitions opt out via `seedGenerationClock: false`.
|
|
157
|
+
requireGenerationClock: existing === undefined
|
|
158
|
+
&& seedGenerationClock
|
|
159
|
+
&& !(kind?.fields.required.includes("timestamp") ?? false)
|
|
160
|
+
&& parseTimestamp(meaningfulChangeTimeValue(candidate.frontmatter)) === null,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/** One stable instant per CAS decision, shared by every clock a bundle Kind explicitly carries. */
|
|
164
|
+
function onceNow(now) {
|
|
165
|
+
let value;
|
|
166
|
+
return () => (value ??= now());
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* True when `existing` already satisfies ITS OWN governing kind — the monotone ratchet's
|
|
170
|
+
* precondition (probe: tasks/overwrite-ratchet-survey). Validates the RAW existing
|
|
171
|
+
* frontmatter — no timestamp defaulting — because the existing doc IS its raw bytes; nothing
|
|
172
|
+
* normalizes it unless written. This keeps the ratchet in agreement with `status`'s
|
|
173
|
+
* `conformance_debt` (both call `validateAgainstKind` on the same unmodified frontmatter;
|
|
174
|
+
* see tasks/conforms-raw-alignment). `validateAgainstKind` only reads `doc.frontmatter`, so
|
|
175
|
+
* passing `existing` directly is safe — no defensive clone needed.
|
|
176
|
+
*/
|
|
177
|
+
function conforms(existing, registry) {
|
|
178
|
+
const kind = registry.kinds.get(String(existing.frontmatter.type));
|
|
179
|
+
if (!kind)
|
|
180
|
+
return false;
|
|
181
|
+
return validateAgainstKind(existing, kind).length === 0;
|
|
182
|
+
}
|
|
183
|
+
export async function mutateDocument(opts) {
|
|
184
|
+
const maxAttempts = opts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
185
|
+
const onAbsent = opts.onAbsent ?? "fail";
|
|
186
|
+
const compareTimestamp = opts.compareTimestamp ?? false;
|
|
187
|
+
const persistActor = opts.persistActor ?? false;
|
|
188
|
+
const seedClock = opts.seedGenerationClock ?? true;
|
|
189
|
+
const okfVersion = await readBundleOkfVersion(opts.bundle) ?? "0.1";
|
|
190
|
+
if (okfVersion !== "0.1" && okfVersion !== "0.2") {
|
|
191
|
+
throw new InvalidInputError(`Unsupported OKF mutation version '${okfVersion}'. This build can mutate 0.1 and 0.2 bundles.`);
|
|
192
|
+
}
|
|
193
|
+
const now = opts.now ?? (() => new Date().toISOString());
|
|
194
|
+
const context = { okfVersion };
|
|
195
|
+
if (opts.mode === "create-only") {
|
|
196
|
+
// Fail closed: create-only issues NO read — its CAS basis is expect-absent — so there is no
|
|
197
|
+
// observed state to evaluate an assertion against. Accepting the option and silently not
|
|
198
|
+
// enforcing it would leave a caller believing an unenforced guard is enforced.
|
|
199
|
+
if (opts.preconditions !== undefined && opts.preconditions.length > 0) {
|
|
200
|
+
throw new InvalidInputError("field preconditions require an observed document; 'create-only' performs no read "
|
|
201
|
+
+ "(use mode 'patch' with onAbsent: 'create')");
|
|
202
|
+
}
|
|
203
|
+
const decisionNow = onceNow(now);
|
|
204
|
+
const attributed = attributeCandidate(await opts.buildCandidate(undefined, context), opts.actor, persistActor, okfVersion, opts.registry);
|
|
205
|
+
const candidate = withV02Metadata(attributed, undefined, okfVersion, opts.registry, decisionNow, seedClock);
|
|
206
|
+
const { warnings } = validateCandidate(opts.id, candidate, opts.registry, opts.strict, okfVersion, decisionNow);
|
|
207
|
+
const { doc, version } = await writeDocVersionedForEdition(opts.bundle, { id: opts.id, ...candidate }, okfVersion, {
|
|
208
|
+
expectedVersion: null,
|
|
209
|
+
actor: opts.actor,
|
|
210
|
+
});
|
|
211
|
+
return { doc, changed: true, version, warnings };
|
|
212
|
+
}
|
|
213
|
+
// The version of the read the CURRENT attempt is deciding over. `versionedMutation` withholds
|
|
214
|
+
// version tokens from `decide` so a decision can never be paired with a different read's token;
|
|
215
|
+
// this records it for the one attempt in flight, and it is only ever REPORTED (the hard-CAS
|
|
216
|
+
// comparison and a precondition refusal's `observedVersion`) — never used to build a candidate.
|
|
217
|
+
let lastReadVersion = null;
|
|
218
|
+
const readExisting = async () => {
|
|
219
|
+
try {
|
|
220
|
+
const { doc, version } = await readDocVersioned(opts.bundle, opts.id);
|
|
221
|
+
lastReadVersion = version;
|
|
222
|
+
return { state: doc, version };
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (error?.code === "ENOENT") {
|
|
226
|
+
lastReadVersion = null;
|
|
227
|
+
return { state: undefined, version: null };
|
|
228
|
+
}
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
if (opts.mode === "overwrite") {
|
|
233
|
+
let savedDoc;
|
|
234
|
+
let warnings = [];
|
|
235
|
+
const outcome = await versionedMutation({
|
|
236
|
+
read: readExisting,
|
|
237
|
+
decide: async (existing) => {
|
|
238
|
+
const decisionNow = onceNow(now);
|
|
239
|
+
assertFieldPreconditions(opts.id, existing?.frontmatter, opts.preconditions, lastReadVersion);
|
|
240
|
+
const attributed = attributeCandidate(await opts.buildCandidate(existing, context), opts.actor, persistActor, okfVersion, opts.registry);
|
|
241
|
+
const candidate = withV02Metadata(attributed, existing, okfVersion, opts.registry, decisionNow, seedClock);
|
|
242
|
+
const validated = validateCandidate(opts.id, candidate, opts.registry, opts.strict, okfVersion, decisionNow);
|
|
243
|
+
warnings = validated.warnings;
|
|
244
|
+
// Monotone conformance ratchet (probe: tasks/overwrite-ratchet-survey, productionized
|
|
245
|
+
// here): a doc that already satisfies its governing kind may not regress into
|
|
246
|
+
// non-conformance through a lenient (non-strict) overwrite — once clean, always clean.
|
|
247
|
+
// A doc that has NEVER conformed keeps today's lenient staging behavior (warn, don't
|
|
248
|
+
// block), and dropping only OPTIONAL fields can't trip this (validateAgainstKind never
|
|
249
|
+
// warns on those), so this only ever fires on a REAL regression. Retyping to an
|
|
250
|
+
// ungoverned type is a documented escape: an ungoverned candidate carries zero
|
|
251
|
+
// warnings by construction, so the `warnings.length > 0` guard below never sees it.
|
|
252
|
+
// Every overwrite caller, including `promote`, inherits this rule from this shared boundary.
|
|
253
|
+
if (!opts.strict && existing && warnings.length > 0 && conforms(existing, opts.registry)) {
|
|
254
|
+
throw new KindConformanceError(opts.id, validated.kind.governs, warnings, okfVersion);
|
|
255
|
+
}
|
|
256
|
+
if (existing
|
|
257
|
+
&& isNoopMutation(existing, candidate, compareTimestamp, okfVersion, persistActor && opts.actor !== undefined, validated.kind?.fields.required.includes("actor") ?? false)) {
|
|
258
|
+
return { action: "done", result: { doc: existing } };
|
|
259
|
+
}
|
|
260
|
+
return { action: "write", next: { id: opts.id, ...candidate }, result: {} };
|
|
261
|
+
},
|
|
262
|
+
write: async (next, expectedVersion) => {
|
|
263
|
+
const written = await writeDocVersionedForEdition(opts.bundle, next, okfVersion, {
|
|
264
|
+
expectedVersion,
|
|
265
|
+
actor: opts.actor,
|
|
266
|
+
});
|
|
267
|
+
savedDoc = written.doc;
|
|
268
|
+
return written.version;
|
|
269
|
+
},
|
|
270
|
+
maxAttempts,
|
|
271
|
+
});
|
|
272
|
+
return outcome.wrote
|
|
273
|
+
? { doc: savedDoc, changed: true, version: outcome.version, warnings }
|
|
274
|
+
: { doc: outcome.result.doc, changed: false, version: outcome.version, warnings };
|
|
275
|
+
}
|
|
276
|
+
let savedDoc;
|
|
277
|
+
const hardCas = opts.expectedVersion !== undefined;
|
|
278
|
+
const outcome = await versionedMutation({
|
|
279
|
+
read: async () => {
|
|
280
|
+
const read = await readExisting();
|
|
281
|
+
if (read.state === undefined && onAbsent === "fail")
|
|
282
|
+
throw new DocumentNotFoundError(opts.id);
|
|
283
|
+
return read;
|
|
284
|
+
},
|
|
285
|
+
decide: async (existing) => {
|
|
286
|
+
const decisionNow = onceNow(now);
|
|
287
|
+
// Ahead of the version comparison on purpose: when both an asserted fact and the caller's
|
|
288
|
+
// version token are stale, the domain answer is the true one and the terminal one. Reporting
|
|
289
|
+
// a conflict instead would tell a refused caller its premise merely moved, and invite the
|
|
290
|
+
// re-read-and-retry that overwrites whoever changed the field.
|
|
291
|
+
assertFieldPreconditions(opts.id, existing?.frontmatter, opts.preconditions, lastReadVersion);
|
|
292
|
+
if (hardCas && lastReadVersion !== opts.expectedVersion) {
|
|
293
|
+
throw new VersionConflict(opts.id, opts.expectedVersion, lastReadVersion);
|
|
294
|
+
}
|
|
295
|
+
const rawCandidate = await opts.buildCandidate(existing, context);
|
|
296
|
+
const candidateForComparison = withV02Metadata(rawCandidate, existing, okfVersion, opts.registry, decisionNow, seedClock);
|
|
297
|
+
if (existing && isNoopMutation(existing, candidateForComparison, compareTimestamp, okfVersion)) {
|
|
298
|
+
return { action: "done", result: { doc: existing, warnings: [] } };
|
|
299
|
+
}
|
|
300
|
+
const attributed = attributeCandidate(rawCandidate, opts.actor, persistActor, okfVersion, opts.registry);
|
|
301
|
+
const candidate = withV02Metadata(attributed, existing, okfVersion, opts.registry, decisionNow, seedClock);
|
|
302
|
+
const { warnings } = validateCandidate(opts.id, candidate, opts.registry, opts.strict, okfVersion, decisionNow);
|
|
303
|
+
return { action: "write", next: { id: opts.id, ...candidate }, result: { warnings } };
|
|
304
|
+
},
|
|
305
|
+
write: async (next, expectedVersion) => {
|
|
306
|
+
const written = await writeDocVersionedForEdition(opts.bundle, next, okfVersion, {
|
|
307
|
+
expectedVersion: hardCas ? opts.expectedVersion : expectedVersion,
|
|
308
|
+
actor: opts.actor,
|
|
309
|
+
});
|
|
310
|
+
savedDoc = written.doc;
|
|
311
|
+
return written.version;
|
|
312
|
+
},
|
|
313
|
+
maxAttempts: hardCas ? 1 : maxAttempts,
|
|
314
|
+
});
|
|
315
|
+
return outcome.wrote
|
|
316
|
+
? { doc: savedDoc, changed: true, version: outcome.version, warnings: outcome.result.warnings }
|
|
317
|
+
: { doc: outcome.result.doc, changed: false, version: outcome.version, warnings: [] };
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=document-mutation.js.map
|