@superbee/core 0.2.0-pre.4 → 0.2.0-pre.40
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/README.md +1209 -0
- package/dist/artifact-contract.d.ts +90 -0
- package/dist/artifact-contract.d.ts.map +1 -0
- package/dist/artifact-contract.js +205 -0
- package/dist/artifact-contract.js.map +1 -0
- package/dist/backend.d.ts +22 -2
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +153 -38
- package/dist/backend.js.map +1 -1
- package/dist/blob-contract.d.ts +82 -0
- package/dist/blob-contract.d.ts.map +1 -0
- package/dist/blob-contract.js +134 -0
- package/dist/blob-contract.js.map +1 -0
- package/dist/blob-journal.d.ts +303 -0
- package/dist/blob-journal.d.ts.map +1 -0
- package/dist/blob-journal.js +541 -0
- package/dist/blob-journal.js.map +1 -0
- package/dist/body-replace-guards.d.ts +52 -0
- package/dist/body-replace-guards.d.ts.map +1 -0
- package/dist/body-replace-guards.js +176 -0
- package/dist/body-replace-guards.js.map +1 -0
- package/dist/builtin-recipes/content.d.ts +93 -0
- package/dist/builtin-recipes/content.d.ts.map +1 -0
- package/dist/builtin-recipes/content.js +288 -0
- package/dist/builtin-recipes/content.js.map +1 -0
- package/dist/builtin-recipes.d.ts +10 -0
- package/dist/builtin-recipes.d.ts.map +1 -0
- package/dist/builtin-recipes.js +97 -0
- package/dist/builtin-recipes.js.map +1 -0
- package/dist/bundle-health/index.d.ts +14 -0
- package/dist/bundle-health/index.d.ts.map +1 -0
- package/dist/bundle-health/index.js +12 -0
- package/dist/bundle-health/index.js.map +1 -0
- package/dist/bundle-health/kind-findings.d.ts +44 -0
- package/dist/bundle-health/kind-findings.d.ts.map +1 -0
- package/dist/bundle-health/kind-findings.js +68 -0
- package/dist/bundle-health/kind-findings.js.map +1 -0
- package/dist/bundle-health/legacy-page.d.ts +39 -0
- package/dist/bundle-health/legacy-page.d.ts.map +1 -0
- package/dist/bundle-health/legacy-page.js +50 -0
- package/dist/bundle-health/legacy-page.js.map +1 -0
- package/dist/bundle-health/report.d.ts +207 -0
- package/dist/bundle-health/report.d.ts.map +1 -0
- package/dist/bundle-health/report.js +454 -0
- package/dist/bundle-health/report.js.map +1 -0
- package/dist/bundle-ops.d.ts +3 -2
- package/dist/bundle-ops.d.ts.map +1 -1
- package/dist/bundle-ops.js +4 -4
- package/dist/bundle-ops.js.map +1 -1
- package/dist/bundle.d.ts +3 -4
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +5 -14
- package/dist/bundle.js.map +1 -1
- package/dist/code-point-order.d.ts +8 -0
- package/dist/code-point-order.d.ts.map +1 -0
- package/dist/code-point-order.js +20 -0
- package/dist/code-point-order.js.map +1 -0
- package/dist/code-unit-order.d.ts +15 -0
- package/dist/code-unit-order.d.ts.map +1 -0
- package/dist/code-unit-order.js +17 -0
- package/dist/code-unit-order.js.map +1 -0
- package/dist/connector-request.d.ts +112 -0
- package/dist/connector-request.d.ts.map +1 -0
- package/dist/connector-request.js +495 -0
- package/dist/connector-request.js.map +1 -0
- package/dist/connectors.d.ts +297 -0
- package/dist/connectors.d.ts.map +1 -0
- package/dist/connectors.js +1233 -0
- package/dist/connectors.js.map +1 -0
- package/dist/cross-bundle.d.ts +135 -0
- package/dist/cross-bundle.d.ts.map +1 -0
- package/dist/cross-bundle.js +191 -0
- package/dist/cross-bundle.js.map +1 -0
- package/dist/dependencies.d.ts +94 -0
- package/dist/dependencies.d.ts.map +1 -0
- package/dist/dependencies.js +180 -0
- package/dist/dependencies.js.map +1 -0
- package/dist/document-codec.d.ts +7 -2
- package/dist/document-codec.d.ts.map +1 -1
- package/dist/document-codec.js +8 -5
- package/dist/document-codec.js.map +1 -1
- package/dist/document-field-actions.d.ts +31 -1
- package/dist/document-field-actions.d.ts.map +1 -1
- package/dist/document-field-actions.js +137 -34
- package/dist/document-field-actions.js.map +1 -1
- package/dist/document-mutation.d.ts +35 -13
- package/dist/document-mutation.d.ts.map +1 -1
- package/dist/document-mutation.js +124 -60
- package/dist/document-mutation.js.map +1 -1
- package/dist/document-transfer.d.ts +208 -0
- package/dist/document-transfer.d.ts.map +1 -0
- package/dist/document-transfer.js +928 -0
- package/dist/document-transfer.js.map +1 -0
- package/dist/document-write-policy.d.ts.map +1 -1
- package/dist/document-write-policy.js +4 -2
- package/dist/document-write-policy.js.map +1 -1
- package/dist/domain-model.d.ts +73 -0
- package/dist/domain-model.d.ts.map +1 -0
- package/dist/domain-model.js +169 -0
- package/dist/domain-model.js.map +1 -0
- package/dist/engine.d.ts +21 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +72 -14
- package/dist/engine.js.map +1 -1
- package/dist/file-journaled-backend-internal.d.ts +21 -0
- package/dist/file-journaled-backend-internal.d.ts.map +1 -0
- package/dist/file-journaled-backend-internal.js +12 -0
- package/dist/file-journaled-backend-internal.js.map +1 -0
- package/dist/file-journaled-backend.d.ts +160 -0
- package/dist/file-journaled-backend.d.ts.map +1 -0
- package/dist/file-journaled-backend.js +1345 -0
- package/dist/file-journaled-backend.js.map +1 -0
- package/dist/filesystem-host.d.ts +15 -0
- package/dist/filesystem-host.d.ts.map +1 -0
- package/dist/filesystem-host.js +20 -0
- package/dist/filesystem-host.js.map +1 -0
- package/dist/filesystem-identity.d.ts +97 -21
- package/dist/filesystem-identity.d.ts.map +1 -1
- package/dist/filesystem-identity.js +398 -119
- package/dist/filesystem-identity.js.map +1 -1
- package/dist/filesystem-lock.d.ts +16 -3
- package/dist/filesystem-lock.d.ts.map +1 -1
- package/dist/filesystem-lock.js +323 -77
- package/dist/filesystem-lock.js.map +1 -1
- package/dist/filesystem-push-role.d.ts +77 -0
- package/dist/filesystem-push-role.d.ts.map +1 -0
- package/dist/filesystem-push-role.js +165 -0
- package/dist/filesystem-push-role.js.map +1 -0
- package/dist/filesystem.d.ts +17 -0
- package/dist/filesystem.d.ts.map +1 -0
- package/dist/filesystem.js +18 -0
- package/dist/filesystem.js.map +1 -0
- package/dist/frontmatter.d.ts +8 -6
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +5 -4
- package/dist/frontmatter.js.map +1 -1
- package/dist/governed-body-write.d.ts +8 -1
- package/dist/governed-body-write.d.ts.map +1 -1
- package/dist/governed-body-write.js +9 -2
- package/dist/governed-body-write.js.map +1 -1
- package/dist/heads-digest.d.ts.map +1 -1
- package/dist/heads-digest.js +2 -1
- package/dist/heads-digest.js.map +1 -1
- package/dist/host-policy-snapshot.d.ts +9 -0
- package/dist/host-policy-snapshot.d.ts.map +1 -0
- package/dist/host-policy-snapshot.js +33 -0
- package/dist/host-policy-snapshot.js.map +1 -0
- package/dist/host-text.d.ts +31 -0
- package/dist/host-text.d.ts.map +1 -0
- package/dist/host-text.js +77 -0
- package/dist/host-text.js.map +1 -0
- package/dist/hosted-client/binding.d.ts +7 -0
- package/dist/hosted-client/binding.d.ts.map +1 -0
- package/dist/hosted-client/binding.js +10 -0
- package/dist/hosted-client/binding.js.map +1 -0
- package/dist/hosted-client/blob-delivery.d.ts +68 -0
- package/dist/hosted-client/blob-delivery.d.ts.map +1 -0
- package/dist/hosted-client/blob-delivery.js +170 -0
- package/dist/hosted-client/blob-delivery.js.map +1 -0
- package/dist/hosted-client/blob-write.d.ts +387 -0
- package/dist/hosted-client/blob-write.d.ts.map +1 -0
- package/dist/hosted-client/blob-write.js +770 -0
- package/dist/hosted-client/blob-write.js.map +1 -0
- package/dist/hosted-client/bundle-listing.d.ts +46 -0
- package/dist/hosted-client/bundle-listing.d.ts.map +1 -0
- package/dist/hosted-client/bundle-listing.js +36 -0
- package/dist/hosted-client/bundle-listing.js.map +1 -0
- package/dist/hosted-client/client.d.ts +219 -0
- package/dist/hosted-client/client.d.ts.map +1 -0
- package/dist/hosted-client/client.js +309 -0
- package/dist/hosted-client/client.js.map +1 -0
- package/dist/hosted-client/directory.d.ts +46 -0
- package/dist/hosted-client/directory.d.ts.map +1 -0
- package/dist/hosted-client/directory.js +74 -0
- package/dist/hosted-client/directory.js.map +1 -0
- package/dist/hosted-client/errors.d.ts +26 -0
- package/dist/hosted-client/errors.d.ts.map +1 -0
- package/dist/hosted-client/errors.js +35 -0
- package/dist/hosted-client/errors.js.map +1 -0
- package/dist/hosted-client/inbox.d.ts +320 -0
- package/dist/hosted-client/inbox.d.ts.map +1 -0
- package/dist/hosted-client/inbox.js +379 -0
- package/dist/hosted-client/inbox.js.map +1 -0
- package/dist/hosted-client/index.d.ts +30 -0
- package/dist/hosted-client/index.d.ts.map +1 -0
- package/dist/hosted-client/index.js +32 -0
- package/dist/hosted-client/index.js.map +1 -0
- package/dist/hosted-client/journal.d.ts +103 -0
- package/dist/hosted-client/journal.d.ts.map +1 -0
- package/dist/hosted-client/journal.js +464 -0
- package/dist/hosted-client/journal.js.map +1 -0
- package/dist/hosted-client/node.d.ts +74 -0
- package/dist/hosted-client/node.d.ts.map +1 -0
- package/dist/hosted-client/node.js +219 -0
- package/dist/hosted-client/node.js.map +1 -0
- package/dist/hosted-client/reference.d.ts +12 -0
- package/dist/hosted-client/reference.d.ts.map +1 -0
- package/dist/hosted-client/reference.js +29 -0
- package/dist/hosted-client/reference.js.map +1 -0
- package/dist/hosted-client/write.d.ts +217 -0
- package/dist/hosted-client/write.d.ts.map +1 -0
- package/dist/hosted-client/write.js +418 -0
- package/dist/hosted-client/write.js.map +1 -0
- package/dist/hosted-transport/answer-rows.d.ts +243 -0
- package/dist/hosted-transport/answer-rows.d.ts.map +1 -0
- package/dist/hosted-transport/answer-rows.js +457 -0
- package/dist/hosted-transport/answer-rows.js.map +1 -0
- package/dist/hosted-transport/blob-reads.d.ts +35 -0
- package/dist/hosted-transport/blob-reads.d.ts.map +1 -0
- package/dist/hosted-transport/blob-reads.js +179 -0
- package/dist/hosted-transport/blob-reads.js.map +1 -0
- package/dist/hosted-transport/blob-write.d.ts +34 -0
- package/dist/hosted-transport/blob-write.d.ts.map +1 -0
- package/dist/hosted-transport/blob-write.js +115 -0
- package/dist/hosted-transport/blob-write.js.map +1 -0
- package/dist/hosted-transport/carrier.d.ts +151 -0
- package/dist/hosted-transport/carrier.d.ts.map +1 -0
- package/dist/hosted-transport/carrier.js +260 -0
- package/dist/hosted-transport/carrier.js.map +1 -0
- package/dist/hosted-transport/history-pages.d.ts +55 -0
- package/dist/hosted-transport/history-pages.d.ts.map +1 -0
- package/dist/hosted-transport/history-pages.js +69 -0
- package/dist/hosted-transport/history-pages.js.map +1 -0
- package/dist/hosted-transport/index.d.ts +18 -0
- package/dist/hosted-transport/index.d.ts.map +1 -0
- package/dist/hosted-transport/index.js +18 -0
- package/dist/hosted-transport/index.js.map +1 -0
- package/dist/hosted-transport/operations.d.ts +85 -0
- package/dist/hosted-transport/operations.d.ts.map +1 -0
- package/dist/hosted-transport/operations.js +128 -0
- package/dist/hosted-transport/operations.js.map +1 -0
- package/dist/hosted-transport/paged-reads.d.ts +87 -0
- package/dist/hosted-transport/paged-reads.d.ts.map +1 -0
- package/dist/hosted-transport/paged-reads.js +238 -0
- package/dist/hosted-transport/paged-reads.js.map +1 -0
- package/dist/hosted-transport/read-adapter.d.ts +256 -0
- package/dist/hosted-transport/read-adapter.d.ts.map +1 -0
- package/dist/hosted-transport/read-adapter.js +568 -0
- package/dist/hosted-transport/read-adapter.js.map +1 -0
- package/dist/hosted-transport/root-write.d.ts +114 -0
- package/dist/hosted-transport/root-write.d.ts.map +1 -0
- package/dist/hosted-transport/root-write.js +171 -0
- package/dist/hosted-transport/root-write.js.map +1 -0
- package/dist/hosted-transport/whole-document-transport.d.ts +204 -0
- package/dist/hosted-transport/whole-document-transport.d.ts.map +1 -0
- package/dist/hosted-transport/whole-document-transport.js +447 -0
- package/dist/hosted-transport/whole-document-transport.js.map +1 -0
- package/dist/identity-fold.d.ts +18 -0
- package/dist/identity-fold.d.ts.map +1 -0
- package/dist/identity-fold.js +22 -0
- package/dist/identity-fold.js.map +1 -0
- package/dist/index.d.ts +41 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -9
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-backend.d.ts +25 -33
- package/dist/indexeddb-backend.d.ts.map +1 -1
- package/dist/indexeddb-backend.js +264 -8
- package/dist/indexeddb-backend.js.map +1 -1
- package/dist/inventory-guard.d.ts +58 -0
- package/dist/inventory-guard.d.ts.map +1 -0
- package/dist/inventory-guard.js +169 -0
- package/dist/inventory-guard.js.map +1 -0
- package/dist/journaled-backend.d.ts +149 -9
- package/dist/journaled-backend.d.ts.map +1 -1
- package/dist/journaled-backend.js +64 -58
- package/dist/journaled-backend.js.map +1 -1
- package/dist/kind-authoring.d.ts +170 -0
- package/dist/kind-authoring.d.ts.map +1 -0
- package/dist/kind-authoring.js +452 -0
- package/dist/kind-authoring.js.map +1 -0
- package/dist/kind-changes.d.ts +45 -0
- package/dist/kind-changes.d.ts.map +1 -0
- package/dist/kind-changes.js +96 -0
- package/dist/kind-changes.js.map +1 -0
- package/dist/kind-conformance-error.d.ts +15 -0
- package/dist/kind-conformance-error.d.ts.map +1 -0
- package/dist/kind-conformance-error.js +20 -0
- package/dist/kind-conformance-error.js.map +1 -0
- package/dist/kind-coverage.d.ts +99 -0
- package/dist/kind-coverage.d.ts.map +1 -0
- package/dist/kind-coverage.js +186 -0
- package/dist/kind-coverage.js.map +1 -0
- package/dist/kind-deprecation.d.ts +32 -0
- package/dist/kind-deprecation.d.ts.map +1 -0
- package/dist/kind-deprecation.js +141 -0
- package/dist/kind-deprecation.js.map +1 -0
- package/dist/kind-field-mutation.d.ts +4 -0
- package/dist/kind-field-mutation.d.ts.map +1 -1
- package/dist/kind-field-mutation.js +28 -3
- package/dist/kind-field-mutation.js.map +1 -1
- package/dist/kinds-load.js +4 -4
- package/dist/kinds-load.js.map +1 -1
- package/dist/kinds.d.ts +111 -2
- package/dist/kinds.d.ts.map +1 -1
- package/dist/kinds.js +386 -20
- package/dist/kinds.js.map +1 -1
- package/dist/link-emit.d.ts +29 -0
- package/dist/link-emit.d.ts.map +1 -0
- package/dist/link-emit.js +39 -0
- package/dist/link-emit.js.map +1 -0
- package/dist/link-types.d.ts +27 -0
- package/dist/link-types.d.ts.map +1 -0
- package/dist/link-types.js +60 -0
- package/dist/link-types.js.map +1 -0
- package/dist/links.d.ts +52 -3
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +259 -18
- package/dist/links.js.map +1 -1
- package/dist/managed-document-metadata.d.ts +2 -0
- package/dist/managed-document-metadata.d.ts.map +1 -0
- package/dist/managed-document-metadata.js +9 -0
- package/dist/managed-document-metadata.js.map +1 -0
- package/dist/memory-backend.d.ts +3 -0
- package/dist/memory-backend.d.ts.map +1 -1
- package/dist/memory-backend.js +44 -2
- package/dist/memory-backend.js.map +1 -1
- package/dist/okf-actor.d.ts +1 -0
- package/dist/okf-actor.d.ts.map +1 -1
- package/dist/okf-actor.js +1 -0
- package/dist/okf-actor.js.map +1 -1
- package/dist/okf-authoring.d.ts +4 -0
- package/dist/okf-authoring.d.ts.map +1 -0
- package/dist/okf-authoring.js +12 -0
- package/dist/okf-authoring.js.map +1 -0
- package/dist/okf-standard-fields.d.ts +3 -0
- package/dist/okf-standard-fields.d.ts.map +1 -1
- package/dist/okf-standard-fields.js +3 -0
- package/dist/okf-standard-fields.js.map +1 -1
- package/dist/path-fold.d.ts +39 -0
- package/dist/path-fold.d.ts.map +1 -0
- package/dist/path-fold.js +41 -0
- package/dist/path-fold.js.map +1 -0
- package/dist/paths.d.ts +18 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +32 -0
- package/dist/paths.js.map +1 -1
- package/dist/platform.d.ts +13 -2
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +16 -0
- package/dist/platform.js.map +1 -1
- package/dist/portable-export.d.ts +119 -0
- package/dist/portable-export.d.ts.map +1 -0
- package/dist/portable-export.js +501 -0
- package/dist/portable-export.js.map +1 -0
- package/dist/publication-filesystem.d.ts +1 -1
- package/dist/publication-filesystem.d.ts.map +1 -1
- package/dist/publication-filesystem.js +1 -1
- package/dist/publication-filesystem.js.map +1 -1
- package/dist/qualified-links.d.ts +67 -0
- package/dist/qualified-links.d.ts.map +1 -0
- package/dist/qualified-links.js +117 -0
- package/dist/qualified-links.js.map +1 -0
- package/dist/query-filter.d.ts +16 -2
- package/dist/query-filter.d.ts.map +1 -1
- package/dist/query-filter.js +40 -1
- package/dist/query-filter.js.map +1 -1
- package/dist/query-order.d.ts +22 -0
- package/dist/query-order.d.ts.map +1 -0
- package/dist/query-order.js +43 -0
- package/dist/query-order.js.map +1 -0
- package/dist/recipe-lifecycle/errors.d.ts +96 -0
- package/dist/recipe-lifecycle/errors.d.ts.map +1 -0
- package/dist/recipe-lifecycle/errors.js +122 -0
- package/dist/recipe-lifecycle/errors.js.map +1 -0
- package/dist/recipe-lifecycle/evolution.d.ts +124 -0
- package/dist/recipe-lifecycle/evolution.d.ts.map +1 -0
- package/dist/recipe-lifecycle/evolution.js +993 -0
- package/dist/recipe-lifecycle/evolution.js.map +1 -0
- package/dist/recipe-lifecycle/index.d.ts +34 -0
- package/dist/recipe-lifecycle/index.d.ts.map +1 -0
- package/dist/recipe-lifecycle/index.js +28 -0
- package/dist/recipe-lifecycle/index.js.map +1 -0
- package/dist/recipe-lifecycle/install.d.ts +155 -0
- package/dist/recipe-lifecycle/install.d.ts.map +1 -0
- package/dist/recipe-lifecycle/install.js +604 -0
- package/dist/recipe-lifecycle/install.js.map +1 -0
- package/dist/recipe-lifecycle/legacy-aliases.d.ts +16 -0
- package/dist/recipe-lifecycle/legacy-aliases.d.ts.map +1 -0
- package/dist/recipe-lifecycle/legacy-aliases.js +51 -0
- package/dist/recipe-lifecycle/legacy-aliases.js.map +1 -0
- package/dist/recipe-lifecycle/retirement-apply.d.ts +59 -0
- package/dist/recipe-lifecycle/retirement-apply.d.ts.map +1 -0
- package/dist/recipe-lifecycle/retirement-apply.js +165 -0
- package/dist/recipe-lifecycle/retirement-apply.js.map +1 -0
- package/dist/recipe-lifecycle/retirement.d.ts +89 -0
- package/dist/recipe-lifecycle/retirement.d.ts.map +1 -0
- package/dist/recipe-lifecycle/retirement.js +347 -0
- package/dist/recipe-lifecycle/retirement.js.map +1 -0
- package/dist/recipe-lifecycle/stamp.d.ts +151 -0
- package/dist/recipe-lifecycle/stamp.d.ts.map +1 -0
- package/dist/recipe-lifecycle/stamp.js +364 -0
- package/dist/recipe-lifecycle/stamp.js.map +1 -0
- package/dist/recipe-set-preflight.d.ts +139 -0
- package/dist/recipe-set-preflight.d.ts.map +1 -0
- package/dist/recipe-set-preflight.js +536 -0
- package/dist/recipe-set-preflight.js.map +1 -0
- package/dist/recipes.d.ts +29 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +137 -1
- package/dist/recipes.js.map +1 -1
- package/dist/record-overlay/apply.d.ts +43 -0
- package/dist/record-overlay/apply.d.ts.map +1 -0
- package/dist/record-overlay/apply.js +527 -0
- package/dist/record-overlay/apply.js.map +1 -0
- package/dist/record-overlay/canonical.d.ts +22 -0
- package/dist/record-overlay/canonical.d.ts.map +1 -0
- package/dist/record-overlay/canonical.js +47 -0
- package/dist/record-overlay/canonical.js.map +1 -0
- package/dist/record-overlay/digest.d.ts +4 -0
- package/dist/record-overlay/digest.d.ts.map +1 -0
- package/dist/record-overlay/digest.js +11 -0
- package/dist/record-overlay/digest.js.map +1 -0
- package/dist/record-overlay/errors.d.ts +44 -0
- package/dist/record-overlay/errors.d.ts.map +1 -0
- package/dist/record-overlay/errors.js +55 -0
- package/dist/record-overlay/errors.js.map +1 -0
- package/dist/record-overlay/findings.d.ts +5 -0
- package/dist/record-overlay/findings.d.ts.map +1 -0
- package/dist/record-overlay/findings.js +83 -0
- package/dist/record-overlay/findings.js.map +1 -0
- package/dist/record-overlay/index.d.ts +23 -0
- package/dist/record-overlay/index.d.ts.map +1 -0
- package/dist/record-overlay/index.js +21 -0
- package/dist/record-overlay/index.js.map +1 -0
- package/dist/record-overlay/plan.d.ts +12 -0
- package/dist/record-overlay/plan.d.ts.map +1 -0
- package/dist/record-overlay/plan.js +285 -0
- package/dist/record-overlay/plan.js.map +1 -0
- package/dist/record-overlay/policy.d.ts +74 -0
- package/dist/record-overlay/policy.d.ts.map +1 -0
- package/dist/record-overlay/policy.js +235 -0
- package/dist/record-overlay/policy.js.map +1 -0
- package/dist/record-overlay/redact.d.ts +3 -0
- package/dist/record-overlay/redact.d.ts.map +1 -0
- package/dist/record-overlay/redact.js +27 -0
- package/dist/record-overlay/redact.js.map +1 -0
- package/dist/record-overlay/signal-kind.d.ts +30 -0
- package/dist/record-overlay/signal-kind.d.ts.map +1 -0
- package/dist/record-overlay/signal-kind.js +38 -0
- package/dist/record-overlay/signal-kind.js.map +1 -0
- package/dist/record-overlay/signal-text.d.ts +45 -0
- package/dist/record-overlay/signal-text.d.ts.map +1 -0
- package/dist/record-overlay/signal-text.js +116 -0
- package/dist/record-overlay/signal-text.js.map +1 -0
- package/dist/record-overlay/source-index.d.ts +19 -0
- package/dist/record-overlay/source-index.d.ts.map +1 -0
- package/dist/record-overlay/source-index.js +102 -0
- package/dist/record-overlay/source-index.js.map +1 -0
- package/dist/record-overlay/types.d.ts +291 -0
- package/dist/record-overlay/types.d.ts.map +1 -0
- package/dist/record-overlay/types.js +2 -0
- package/dist/record-overlay/types.js.map +1 -0
- package/dist/remote-backend.d.ts +58 -13
- package/dist/remote-backend.d.ts.map +1 -1
- package/dist/remote-backend.js +156 -36
- package/dist/remote-backend.js.map +1 -1
- package/dist/remote-document-codec.d.ts +2 -1
- package/dist/remote-document-codec.d.ts.map +1 -1
- package/dist/remote-document-codec.js +3 -2
- package/dist/remote-document-codec.js.map +1 -1
- package/dist/remote-error.d.ts +17 -1
- package/dist/remote-error.d.ts.map +1 -1
- package/dist/remote-error.js +32 -2
- package/dist/remote-error.js.map +1 -1
- package/dist/remote-operations.d.ts +0 -40
- package/dist/remote-operations.d.ts.map +1 -1
- package/dist/remote-operations.js +26 -6
- package/dist/remote-operations.js.map +1 -1
- package/dist/remote-parsers.d.ts +1 -1
- package/dist/remote-parsers.js +1 -1
- package/dist/search/backend-source.d.ts +6 -0
- package/dist/search/backend-source.d.ts.map +1 -0
- package/dist/search/backend-source.js +33 -0
- package/dist/search/backend-source.js.map +1 -0
- package/dist/search/contract.d.ts +151 -0
- package/dist/search/contract.d.ts.map +1 -0
- package/dist/search/contract.js +78 -0
- package/dist/search/contract.js.map +1 -0
- package/dist/search/errors.d.ts +30 -0
- package/dist/search/errors.d.ts.map +1 -0
- package/dist/search/errors.js +50 -0
- package/dist/search/errors.js.map +1 -0
- package/dist/search/identity.d.ts +12 -0
- package/dist/search/identity.d.ts.map +1 -0
- package/dist/search/identity.js +90 -0
- package/dist/search/identity.js.map +1 -0
- package/dist/search/index.d.ts +16 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +15 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/memory-source.d.ts +36 -0
- package/dist/search/memory-source.d.ts.map +1 -0
- package/dist/search/memory-source.js +235 -0
- package/dist/search/memory-source.js.map +1 -0
- package/dist/search/rank.d.ts +34 -0
- package/dist/search/rank.d.ts.map +1 -0
- package/dist/search/rank.js +129 -0
- package/dist/search/rank.js.map +1 -0
- package/dist/search/row-text.d.ts +38 -0
- package/dist/search/row-text.d.ts.map +1 -0
- package/dist/search/row-text.js +88 -0
- package/dist/search/row-text.js.map +1 -0
- package/dist/search/search.d.ts +28 -0
- package/dist/search/search.d.ts.map +1 -0
- package/dist/search/search.js +257 -0
- package/dist/search/search.js.map +1 -0
- package/dist/search/source.d.ts +67 -0
- package/dist/search/source.d.ts.map +1 -0
- package/dist/search/source.js +2 -0
- package/dist/search/source.js.map +1 -0
- package/dist/search/text.d.ts +47 -0
- package/dist/search/text.d.ts.map +1 -0
- package/dist/search/text.js +150 -0
- package/dist/search/text.js.map +1 -0
- package/dist/site-capabilities.d.ts +345 -0
- package/dist/site-capabilities.d.ts.map +1 -0
- package/dist/site-capabilities.js +724 -0
- package/dist/site-capabilities.js.map +1 -0
- package/dist/source-engine/driver.d.ts +40 -0
- package/dist/source-engine/driver.d.ts.map +1 -0
- package/dist/source-engine/driver.js +392 -0
- package/dist/source-engine/driver.js.map +1 -0
- package/dist/source-engine/evidence.d.ts +39 -0
- package/dist/source-engine/evidence.d.ts.map +1 -0
- package/dist/source-engine/evidence.js +54 -0
- package/dist/source-engine/evidence.js.map +1 -0
- package/dist/source-engine/guard.d.ts +87 -0
- package/dist/source-engine/guard.d.ts.map +1 -0
- package/dist/source-engine/guard.js +427 -0
- package/dist/source-engine/guard.js.map +1 -0
- package/dist/source-engine/index.d.ts +33 -0
- package/dist/source-engine/index.d.ts.map +1 -0
- package/dist/source-engine/index.js +26 -0
- package/dist/source-engine/index.js.map +1 -0
- package/dist/source-engine/machine-state.d.ts +74 -0
- package/dist/source-engine/machine-state.d.ts.map +1 -0
- package/dist/source-engine/machine-state.js +48 -0
- package/dist/source-engine/machine-state.js.map +1 -0
- package/dist/source-engine/observe.d.ts +10 -0
- package/dist/source-engine/observe.d.ts.map +1 -0
- package/dist/source-engine/observe.js +270 -0
- package/dist/source-engine/observe.js.map +1 -0
- package/dist/source-engine/overlay.d.ts +50 -0
- package/dist/source-engine/overlay.d.ts.map +1 -0
- package/dist/source-engine/overlay.js +114 -0
- package/dist/source-engine/overlay.js.map +1 -0
- package/dist/source-engine/robots.d.ts +53 -0
- package/dist/source-engine/robots.d.ts.map +1 -0
- package/dist/source-engine/robots.js +158 -0
- package/dist/source-engine/robots.js.map +1 -0
- package/dist/source-engine/snapshot.d.ts +104 -0
- package/dist/source-engine/snapshot.d.ts.map +1 -0
- package/dist/source-engine/snapshot.js +240 -0
- package/dist/source-engine/snapshot.js.map +1 -0
- package/dist/source-engine/types.d.ts +292 -0
- package/dist/source-engine/types.d.ts.map +1 -0
- package/dist/source-engine/types.js +2 -0
- package/dist/source-engine/types.js.map +1 -0
- package/dist/source-harness/conformance.d.ts +16 -0
- package/dist/source-harness/conformance.d.ts.map +1 -0
- package/dist/source-harness/conformance.js +266 -0
- package/dist/source-harness/conformance.js.map +1 -0
- package/dist/source-harness/fixture.d.ts +97 -0
- package/dist/source-harness/fixture.d.ts.map +1 -0
- package/dist/source-harness/fixture.js +276 -0
- package/dist/source-harness/fixture.js.map +1 -0
- package/dist/source-harness/index.d.ts +22 -0
- package/dist/source-harness/index.d.ts.map +1 -0
- package/dist/source-harness/index.js +19 -0
- package/dist/source-harness/index.js.map +1 -0
- package/dist/source-harness/peer.d.ts +74 -0
- package/dist/source-harness/peer.d.ts.map +1 -0
- package/dist/source-harness/peer.js +106 -0
- package/dist/source-harness/peer.js.map +1 -0
- package/dist/source-profiles/index.d.ts +31 -0
- package/dist/source-profiles/index.d.ts.map +1 -0
- package/dist/source-profiles/index.js +206 -0
- package/dist/source-profiles/index.js.map +1 -0
- package/dist/source-profiles/link-header.d.ts +6 -0
- package/dist/source-profiles/link-header.d.ts.map +1 -0
- package/dist/source-profiles/link-header.js +131 -0
- package/dist/source-profiles/link-header.js.map +1 -0
- package/dist/storage.d.ts +9 -2
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +6 -1
- package/dist/storage.js.map +1 -1
- package/dist/string-identity-agreement.d.ts +35 -0
- package/dist/string-identity-agreement.d.ts.map +1 -0
- package/dist/string-identity-agreement.js +72 -0
- package/dist/string-identity-agreement.js.map +1 -0
- package/dist/types.d.ts +41 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -10
- package/dist/types.js.map +1 -1
- package/dist/uncertain-write.d.ts +157 -17
- package/dist/uncertain-write.d.ts.map +1 -1
- package/dist/uncertain-write.js +86 -30
- package/dist/uncertain-write.js.map +1 -1
- package/dist/versioning.d.ts +9 -4
- package/dist/versioning.d.ts.map +1 -1
- package/dist/versioning.js +9 -4
- package/dist/versioning.js.map +1 -1
- package/dist/view-launch-contract.d.ts +14 -0
- package/dist/view-launch-contract.d.ts.map +1 -0
- package/dist/view-launch-contract.js +23 -0
- package/dist/view-launch-contract.js.map +1 -0
- package/dist/write-identity.d.ts +5 -0
- package/dist/write-identity.d.ts.map +1 -0
- package/dist/write-identity.js +9 -0
- package/dist/write-identity.js.map +1 -0
- package/package.json +140 -3
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document transfer: copy chosen documents from one bundle into another with ids remapped, links
|
|
3
|
+
* rewritten, provenance extended and one Promotion record of the act.
|
|
4
|
+
*
|
|
5
|
+
* Plan and apply share one computation. A plan reads every source document, every governing
|
|
6
|
+
* convention, the target's `docs/dependencies` and the existence of every target id once, and
|
|
7
|
+
* binds all of it into a plan token; apply recomputes the plan from fresh reads and refuses
|
|
8
|
+
* unless the token is equal, so what applies is exactly what was shown.
|
|
9
|
+
*
|
|
10
|
+
* There is no multi-document transaction on any local backend, so apply never claims
|
|
11
|
+
* all-or-nothing. It writes the Promotion record first (`promotion_state: applying`, with the
|
|
12
|
+
* whole mapping) as the journal, then creates every document with an expect-absent write, then
|
|
13
|
+
* marks the record `applied`. A failed write stops the apply and leaves the record at `applying`;
|
|
14
|
+
* {@link resumeDocumentTransfer} is the one recovery path. Nothing here deletes or overwrites a
|
|
15
|
+
* document.
|
|
16
|
+
*
|
|
17
|
+
* v1 caveats: resume writes the remaining documents even if the target has since dropped a Kind
|
|
18
|
+
* that governed them. Reference-style link definitions (`[x]: ../f.md`) are copied unchanged and
|
|
19
|
+
* reported as `reference_definition`, so the caller sees links that may not resolve in the target.
|
|
20
|
+
*
|
|
21
|
+
* Storage-neutral: the source is a {@link BundleBinding} (any backend), the target is written
|
|
22
|
+
* through the shared document mutation policy, and no Node module is imported.
|
|
23
|
+
*/
|
|
24
|
+
import { backendFor, readBundleOkfVersion, readDocVersioned } from "./bundle-ops.js";
|
|
25
|
+
import { BUNDLE_NAME_DOC_ID, declaredBundleId, DEPENDENCIES_DOC_ID, parseDependencies, } from "./dependencies.js";
|
|
26
|
+
import { DocumentNotFoundError, mutateDocument, prepareDocumentMutationCandidate } from "./document-mutation.js";
|
|
27
|
+
import { readManyExisting } from "./engine.js";
|
|
28
|
+
import { FilesystemIdentityAliasError, InvalidInputError, OkfActorError } from "./errors.js";
|
|
29
|
+
import { foldIdentityPath } from "./identity-fold.js";
|
|
30
|
+
import { loadKinds } from "./kinds-load.js";
|
|
31
|
+
import { CONVENTIONS_PREFIX } from "./kinds.js";
|
|
32
|
+
import { emitLinkHref } from "./link-emit.js";
|
|
33
|
+
import { extractMarkdownLinkSpans, maskMarkdownCode, normalizeSegments, resolveConceptId, rewriteMarkdownLinks } from "./links.js";
|
|
34
|
+
import { isOkfActor } from "./okf-actor.js";
|
|
35
|
+
import { assertSafeConceptId, compareStorageKeys, isReservedFile, pathFromConceptId, trimTrailingSlashes } from "./paths.js";
|
|
36
|
+
import { isQualifiedHref, parseQualifiedHref, qualifiedHref } from "./qualified-links.js";
|
|
37
|
+
import { sha256HexOfUtf8 } from "./sha256.js";
|
|
38
|
+
import { VersionConflict } from "./versioning.js";
|
|
39
|
+
export const DOCUMENT_TRANSFER_CONTRACT = "document-transfer/v1";
|
|
40
|
+
/** The `type` of the record each transfer writes. Not the `promote` command, which copies one file. */
|
|
41
|
+
export const PROMOTION_TYPE = "Promotion";
|
|
42
|
+
export const PROMOTIONS_PREFIX = "promotions/";
|
|
43
|
+
// ── Typed failures ─────────────────────────────────────────────────────────
|
|
44
|
+
/** Apply was asked for a plan that has conflicts. Nothing was written. */
|
|
45
|
+
export class TransferConflictError extends Error {
|
|
46
|
+
conflicts;
|
|
47
|
+
constructor(conflicts) {
|
|
48
|
+
const first = conflicts[0];
|
|
49
|
+
super(`the import plan has ${conflicts.length} conflict${conflicts.length === 1 ? "" : "s"}${first ? `; first: ${first.code}: ${first.detail}` : ""}. Nothing was written.`);
|
|
50
|
+
this.name = "TransferConflictError";
|
|
51
|
+
this.conflicts = conflicts;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** The plan recomputed at apply differs from the token the caller presented. Nothing was written. */
|
|
55
|
+
export class TransferPlanChanged extends Error {
|
|
56
|
+
expected;
|
|
57
|
+
current;
|
|
58
|
+
constructor(expected, current) {
|
|
59
|
+
super(`the import plan changed since it was shown (expected ${expected}, current ${current}). Nothing was written; inspect a fresh plan.`);
|
|
60
|
+
this.name = "TransferPlanChanged";
|
|
61
|
+
this.expected = expected;
|
|
62
|
+
this.current = current;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A write failed after the Promotion record was created. The record stays at `applying`; the
|
|
67
|
+
* listed documents were written and the pending ones were not. Resume the record to finish.
|
|
68
|
+
*/
|
|
69
|
+
export class TransferInterrupted extends Error {
|
|
70
|
+
recordId;
|
|
71
|
+
written;
|
|
72
|
+
pending;
|
|
73
|
+
cause;
|
|
74
|
+
constructor(recordId, written, pending, cause) {
|
|
75
|
+
const why = cause instanceof Error ? cause.message : String(cause);
|
|
76
|
+
super(`the import stopped after writing ${written.length} document${written.length === 1 ? "" : "s"}` +
|
|
77
|
+
`${pending.length > 0 ? ` (${pending.length} not written, first '${pending[0].target}')` : ""}: ${why}. ` +
|
|
78
|
+
`'${recordId}' stays at promotion_state applying; resume it to finish.`);
|
|
79
|
+
this.name = "TransferInterrupted";
|
|
80
|
+
this.recordId = recordId;
|
|
81
|
+
this.written = written;
|
|
82
|
+
this.pending = pending;
|
|
83
|
+
this.cause = cause;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Resume met a record, source or target it cannot finish safely. Nothing further was written. */
|
|
87
|
+
export class TransferResumeConflict extends Error {
|
|
88
|
+
recordId;
|
|
89
|
+
id;
|
|
90
|
+
written;
|
|
91
|
+
constructor(recordId, detail, options = {}) {
|
|
92
|
+
super(`cannot resume '${recordId}': ${detail}`);
|
|
93
|
+
this.name = "TransferResumeConflict";
|
|
94
|
+
this.recordId = recordId;
|
|
95
|
+
if (options.id !== undefined)
|
|
96
|
+
this.id = options.id;
|
|
97
|
+
this.written = options.written ?? [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// ── Snapshots ──────────────────────────────────────────────────────────────
|
|
101
|
+
/** Documents that describe the source bundle itself; they never transfer. */
|
|
102
|
+
const SOURCE_SELF_DOCS = new Set([BUNDLE_NAME_DOC_ID, DEPENDENCIES_DOC_ID]);
|
|
103
|
+
function isAbsent(error) {
|
|
104
|
+
return error?.code === "ENOENT";
|
|
105
|
+
}
|
|
106
|
+
async function readOptional(bundle, id) {
|
|
107
|
+
try {
|
|
108
|
+
return await readDocVersioned(bundle, id);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (isAbsent(error))
|
|
112
|
+
return null;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function readSource(binding) {
|
|
117
|
+
const bundle = { root: binding.label, backend: binding.backend };
|
|
118
|
+
const listed = await binding.backend.list();
|
|
119
|
+
const malformed = new Map();
|
|
120
|
+
const results = await readManyExisting(binding.backend, listed, (skip) => malformed.set(skip.id, skip.reason));
|
|
121
|
+
const docs = new Map(results.map((result) => [result.doc.id, result]));
|
|
122
|
+
const ids = new Set([...docs.keys(), ...malformed.keys()]);
|
|
123
|
+
const dependencies = parseDependencies(docs.get(DEPENDENCIES_DOC_ID)?.doc ?? null, { selfBundleId: binding.identity });
|
|
124
|
+
const folds = new Set([...ids].map(foldIdentityPath));
|
|
125
|
+
return { ids, folds, docs, malformed, dependencies, registry: await loadKinds(bundle), bundle };
|
|
126
|
+
}
|
|
127
|
+
async function readTarget(target) {
|
|
128
|
+
const edition = (await readBundleOkfVersion(target)) ?? "0.1";
|
|
129
|
+
if (edition !== "0.1" && edition !== "0.2") {
|
|
130
|
+
throw new InvalidInputError(`Unsupported OKF version '${edition}' in the target bundle. This build can write 0.1 and 0.2 bundles.`);
|
|
131
|
+
}
|
|
132
|
+
const named = await readOptional(target, BUNDLE_NAME_DOC_ID);
|
|
133
|
+
const bundleId = declaredBundleId(named?.doc);
|
|
134
|
+
const deps = await readOptional(target, DEPENDENCIES_DOC_ID);
|
|
135
|
+
const ids = new Set(await backendFor(target).list());
|
|
136
|
+
const snapshot = {
|
|
137
|
+
okfVersion: edition,
|
|
138
|
+
dependencies: parseDependencies(deps?.doc ?? null, { selfBundleId: bundleId }),
|
|
139
|
+
dependenciesVersion: deps?.version ?? null,
|
|
140
|
+
ids,
|
|
141
|
+
registry: await loadKinds(target),
|
|
142
|
+
};
|
|
143
|
+
if (bundleId !== undefined)
|
|
144
|
+
snapshot.bundleId = bundleId;
|
|
145
|
+
return snapshot;
|
|
146
|
+
}
|
|
147
|
+
// ── Link rewrite ───────────────────────────────────────────────────────────
|
|
148
|
+
const ANY_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
149
|
+
function folderOf(id) {
|
|
150
|
+
const slash = id.lastIndexOf("/");
|
|
151
|
+
return slash >= 0 ? id.slice(0, slash) : "";
|
|
152
|
+
}
|
|
153
|
+
function targetAliasFor(dependencies, bundleId) {
|
|
154
|
+
for (const entry of dependencies.dependencies.values())
|
|
155
|
+
if (entry.bundle_id === bundleId)
|
|
156
|
+
return entry.alias;
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
/** Decide one link of `sourceId` (written at `targetId`). Pushes a conflict when the link cannot be carried. */
|
|
160
|
+
function planLink(ctx, sourceId, targetId, span, conflicts) {
|
|
161
|
+
const href = span.href;
|
|
162
|
+
const base = { offset: span.hrefStart, from_href: href };
|
|
163
|
+
const keep = (reason, extra = {}) => ({ ...base, to_href: href, kind: "unchanged", reason, ...extra });
|
|
164
|
+
if (isQualifiedHref(href)) {
|
|
165
|
+
if (span.image)
|
|
166
|
+
return keep("image");
|
|
167
|
+
const parsed = parseQualifiedHref(href);
|
|
168
|
+
if (!parsed.ok)
|
|
169
|
+
return keep("invalid_qualified");
|
|
170
|
+
const declared = ctx.source.dependencies.dependencies.get(parsed.ref.alias);
|
|
171
|
+
if (!declared) {
|
|
172
|
+
conflicts.push({ code: "qualified_alias_mismatch", source: sourceId, target: targetId, detail: `'${href}' uses alias '${parsed.ref.alias}', which the source bundle does not declare` });
|
|
173
|
+
return keep("already_qualified");
|
|
174
|
+
}
|
|
175
|
+
if (ctx.target.bundleId !== undefined && declared.bundle_id === ctx.target.bundleId) {
|
|
176
|
+
try {
|
|
177
|
+
const to = emitLinkHref({ kind: "local", from: targetId, to: parsed.ref.id, ...(parsed.fragment === undefined ? {} : { fragment: parsed.fragment }) });
|
|
178
|
+
return { ...base, to_href: to, kind: "dequalified" };
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (!(error instanceof InvalidInputError))
|
|
182
|
+
throw error;
|
|
183
|
+
return keep("not_qualifiable");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const alias = targetAliasFor(ctx.target.dependencies, declared.bundle_id);
|
|
187
|
+
if (alias === undefined) {
|
|
188
|
+
conflicts.push({ code: "qualified_alias_mismatch", source: sourceId, target: targetId, detail: `'${href}' points into '${declared.bundle_id}', which the target bundle does not declare` });
|
|
189
|
+
return keep("already_qualified");
|
|
190
|
+
}
|
|
191
|
+
if (alias === parsed.ref.alias)
|
|
192
|
+
return keep("already_qualified");
|
|
193
|
+
const to = emitLinkHref({ kind: "qualified", alias, id: parsed.ref.id, ...(parsed.fragment === undefined ? {} : { fragment: parsed.fragment }) });
|
|
194
|
+
return { ...base, to_href: to, kind: "qualified" };
|
|
195
|
+
}
|
|
196
|
+
if (ANY_SCHEME.test(href) || href.startsWith("//"))
|
|
197
|
+
return keep("external");
|
|
198
|
+
if (href.startsWith("#"))
|
|
199
|
+
return keep("anchor");
|
|
200
|
+
if (span.image)
|
|
201
|
+
return keep("image");
|
|
202
|
+
const hash = href.indexOf("#");
|
|
203
|
+
const pathPart = hash < 0 ? href : href.slice(0, hash);
|
|
204
|
+
const fragment = hash < 0 ? undefined : href.slice(hash + 1);
|
|
205
|
+
if (pathPart.includes("?"))
|
|
206
|
+
return keep("has_query");
|
|
207
|
+
if (!pathPart.endsWith(".md"))
|
|
208
|
+
return keep("non_document");
|
|
209
|
+
// Normalize against the source document's folder before any stripping: a link that climbs
|
|
210
|
+
// above the root has no target in the source bundle, so no id is guessed for it.
|
|
211
|
+
const segments = pathPart.startsWith("/")
|
|
212
|
+
? normalizeSegments(pathPart.slice(1).split("/"))
|
|
213
|
+
: normalizeSegments([...folderOf(sourceId).split("/"), ...pathPart.split("/")]);
|
|
214
|
+
if (segments[0] === "..")
|
|
215
|
+
return keep("escapes_source_root");
|
|
216
|
+
if (isReservedFile(segments.join("/")))
|
|
217
|
+
return keep("non_document");
|
|
218
|
+
const resolved = resolveConceptId(sourceId, pathPart);
|
|
219
|
+
if (resolved === null)
|
|
220
|
+
return keep("non_document");
|
|
221
|
+
const present = ctx.source.ids.has(resolved);
|
|
222
|
+
// A spelling variant of a source id names no document under the exact-identity rule; qualifying
|
|
223
|
+
// it would send a reader to an id the source bundle refuses to serve.
|
|
224
|
+
if (!present && ctx.source.folds.has(foldIdentityPath(resolved)))
|
|
225
|
+
return keep("case_variant", { source_target: resolved });
|
|
226
|
+
const withFragment = fragment === undefined ? {} : { fragment };
|
|
227
|
+
const mapped = ctx.mapping.get(resolved);
|
|
228
|
+
if (mapped !== undefined) {
|
|
229
|
+
try {
|
|
230
|
+
const to = emitLinkHref({ kind: "local", from: targetId, to: mapped, ...withFragment });
|
|
231
|
+
return { ...base, to_href: to, kind: "local", source_target: resolved, target_present_in_source: true };
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
if (!(error instanceof InvalidInputError))
|
|
235
|
+
throw error;
|
|
236
|
+
return keep("not_qualifiable", { source_target: resolved, target_present_in_source: true });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const to = emitLinkHref({ kind: "qualified", alias: ctx.alias, id: resolved, ...withFragment });
|
|
241
|
+
return { ...base, to_href: to, kind: "qualified", source_target: resolved, target_present_in_source: present };
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
if (!(error instanceof InvalidInputError))
|
|
245
|
+
throw error;
|
|
246
|
+
return keep("not_qualifiable", { source_target: resolved, target_present_in_source: present });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const REFERENCE_DEFINITION = /^ {0,3}\[[^\]\n]+\]:[ \t]*<?([^\s>]+)/gm;
|
|
250
|
+
/** Reference-style link definitions outside code, which v1 copies unchanged and only reports. */
|
|
251
|
+
function referenceDefinitions(body) {
|
|
252
|
+
const masked = maskMarkdownCode(body);
|
|
253
|
+
const out = [];
|
|
254
|
+
for (const match of masked.matchAll(REFERENCE_DEFINITION)) {
|
|
255
|
+
const href = match[1];
|
|
256
|
+
const offset = match.index + match[0].length - href.length;
|
|
257
|
+
out.push({ offset, href: body.slice(offset, offset + href.length) });
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
/** Rewrite every link of one body. Links inside code are reported as `in_code` and left alone. */
|
|
262
|
+
function rewriteTransferBody(ctx, sourceId, targetId, body, conflicts) {
|
|
263
|
+
const spans = extractMarkdownLinkSpans(body, { skipCode: true });
|
|
264
|
+
const live = new Set(spans.map((span) => span.hrefStart));
|
|
265
|
+
const rewrites = [];
|
|
266
|
+
for (const span of extractMarkdownLinkSpans(body)) {
|
|
267
|
+
if (!live.has(span.hrefStart)) {
|
|
268
|
+
rewrites.push({ offset: span.hrefStart, from_href: span.href, to_href: span.href, kind: "unchanged", reason: "in_code" });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const byOffset = new Map();
|
|
272
|
+
for (const span of spans) {
|
|
273
|
+
const rewrite = planLink(ctx, sourceId, targetId, span, conflicts);
|
|
274
|
+
byOffset.set(span.hrefStart, rewrite);
|
|
275
|
+
rewrites.push(rewrite);
|
|
276
|
+
}
|
|
277
|
+
for (const definition of referenceDefinitions(body)) {
|
|
278
|
+
rewrites.push({ offset: definition.offset, from_href: definition.href, to_href: definition.href, kind: "unchanged", reason: "reference_definition" });
|
|
279
|
+
}
|
|
280
|
+
rewrites.sort((a, b) => a.offset - b.offset);
|
|
281
|
+
const next = rewriteMarkdownLinks(body, spans, (span) => {
|
|
282
|
+
const rewrite = byOffset.get(span.hrefStart);
|
|
283
|
+
return rewrite.kind === "unchanged" ? undefined : rewrite.to_href;
|
|
284
|
+
});
|
|
285
|
+
return { body: next, rewrites };
|
|
286
|
+
}
|
|
287
|
+
function emptyRewriteCounts() {
|
|
288
|
+
return { local: 0, qualified: 0, dequalified: 0, unchanged: 0 };
|
|
289
|
+
}
|
|
290
|
+
function idMentions(value, selected, out) {
|
|
291
|
+
if (typeof value === "string") {
|
|
292
|
+
if (selected.has(value))
|
|
293
|
+
out.add(value);
|
|
294
|
+
}
|
|
295
|
+
else if (Array.isArray(value)) {
|
|
296
|
+
for (const item of value)
|
|
297
|
+
idMentions(item, selected, out);
|
|
298
|
+
}
|
|
299
|
+
else if (value !== null && typeof value === "object" && !(value instanceof Date)) {
|
|
300
|
+
for (const item of Object.values(value))
|
|
301
|
+
idMentions(item, selected, out);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function sourcesEntryTitle(bundleId, version) {
|
|
305
|
+
return `Imported from ${bundleId} at ${version}`;
|
|
306
|
+
}
|
|
307
|
+
function hashOf(value) {
|
|
308
|
+
return sha256HexOfUtf8(JSON.stringify(value));
|
|
309
|
+
}
|
|
310
|
+
function defaultRecordId(alias, bundleId, prefix, ids) {
|
|
311
|
+
const digest = hashOf({ bundle_id: bundleId, prefix: prefix ?? null, ids: [...ids].sort(compareStorageKeys) }).slice(0, 12);
|
|
312
|
+
return `${PROMOTIONS_PREFIX}${alias}-${digest}`;
|
|
313
|
+
}
|
|
314
|
+
function normalizePrefix(prefix) {
|
|
315
|
+
if (prefix === undefined)
|
|
316
|
+
return undefined;
|
|
317
|
+
const trimmed = trimTrailingSlashes(prefix.trim());
|
|
318
|
+
return trimmed === "" ? undefined : trimmed;
|
|
319
|
+
}
|
|
320
|
+
/** Every planned id must be safe, absent, unique and distinct under the filesystem identity fold. */
|
|
321
|
+
const PROTECTED_TARGET_FOLDS = new Set([BUNDLE_NAME_DOC_ID, DEPENDENCIES_DOC_ID].map(foldIdentityPath));
|
|
322
|
+
const CONVENTIONS_FOLD = `${foldIdentityPath(CONVENTIONS_PREFIX.replace(/\/$/, ""))}/`;
|
|
323
|
+
/**
|
|
324
|
+
* Throw {@link InvalidInputError} unless an import may create `id`: a safe, unreserved concept id
|
|
325
|
+
* that is not the target's own identity or dependency declaration and not a Kind convention
|
|
326
|
+
* (installing Kinds is not part of v1). Compared under the identity fold, so a case or
|
|
327
|
+
* normalization variant of a protected id is refused too.
|
|
328
|
+
*/
|
|
329
|
+
function assertImportableTargetId(id) {
|
|
330
|
+
assertSafeConceptId(id);
|
|
331
|
+
if (isReservedFile(pathFromConceptId(id)))
|
|
332
|
+
throw new InvalidInputError("index.md and log.md are reserved files, not documents");
|
|
333
|
+
const fold = foldIdentityPath(id);
|
|
334
|
+
if (PROTECTED_TARGET_FOLDS.has(fold))
|
|
335
|
+
throw new InvalidInputError("it is the bundle's own identity or dependency declaration");
|
|
336
|
+
if (fold.startsWith(CONVENTIONS_FOLD))
|
|
337
|
+
throw new InvalidInputError("an import never installs a Kind convention");
|
|
338
|
+
}
|
|
339
|
+
function checkTargetIds(planned, existing, conflicts) {
|
|
340
|
+
const folds = new Map();
|
|
341
|
+
const remember = (id) => {
|
|
342
|
+
const keys = [[`${foldIdentityPath(id)}.md`, `${id}.md`]];
|
|
343
|
+
const segments = id.split("/");
|
|
344
|
+
for (let i = 1; i < segments.length; i++) {
|
|
345
|
+
const dir = segments.slice(0, i).join("/");
|
|
346
|
+
keys.push([`${foldIdentityPath(dir)}/`, `${dir}/`]);
|
|
347
|
+
}
|
|
348
|
+
return keys;
|
|
349
|
+
};
|
|
350
|
+
for (const id of existing)
|
|
351
|
+
for (const [fold, exact] of remember(id))
|
|
352
|
+
if (!folds.has(fold))
|
|
353
|
+
folds.set(fold, exact);
|
|
354
|
+
const seen = new Set();
|
|
355
|
+
for (const row of planned) {
|
|
356
|
+
const where = { ...(row.source === undefined ? {} : { source: row.source }), target: row.target };
|
|
357
|
+
try {
|
|
358
|
+
assertImportableTargetId(row.target);
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
if (!(error instanceof InvalidInputError))
|
|
362
|
+
throw error;
|
|
363
|
+
conflicts.push({ code: "unsafe_target_id", ...where, detail: `'${row.target}' is not a usable document id: ${error.message}` });
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (existing.has(row.target)) {
|
|
367
|
+
conflicts.push(row.record
|
|
368
|
+
? { code: "record_exists", target: row.target, detail: `the Promotion record '${row.target}' already exists` }
|
|
369
|
+
: { code: "target_exists", ...where, detail: `'${row.target}' already exists in the target bundle; import never overwrites` });
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (seen.has(row.target)) {
|
|
373
|
+
conflicts.push({ code: "duplicate_target", ...where, detail: `two documents map to '${row.target}'` });
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
seen.add(row.target);
|
|
377
|
+
let collided = false;
|
|
378
|
+
for (const [fold, exact] of remember(row.target)) {
|
|
379
|
+
const prior = folds.get(fold);
|
|
380
|
+
if (prior !== undefined && prior !== exact && !collided) {
|
|
381
|
+
conflicts.push({ code: "case_collision", ...where, detail: `'${exact}' and '${prior}' name one file on a case- or normalization-insensitive filesystem` });
|
|
382
|
+
collided = true;
|
|
383
|
+
}
|
|
384
|
+
if (prior === undefined)
|
|
385
|
+
folds.set(fold, exact);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function recordFrontmatter(plan, prefix, state, token, targetVersions) {
|
|
390
|
+
const n = plan.entries.length;
|
|
391
|
+
const frontmatter = {
|
|
392
|
+
type: PROMOTION_TYPE,
|
|
393
|
+
title: `Import of ${n} document${n === 1 ? "" : "s"} from ${plan.from.alias}`,
|
|
394
|
+
description: `${n} document${n === 1 ? "" : "s"} from ${plan.from.bundle_id} into ${prefix ?? "the same ids"}, ${plan.counts.qualified} link${plan.counts.qualified === 1 ? "" : "s"} qualified.`,
|
|
395
|
+
promotion_state: state,
|
|
396
|
+
from_alias: plan.from.alias,
|
|
397
|
+
from_bundle_id: plan.from.bundle_id,
|
|
398
|
+
from_label: plan.from.label,
|
|
399
|
+
};
|
|
400
|
+
if (plan.from.pin)
|
|
401
|
+
frontmatter.from_pin = plan.from.pin.value;
|
|
402
|
+
if (prefix !== undefined)
|
|
403
|
+
frontmatter.target_prefix = prefix;
|
|
404
|
+
if (token !== undefined)
|
|
405
|
+
frontmatter.plan_token = token;
|
|
406
|
+
frontmatter.counts = { ...plan.counts };
|
|
407
|
+
frontmatter.left_behind = { ...plan.left_behind };
|
|
408
|
+
frontmatter.entries = plan.entries.map((entry) => {
|
|
409
|
+
const row = { source: entry.source, source_version: entry.source_version, target: entry.target };
|
|
410
|
+
const version = targetVersions.get(entry.target);
|
|
411
|
+
if (version !== undefined)
|
|
412
|
+
row.target_version = version;
|
|
413
|
+
return row;
|
|
414
|
+
});
|
|
415
|
+
return frontmatter;
|
|
416
|
+
}
|
|
417
|
+
function recordBody(recordId, plan) {
|
|
418
|
+
const lines = [
|
|
419
|
+
`Imported ${plan.entries.length} document${plan.entries.length === 1 ? "" : "s"} from the bundle ` +
|
|
420
|
+
`\`${plan.from.bundle_id}\` (alias \`${plan.from.alias}\`). This record is the journal of the import: ` +
|
|
421
|
+
"`promotion_state` stays `applying` until every document is written, and resuming this record " +
|
|
422
|
+
"finishes an interrupted import. Each line links the imported document and its origin.",
|
|
423
|
+
"",
|
|
424
|
+
];
|
|
425
|
+
for (const entry of plan.entries) {
|
|
426
|
+
const parts = [];
|
|
427
|
+
try {
|
|
428
|
+
parts.push(`[imported](${emitLinkHref({ kind: "local", from: recordId, to: entry.target })})`);
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
if (!(error instanceof InvalidInputError))
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
parts.push(`[from](${emitLinkHref({ kind: "qualified", alias: plan.from.alias, id: entry.source })})`);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
if (!(error instanceof InvalidInputError))
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
lines.push(`- \`${entry.source}\` ${parts.join(" ")}`.trimEnd());
|
|
442
|
+
}
|
|
443
|
+
return `${lines.join("\n")}\n`;
|
|
444
|
+
}
|
|
445
|
+
/** Rewrite, provenance and validation for one selected document. Shared by plan and resume. */
|
|
446
|
+
function buildEntry(ctx, fromBundleId, recordId, read, targetId, selected, allowUngoverned, actor, now, conflicts) {
|
|
447
|
+
const { doc, version } = read;
|
|
448
|
+
const type = String(doc.frontmatter.type ?? "");
|
|
449
|
+
const { body, rewrites } = rewriteTransferBody(ctx, doc.id, targetId, doc.body, conflicts);
|
|
450
|
+
const counts = emptyRewriteCounts();
|
|
451
|
+
for (const rewrite of rewrites)
|
|
452
|
+
counts[rewrite.kind]++;
|
|
453
|
+
const mentions = new Set();
|
|
454
|
+
idMentions(doc.frontmatter, selected, mentions);
|
|
455
|
+
const frontmatter = structuredClone(doc.frontmatter);
|
|
456
|
+
let resource;
|
|
457
|
+
try {
|
|
458
|
+
resource = qualifiedHref({ alias: ctx.alias, id: doc.id });
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
if (!(error instanceof InvalidInputError))
|
|
462
|
+
throw error;
|
|
463
|
+
resource = `okf://${ctx.alias}/${doc.id}.md`;
|
|
464
|
+
conflicts.push({ code: "source_malformed", source: doc.id, target: targetId, detail: `'${doc.id}' cannot be named by a cross-bundle href: ${error.message}` });
|
|
465
|
+
}
|
|
466
|
+
const provenance = { resource, id: recordId, title: sourcesEntryTitle(fromBundleId, version) };
|
|
467
|
+
if (frontmatter.sources === undefined)
|
|
468
|
+
frontmatter.sources = [provenance];
|
|
469
|
+
else if (Array.isArray(frontmatter.sources))
|
|
470
|
+
frontmatter.sources = [...frontmatter.sources, provenance];
|
|
471
|
+
else
|
|
472
|
+
conflicts.push({ code: "source_malformed", source: doc.id, target: targetId, detail: "its sources field is not a list, so no provenance entry can be added" });
|
|
473
|
+
const sourceGoverned = ctx.source.registry.kinds.has(type);
|
|
474
|
+
const targetGoverned = ctx.target.registry.kinds.has(type);
|
|
475
|
+
const kindState = targetGoverned
|
|
476
|
+
? sourceGoverned ? "governed_both" : "governed_target"
|
|
477
|
+
: sourceGoverned ? allowUngoverned ? "allowed_ungoverned" : "missing" : "ungoverned_both";
|
|
478
|
+
const candidate = { frontmatter, body };
|
|
479
|
+
let warnings = [];
|
|
480
|
+
try {
|
|
481
|
+
warnings = prepareDocumentMutationCandidate(undefined, structuredClone(candidate), {
|
|
482
|
+
id: targetId,
|
|
483
|
+
registry: ctx.target.registry,
|
|
484
|
+
strict: false,
|
|
485
|
+
okfVersion: ctx.target.okfVersion,
|
|
486
|
+
actor,
|
|
487
|
+
now,
|
|
488
|
+
seedGenerationClock: false,
|
|
489
|
+
}).warnings;
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
if (error instanceof OkfActorError)
|
|
493
|
+
throw error;
|
|
494
|
+
conflicts.push({ code: "source_malformed", source: doc.id, target: targetId, detail: `the target would refuse this document: ${error instanceof Error ? error.message : String(error)}` });
|
|
495
|
+
}
|
|
496
|
+
return {
|
|
497
|
+
entry: {
|
|
498
|
+
source: doc.id,
|
|
499
|
+
source_version: version,
|
|
500
|
+
target: targetId,
|
|
501
|
+
type,
|
|
502
|
+
rewrites,
|
|
503
|
+
rewrite_counts: counts,
|
|
504
|
+
frontmatter_id_mentions: [...mentions].sort(compareStorageKeys),
|
|
505
|
+
kind_state: kindState,
|
|
506
|
+
validation_warnings: warnings,
|
|
507
|
+
},
|
|
508
|
+
candidate,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
async function sourceIdentity(from, target, conflicts) {
|
|
512
|
+
const { alias, binding } = from;
|
|
513
|
+
const declared = target.dependencies.dependencies.get(alias);
|
|
514
|
+
const bundleId = declared?.bundle_id ?? binding.identity ?? "";
|
|
515
|
+
if (!declared) {
|
|
516
|
+
const rejected = target.dependencies.rejected.get(alias);
|
|
517
|
+
conflicts.push({ code: "alias_undeclared", detail: rejected === undefined
|
|
518
|
+
? `alias '${alias}' is not declared in the target's docs/dependencies`
|
|
519
|
+
: `alias '${alias}' is declared in the target's docs/dependencies but rejected (${rejected})` });
|
|
520
|
+
return { bundleId };
|
|
521
|
+
}
|
|
522
|
+
if (binding.identity === undefined) {
|
|
523
|
+
conflicts.push({ code: "alias_identity", detail: `${binding.label} declares no bundle_id; '${alias}' expects '${declared.bundle_id}'` });
|
|
524
|
+
}
|
|
525
|
+
else if (binding.identity !== declared.bundle_id) {
|
|
526
|
+
conflicts.push({ code: "alias_identity", detail: `${binding.label} is '${binding.identity}', not '${declared.bundle_id}' as '${alias}' declares` });
|
|
527
|
+
}
|
|
528
|
+
const pin = declared.pin;
|
|
529
|
+
if (pin === undefined)
|
|
530
|
+
return { bundleId };
|
|
531
|
+
if (pin.kind === "digest") {
|
|
532
|
+
conflicts.push({ code: "alias_identity", detail: "a digest pin is verified only by a hosted resolver" });
|
|
533
|
+
return { bundleId };
|
|
534
|
+
}
|
|
535
|
+
const commit = { kind: "commit", value: pin.value };
|
|
536
|
+
if (!binding.pin) {
|
|
537
|
+
conflicts.push({ code: "alias_identity", detail: `no pin check is available for ${binding.label}` });
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
const verdict = await binding.pin(commit).catch((error) => ({ ok: false, detail: `pin check failed: ${error instanceof Error ? error.message : String(error)}` }));
|
|
541
|
+
if (!verdict.ok)
|
|
542
|
+
conflicts.push({ code: "alias_identity", detail: verdict.detail });
|
|
543
|
+
}
|
|
544
|
+
return { bundleId, pin: commit };
|
|
545
|
+
}
|
|
546
|
+
function assertActor(actor, okfVersion) {
|
|
547
|
+
if (actor !== undefined && okfVersion === "0.2" && !isOkfActor(actor)) {
|
|
548
|
+
throw new OkfActorError(actor, `OKF v0.2 mutation actor '${actor}' must be human:<id>, process:<id>, or <producer>/<version>`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function computeTransfer(request) {
|
|
552
|
+
const conflicts = [];
|
|
553
|
+
const now = request.now ?? (() => new Date().toISOString());
|
|
554
|
+
const target = await readTarget(request.target);
|
|
555
|
+
assertActor(request.actor, target.okfVersion);
|
|
556
|
+
const { bundleId, pin } = await sourceIdentity(request.from, target, conflicts);
|
|
557
|
+
const source = await readSource(request.from.binding);
|
|
558
|
+
const prefix = normalizePrefix(request.prefix);
|
|
559
|
+
// Selection: the union of ids, types and prefixes. Reserved files are never listed.
|
|
560
|
+
const selected = new Set();
|
|
561
|
+
for (const id of request.select.ids ?? []) {
|
|
562
|
+
if (SOURCE_SELF_DOCS.has(id)) {
|
|
563
|
+
conflicts.push({ code: "source_missing", source: id, detail: `'${id}' describes the source bundle itself and never transfers` });
|
|
564
|
+
}
|
|
565
|
+
else if (source.malformed.has(id)) {
|
|
566
|
+
conflicts.push({ code: "source_malformed", source: id, detail: `'${id}' is malformed in the source: ${source.malformed.get(id)}` });
|
|
567
|
+
}
|
|
568
|
+
else if (!source.docs.has(id)) {
|
|
569
|
+
conflicts.push({ code: "source_missing", source: id, detail: `'${id}' is not in ${request.from.binding.label}` });
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
selected.add(id);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const types = new Set(request.select.types ?? []);
|
|
576
|
+
const prefixes = (request.select.prefixes ?? []).map((p) => trimTrailingSlashes(p.trim())).filter((p) => p !== "");
|
|
577
|
+
const underPrefix = (id) => prefixes.some((p) => id === p || id.startsWith(`${p}/`));
|
|
578
|
+
for (const [id, { doc }] of source.docs) {
|
|
579
|
+
if (SOURCE_SELF_DOCS.has(id))
|
|
580
|
+
continue;
|
|
581
|
+
if (types.has(String(doc.frontmatter.type)) || underPrefix(id))
|
|
582
|
+
selected.add(id);
|
|
583
|
+
}
|
|
584
|
+
for (const [id, reason] of source.malformed) {
|
|
585
|
+
if (!SOURCE_SELF_DOCS.has(id) && underPrefix(id)) {
|
|
586
|
+
conflicts.push({ code: "source_malformed", source: id, detail: `'${id}' is malformed in the source: ${reason}` });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (selected.size === 0 && conflicts.every((c) => c.code !== "source_missing" && c.code !== "source_malformed")) {
|
|
590
|
+
conflicts.push({ code: "selection_empty", detail: "the selection matches no source document" });
|
|
591
|
+
}
|
|
592
|
+
const selectedIds = [...selected].sort(compareStorageKeys);
|
|
593
|
+
const mapping = new Map(selectedIds.map((id) => [id, prefix === undefined ? id : `${prefix}/${id}`]));
|
|
594
|
+
const recordId = request.recordId?.trim() || defaultRecordId(request.from.alias, bundleId, prefix, selectedIds);
|
|
595
|
+
checkTargetIds([...[...mapping].map(([s, t]) => ({ source: s, target: t })), { target: recordId, record: true }], target.ids, conflicts);
|
|
596
|
+
const ctx = { alias: request.from.alias, source, target, mapping };
|
|
597
|
+
const built = [];
|
|
598
|
+
for (const id of selectedIds) {
|
|
599
|
+
built.push(buildEntry(ctx, bundleId, recordId, source.docs.get(id), mapping.get(id), selected, request.allowUngoverned === true, request.actor, now, conflicts));
|
|
600
|
+
}
|
|
601
|
+
built.sort((a, b) => compareStorageKeys(a.entry.target, b.entry.target));
|
|
602
|
+
const missingKinds = new Map();
|
|
603
|
+
for (const { entry } of built)
|
|
604
|
+
if (entry.kind_state === "missing")
|
|
605
|
+
missingKinds.set(entry.type, (missingKinds.get(entry.type) ?? 0) + 1);
|
|
606
|
+
for (const [type, count] of missingKinds) {
|
|
607
|
+
conflicts.push({ code: "kind_missing", detail: `'${type}' is governed in the source but not in the target (${count} selected document${count === 1 ? "" : "s"}); declare the Kind in the target or allow ungoverned documents` });
|
|
608
|
+
}
|
|
609
|
+
const leftBehind = {};
|
|
610
|
+
for (const [id, { doc }] of [...source.docs].sort(([a], [b]) => compareStorageKeys(a, b))) {
|
|
611
|
+
if (selected.has(id) || SOURCE_SELF_DOCS.has(id))
|
|
612
|
+
continue;
|
|
613
|
+
const type = String(doc.frontmatter.type);
|
|
614
|
+
leftBehind[type] = (leftBehind[type] ?? 0) + 1;
|
|
615
|
+
}
|
|
616
|
+
const counts = { documents: built.length, local: 0, qualified: 0, dequalified: 0, unchanged: 0, dangling_in_source: 0 };
|
|
617
|
+
for (const { entry } of built) {
|
|
618
|
+
for (const rewrite of entry.rewrites) {
|
|
619
|
+
counts[rewrite.kind]++;
|
|
620
|
+
if (rewrite.kind === "qualified" && rewrite.target_present_in_source === false)
|
|
621
|
+
counts.dangling_in_source++;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const from = { alias: request.from.alias, bundle_id: bundleId, label: request.from.label ?? request.from.binding.label };
|
|
625
|
+
if (pin)
|
|
626
|
+
from.pin = pin;
|
|
627
|
+
const targetFacts = { okf_version: target.okfVersion, dependencies_version: target.dependenciesVersion };
|
|
628
|
+
if (target.bundleId !== undefined)
|
|
629
|
+
targetFacts.bundle_id = target.bundleId;
|
|
630
|
+
// Every governing convention the selection's types touch, on both sides.
|
|
631
|
+
const conventionVersions = [];
|
|
632
|
+
for (const type of [...new Set(built.map((b) => b.entry.type))].sort(compareStorageKeys)) {
|
|
633
|
+
const sourceKind = source.registry.kinds.get(type);
|
|
634
|
+
const targetKind = target.registry.kinds.get(type);
|
|
635
|
+
conventionVersions.push({
|
|
636
|
+
type,
|
|
637
|
+
source: sourceKind ? (source.docs.get(sourceKind.id)?.version ?? null) : null,
|
|
638
|
+
target: targetKind ? ((await readOptional(request.target, targetKind.id))?.version ?? null) : null,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
const entries = built.map((b) => b.entry);
|
|
642
|
+
const ready = conflicts.length === 0;
|
|
643
|
+
const partial = { from, counts, left_behind: leftBehind, entries };
|
|
644
|
+
let token;
|
|
645
|
+
if (ready) {
|
|
646
|
+
token = `${DOCUMENT_TRANSFER_CONTRACT}:sha256:${hashOf({
|
|
647
|
+
contract: DOCUMENT_TRANSFER_CONTRACT,
|
|
648
|
+
from: { bundle_id: bundleId, pin: pin?.value ?? null },
|
|
649
|
+
target: { okf_version: target.okfVersion, bundle_id: target.bundleId ?? null, dependencies_version: target.dependenciesVersion },
|
|
650
|
+
record: recordId,
|
|
651
|
+
prefix: prefix ?? null,
|
|
652
|
+
allow_ungoverned: request.allowUngoverned === true,
|
|
653
|
+
conventions: conventionVersions,
|
|
654
|
+
entries: built.map((b) => ({ source: b.entry.source, source_version: b.entry.source_version, target: b.entry.target, candidate: hashOf(b.candidate) })),
|
|
655
|
+
})}`;
|
|
656
|
+
}
|
|
657
|
+
const plan = {
|
|
658
|
+
contract: DOCUMENT_TRANSFER_CONTRACT,
|
|
659
|
+
from,
|
|
660
|
+
target: targetFacts,
|
|
661
|
+
entries,
|
|
662
|
+
record: { id: recordId, frontmatter: recordFrontmatter(partial, prefix, "applying", token, new Map()), body: recordBody(recordId, partial) },
|
|
663
|
+
left_behind: leftBehind,
|
|
664
|
+
counts,
|
|
665
|
+
conflicts,
|
|
666
|
+
ready,
|
|
667
|
+
};
|
|
668
|
+
if (token !== undefined)
|
|
669
|
+
plan.plan_token = token;
|
|
670
|
+
return { plan, candidates: new Map(built.map((b) => [b.entry.source, b.candidate])) };
|
|
671
|
+
}
|
|
672
|
+
/** Read-only: compute what an import would write, its conflicts and, when it has none, its plan token. */
|
|
673
|
+
export async function planDocumentTransfer(request) {
|
|
674
|
+
return (await computeTransfer(request)).plan;
|
|
675
|
+
}
|
|
676
|
+
// ── Writes ─────────────────────────────────────────────────────────────────
|
|
677
|
+
async function writeRecordState(target, registry, recordId, expectedVersion, state, versions, actor, now) {
|
|
678
|
+
const result = await mutateDocument({
|
|
679
|
+
bundle: target,
|
|
680
|
+
id: recordId,
|
|
681
|
+
mode: "replace-document",
|
|
682
|
+
onAbsent: "fail",
|
|
683
|
+
expectedVersion,
|
|
684
|
+
registry,
|
|
685
|
+
strict: false,
|
|
686
|
+
actor,
|
|
687
|
+
...(now ? { now } : {}),
|
|
688
|
+
buildCandidate: (existing) => {
|
|
689
|
+
const frontmatter = structuredClone(existing.frontmatter);
|
|
690
|
+
frontmatter.promotion_state = state;
|
|
691
|
+
if (Array.isArray(frontmatter.entries)) {
|
|
692
|
+
frontmatter.entries = frontmatter.entries.map((row) => {
|
|
693
|
+
if (row === null || typeof row !== "object")
|
|
694
|
+
return row;
|
|
695
|
+
const entry = { ...row };
|
|
696
|
+
const version = versions.get(String(entry.target));
|
|
697
|
+
if (version !== undefined)
|
|
698
|
+
entry.target_version = version;
|
|
699
|
+
return entry;
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
return { frontmatter, body: existing.body };
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
return result.version;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Recompute the plan from fresh reads, refuse unless it is ready and its token equals
|
|
709
|
+
* `planToken`, then write: the record (`applying`), each document create-only in plan order, and
|
|
710
|
+
* the record again (`applied`). A failed document write throws {@link TransferInterrupted} and
|
|
711
|
+
* leaves the record at `applying`; nothing is deleted or overwritten.
|
|
712
|
+
*/
|
|
713
|
+
export async function applyDocumentTransfer(request, planToken) {
|
|
714
|
+
const { plan, candidates } = await computeTransfer(request);
|
|
715
|
+
if (!plan.ready) {
|
|
716
|
+
// Idempotence: the same token applied again after it completed is a no-op success.
|
|
717
|
+
const prior = plan.conflicts.some((c) => c.code === "record_exists") ? await readOptional(request.target, plan.record.id) : null;
|
|
718
|
+
if (prior && prior.doc.frontmatter.type === PROMOTION_TYPE && prior.doc.frontmatter.plan_token === planToken.trim()) {
|
|
719
|
+
const record = parseRecord(plan.record.id, prior.doc);
|
|
720
|
+
if (record.state === "applied")
|
|
721
|
+
return appliedResult(plan.record.id, prior.version, record.entries, false);
|
|
722
|
+
throw new TransferConflictError([{ code: "record_exists", target: plan.record.id, detail: `'${plan.record.id}' from this plan is still applying; resume it to finish` }]);
|
|
723
|
+
}
|
|
724
|
+
throw new TransferConflictError(plan.conflicts);
|
|
725
|
+
}
|
|
726
|
+
if (plan.plan_token !== planToken.trim())
|
|
727
|
+
throw new TransferPlanChanged(planToken.trim(), plan.plan_token);
|
|
728
|
+
const registry = await loadKinds(request.target);
|
|
729
|
+
const recordId = plan.record.id;
|
|
730
|
+
let recordVersion;
|
|
731
|
+
try {
|
|
732
|
+
recordVersion = (await mutateDocument({
|
|
733
|
+
bundle: request.target,
|
|
734
|
+
id: recordId,
|
|
735
|
+
mode: "create-only",
|
|
736
|
+
registry,
|
|
737
|
+
strict: false,
|
|
738
|
+
actor: request.actor,
|
|
739
|
+
...(request.now ? { now: request.now } : {}),
|
|
740
|
+
buildCandidate: () => ({ frontmatter: structuredClone(plan.record.frontmatter), body: plan.record.body }),
|
|
741
|
+
})).version;
|
|
742
|
+
}
|
|
743
|
+
catch (error) {
|
|
744
|
+
if (error instanceof VersionConflict) {
|
|
745
|
+
throw new TransferConflictError([{ code: "record_exists", target: recordId, detail: `the Promotion record '${recordId}' was created by another writer` }]);
|
|
746
|
+
}
|
|
747
|
+
throw error;
|
|
748
|
+
}
|
|
749
|
+
const written = [];
|
|
750
|
+
const versions = new Map();
|
|
751
|
+
for (let index = 0; index < plan.entries.length; index++) {
|
|
752
|
+
const entry = plan.entries[index];
|
|
753
|
+
try {
|
|
754
|
+
const result = await mutateDocument({
|
|
755
|
+
bundle: request.target,
|
|
756
|
+
id: entry.target,
|
|
757
|
+
mode: "create-only",
|
|
758
|
+
registry,
|
|
759
|
+
strict: false,
|
|
760
|
+
actor: request.actor,
|
|
761
|
+
seedGenerationClock: false,
|
|
762
|
+
...(request.now ? { now: request.now } : {}),
|
|
763
|
+
buildCandidate: () => structuredClone(candidates.get(entry.source)),
|
|
764
|
+
});
|
|
765
|
+
written.push({ source: entry.source, target: entry.target, version: result.version });
|
|
766
|
+
versions.set(entry.target, result.version);
|
|
767
|
+
}
|
|
768
|
+
catch (cause) {
|
|
769
|
+
// Best effort: record the versions written so far; the record stays `applying` either way.
|
|
770
|
+
await writeRecordState(request.target, registry, recordId, recordVersion, "applying", versions, request.actor, request.now).catch(() => undefined);
|
|
771
|
+
throw new TransferInterrupted(recordId, written, plan.entries.slice(index).map((e) => ({ source: e.source, target: e.target })), cause);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
recordVersion = await writeRecordState(request.target, registry, recordId, recordVersion, "applied", versions, request.actor, request.now);
|
|
776
|
+
}
|
|
777
|
+
catch (cause) {
|
|
778
|
+
throw new TransferInterrupted(recordId, written, [], cause);
|
|
779
|
+
}
|
|
780
|
+
return { record: { id: recordId, version: recordVersion, state: "applied" }, written, changed: true };
|
|
781
|
+
}
|
|
782
|
+
function parseRecord(recordId, doc) {
|
|
783
|
+
const fm = doc.frontmatter;
|
|
784
|
+
const refuse = (detail) => { throw new TransferResumeConflict(recordId, detail); };
|
|
785
|
+
if (String(fm.type).trim() !== PROMOTION_TYPE)
|
|
786
|
+
refuse(`it is not a ${PROMOTION_TYPE} record (type '${String(fm.type)}')`);
|
|
787
|
+
const state = fm.promotion_state;
|
|
788
|
+
if (state !== "applying" && state !== "applied")
|
|
789
|
+
refuse("promotion_state must be applying or applied");
|
|
790
|
+
if (typeof fm.from_alias !== "string" || typeof fm.from_bundle_id !== "string")
|
|
791
|
+
refuse("from_alias and from_bundle_id are required");
|
|
792
|
+
if (!Array.isArray(fm.entries))
|
|
793
|
+
refuse("entries must be a list");
|
|
794
|
+
if (fm.target_prefix !== undefined && typeof fm.target_prefix !== "string")
|
|
795
|
+
refuse("target_prefix must be a string");
|
|
796
|
+
const prefix = normalizePrefix(fm.target_prefix);
|
|
797
|
+
const entries = [];
|
|
798
|
+
for (const row of fm.entries) {
|
|
799
|
+
const r = row;
|
|
800
|
+
if (!r || typeof r.source !== "string" || typeof r.source_version !== "string" || typeof r.target !== "string") {
|
|
801
|
+
refuse("every entry needs source, source_version and target");
|
|
802
|
+
}
|
|
803
|
+
const entry = { source: r.source, source_version: r.source_version, target: r.target };
|
|
804
|
+
if (typeof r.target_version === "string")
|
|
805
|
+
entry.target_version = r.target_version;
|
|
806
|
+
// The record is a document anyone can edit: every target is recomputed from its source and
|
|
807
|
+
// the record's prefix, and must pass the same rule a plan applies, before resume trusts it.
|
|
808
|
+
if (SOURCE_SELF_DOCS.has(entry.source))
|
|
809
|
+
refuse(`'${entry.source}' describes the source bundle itself and never transfers`);
|
|
810
|
+
const expected = prefix === undefined ? entry.source : `${prefix}/${entry.source}`;
|
|
811
|
+
if (entry.target !== expected)
|
|
812
|
+
refuse(`entry '${entry.source}' targets '${entry.target}', not '${expected}' as the record's prefix requires`);
|
|
813
|
+
try {
|
|
814
|
+
assertSafeConceptId(entry.source);
|
|
815
|
+
assertImportableTargetId(entry.target);
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
if (!(error instanceof InvalidInputError))
|
|
819
|
+
throw error;
|
|
820
|
+
refuse(`'${entry.target}' is not a target an import may write: ${error.message}`);
|
|
821
|
+
}
|
|
822
|
+
entries.push(entry);
|
|
823
|
+
}
|
|
824
|
+
return { state: state, alias: fm.from_alias, bundleId: fm.from_bundle_id, ...(prefix === undefined ? {} : { prefix }), entries };
|
|
825
|
+
}
|
|
826
|
+
/** The result an already-applied record stands for: nothing is written again. */
|
|
827
|
+
function appliedResult(recordId, version, entries, changed) {
|
|
828
|
+
const written = [];
|
|
829
|
+
for (const entry of entries) {
|
|
830
|
+
if (entry.target_version === undefined)
|
|
831
|
+
throw new TransferResumeConflict(recordId, `it is applied but '${entry.target}' has no target_version`);
|
|
832
|
+
written.push({ source: entry.source, target: entry.target, version: entry.target_version });
|
|
833
|
+
}
|
|
834
|
+
return { record: { id: recordId, version, state: "applied" }, written, changed };
|
|
835
|
+
}
|
|
836
|
+
function hasProvenance(doc, recordId) {
|
|
837
|
+
const sources = doc.frontmatter.sources;
|
|
838
|
+
return Array.isArray(sources) && sources.some((s) => s !== null && typeof s === "object" && s.id === recordId);
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Finish an `applying` Promotion record. For each entry: an absent target is created from the
|
|
842
|
+
* source at the recorded source version (a changed source refuses); a present target carrying a
|
|
843
|
+
* `sources` entry with this record's id is already done; anything else refuses. When every entry
|
|
844
|
+
* is done the record becomes `applied`. Running it again on an `applied` record is a no-op.
|
|
845
|
+
*/
|
|
846
|
+
export async function resumeDocumentTransfer(target, recordId, from, options = {}) {
|
|
847
|
+
const read = await readOptional(target, recordId);
|
|
848
|
+
if (!read)
|
|
849
|
+
throw new DocumentNotFoundError(recordId);
|
|
850
|
+
const record = parseRecord(recordId, read.doc);
|
|
851
|
+
if (record.alias !== from.alias)
|
|
852
|
+
throw new TransferResumeConflict(recordId, `it was written for alias '${record.alias}', not '${from.alias}'`);
|
|
853
|
+
if (from.binding.identity !== record.bundleId) {
|
|
854
|
+
throw new TransferResumeConflict(recordId, `${from.binding.label} is '${from.binding.identity ?? "undeclared"}', not '${record.bundleId}'`);
|
|
855
|
+
}
|
|
856
|
+
if (record.state === "applied")
|
|
857
|
+
return appliedResult(recordId, read.version, record.entries, false);
|
|
858
|
+
const snapshot = await readTarget(target);
|
|
859
|
+
assertActor(options.actor, snapshot.okfVersion);
|
|
860
|
+
// The same alias and identity check a plan runs: the target's declaration may have moved since.
|
|
861
|
+
const identity = [];
|
|
862
|
+
await sourceIdentity(from, snapshot, identity);
|
|
863
|
+
if (identity.length > 0)
|
|
864
|
+
throw new TransferResumeConflict(recordId, `${identity[0].code}: ${identity[0].detail}`);
|
|
865
|
+
const source = await readSource(from.binding);
|
|
866
|
+
const mapping = new Map(record.entries.map((e) => [e.source, e.target]));
|
|
867
|
+
const selected = new Set(mapping.keys());
|
|
868
|
+
const ctx = { alias: from.alias, source, target: snapshot, mapping };
|
|
869
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
870
|
+
const registry = snapshot.registry;
|
|
871
|
+
const written = [];
|
|
872
|
+
const versions = new Map();
|
|
873
|
+
for (const entry of record.entries) {
|
|
874
|
+
let existing;
|
|
875
|
+
try {
|
|
876
|
+
existing = await readOptional(target, entry.target);
|
|
877
|
+
}
|
|
878
|
+
catch (error) {
|
|
879
|
+
if (error instanceof FilesystemIdentityAliasError) {
|
|
880
|
+
throw new TransferResumeConflict(recordId, `'${entry.target}' is spelled differently on disk: ${error.message}`, { id: entry.target, written });
|
|
881
|
+
}
|
|
882
|
+
throw error;
|
|
883
|
+
}
|
|
884
|
+
if (existing) {
|
|
885
|
+
if (!hasProvenance(existing.doc, recordId)) {
|
|
886
|
+
throw new TransferResumeConflict(recordId, `'${entry.target}' exists and was not written by this import`, { id: entry.target, written });
|
|
887
|
+
}
|
|
888
|
+
written.push({ source: entry.source, target: entry.target, version: existing.version });
|
|
889
|
+
versions.set(entry.target, existing.version);
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
const sourceDoc = source.docs.get(entry.source);
|
|
893
|
+
if (!sourceDoc || sourceDoc.version !== entry.source_version) {
|
|
894
|
+
throw new TransferResumeConflict(recordId, `the source '${entry.source}' is ${sourceDoc ? "changed" : "missing"} since the import was planned`, { id: entry.target, written });
|
|
895
|
+
}
|
|
896
|
+
const conflicts = [];
|
|
897
|
+
const built = buildEntry(ctx, record.bundleId, recordId, sourceDoc, entry.target, selected, true, options.actor, now, conflicts);
|
|
898
|
+
if (conflicts.length > 0)
|
|
899
|
+
throw new TransferResumeConflict(recordId, `${conflicts[0].code}: ${conflicts[0].detail}`, { id: entry.target, written });
|
|
900
|
+
try {
|
|
901
|
+
const result = await mutateDocument({
|
|
902
|
+
bundle: target,
|
|
903
|
+
id: entry.target,
|
|
904
|
+
mode: "create-only",
|
|
905
|
+
registry,
|
|
906
|
+
strict: false,
|
|
907
|
+
actor: options.actor,
|
|
908
|
+
seedGenerationClock: false,
|
|
909
|
+
...(options.now ? { now: options.now } : {}),
|
|
910
|
+
buildCandidate: () => built.candidate,
|
|
911
|
+
});
|
|
912
|
+
written.push({ source: entry.source, target: entry.target, version: result.version });
|
|
913
|
+
versions.set(entry.target, result.version);
|
|
914
|
+
}
|
|
915
|
+
catch (error) {
|
|
916
|
+
if (error instanceof VersionConflict) {
|
|
917
|
+
throw new TransferResumeConflict(recordId, `'${entry.target}' was created by another writer during the resume`, { id: entry.target, written });
|
|
918
|
+
}
|
|
919
|
+
if (error instanceof FilesystemIdentityAliasError) {
|
|
920
|
+
throw new TransferResumeConflict(recordId, `'${entry.target}' is spelled differently on disk: ${error.message}`, { id: entry.target, written });
|
|
921
|
+
}
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const version = await writeRecordState(target, registry, recordId, read.version, "applied", versions, options.actor, options.now);
|
|
926
|
+
return { record: { id: recordId, version, state: "applied" }, written, changed: true };
|
|
927
|
+
}
|
|
928
|
+
//# sourceMappingURL=document-transfer.js.map
|