@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
@@ -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