@amalgm/shell 0.1.164-preview.36072132286 → 0.1.164-preview.36809057014
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/PURPOSE.md +642 -128
- package/README.md +10 -8
- package/dist/accepted-tail.d.ts +26 -0
- package/dist/accepted-tail.js +37 -0
- package/dist/accepted-tail.js.map +1 -0
- package/dist/agent-execution.d.ts +16 -1
- package/dist/agent-execution.js +13 -5
- package/dist/agent-execution.js.map +1 -1
- package/dist/apply-cleanup.d.ts +28 -0
- package/dist/apply-cleanup.js +166 -0
- package/dist/apply-cleanup.js.map +1 -0
- package/dist/apply-recovery.d.ts +47 -0
- package/dist/apply-recovery.js +99 -0
- package/dist/apply-recovery.js.map +1 -0
- package/dist/apply-rows.d.ts +72 -0
- package/dist/apply-rows.js +150 -0
- package/dist/apply-rows.js.map +1 -0
- package/dist/apply-saves.d.ts +85 -0
- package/dist/apply-saves.js +238 -0
- package/dist/apply-saves.js.map +1 -0
- package/dist/boot.js +2 -1
- package/dist/boot.js.map +1 -1
- package/dist/capture-work.d.ts +8 -0
- package/dist/capture-work.js +67 -0
- package/dist/capture-work.js.map +1 -0
- package/dist/cargo-group.d.ts +45 -0
- package/dist/cargo-group.js +88 -0
- package/dist/cargo-group.js.map +1 -0
- package/dist/channel-session.d.ts +4 -2
- package/dist/channel-session.js +4 -3
- package/dist/channel-session.js.map +1 -1
- package/dist/chat-composition.d.ts +2 -0
- package/dist/chat-composition.js +1 -0
- package/dist/chat-composition.js.map +1 -1
- package/dist/cli.js +20 -1
- package/dist/cli.js.map +1 -1
- package/dist/construct-apply-host.d.ts +4 -2
- package/dist/construct-apply-host.js +8 -3
- package/dist/construct-apply-host.js.map +1 -1
- package/dist/construct-door.js +1 -1
- package/dist/construct-door.js.map +1 -1
- package/dist/construct-ledger.d.ts +6 -1
- package/dist/construct-ledger.js +21 -33
- package/dist/construct-ledger.js.map +1 -1
- package/dist/content-cache-collection.d.ts +18 -0
- package/dist/content-cache-collection.js +99 -0
- package/dist/content-cache-collection.js.map +1 -0
- package/dist/content-cache-host.d.ts +83 -17
- package/dist/content-cache-host.js +335 -95
- package/dist/content-cache-host.js.map +1 -1
- package/dist/content-cache-namespace.d.ts +59 -0
- package/dist/content-cache-namespace.js +210 -0
- package/dist/content-cache-namespace.js.map +1 -0
- package/dist/content-cache-ownership.d.ts +13 -0
- package/dist/content-cache-ownership.js +75 -0
- package/dist/content-cache-ownership.js.map +1 -0
- package/dist/content-cache-retention.d.ts +23 -0
- package/dist/content-cache-retention.js +195 -0
- package/dist/content-cache-retention.js.map +1 -0
- package/dist/content-capture-pool.d.ts +287 -0
- package/dist/content-capture-pool.js +461 -0
- package/dist/content-capture-pool.js.map +1 -0
- package/dist/content-capture-worker.d.ts +8 -0
- package/dist/content-capture-worker.js +109 -0
- package/dist/content-capture-worker.js.map +1 -0
- package/dist/content-clock.d.ts +42 -0
- package/dist/content-clock.js +166 -0
- package/dist/content-clock.js.map +1 -0
- package/dist/content-direct.d.ts +2 -0
- package/dist/content-direct.js +12 -0
- package/dist/content-direct.js.map +1 -1
- package/dist/content-failure.d.ts +18 -0
- package/dist/content-failure.js +38 -0
- package/dist/content-failure.js.map +1 -0
- package/dist/content-stream-thread.d.ts +60 -0
- package/dist/content-stream-thread.js +95 -0
- package/dist/content-stream-thread.js.map +1 -0
- package/dist/content-stream-worker.d.ts +7 -0
- package/dist/content-stream-worker.js +52 -0
- package/dist/content-stream-worker.js.map +1 -0
- package/dist/declaration-absorption.d.ts +37 -0
- package/dist/declaration-absorption.js +119 -0
- package/dist/declaration-absorption.js.map +1 -0
- package/dist/declaration-chain.d.ts +36 -0
- package/dist/declaration-chain.js +96 -0
- package/dist/declaration-chain.js.map +1 -0
- package/dist/declaration-commit.d.ts +31 -0
- package/dist/declaration-commit.js +49 -0
- package/dist/declaration-commit.js.map +1 -0
- package/dist/declaration-hold.d.ts +22 -0
- package/dist/declaration-hold.js +24 -0
- package/dist/declaration-hold.js.map +1 -0
- package/dist/declaration-journal.d.ts +62 -0
- package/dist/declaration-journal.js +99 -0
- package/dist/declaration-journal.js.map +1 -0
- package/dist/declaration-save.d.ts +31 -0
- package/dist/declaration-save.js +134 -0
- package/dist/declaration-save.js.map +1 -0
- package/dist/detection/accepted-result.d.ts +19 -1
- package/dist/detection/accepted-result.js +36 -5
- package/dist/detection/accepted-result.js.map +1 -1
- package/dist/detection/admission-save.d.ts +55 -0
- package/dist/detection/admission-save.js +48 -0
- package/dist/detection/admission-save.js.map +1 -0
- package/dist/detection/admission-units.d.ts +66 -0
- package/dist/detection/admission-units.js +322 -0
- package/dist/detection/admission-units.js.map +1 -0
- package/dist/detection/alias-discovery.d.ts +12 -0
- package/dist/detection/alias-discovery.js +80 -0
- package/dist/detection/alias-discovery.js.map +1 -0
- package/dist/detection/alias-examination.d.ts +30 -0
- package/dist/detection/alias-examination.js +49 -0
- package/dist/detection/alias-examination.js.map +1 -0
- package/dist/detection/apply-ownership.d.ts +81 -0
- package/dist/detection/apply-ownership.js +232 -0
- package/dist/detection/apply-ownership.js.map +1 -0
- package/dist/detection/basis-advanced.d.ts +11 -0
- package/dist/detection/basis-advanced.js +26 -0
- package/dist/detection/basis-advanced.js.map +1 -0
- package/dist/detection/capture-outcome.d.ts +16 -0
- package/dist/detection/capture-outcome.js +92 -0
- package/dist/detection/capture-outcome.js.map +1 -0
- package/dist/detection/container-membership.d.ts +32 -0
- package/dist/detection/container-membership.js +49 -0
- package/dist/detection/container-membership.js.map +1 -0
- package/dist/detection/detect-cancelled.d.ts +6 -0
- package/dist/detection/detect-cancelled.js +10 -0
- package/dist/detection/detect-cancelled.js.map +1 -0
- package/dist/detection/directory-proof.d.ts +47 -0
- package/dist/detection/directory-proof.js +77 -0
- package/dist/detection/directory-proof.js.map +1 -0
- package/dist/detection/disposition-rows.d.ts +18 -0
- package/dist/detection/disposition-rows.js +29 -0
- package/dist/detection/disposition-rows.js.map +1 -0
- package/dist/detection/dispositions.d.ts +130 -0
- package/dist/detection/dispositions.js +248 -0
- package/dist/detection/dispositions.js.map +1 -0
- package/dist/detection/git-notice.d.ts +88 -0
- package/dist/detection/git-notice.js +444 -0
- package/dist/detection/git-notice.js.map +1 -0
- package/dist/detection/ground-proof.d.ts +130 -0
- package/dist/detection/ground-proof.js +274 -0
- package/dist/detection/ground-proof.js.map +1 -0
- package/dist/detection/ground-reader.d.ts +29 -0
- package/dist/detection/ground-reader.js +102 -0
- package/dist/detection/ground-reader.js.map +1 -0
- package/dist/detection/held-territory.d.ts +51 -0
- package/dist/detection/held-territory.js +205 -0
- package/dist/detection/held-territory.js.map +1 -0
- package/dist/detection/identity-discovery.d.ts +45 -0
- package/dist/detection/identity-discovery.js +287 -0
- package/dist/detection/identity-discovery.js.map +1 -0
- package/dist/detection/maintenance-sweep.d.ts +108 -0
- package/dist/detection/maintenance-sweep.js +325 -0
- package/dist/detection/maintenance-sweep.js.map +1 -0
- package/dist/detection/name-reach.d.ts +12 -0
- package/dist/detection/name-reach.js +28 -0
- package/dist/detection/name-reach.js.map +1 -0
- package/dist/detection/notebook-basis.d.ts +5 -7
- package/dist/detection/notebook-basis.js +6 -7
- package/dist/detection/notebook-basis.js.map +1 -1
- package/dist/detection/owed-selection.d.ts +51 -0
- package/dist/detection/owed-selection.js +169 -0
- package/dist/detection/owed-selection.js.map +1 -0
- package/dist/detection/portable.d.ts +34 -34
- package/dist/detection/portable.js +67 -93
- package/dist/detection/portable.js.map +1 -1
- package/dist/detection/reconciliation-coverage.d.ts +7 -0
- package/dist/detection/reconciliation-coverage.js +16 -0
- package/dist/detection/reconciliation-coverage.js.map +1 -0
- package/dist/detection/repository-publication.d.ts +20 -0
- package/dist/detection/repository-publication.js +79 -0
- package/dist/detection/repository-publication.js.map +1 -0
- package/dist/detection/repository-routes.d.ts +43 -0
- package/dist/detection/repository-routes.js +97 -0
- package/dist/detection/repository-routes.js.map +1 -0
- package/dist/detection/root-examination.d.ts +94 -0
- package/dist/detection/root-examination.js +523 -0
- package/dist/detection/root-examination.js.map +1 -0
- package/dist/detection/runtime.d.ts +131 -64
- package/dist/detection/runtime.js +808 -1103
- package/dist/detection/runtime.js.map +1 -1
- package/dist/detection/scoped-identity-port.d.ts +34 -0
- package/dist/detection/scoped-identity-port.js +81 -0
- package/dist/detection/scoped-identity-port.js.map +1 -0
- package/dist/detection/seal-law.d.ts +66 -0
- package/dist/detection/seal-law.js +116 -0
- package/dist/detection/seal-law.js.map +1 -0
- package/dist/detection/sharing-policy.d.ts +11 -0
- package/dist/detection/sharing-policy.js +32 -0
- package/dist/detection/sharing-policy.js.map +1 -0
- package/dist/detection/thread-database.d.ts +4 -0
- package/dist/detection/thread-database.js +30 -0
- package/dist/detection/thread-database.js.map +1 -0
- package/dist/detection/unreadable.d.ts +11 -0
- package/dist/detection/unreadable.js +9 -0
- package/dist/detection/unreadable.js.map +1 -0
- package/dist/detection/verified-content.d.ts +6 -0
- package/dist/detection/verified-content.js +12 -0
- package/dist/detection/verified-content.js.map +1 -0
- package/dist/detection/walk-law.d.ts +34 -0
- package/dist/detection/walk-law.js +55 -0
- package/dist/detection/walk-law.js.map +1 -0
- package/dist/development/fly-guest.js +2 -1
- package/dist/development/fly-guest.js.map +1 -1
- package/dist/entity-apply-host.d.ts +29 -13
- package/dist/entity-apply-host.js +1426 -461
- package/dist/entity-apply-host.js.map +1 -1
- package/dist/entity-apply-store.d.ts +77 -30
- package/dist/entity-apply-store.js +221 -516
- package/dist/entity-apply-store.js.map +1 -1
- package/dist/entity-record-observability.js +2 -0
- package/dist/entity-record-observability.js.map +1 -1
- package/dist/entity-record-saves.d.ts +20 -0
- package/dist/entity-record-saves.js +91 -0
- package/dist/entity-record-saves.js.map +1 -0
- package/dist/entity-record-store.d.ts +15 -7
- package/dist/entity-record-store.js +57 -117
- package/dist/entity-record-store.js.map +1 -1
- package/dist/files-register-host.d.ts +22 -3
- package/dist/files-register-host.js +70 -31
- package/dist/files-register-host.js.map +1 -1
- package/dist/filesystem.js +3 -2
- package/dist/filesystem.js.map +1 -1
- package/dist/git-binding.d.ts +65 -0
- package/dist/git-binding.js +220 -0
- package/dist/git-binding.js.map +1 -0
- package/dist/git-capture.d.ts +39 -0
- package/dist/git-capture.js +86 -0
- package/dist/git-capture.js.map +1 -0
- package/dist/git-card.d.ts +12 -0
- package/dist/git-card.js +123 -0
- package/dist/git-card.js.map +1 -0
- package/dist/git-checkpoint-construction.d.ts +41 -0
- package/dist/git-checkpoint-construction.js +96 -0
- package/dist/git-checkpoint-construction.js.map +1 -0
- package/dist/git-checkpoint-preparation.d.ts +37 -0
- package/dist/git-checkpoint-preparation.js +145 -0
- package/dist/git-checkpoint-preparation.js.map +1 -0
- package/dist/git-command.d.ts +61 -8
- package/dist/git-command.js +217 -63
- package/dist/git-command.js.map +1 -1
- package/dist/git-configuration.d.ts +40 -0
- package/dist/git-configuration.js +217 -0
- package/dist/git-configuration.js.map +1 -0
- package/dist/git-evidence.d.ts +61 -0
- package/dist/git-evidence.js +181 -0
- package/dist/git-evidence.js.map +1 -0
- package/dist/git-index-entries.d.ts +15 -0
- package/dist/git-index-entries.js +44 -0
- package/dist/git-index-entries.js.map +1 -0
- package/dist/git-index-format.d.ts +35 -0
- package/dist/git-index-format.js +293 -0
- package/dist/git-index-format.js.map +1 -0
- package/dist/git-index-witness.d.ts +33 -0
- package/dist/git-index-witness.js +69 -0
- package/dist/git-index-witness.js.map +1 -0
- package/dist/git-literal-host.d.ts +82 -0
- package/dist/git-literal-host.js +172 -0
- package/dist/git-literal-host.js.map +1 -0
- package/dist/git-literal-read.d.ts +88 -0
- package/dist/git-literal-read.js +170 -0
- package/dist/git-literal-read.js.map +1 -0
- package/dist/git-literal-replay.d.ts +33 -0
- package/dist/git-literal-replay.js +268 -0
- package/dist/git-literal-replay.js.map +1 -0
- package/dist/git-native-file.d.ts +42 -0
- package/dist/git-native-file.js +145 -0
- package/dist/git-native-file.js.map +1 -0
- package/dist/git-native-index.d.ts +21 -0
- package/dist/git-native-index.js +44 -0
- package/dist/git-native-index.js.map +1 -0
- package/dist/git-operation-host.d.ts +48 -0
- package/dist/git-operation-host.js +127 -0
- package/dist/git-operation-host.js.map +1 -0
- package/dist/git-private-index.d.ts +11 -0
- package/dist/git-private-index.js +42 -0
- package/dist/git-private-index.js.map +1 -0
- package/dist/git-private-repository.d.ts +4 -0
- package/dist/git-private-repository.js +69 -0
- package/dist/git-private-repository.js.map +1 -0
- package/dist/git-private-view.d.ts +39 -0
- package/dist/git-private-view.js +162 -0
- package/dist/git-private-view.js.map +1 -0
- package/dist/git-ref-store.d.ts +12 -0
- package/dist/git-ref-store.js +68 -0
- package/dist/git-ref-store.js.map +1 -0
- package/dist/git-registration-host.d.ts +20 -30
- package/dist/git-registration-host.js +86 -249
- package/dist/git-registration-host.js.map +1 -1
- package/dist/git-repository-host.d.ts +148 -22
- package/dist/git-repository-host.js +633 -650
- package/dist/git-repository-host.js.map +1 -1
- package/dist/git-worktree-host.d.ts +2 -0
- package/dist/git-worktree-host.js +16 -0
- package/dist/git-worktree-host.js.map +1 -0
- package/dist/graph-publication.d.ts +75 -0
- package/dist/graph-publication.js +203 -0
- package/dist/graph-publication.js.map +1 -0
- package/dist/ground-basis.d.ts +242 -0
- package/dist/ground-basis.js +1023 -0
- package/dist/ground-basis.js.map +1 -0
- package/dist/ground-checkpoint-worker.d.ts +1 -0
- package/dist/ground-checkpoint-worker.js +84 -0
- package/dist/ground-checkpoint-worker.js.map +1 -0
- package/dist/ground-checkpoints.d.ts +16 -0
- package/dist/ground-checkpoints.js +45 -0
- package/dist/ground-checkpoints.js.map +1 -0
- package/dist/ground-coordination.d.ts +107 -3
- package/dist/ground-coordination.js +249 -24
- package/dist/ground-coordination.js.map +1 -1
- package/dist/ground-database.d.ts +42 -0
- package/dist/ground-database.js +290 -0
- package/dist/ground-database.js.map +1 -0
- package/dist/ground-differences.d.ts +23 -0
- package/dist/ground-differences.js +47 -0
- package/dist/ground-differences.js.map +1 -0
- package/dist/ground-holders.d.ts +4 -0
- package/dist/ground-holders.js +15 -0
- package/dist/ground-holders.js.map +1 -0
- package/dist/ground-initialization-worker.d.ts +1 -0
- package/dist/ground-initialization-worker.js +20 -0
- package/dist/ground-initialization-worker.js.map +1 -0
- package/dist/ground-initialization.d.ts +35 -0
- package/dist/ground-initialization.js +102 -0
- package/dist/ground-initialization.js.map +1 -0
- package/dist/ground-install-saves.d.ts +63 -0
- package/dist/ground-install-saves.js +139 -0
- package/dist/ground-install-saves.js.map +1 -0
- package/dist/ground-inventory-schema.d.ts +18 -0
- package/dist/ground-inventory-schema.js +168 -0
- package/dist/ground-inventory-schema.js.map +1 -0
- package/dist/ground-profile.d.ts +7 -0
- package/dist/ground-profile.js +52 -0
- package/dist/ground-profile.js.map +1 -0
- package/dist/ground-root-index.d.ts +2 -1
- package/dist/ground-root-index.js +16 -14
- package/dist/ground-root-index.js.map +1 -1
- package/dist/ground-row.d.ts +62 -0
- package/dist/ground-row.js +70 -0
- package/dist/ground-row.js.map +1 -0
- package/dist/ground-save-ops.d.ts +44 -0
- package/dist/ground-save-ops.js +41 -0
- package/dist/ground-save-ops.js.map +1 -0
- package/dist/ground-saver-worker.d.ts +1 -0
- package/dist/ground-saver-worker.js +156 -0
- package/dist/ground-saver-worker.js.map +1 -0
- package/dist/ground-saver.d.ts +75 -0
- package/dist/ground-saver.js +216 -0
- package/dist/ground-saver.js.map +1 -0
- package/dist/ground-store.d.ts +84 -0
- package/dist/ground-store.js +473 -0
- package/dist/ground-store.js.map +1 -0
- package/dist/ground-territory.d.ts +130 -0
- package/dist/ground-territory.js +511 -0
- package/dist/ground-territory.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/installation/ownership.d.ts +0 -2
- package/dist/installation/ownership.js +3 -15
- package/dist/installation/ownership.js.map +1 -1
- package/dist/machine-host.d.ts +3 -2
- package/dist/machine-host.js +28 -16
- package/dist/machine-host.js.map +1 -1
- package/dist/materialized-graph.d.ts +7 -7
- package/dist/materialized-graph.js +11 -23
- package/dist/materialized-graph.js.map +1 -1
- package/dist/module-thread.d.ts +9 -0
- package/dist/module-thread.js +10 -0
- package/dist/module-thread.js.map +1 -0
- package/dist/native/fsevents-stream.node +0 -0
- package/dist/physical-path-witness.d.ts +64 -0
- package/dist/physical-path-witness.js +168 -0
- package/dist/physical-path-witness.js.map +1 -0
- package/dist/register-upload-fusion.d.ts +5 -4
- package/dist/register-upload-fusion.js +9 -6
- package/dist/register-upload-fusion.js.map +1 -1
- package/dist/registry-graph.d.ts +3 -2
- package/dist/registry-graph.js +9 -6
- package/dist/registry-graph.js.map +1 -1
- package/dist/registry-page-cache.d.ts +5 -2
- package/dist/registry-page-cache.js.map +1 -1
- package/dist/repository-identity-steps.d.ts +5 -0
- package/dist/repository-identity-steps.js +33 -0
- package/dist/repository-identity-steps.js.map +1 -0
- package/dist/runtime-node-flags.d.ts +10 -0
- package/dist/runtime-node-flags.js +11 -0
- package/dist/runtime-node-flags.js.map +1 -0
- package/dist/service/adapter.d.ts +1 -1
- package/dist/service/adapter.js +1 -1
- package/dist/service/adapter.js.map +1 -1
- package/dist/service/cli.js +16 -20
- package/dist/service/cli.js.map +1 -1
- package/dist/service/control.d.ts +2 -2
- package/dist/service/keepalive.d.ts +4 -11
- package/dist/service/keepalive.js +65 -118
- package/dist/service/keepalive.js.map +1 -1
- package/dist/service/policy.d.ts +36 -0
- package/dist/service/policy.js +25 -0
- package/dist/service/policy.js.map +1 -0
- package/dist/service/portable.d.ts +1 -1
- package/dist/service/portable.js +1 -1
- package/dist/service/portable.js.map +1 -1
- package/dist/service/recovery.d.ts +10 -9
- package/dist/service/recovery.js +29 -19
- package/dist/service/recovery.js.map +1 -1
- package/dist/service/windows-action.d.ts +5 -3
- package/dist/service/windows-action.js +6 -9
- package/dist/service/windows-action.js.map +1 -1
- package/dist/service/windows-task.js +5 -5
- package/dist/service/windows-task.js.map +1 -1
- package/dist/sharing-admission.d.ts +17 -0
- package/dist/sharing-admission.js +128 -0
- package/dist/sharing-admission.js.map +1 -0
- package/dist/snapshot-delivery-baseline.d.ts +12 -3
- package/dist/snapshot-delivery-baseline.js +41 -42
- package/dist/snapshot-delivery-baseline.js.map +1 -1
- package/dist/source-proof.d.ts +36 -0
- package/dist/source-proof.js +70 -0
- package/dist/source-proof.js.map +1 -0
- package/dist/source-signature.d.ts +56 -0
- package/dist/source-signature.js +56 -0
- package/dist/source-signature.js.map +1 -0
- package/dist/source-witness.d.ts +15 -0
- package/dist/source-witness.js +29 -0
- package/dist/source-witness.js.map +1 -0
- package/dist/sqlite-durability.d.ts +31 -0
- package/dist/sqlite-durability.js +28 -0
- package/dist/sqlite-durability.js.map +1 -0
- package/dist/sqlite.d.ts +8 -2
- package/dist/sqlite.js +27 -4
- package/dist/sqlite.js.map +1 -1
- package/dist/sync-address.d.ts +19 -0
- package/dist/sync-address.js +52 -0
- package/dist/sync-address.js.map +1 -0
- package/dist/sync-setting.d.ts +15 -0
- package/dist/sync-setting.js +13 -0
- package/dist/sync-setting.js.map +1 -1
- package/dist/turns.d.ts +42 -0
- package/dist/turns.js +112 -0
- package/dist/turns.js.map +1 -0
- package/dist/updates/bootstrap.d.ts +53 -7
- package/dist/updates/bootstrap.js +224 -137
- package/dist/updates/bootstrap.js.map +1 -1
- package/dist/updates/controller.d.ts +8 -0
- package/dist/updates/controller.js +27 -3
- package/dist/updates/controller.js.map +1 -1
- package/dist/updates/store.d.ts +3 -0
- package/dist/updates/store.js +12 -0
- package/dist/updates/store.js.map +1 -1
- package/dist/user-ground-host.d.ts +364 -69
- package/dist/user-ground-host.js +5231 -3366
- package/dist/user-ground-host.js.map +1 -1
- package/dist/watching/fsevents.d.ts +52 -1
- package/dist/watching/fsevents.js +60 -25
- package/dist/watching/fsevents.js.map +1 -1
- package/dist/watching/host.d.ts +129 -5
- package/dist/watching/host.js +433 -80
- package/dist/watching/host.js.map +1 -1
- package/dist/watching/index.d.ts +1 -1
- package/dist/wire-binary.d.ts +3 -2
- package/dist/wire-binary.js +10 -8
- package/dist/wire-binary.js.map +1 -1
- package/dist/wire-client.d.ts +3 -4
- package/dist/wire-client.js +18 -44
- package/dist/wire-client.js.map +1 -1
- package/dist/wire-outbox.d.ts +34 -0
- package/dist/wire-outbox.js +127 -0
- package/dist/wire-outbox.js.map +1 -0
- package/dist/workspace-host.d.ts +4 -0
- package/dist/workspace-host.js +12 -1
- package/dist/workspace-host.js.map +1 -1
- package/examinations/fixtures/private-wire-authority.js +133 -10
- package/npm-shrinkwrap.json +43 -56
- package/package.json +26 -13
|
@@ -0,0 +1,1023 @@
|
|
|
1
|
+
/** Fixed SQLite facts used to authorize a later ground replacement. Source
|
|
2
|
+
* witnesses still prove disk; this read set proves the database basis.
|
|
3
|
+
*
|
|
4
|
+
* One counter orders every committed ground write. The write that changes a
|
|
5
|
+
* fact stamps that fact's last-change sequence in the same transaction, so a
|
|
6
|
+
* basis read at sequence S0 stays valid exactly while none of its facts has a
|
|
7
|
+
* stamp after S0. Within one epoch stamps are never deleted: a removed row, or
|
|
8
|
+
* a query answer that became empty, keeps its tombstone and still invalidates.
|
|
9
|
+
*
|
|
10
|
+
* Every basis and revision string lives in process memory, so none outlives
|
|
11
|
+
* its process. A process's first open of the ground file therefore begins a
|
|
12
|
+
* new epoch: a new incarnation retires every older basis and the stamps start
|
|
13
|
+
* empty. Which identities ever existed is a separate, permanent fact. */
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { deserialize, serialize } from "node:v8";
|
|
16
|
+
import { GROUND_ROW_PROJECTION, groundRowProjection, groundRowValues } from "./ground-row.js";
|
|
17
|
+
import { activeRepository, readSubtree, subtreePages, TerritoryHandle, TerritoryView } from "./ground-territory.js";
|
|
18
|
+
import { cachedStatement } from "./sqlite.js";
|
|
19
|
+
import { turnBoundary } from "./turns.js";
|
|
20
|
+
const INVENTORIES = ["entities", "detection_notebook"];
|
|
21
|
+
const key = (dependency) => JSON.stringify([dependency.inventory, dependency.aspect, dependency.id]);
|
|
22
|
+
const reading = new WeakSet();
|
|
23
|
+
export class GroundBasisChangedError extends Error {
|
|
24
|
+
}
|
|
25
|
+
/** The same phase owner applies to both the store and direct snapshot entry. */
|
|
26
|
+
export function assertGroundWriteAllowed(database) {
|
|
27
|
+
if (reading.has(database))
|
|
28
|
+
throw new Error("ground writes cannot run inside an observation snapshot");
|
|
29
|
+
}
|
|
30
|
+
function ensureGroundBasisSchema(database) {
|
|
31
|
+
database.exec(`
|
|
32
|
+
CREATE TABLE IF NOT EXISTS ground_incarnation (
|
|
33
|
+
singleton INTEGER PRIMARY KEY CHECK(singleton = 1), identity TEXT NOT NULL
|
|
34
|
+
);
|
|
35
|
+
CREATE TABLE IF NOT EXISTS ground_sequence (
|
|
36
|
+
singleton INTEGER PRIMARY KEY CHECK(singleton = 1), value INTEGER NOT NULL
|
|
37
|
+
);
|
|
38
|
+
CREATE TABLE IF NOT EXISTS ground_revisions (
|
|
39
|
+
inventory TEXT NOT NULL, aspect TEXT NOT NULL, identity TEXT NOT NULL, revision INTEGER NOT NULL,
|
|
40
|
+
PRIMARY KEY(inventory, aspect, identity)
|
|
41
|
+
) WITHOUT ROWID;
|
|
42
|
+
CREATE INDEX IF NOT EXISTS ground_revisions_by_revision ON ground_revisions(revision);
|
|
43
|
+
`);
|
|
44
|
+
database.prepare("INSERT OR IGNORE INTO ground_incarnation(singleton, identity) VALUES (1, ?)").run(randomUUID());
|
|
45
|
+
database.prepare("INSERT OR IGNORE INTO ground_sequence(singleton, value) VALUES (1, 0)").run();
|
|
46
|
+
// Unordered random revisions cannot be compared with a sequence. Every fact
|
|
47
|
+
// they name keeps its history at sequence 0 (identity history and
|
|
48
|
+
// tombstones survive), and the new incarnation retires every older basis.
|
|
49
|
+
if (database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'ground_dependency_revisions'").get()) {
|
|
50
|
+
database.transaction(() => {
|
|
51
|
+
database.exec(`INSERT OR IGNORE INTO ground_revisions(inventory, aspect, identity, revision)
|
|
52
|
+
SELECT inventory, aspect, identity, 0 FROM ground_dependency_revisions;
|
|
53
|
+
DROP TABLE ground_dependency_revisions;`);
|
|
54
|
+
database.prepare("UPDATE ground_incarnation SET identity = ? WHERE singleton = 1").run(randomUUID());
|
|
55
|
+
})();
|
|
56
|
+
}
|
|
57
|
+
// Every UUID ever removed from an inventory. Entity stamps were that record
|
|
58
|
+
// before this table: each one names a UUID that existed, so all of them
|
|
59
|
+
// are carried once, a superset that includes UUIDs still present.
|
|
60
|
+
if (!database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'ground_identity_history'").get()) {
|
|
61
|
+
database.transaction(() => {
|
|
62
|
+
database.exec(`CREATE TABLE ground_identity_history (
|
|
63
|
+
inventory TEXT NOT NULL, uuid TEXT NOT NULL, PRIMARY KEY(inventory, uuid)
|
|
64
|
+
) WITHOUT ROWID;
|
|
65
|
+
INSERT INTO ground_identity_history(inventory, uuid)
|
|
66
|
+
SELECT inventory, identity FROM ground_revisions WHERE aspect = 'entity';`);
|
|
67
|
+
})();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** A process's first open of the ground file. No basis or revision string of
|
|
71
|
+
* an earlier process can reach this one, so the stamps restart empty under a
|
|
72
|
+
* new incarnation; a basis read before this commit, by any connection, fails
|
|
73
|
+
* its incarnation check instead of reading a missing stamp as unchanged. The
|
|
74
|
+
* counter stays monotonic, so no S0 or sequence string repeats. */
|
|
75
|
+
export function beginGroundEpoch(database) {
|
|
76
|
+
ensureGroundBasisSchema(database);
|
|
77
|
+
database.transaction(() => {
|
|
78
|
+
database.prepare("UPDATE ground_incarnation SET identity = ? WHERE singleton = 1").run(randomUUID());
|
|
79
|
+
database.exec("DELETE FROM ground_revisions");
|
|
80
|
+
})();
|
|
81
|
+
}
|
|
82
|
+
/** One row by UUID, outside any basis: a read that proves nothing. */
|
|
83
|
+
export const readGroundRow = (database, inventory, uuid) => cachedStatement(database, `SELECT ${groundRowProjection(inventory)} FROM ${inventory} WHERE uuid = ?`).get(uuid);
|
|
84
|
+
/** The database incarnation that retires every basis read before a reset. */
|
|
85
|
+
export const groundIncarnation = (database) => cachedStatement(database, "SELECT identity FROM ground_incarnation WHERE singleton = 1").get().identity;
|
|
86
|
+
/** The last stamp any committed write issued: S0 for a read made with it. */
|
|
87
|
+
export const currentSequence = (database) => cachedStatement(database, "SELECT value FROM ground_sequence WHERE singleton = 1", true).get().value;
|
|
88
|
+
const revisionOf = (database, dependency) => cachedStatement(database, "SELECT revision FROM ground_revisions WHERE inventory = ? AND aspect = ? AND identity = ?", true)
|
|
89
|
+
.get(dependency.inventory, dependency.aspect, dependency.id)?.revision;
|
|
90
|
+
/** Every fact stamped after a sequence, with its stamp: one indexed range
|
|
91
|
+
* read, empty while nothing has been written since. */
|
|
92
|
+
const stampsAfter = (database, sequence) => cachedStatement(database, "SELECT inventory, aspect, identity, revision FROM ground_revisions WHERE revision > ?", true)
|
|
93
|
+
.all(sequence)
|
|
94
|
+
.map(row => ({ dependency: { inventory: row.inventory, aspect: row.aspect, id: row.identity }, revision: row.revision }));
|
|
95
|
+
/** The facts stamped after an open observation's S0, as of its latest read.
|
|
96
|
+
* Within an epoch a stamp only rises, so a fact once stamped after S0 stays
|
|
97
|
+
* so, and each late read adds only what was stamped since the read before.
|
|
98
|
+
* A late read then costs one range read over the writes since, however many
|
|
99
|
+
* facts it consumes, instead of a lookup per fact. */
|
|
100
|
+
class LaterStamps {
|
|
101
|
+
#keys = new Set();
|
|
102
|
+
#through;
|
|
103
|
+
constructor(sequence) { this.#through = sequence; }
|
|
104
|
+
/** Bring the set to the snapshot of the read transaction in progress. */
|
|
105
|
+
refresh(database) {
|
|
106
|
+
const current = currentSequence(database);
|
|
107
|
+
if (current <= this.#through)
|
|
108
|
+
return;
|
|
109
|
+
for (const { dependency } of stampsAfter(database, this.#through))
|
|
110
|
+
this.#keys.add(key(dependency));
|
|
111
|
+
this.#through = current;
|
|
112
|
+
}
|
|
113
|
+
has(id) { return this.#keys.has(id); }
|
|
114
|
+
}
|
|
115
|
+
/** One root's committed O/B state: equal exactly while no row of that root,
|
|
116
|
+
* in either inventory, has been written. */
|
|
117
|
+
export function rootRevision(database, rootUUID) {
|
|
118
|
+
return JSON.stringify([groundIncarnation(database), ...["entities", "detection_notebook"]
|
|
119
|
+
.map(inventory => revisionOf(database, { inventory, aspect: "inventory", id: rootUUID })?.toString() ?? null)]);
|
|
120
|
+
}
|
|
121
|
+
/** A database incarnation, the sequence S0 it was read at, and the facts it
|
|
122
|
+
* consumed. Only an open observation can accumulate facts; admission receives
|
|
123
|
+
* a closed basis. */
|
|
124
|
+
export class GroundBasis {
|
|
125
|
+
#incarnation;
|
|
126
|
+
#sequence;
|
|
127
|
+
#dependencies = new Map();
|
|
128
|
+
/** Rows read by identity, and whole repository subtrees, per inventory. */
|
|
129
|
+
#entities = { entities: new Set(), detection_notebook: new Set() };
|
|
130
|
+
#territories = { entities: [], detection_notebook: [] };
|
|
131
|
+
#inventories = new Set();
|
|
132
|
+
/** The activation each row read consumed with its answer. */
|
|
133
|
+
#activations = new Map();
|
|
134
|
+
constructor(incarnation, sequence) {
|
|
135
|
+
this.#incarnation = incarnation;
|
|
136
|
+
this.#sequence = sequence;
|
|
137
|
+
}
|
|
138
|
+
/** How many facts admission must check; the cost of this basis. */
|
|
139
|
+
get size() { return this.#dependencies.size; }
|
|
140
|
+
/** Plain evidence of this basis. It lists the facts of a basis small
|
|
141
|
+
* enough to check one by one and counts a larger one. */
|
|
142
|
+
describe() {
|
|
143
|
+
const facts = [...this.#dependencies.values()];
|
|
144
|
+
return Object.freeze({ database: this.#incarnation, sequence: this.#sequence.toString(), facts: facts.length,
|
|
145
|
+
listed: facts.length > POINT_LOOKUPS ? null
|
|
146
|
+
: Object.freeze(facts.map(({ inventory, aspect, id }) => Object.freeze([inventory, aspect, id]))) });
|
|
147
|
+
}
|
|
148
|
+
/** What a saver thread needs to accept this basis: its facts, and every
|
|
149
|
+
* row it may protect. */
|
|
150
|
+
toData() {
|
|
151
|
+
const protectedRows = (inventory) => [...new Set([...this.#entities[inventory], ...this.#territories[inventory].flatMap(uuids => [...uuids])])];
|
|
152
|
+
return { incarnation: this.#incarnation, sequence: this.#sequence, facts: [...this.#dependencies.keys()],
|
|
153
|
+
protected: { entities: protectedRows("entities"), detection_notebook: protectedRows("detection_notebook") },
|
|
154
|
+
inventories: [...this.#inventories] };
|
|
155
|
+
}
|
|
156
|
+
static fromData(data) {
|
|
157
|
+
const basis = new GroundBasis(data.incarnation, data.sequence);
|
|
158
|
+
for (const id of data.facts) {
|
|
159
|
+
const [inventory, aspect, fact] = JSON.parse(id);
|
|
160
|
+
basis.#dependencies.set(id, { inventory, aspect, id: fact });
|
|
161
|
+
}
|
|
162
|
+
for (const inventory of INVENTORIES)
|
|
163
|
+
for (const uuid of data.protected[inventory])
|
|
164
|
+
basis.#entities[inventory].add(uuid);
|
|
165
|
+
for (const inventory of data.inventories)
|
|
166
|
+
basis.#inventories.add(inventory);
|
|
167
|
+
return basis;
|
|
168
|
+
}
|
|
169
|
+
/** One basis consuming every part's facts and protecting every part's
|
|
170
|
+
* rows; all parts were read at one incarnation and S0. */
|
|
171
|
+
static union(parts) {
|
|
172
|
+
const [first] = parts;
|
|
173
|
+
if (!first)
|
|
174
|
+
throw new Error("a ground basis union needs a part");
|
|
175
|
+
const union = new GroundBasis(first.#incarnation, first.#sequence);
|
|
176
|
+
for (const part of parts) {
|
|
177
|
+
if (part.#incarnation !== first.#incarnation || part.#sequence !== first.#sequence) {
|
|
178
|
+
throw new Error("a ground basis union needs one incarnation and sequence");
|
|
179
|
+
}
|
|
180
|
+
for (const [id, dependency] of part.#dependencies)
|
|
181
|
+
union.#dependencies.set(id, dependency);
|
|
182
|
+
for (const inventory of INVENTORIES) {
|
|
183
|
+
for (const uuid of part.#entities[inventory])
|
|
184
|
+
union.#entities[inventory].add(uuid);
|
|
185
|
+
union.#territories[inventory].push(...part.#territories[inventory]);
|
|
186
|
+
}
|
|
187
|
+
for (const inventory of part.#inventories)
|
|
188
|
+
union.#inventories.add(inventory);
|
|
189
|
+
for (const [id, dependency] of part.#activations)
|
|
190
|
+
union.#activations.set(id, dependency);
|
|
191
|
+
}
|
|
192
|
+
return union;
|
|
193
|
+
}
|
|
194
|
+
static observe(database) {
|
|
195
|
+
let late;
|
|
196
|
+
let closed = false;
|
|
197
|
+
let failed = false;
|
|
198
|
+
let readingNow = false;
|
|
199
|
+
const assertOpen = () => {
|
|
200
|
+
if (closed || failed)
|
|
201
|
+
throw new Error("ground observation is closed or failed");
|
|
202
|
+
if (readingNow) {
|
|
203
|
+
failed = true;
|
|
204
|
+
throw new Error("ground observation cannot read or finish inside its callback");
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
return Object.freeze({
|
|
208
|
+
read(operation) {
|
|
209
|
+
assertOpen();
|
|
210
|
+
readingNow = true;
|
|
211
|
+
try {
|
|
212
|
+
const captured = GroundBasis.capture(database, operation, late);
|
|
213
|
+
if (failed)
|
|
214
|
+
throw new Error("ground observation failed during its callback");
|
|
215
|
+
late ??= { basis: captured.basis, stamps: new LaterStamps(captured.basis.#sequence) };
|
|
216
|
+
return captured.value;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
failed = true;
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
readingNow = false;
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
finish() {
|
|
227
|
+
assertOpen();
|
|
228
|
+
if (!late)
|
|
229
|
+
throw new Error("ground observation has no reads");
|
|
230
|
+
closed = true;
|
|
231
|
+
return late.basis;
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
static capture(database, operation, late) {
|
|
236
|
+
if (database.inTransaction)
|
|
237
|
+
throw new Error("ground observation snapshots require a separate read transaction");
|
|
238
|
+
drainReceipts(database);
|
|
239
|
+
const capture = database.transaction(() => {
|
|
240
|
+
if (late && groundIncarnation(database) !== late.basis.#incarnation) {
|
|
241
|
+
throw new GroundBasisChangedError("ground incarnation changed during observation");
|
|
242
|
+
}
|
|
243
|
+
late?.stamps.refresh(database);
|
|
244
|
+
const basis = late?.basis ?? new GroundBasis(groundIncarnation(database), currentSequence(database));
|
|
245
|
+
// Intern within one inventory. O and B have different evidence even
|
|
246
|
+
// when their common row fields happen to match.
|
|
247
|
+
const retained = new Map();
|
|
248
|
+
const retain = (inventory, row) => {
|
|
249
|
+
const key = `${inventory}:${row.uuid}`, previous = retained.get(key);
|
|
250
|
+
const values = groundRowValues(row, inventory);
|
|
251
|
+
if (previous && groundRowValues(previous, inventory).every((value, index) => value === values[index]))
|
|
252
|
+
return previous;
|
|
253
|
+
const frozen = Object.freeze(row);
|
|
254
|
+
retained.set(key, frozen);
|
|
255
|
+
return frozen;
|
|
256
|
+
};
|
|
257
|
+
let active = true;
|
|
258
|
+
// A first read is S0 itself. A late read recovers S0's answer only
|
|
259
|
+
// while each consumed fact is still unchanged since S0.
|
|
260
|
+
// A repeated late read is checked too: its answer is current, and it
|
|
261
|
+
// may stand for the original only while the fact is unchanged.
|
|
262
|
+
const remember = (dependency) => {
|
|
263
|
+
const id = key(dependency);
|
|
264
|
+
if (late?.stamps.has(id))
|
|
265
|
+
throw new GroundBasisChangedError("ground dependency changed after its observation sequence");
|
|
266
|
+
basis.#dependencies.set(id, dependency);
|
|
267
|
+
};
|
|
268
|
+
const assertActive = () => {
|
|
269
|
+
if (!active)
|
|
270
|
+
throw new Error("ground snapshot reader used outside its transaction");
|
|
271
|
+
};
|
|
272
|
+
// Read in the same transaction as the answer it qualifies.
|
|
273
|
+
const activated = (inventory, uuid, row) => {
|
|
274
|
+
const dependency = activation(inventory, row?.rootUUID ?? "*");
|
|
275
|
+
remember(dependency);
|
|
276
|
+
basis.#activations.set(JSON.stringify([inventory, uuid]), dependency);
|
|
277
|
+
};
|
|
278
|
+
const entity = (inventory, uuid) => {
|
|
279
|
+
assertActive();
|
|
280
|
+
remember({ inventory, aspect: "entity", id: uuid });
|
|
281
|
+
basis.#entities[inventory].add(uuid);
|
|
282
|
+
const row = readGroundRow(database, inventory, uuid);
|
|
283
|
+
activated(inventory, uuid, row);
|
|
284
|
+
return row && retain(inventory, row);
|
|
285
|
+
};
|
|
286
|
+
// A returned row's fields can change with its root's visible
|
|
287
|
+
// generation, whatever key found it.
|
|
288
|
+
const shown = (inventory, found) => {
|
|
289
|
+
for (const rootUUID of new Set(found.map(row => row.rootUUID)))
|
|
290
|
+
remember(activation(inventory, rootUUID));
|
|
291
|
+
};
|
|
292
|
+
const rows = (inventory, values) => {
|
|
293
|
+
shown(inventory, values);
|
|
294
|
+
return Object.freeze(values.map(row => {
|
|
295
|
+
remember({ inventory, aspect: "entity", id: row.uuid });
|
|
296
|
+
basis.#entities[inventory].add(row.uuid);
|
|
297
|
+
return retain(inventory, row);
|
|
298
|
+
}));
|
|
299
|
+
};
|
|
300
|
+
const reader = {
|
|
301
|
+
repositoryMembers() {
|
|
302
|
+
assertActive();
|
|
303
|
+
remember({ inventory: "entities", aspect: "repositories", id: "*" });
|
|
304
|
+
return effectiveRepositories(database);
|
|
305
|
+
},
|
|
306
|
+
physical: uuid => entity("entities", uuid),
|
|
307
|
+
sharing: uuid => entity("detection_notebook", uuid),
|
|
308
|
+
atPath(inventory, rootUUID, relativePath) {
|
|
309
|
+
assertActive();
|
|
310
|
+
remember({ inventory, aspect: "path", id: pathKey(rootUUID, relativePath) });
|
|
311
|
+
remember(activation(inventory, rootUUID));
|
|
312
|
+
return rows(inventory, cachedStatement(database, `SELECT ${groundRowProjection(inventory)} FROM ${inventory}
|
|
313
|
+
WHERE root_uuid = ? AND relative_path = ? ORDER BY uuid`).all(rootUUID, relativePath));
|
|
314
|
+
},
|
|
315
|
+
physicalCandidates(inventory, rootUUID, physicalKey) {
|
|
316
|
+
assertActive();
|
|
317
|
+
remember({ inventory, aspect: "physical", id: physicalAnswer(rootUUID, physicalKey) });
|
|
318
|
+
remember(activation(inventory, rootUUID));
|
|
319
|
+
return rows(inventory, cachedStatement(database, `SELECT ${groundRowProjection(inventory)} FROM ${inventory}
|
|
320
|
+
WHERE physical_key = ? AND root_uuid = ? ORDER BY uuid`).all(physicalKey, rootUUID));
|
|
321
|
+
},
|
|
322
|
+
objectRows(inventory, physicalKey) {
|
|
323
|
+
assertActive();
|
|
324
|
+
remember({ inventory, aspect: "object", id: physicalKey });
|
|
325
|
+
remember(activation(inventory, "*"));
|
|
326
|
+
return rows(inventory, cachedStatement(database, `SELECT ${groundRowProjection(inventory)} FROM ${inventory}
|
|
327
|
+
WHERE physical_key = ? ORDER BY uuid`).all(physicalKey));
|
|
328
|
+
},
|
|
329
|
+
subtree(inventory, uuid) {
|
|
330
|
+
assertActive();
|
|
331
|
+
const owned = reader.territory(inventory, uuid);
|
|
332
|
+
if (owned)
|
|
333
|
+
return owned.rows();
|
|
334
|
+
// Any other subtree is its members' complete child relations, each
|
|
335
|
+
// including an empty one; a new descendant invalidates its nearest
|
|
336
|
+
// old parent.
|
|
337
|
+
const values = cachedStatement(database, `WITH RECURSIVE descendants(uuid) AS (
|
|
338
|
+
SELECT uuid FROM ${inventory} WHERE uuid = ?
|
|
339
|
+
UNION SELECT child.uuid FROM ${inventory} AS child JOIN descendants ON child.parent_uuid = descendants.uuid
|
|
340
|
+
) SELECT ${groundRowProjection(inventory)} FROM ${inventory}
|
|
341
|
+
WHERE uuid IN (SELECT uuid FROM descendants) ORDER BY uuid`).all(uuid);
|
|
342
|
+
for (const row of values)
|
|
343
|
+
remember({ inventory, aspect: "namespace", id: row.uuid });
|
|
344
|
+
return rows(inventory, values);
|
|
345
|
+
},
|
|
346
|
+
territory(inventory, uuid) {
|
|
347
|
+
assertActive();
|
|
348
|
+
if (!activeRepository(entity(inventory, uuid)))
|
|
349
|
+
return undefined; // An absent root is itself evidence.
|
|
350
|
+
// A repository's subtree is exactly its territory and, for each
|
|
351
|
+
// active repository within it, that one's own row and territory.
|
|
352
|
+
const view = warmTerritory(database, inventory, uuid);
|
|
353
|
+
for (const fact of territoryFacts(view))
|
|
354
|
+
remember(fact);
|
|
355
|
+
return new TerritoryHandle(view, {
|
|
356
|
+
read(row) {
|
|
357
|
+
basis.#entities[inventory].add(row.uuid);
|
|
358
|
+
basis.#activations.set(JSON.stringify([inventory, row.uuid]), activation(inventory, row.rootUUID));
|
|
359
|
+
},
|
|
360
|
+
readAll(uuids) { basis.#territories[inventory].push(uuids); },
|
|
361
|
+
moved: () => new GroundBasisChangedError("territory view moved after its observation"),
|
|
362
|
+
});
|
|
363
|
+
},
|
|
364
|
+
rootsAtPath(inventory, absolutePath) {
|
|
365
|
+
assertActive();
|
|
366
|
+
remember({ inventory, aspect: "roots", id: absolutePath });
|
|
367
|
+
const found = cachedStatement(database, `SELECT ${GROUND_ROW_PROJECTION} FROM ${inventory}
|
|
368
|
+
WHERE absolute_path = ? AND relative_path = '' AND status = 'active' ORDER BY uuid`).all(absolutePath);
|
|
369
|
+
for (const row of found)
|
|
370
|
+
remember({ inventory, aspect: "structure", id: row.uuid });
|
|
371
|
+
shown(inventory, found);
|
|
372
|
+
return Object.freeze(found.map(row => Object.freeze(structure(inventory, row))));
|
|
373
|
+
},
|
|
374
|
+
structure(inventory, uuid) {
|
|
375
|
+
assertActive();
|
|
376
|
+
remember({ inventory, aspect: "structure", id: uuid });
|
|
377
|
+
const row = cachedStatement(database, `SELECT ${GROUND_ROW_PROJECTION} FROM ${inventory} WHERE uuid = ?`).get(uuid);
|
|
378
|
+
activated(inventory, uuid, row);
|
|
379
|
+
return row && Object.freeze(structure(inventory, row));
|
|
380
|
+
},
|
|
381
|
+
children(inventory, parentUUID) {
|
|
382
|
+
assertActive();
|
|
383
|
+
reader.structure(inventory, parentUUID);
|
|
384
|
+
remember({ inventory, aspect: "namespace", id: parentUUID });
|
|
385
|
+
const rows = cachedStatement(database, `SELECT ${NAMESPACE_PROJECTION}
|
|
386
|
+
FROM ${inventory} WHERE parent_uuid = ? ORDER BY uuid`)
|
|
387
|
+
.all(parentUUID);
|
|
388
|
+
shown(inventory, rows);
|
|
389
|
+
return Object.freeze(rows.map(row => Object.freeze(row)));
|
|
390
|
+
},
|
|
391
|
+
slot(inventory, parentUUID, name) {
|
|
392
|
+
assertActive();
|
|
393
|
+
reader.structure(inventory, parentUUID);
|
|
394
|
+
remember({ inventory, aspect: "slot", id: slotKey(parentUUID, name) });
|
|
395
|
+
const rows = cachedStatement(database, `SELECT ${NAMESPACE_PROJECTION}
|
|
396
|
+
FROM ${inventory} WHERE parent_uuid = ? AND name = ? ORDER BY uuid`)
|
|
397
|
+
.all(parentUUID, name);
|
|
398
|
+
shown(inventory, rows);
|
|
399
|
+
return Object.freeze(rows.map(row => Object.freeze(row)));
|
|
400
|
+
},
|
|
401
|
+
members(inventory, parentUUID) {
|
|
402
|
+
assertActive();
|
|
403
|
+
entity(inventory, parentUUID); // An absent container is itself evidence.
|
|
404
|
+
remember({ inventory, aspect: "namespace", id: parentUUID });
|
|
405
|
+
return rows(inventory, cachedStatement(database, `SELECT ${groundRowProjection(inventory)} FROM ${inventory}
|
|
406
|
+
WHERE parent_uuid = ? ORDER BY uuid`).all(parentUUID));
|
|
407
|
+
},
|
|
408
|
+
inventory(inventory, rootUUID) {
|
|
409
|
+
assertActive();
|
|
410
|
+
remember({ inventory, aspect: "inventory", id: rootUUID ?? "*" });
|
|
411
|
+
basis.#inventories.add(JSON.stringify([inventory, rootUUID]));
|
|
412
|
+
const sql = `SELECT ${groundRowProjection(inventory)} FROM ${inventory}`;
|
|
413
|
+
const cursor = (rootUUID === null ? cachedStatement(database, sql).iterate()
|
|
414
|
+
: cachedStatement(database, `${sql} WHERE root_uuid = ?`).iterate(rootUUID));
|
|
415
|
+
const rows = [];
|
|
416
|
+
for (const row of cursor)
|
|
417
|
+
rows.push(retain(inventory, row));
|
|
418
|
+
return Object.freeze(rows);
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
try {
|
|
422
|
+
const value = operation(reader);
|
|
423
|
+
if (value && typeof value.then === "function")
|
|
424
|
+
throw new Error("ground snapshots cannot contain asynchronous work");
|
|
425
|
+
return { value, basis };
|
|
426
|
+
}
|
|
427
|
+
finally {
|
|
428
|
+
active = false;
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
reading.add(database);
|
|
432
|
+
try {
|
|
433
|
+
return capture();
|
|
434
|
+
}
|
|
435
|
+
finally {
|
|
436
|
+
reading.delete(database);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/** Same incarnation, and no consumed fact stamped after S0. Never compares
|
|
440
|
+
* the global counter with S0: unrelated writes do not invalidate. A small
|
|
441
|
+
* basis looks up each fact; a large one intersects the few facts stamped
|
|
442
|
+
* since S0, so the check is bounded by the smaller side. */
|
|
443
|
+
unchanged(database) {
|
|
444
|
+
if (groundIncarnation(database) !== this.#incarnation)
|
|
445
|
+
return false;
|
|
446
|
+
if (this.#dependencies.size <= POINT_LOOKUPS) {
|
|
447
|
+
for (const dependency of this.#dependencies.values()) {
|
|
448
|
+
const stamp = revisionOf(database, dependency);
|
|
449
|
+
if (stamp !== undefined && stamp > this.#sequence)
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
return !stampsAfter(database, this.#sequence).some(({ dependency }) => this.#dependencies.has(key(dependency)));
|
|
455
|
+
}
|
|
456
|
+
/** Every fact of this basis is a fact of one of these, read against the
|
|
457
|
+
* same incarnation and S0, so their unchanged answers are its answer. No
|
|
458
|
+
* expectation is merged: all of them are S0. */
|
|
459
|
+
within(parts) {
|
|
460
|
+
const same = parts.filter(part => part.#incarnation === this.#incarnation && part.#sequence === this.#sequence);
|
|
461
|
+
for (const id of this.#dependencies.keys())
|
|
462
|
+
if (!same.some(part => part.#dependencies.has(id)))
|
|
463
|
+
return false;
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
/** Entities of one inventory written by anyone after S0, and roots whose
|
|
467
|
+
* visible generation changed after S0, each with the sequence that last
|
|
468
|
+
* stamped it. Each such root is a responsibility of its own: its rows can
|
|
469
|
+
* change without their own stamps. */
|
|
470
|
+
changedSince(database, inventory) {
|
|
471
|
+
if (groundIncarnation(database) !== this.#incarnation)
|
|
472
|
+
throw new GroundBasisChangedError("ground incarnation changed");
|
|
473
|
+
const stamped = stampsAfter(database, this.#sequence).filter(({ dependency }) => dependency.inventory === inventory);
|
|
474
|
+
return {
|
|
475
|
+
entities: stamped.filter(({ dependency }) => dependency.aspect === "entity").map(({ dependency, revision }) => [dependency.id, revision]),
|
|
476
|
+
roots: stamped.filter(({ dependency: { aspect, id } }) => aspect === "activation" && id !== "*")
|
|
477
|
+
.map(({ dependency, revision }) => [dependency.id, revision]),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
/** One admission unit's basis: the same incarnation and S0, over only the
|
|
481
|
+
* facts that unit consumed. Its answers derive from the S0 snapshot, so a
|
|
482
|
+
* fact unchanged since S0 is exactly the validity condition. An entity is
|
|
483
|
+
* protected only when the original observation could protect it; a
|
|
484
|
+
* scoped basis never widens allocation authority. */
|
|
485
|
+
scope(build) {
|
|
486
|
+
const scoped = new GroundBasis(this.#incarnation, this.#sequence);
|
|
487
|
+
const add = (dependency) => { scoped.#dependencies.set(key(dependency), dependency); };
|
|
488
|
+
let open = true;
|
|
489
|
+
const guard = (operation) => (...values) => {
|
|
490
|
+
if (!open)
|
|
491
|
+
throw new Error("a ground scope cannot grow after it is built");
|
|
492
|
+
operation(...values);
|
|
493
|
+
};
|
|
494
|
+
// A row fact carries the activation its original read consumed; one the
|
|
495
|
+
// original never read by identity consumes the widest.
|
|
496
|
+
const consumed = (inventory, uuid) => this.#activations.get(JSON.stringify([inventory, uuid])) ?? activation(inventory, "*");
|
|
497
|
+
build(Object.freeze({
|
|
498
|
+
entity: guard((inventory, uuid, before) => {
|
|
499
|
+
add({ inventory, aspect: "entity", id: uuid });
|
|
500
|
+
add(activation(inventory, before?.rootUUID ?? "*"));
|
|
501
|
+
if (this.protects(inventory, uuid, before))
|
|
502
|
+
scoped.#entities[inventory].add(uuid);
|
|
503
|
+
}),
|
|
504
|
+
structure: guard((inventory, uuid) => {
|
|
505
|
+
add({ inventory, aspect: "structure", id: uuid });
|
|
506
|
+
add(consumed(inventory, uuid));
|
|
507
|
+
}),
|
|
508
|
+
namespace: guard((inventory, uuid) => {
|
|
509
|
+
add({ inventory, aspect: "namespace", id: uuid });
|
|
510
|
+
add(consumed(inventory, uuid));
|
|
511
|
+
}),
|
|
512
|
+
path: guard((inventory, rootUUID, relativePath) => {
|
|
513
|
+
add({ inventory, aspect: "path", id: pathKey(rootUUID, relativePath) });
|
|
514
|
+
add(activation(inventory, rootUUID));
|
|
515
|
+
}),
|
|
516
|
+
physical: guard((inventory, rootUUID, physicalKey) => {
|
|
517
|
+
add({ inventory, aspect: "physical", id: physicalAnswer(rootUUID, physicalKey) });
|
|
518
|
+
add(activation(inventory, rootUUID));
|
|
519
|
+
}),
|
|
520
|
+
roots: guard((inventory, absolutePath) => add({ inventory, aspect: "roots", id: absolutePath })),
|
|
521
|
+
repositories: guard(() => add({ inventory: "entities", aspect: "repositories", id: "*" })),
|
|
522
|
+
territory: guard((inventory, uuid) => {
|
|
523
|
+
add(territory(inventory, uuid));
|
|
524
|
+
add(consumed(inventory, uuid));
|
|
525
|
+
}),
|
|
526
|
+
}));
|
|
527
|
+
open = false;
|
|
528
|
+
return scoped;
|
|
529
|
+
}
|
|
530
|
+
/** A scoped inventory protects its existing rows. A proposed destination
|
|
531
|
+
* cannot prove that a missing UUID was originally absent everywhere. */
|
|
532
|
+
protects(inventory, uuid, before) {
|
|
533
|
+
return this.#entities[inventory].has(uuid) || this.#territories[inventory].some(uuids => uuids.has(uuid))
|
|
534
|
+
|| this.#inventories.has(JSON.stringify([inventory, null]))
|
|
535
|
+
|| (before !== undefined && this.includesInventory(inventory, before.rootUUID));
|
|
536
|
+
}
|
|
537
|
+
includesInventory(inventory, rootUUID) {
|
|
538
|
+
return this.#inventories.has(JSON.stringify([inventory, null]))
|
|
539
|
+
|| this.#inventories.has(JSON.stringify([inventory, rootUUID]));
|
|
540
|
+
}
|
|
541
|
+
/** May place a row this inventory never held: its root inventory, or its
|
|
542
|
+
* exact destination slot, was observed free. Identity history remains a
|
|
543
|
+
* separate proof; revision <= S0 never authorizes resurrection. */
|
|
544
|
+
allocates(inventory, after) {
|
|
545
|
+
return this.includesInventory(inventory, after.rootUUID)
|
|
546
|
+
|| this.#dependencies.has(key({ inventory, aspect: "path", id: pathKey(after.rootUUID, after.relativePath) }));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const POINT_LOOKUPS = 64;
|
|
550
|
+
const pathKey = (rootUUID, relativePath) => JSON.stringify([rootUUID, relativePath]);
|
|
551
|
+
const physicalAnswer = (rootUUID, physicalKey) => JSON.stringify([rootUUID, physicalKey]);
|
|
552
|
+
const slotKey = (parentUUID, name) => JSON.stringify([parentUUID, name]);
|
|
553
|
+
const NAMESPACE_PROJECTION = `uuid, resource_id AS resourceId, root_uuid AS rootUUID, parent_uuid AS parentUUID,
|
|
554
|
+
name, type, status, relative_path AS relativePath, absolute_path AS absolutePath`;
|
|
555
|
+
const activation = (inventory, rootUUID) => ({ inventory, aspect: "activation", id: rootUUID });
|
|
556
|
+
const territory = (inventory, uuid) => ({ inventory, aspect: "territory", id: uuid });
|
|
557
|
+
/** The write changed an active repository's own head only; its placement in
|
|
558
|
+
* its owner's package, and so that territory, is unchanged. */
|
|
559
|
+
export const repositoryHeadOnly = (before, after) => activeRepository(before) && activeRepository(after) && before.filesystemMode === after.filesystemMode
|
|
560
|
+
&& equal(namespace(before), namespace(after));
|
|
561
|
+
/** The nearest active repository strictly above each identity in one graph,
|
|
562
|
+
* memoized and safe on cycles: the owner whose territory the identity is. */
|
|
563
|
+
export function repositoryOwners(lookup) {
|
|
564
|
+
// The nearest active repository at or above each walked identity.
|
|
565
|
+
const memo = new Map();
|
|
566
|
+
return (uuid) => {
|
|
567
|
+
const chain = new Set();
|
|
568
|
+
let owner = null;
|
|
569
|
+
for (let current = lookup(uuid)?.parentUUID ?? null; current !== null;) {
|
|
570
|
+
if (memo.has(current)) {
|
|
571
|
+
owner = memo.get(current);
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
const row = lookup(current);
|
|
575
|
+
if (!row || chain.has(current))
|
|
576
|
+
break;
|
|
577
|
+
if (activeRepository(row)) {
|
|
578
|
+
owner = current;
|
|
579
|
+
break;
|
|
580
|
+
}
|
|
581
|
+
chain.add(current);
|
|
582
|
+
current = row.parentUUID;
|
|
583
|
+
}
|
|
584
|
+
for (const walked of chain)
|
|
585
|
+
memo.set(walked, owner);
|
|
586
|
+
if (owner !== null)
|
|
587
|
+
memo.set(owner, owner);
|
|
588
|
+
return owner;
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
const namespace = (row) => row && ({
|
|
592
|
+
uuid: row.uuid, resourceId: row.resourceId, rootUUID: row.rootUUID, parentUUID: row.parentUUID,
|
|
593
|
+
name: row.name, type: row.type, status: row.status, relativePath: row.relativePath, absolutePath: row.absolutePath,
|
|
594
|
+
});
|
|
595
|
+
const structure = (inventory, row) => row && ({
|
|
596
|
+
...namespace(row),
|
|
597
|
+
association: inventory === "entities" ? Object.freeze({
|
|
598
|
+
physicalKey: row.physicalKey, kind: (row.filesystemMode ?? 0) & 0o170000,
|
|
599
|
+
}) : null,
|
|
600
|
+
});
|
|
601
|
+
const equal = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
602
|
+
/** Each query answer a row belongs to, keyed exactly by the query arguments
|
|
603
|
+
* that would return it. A row entering or leaving an answer stamps it. */
|
|
604
|
+
function answers(inventory, row) {
|
|
605
|
+
if (!row)
|
|
606
|
+
return [];
|
|
607
|
+
const found = [{ inventory, aspect: "path", id: pathKey(row.rootUUID, row.relativePath) }];
|
|
608
|
+
if (row.physicalKey !== null && row.physicalKey !== undefined) {
|
|
609
|
+
found.push({ inventory, aspect: "physical", id: physicalAnswer(row.rootUUID, row.physicalKey) }, { inventory, aspect: "object", id: row.physicalKey });
|
|
610
|
+
}
|
|
611
|
+
if (row.relativePath === "" && row.status === "active")
|
|
612
|
+
found.push({ inventory, aspect: "roots", id: row.absolutePath });
|
|
613
|
+
return found;
|
|
614
|
+
}
|
|
615
|
+
/** A row's place in the repository membership answer: an active repository's
|
|
616
|
+
* identity and address. Its state never enters that answer. */
|
|
617
|
+
const member = (inventory, row) => inventory === "entities" && row?.type === "repo.git" && row.status === "active"
|
|
618
|
+
? [row.uuid, row.rootUUID, row.absolutePath] : null;
|
|
619
|
+
/** A row's place in the ancestry that decides effective activity. */
|
|
620
|
+
const container = (inventory, row) => inventory === "entities" && row && ["workspace", "folder", "repo.git"].includes(row.type)
|
|
621
|
+
? [row.type, row.status, row.parentUUID] : null;
|
|
622
|
+
/** Active repositories with no inactive ancestor. The answer is a function of
|
|
623
|
+
* repository identities and container ancestry, so it is derived once per
|
|
624
|
+
* pair of their stamps and reused while neither has advanced. The climb
|
|
625
|
+
* carries each ancestor's status, so it reads only repositories and their
|
|
626
|
+
* ancestors by key, never the whole inventory. */
|
|
627
|
+
const effectiveMembers = new WeakMap();
|
|
628
|
+
function effectiveRepositories(database) {
|
|
629
|
+
const witness = JSON.stringify([groundIncarnation(database), ...["repositories", "containers"]
|
|
630
|
+
.map(aspect => revisionOf(database, { inventory: "entities", aspect, id: "*" })?.toString() ?? null)]);
|
|
631
|
+
const known = effectiveMembers.get(database);
|
|
632
|
+
if (known?.witness === witness)
|
|
633
|
+
return known.members;
|
|
634
|
+
const members = Object.freeze(cachedStatement(database, `WITH RECURSIVE ancestry(repository, ancestor, inactive) AS (
|
|
635
|
+
SELECT uuid, parent_uuid, 0 FROM entities WHERE type = 'repo.git' AND status = 'active'
|
|
636
|
+
UNION SELECT ancestry.repository, entities.parent_uuid, entities.status <> 'active'
|
|
637
|
+
FROM ancestry JOIN entities ON entities.uuid = ancestry.ancestor WHERE NOT ancestry.inactive
|
|
638
|
+
)
|
|
639
|
+
SELECT uuid, root_uuid AS rootUUID, absolute_path AS absolutePath FROM entities
|
|
640
|
+
WHERE type = 'repo.git' AND status = 'active'
|
|
641
|
+
AND uuid NOT IN (SELECT repository FROM ancestry WHERE inactive)
|
|
642
|
+
ORDER BY uuid`).all().map(member => Object.freeze(member)));
|
|
643
|
+
effectiveMembers.set(database, { witness, members });
|
|
644
|
+
return members;
|
|
645
|
+
}
|
|
646
|
+
/** The facts a repository's subtree consumes: the top's row and territory,
|
|
647
|
+
* each active repository's row and territory beneath it, and the visible
|
|
648
|
+
* generation of every root its rows belong to. */
|
|
649
|
+
const territoryFacts = (view) => [
|
|
650
|
+
{ inventory: view.inventory, aspect: "entity", id: view.uuid }, territory(view.inventory, view.uuid),
|
|
651
|
+
...[...view.nested].flatMap((uuid) => [{ inventory: view.inventory, aspect: "entity", id: uuid },
|
|
652
|
+
territory(view.inventory, uuid)]),
|
|
653
|
+
...[...view.roots].map(rootUUID => activation(view.inventory, rootUUID)),
|
|
654
|
+
];
|
|
655
|
+
const stampedAfter = (database, dependency, sequence) => {
|
|
656
|
+
const stamp = revisionOf(database, dependency);
|
|
657
|
+
return stamp !== undefined && stamp > sequence;
|
|
658
|
+
};
|
|
659
|
+
/** Warm repository subtrees per ground file, most recently read last, each
|
|
660
|
+
* with the sequence it is exact at. A few cover the repositories a machine
|
|
661
|
+
* edits at once; any other read reloads, which is always exact. Only the
|
|
662
|
+
* main thread keeps them; every write it does not make itself reaches them
|
|
663
|
+
* as a saver receipt, in sequence order. */
|
|
664
|
+
const WARM_TERRITORIES = 16;
|
|
665
|
+
const warmFiles = new Map();
|
|
666
|
+
/** Inside a snapshot: the kept view while none of its facts is stamped after
|
|
667
|
+
* its sequence, since then its answer is this snapshot's; else a reload. */
|
|
668
|
+
function warmTerritory(database, inventory, uuid) {
|
|
669
|
+
const id = JSON.stringify([inventory, uuid]);
|
|
670
|
+
return keptTerritory(database, id) ?? keepTerritory(database, id, { incarnation: groundIncarnation(database),
|
|
671
|
+
sequence: currentSequence(database), view: TerritoryView.load(database, inventory, uuid) });
|
|
672
|
+
}
|
|
673
|
+
/** Inside a read: the kept view, now the most recently read and exact at this
|
|
674
|
+
* read's sequence, while none of its facts was stamped since; else none. */
|
|
675
|
+
function keptTerritory(database, id) {
|
|
676
|
+
if (!database.inTransaction)
|
|
677
|
+
throw new Error("a kept territory is judged inside one read");
|
|
678
|
+
const warm = warmFiles.get(database.name), known = warm?.get(id);
|
|
679
|
+
if (!known)
|
|
680
|
+
return undefined;
|
|
681
|
+
warm.delete(id);
|
|
682
|
+
if (known.incarnation !== groundIncarnation(database)
|
|
683
|
+
|| territoryFacts(known.view).some(fact => stampedAfter(database, fact, known.sequence))) {
|
|
684
|
+
known.view.retire();
|
|
685
|
+
return undefined;
|
|
686
|
+
}
|
|
687
|
+
warm.set(id, known);
|
|
688
|
+
known.sequence = currentSequence(database);
|
|
689
|
+
return known.view;
|
|
690
|
+
}
|
|
691
|
+
function keepTerritory(database, id, kept) {
|
|
692
|
+
let warm = warmFiles.get(database.name);
|
|
693
|
+
if (!warm)
|
|
694
|
+
warmFiles.set(database.name, warm = new Map());
|
|
695
|
+
warm.get(id)?.view.retire();
|
|
696
|
+
warm.delete(id);
|
|
697
|
+
warm.set(id, kept);
|
|
698
|
+
if (warm.size > WARM_TERRITORIES) {
|
|
699
|
+
const [oldest, evicted] = warm.entries().next().value;
|
|
700
|
+
warm.delete(oldest);
|
|
701
|
+
evicted.view.retire();
|
|
702
|
+
}
|
|
703
|
+
return kept.view;
|
|
704
|
+
}
|
|
705
|
+
/** Every warm view of one file stops: its receipts may be missing, or the
|
|
706
|
+
* file closed. */
|
|
707
|
+
export function retireGroundViews(file) {
|
|
708
|
+
for (const { view } of warmFiles.get(file)?.values() ?? [])
|
|
709
|
+
view.retire();
|
|
710
|
+
warmFiles.delete(file);
|
|
711
|
+
}
|
|
712
|
+
/** On the saver's own connection: every stamping write's receipt. */
|
|
713
|
+
const receiptSinks = new WeakMap();
|
|
714
|
+
export function saveGroundReceipts(database, sink) {
|
|
715
|
+
receiptSinks.set(database, sink);
|
|
716
|
+
}
|
|
717
|
+
/** On the main thread: each file's receipts already sent, taken now. A read
|
|
718
|
+
* drains before its snapshot, never inside it, so no view runs ahead of the
|
|
719
|
+
* snapshot that uses it; a write drains inside its own transaction, which
|
|
720
|
+
* already sees every committed receipt. */
|
|
721
|
+
const receiptSources = new Map();
|
|
722
|
+
export function registerReceiptSource(file, drain) {
|
|
723
|
+
if (drain)
|
|
724
|
+
receiptSources.set(file, drain);
|
|
725
|
+
else
|
|
726
|
+
receiptSources.delete(file);
|
|
727
|
+
}
|
|
728
|
+
const drainReceipts = (database) => receiptSources.get(database.name)?.();
|
|
729
|
+
/** Receipts arrive in sequence order, and the main thread's own writes carry
|
|
730
|
+
* its views synchronously, so a view exact at an earlier sequence is exact
|
|
731
|
+
* just before this one. A view this write touches is carried from the rows
|
|
732
|
+
* it stored; one it cannot carry is dropped. */
|
|
733
|
+
export function carryGroundReceipt(file, receipt) {
|
|
734
|
+
const warm = warmFiles.get(file);
|
|
735
|
+
if (!warm || receipt.stamped.length === 0)
|
|
736
|
+
return;
|
|
737
|
+
const stamped = new Set(receipt.stamped);
|
|
738
|
+
const decoded = new Map();
|
|
739
|
+
const writtenIn = (inventory) => {
|
|
740
|
+
let rows = decoded.get(inventory);
|
|
741
|
+
if (!rows) {
|
|
742
|
+
const bytes = receipt.written[inventory];
|
|
743
|
+
const entries = bytes ? deserialize(bytes) : [];
|
|
744
|
+
decoded.set(inventory, rows = new Map(entries.map(([uuid, row, children]) => [uuid, { row, children }])));
|
|
745
|
+
}
|
|
746
|
+
return rows;
|
|
747
|
+
};
|
|
748
|
+
for (const [id, known] of warm) {
|
|
749
|
+
if (receipt.sequence <= known.sequence)
|
|
750
|
+
continue;
|
|
751
|
+
const facts = territoryFacts(known.view);
|
|
752
|
+
if (!facts.some(fact => stamped.has(key(fact))))
|
|
753
|
+
continue;
|
|
754
|
+
const written = writtenIn(known.view.inventory);
|
|
755
|
+
const plan = known.incarnation === receipt.incarnation
|
|
756
|
+
&& !facts.some(fact => fact.aspect === "activation" && stamped.has(key(fact)))
|
|
757
|
+
? known.view.plan(uuid => written.get(uuid)?.row ?? undefined, uuid => written.get(uuid)?.children ?? undefined, written.keys())
|
|
758
|
+
: null;
|
|
759
|
+
if (plan) {
|
|
760
|
+
known.view.apply(plan);
|
|
761
|
+
known.sequence = receipt.sequence;
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
warm.delete(id);
|
|
765
|
+
known.view.retire();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/** On a content thread's read-only connection. */
|
|
770
|
+
export function readTerritories(database, uuid, inventories) {
|
|
771
|
+
const read = database.transaction(() => ({ incarnation: groundIncarnation(database), sequence: currentSequence(database),
|
|
772
|
+
subtrees: inventories.map(inventory => readSubtree(database, inventory, uuid)) }))();
|
|
773
|
+
return { ...read, subtrees: read.subtrees.map(rows => rows && subtreePages(rows)) };
|
|
774
|
+
}
|
|
775
|
+
/** Outside every snapshot, before one reads these repositories: the views no
|
|
776
|
+
* snapshot would find exact are read off this thread (`read`), built a page
|
|
777
|
+
* per step, and kept once a short read finds them exact now; each pair's
|
|
778
|
+
* agreement is compared a few rows per step. A view some write moved after
|
|
779
|
+
* its read is read again, a few times at most; one still moving is left to
|
|
780
|
+
* the snapshot, which reads it inside itself. */
|
|
781
|
+
export async function prepareTerritories(database, repositories, read) {
|
|
782
|
+
for (const uuid of repositories) {
|
|
783
|
+
const views = new Map();
|
|
784
|
+
for (let attempt = 0; attempt < PREPARE_ATTEMPTS; attempt++) {
|
|
785
|
+
drainReceipts(database);
|
|
786
|
+
for (const inventory of INVENTORIES) {
|
|
787
|
+
const view = views.get(inventory) ?? currentTerritory(database, JSON.stringify([inventory, uuid]));
|
|
788
|
+
if (view)
|
|
789
|
+
views.set(inventory, view);
|
|
790
|
+
}
|
|
791
|
+
const missing = INVENTORIES.filter(inventory => !views.has(inventory));
|
|
792
|
+
if (!missing.length)
|
|
793
|
+
break;
|
|
794
|
+
const { incarnation, sequence, subtrees } = await read(uuid, missing);
|
|
795
|
+
let absent = 0;
|
|
796
|
+
for (const [at, inventory] of missing.entries()) {
|
|
797
|
+
// No active repository: the snapshot answers that itself.
|
|
798
|
+
if (!subtrees[at]) {
|
|
799
|
+
absent += 1;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
const building = TerritoryView.building(inventory, uuid, subtrees[at]);
|
|
803
|
+
let step = building.next();
|
|
804
|
+
for (; !step.done; step = building.next())
|
|
805
|
+
await turnBoundary();
|
|
806
|
+
const id = JSON.stringify([inventory, uuid]);
|
|
807
|
+
drainReceipts(database);
|
|
808
|
+
if (!currentTerritory(database, id))
|
|
809
|
+
keepTerritory(database, id, { incarnation, sequence, view: step.value });
|
|
810
|
+
}
|
|
811
|
+
if (absent === missing.length)
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
const o = views.get("entities"), b = views.get("detection_notebook");
|
|
815
|
+
if (!o || !b)
|
|
816
|
+
continue;
|
|
817
|
+
const agreeing = TerritoryView.agreeing(o, b);
|
|
818
|
+
while (!agreeing.next().done)
|
|
819
|
+
await turnBoundary();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const PREPARE_ATTEMPTS = 3;
|
|
823
|
+
/** Outside every snapshot: the kept view when it is exact now, in one short read. */
|
|
824
|
+
const currentTerritory = (database, id) => database.transaction(() => keptTerritory(database, id))();
|
|
825
|
+
/** Inside a write, after its rows and before its stamps. A view whose facts
|
|
826
|
+
* this write stamps is carried to the write's sequence only by an outermost
|
|
827
|
+
* commit, only when it was exact before the write, and only from the rows
|
|
828
|
+
* the write stored; any other such view is dropped. A savepoint cannot
|
|
829
|
+
* certify its commit, and a changed visible generation is no row change.
|
|
830
|
+
* The returned step runs after the commit and never before it. */
|
|
831
|
+
function carryTerritories(database, stamped, written, outermost) {
|
|
832
|
+
drainReceipts(database);
|
|
833
|
+
const warm = warmFiles.get(database.name);
|
|
834
|
+
if (!warm)
|
|
835
|
+
return () => { };
|
|
836
|
+
const incarnation = groundIncarnation(database);
|
|
837
|
+
const stored = new Map();
|
|
838
|
+
const carried = [];
|
|
839
|
+
for (const [id, known] of warm) {
|
|
840
|
+
const facts = territoryFacts(known.view);
|
|
841
|
+
if (!facts.some(fact => stamped.has(key(fact))))
|
|
842
|
+
continue;
|
|
843
|
+
const { inventory } = known.view;
|
|
844
|
+
const plan = outermost && known.incarnation === incarnation
|
|
845
|
+
&& !facts.some(fact => fact.aspect === "activation" && stamped.has(key(fact)))
|
|
846
|
+
&& !facts.some(fact => stampedAfter(database, fact, known.sequence))
|
|
847
|
+
? known.view.plan(uuid => {
|
|
848
|
+
const id = JSON.stringify([inventory, uuid]);
|
|
849
|
+
if (!stored.has(id))
|
|
850
|
+
stored.set(id, readGroundRow(database, inventory, uuid));
|
|
851
|
+
return stored.get(id);
|
|
852
|
+
}, uuid => cachedStatement(database, `SELECT uuid FROM ${inventory} WHERE parent_uuid = ?`).pluck().all(uuid), written.filter(change => change.inventory === inventory).map(({ before, after }) => (after ?? before).uuid))
|
|
853
|
+
: null;
|
|
854
|
+
if (plan)
|
|
855
|
+
carried.push({ known, plan });
|
|
856
|
+
else {
|
|
857
|
+
warm.delete(id);
|
|
858
|
+
known.view.retire();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return sequence => {
|
|
862
|
+
for (const { known, plan } of carried) {
|
|
863
|
+
known.view.apply(plan);
|
|
864
|
+
known.sequence = sequence;
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
/** Every fact a change of this root's visible generation can change without
|
|
869
|
+
* a member write, in both inventories, read inside the changing transaction
|
|
870
|
+
* after the change: the root's own activation and aggregates, the roots
|
|
871
|
+
* answers its root rows supply, the namespace and slot their portable
|
|
872
|
+
* parents list them in, and repository membership when it holds a repository. Ancestry
|
|
873
|
+
* is stamped as a whole. It names the answers the root now supplies; a
|
|
874
|
+
* change that also hides stored rows must add the answers they supplied. */
|
|
875
|
+
function activationVector(database, rootUUID) {
|
|
876
|
+
const vector = [{ inventory: "entities", aspect: "containers", id: "*" }];
|
|
877
|
+
for (const inventory of ["entities", "detection_notebook"]) {
|
|
878
|
+
vector.push(activation(inventory, rootUUID), activation(inventory, "*"), { inventory, aspect: "inventory", id: rootUUID }, { inventory, aspect: "inventory", id: "*" });
|
|
879
|
+
for (const { absolutePath, parentUUID, name, status } of cachedStatement(database, `SELECT absolute_path AS absolutePath,
|
|
880
|
+
parent_uuid AS parentUUID, name, status FROM ${inventory} WHERE root_uuid = ? AND relative_path = ''`)
|
|
881
|
+
.all(rootUUID)) {
|
|
882
|
+
if (status === "active")
|
|
883
|
+
vector.push({ inventory, aspect: "roots", id: absolutePath });
|
|
884
|
+
if (parentUUID !== null) {
|
|
885
|
+
vector.push({ inventory, aspect: "namespace", id: parentUUID }, { inventory, aspect: "slot", id: slotKey(parentUUID, name) });
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (cachedStatement(database, `SELECT EXISTS(SELECT 1 FROM entities
|
|
890
|
+
WHERE root_uuid = ? AND type = 'repo.git' AND status = 'active')`).pluck().get(rootUUID)) {
|
|
891
|
+
vector.push({ inventory: "entities", aspect: "repositories", id: "*" });
|
|
892
|
+
}
|
|
893
|
+
return vector;
|
|
894
|
+
}
|
|
895
|
+
/** Stamps every fact these committed transitions change, and the activation
|
|
896
|
+
* vector of each root whose visible generation this transaction changed,
|
|
897
|
+
* with one new sequence, inside the caller's write transaction. Returns the
|
|
898
|
+
* stamp count and the step the caller runs after its outermost commit.
|
|
899
|
+
*
|
|
900
|
+
* A row placed where none stood, in a root whose first generation this
|
|
901
|
+
* transaction installs, beneath a parent placed the same way or none, is
|
|
902
|
+
* stamped by that vector alone. Every earlier answer it could join was
|
|
903
|
+
* empty of it and consumed that root's activation or `*`: an answer keyed
|
|
904
|
+
* inside the root names the root, one keyed by an absent identity or an
|
|
905
|
+
* object names `*`, and the aggregates and the root rows' namespaces
|
|
906
|
+
* are the vector's. A child list of a parent that stood consumed its own
|
|
907
|
+
* root's activation, so a row placed beneath one is stamped as usual. An
|
|
908
|
+
* installation therefore stamps a few facts however many rows it places,
|
|
909
|
+
* and no territory can hold its rows unless its activation is stamped. */
|
|
910
|
+
export function advanceGroundBasis(database, transitions, activated, outermost) {
|
|
911
|
+
const changed = new Map();
|
|
912
|
+
const add = (dependency) => { changed.set(key(dependency), dependency); };
|
|
913
|
+
const installed = new Set(activated);
|
|
914
|
+
for (const rootUUID of installed)
|
|
915
|
+
for (const dependency of activationVector(database, rootUUID))
|
|
916
|
+
add(dependency);
|
|
917
|
+
const written = [...transitions].filter(({ inventory, before, after }) => !equal(before && groundRowValues(before, inventory), after && groundRowValues(after, inventory)));
|
|
918
|
+
const placed = new Set(written.filter(({ before, after }) => !before && installed.has(after.rootUUID))
|
|
919
|
+
.map(({ inventory, after }) => JSON.stringify([inventory, after.uuid])));
|
|
920
|
+
const stamping = placed.size === 0 ? written : written.filter(({ inventory, before, after }) => before || !placed.has(JSON.stringify([inventory, after.uuid]))
|
|
921
|
+
|| (after.parentUUID !== null && !placed.has(JSON.stringify([inventory, after.parentUUID]))));
|
|
922
|
+
// Owners resolve in the graph before this transaction, from each written
|
|
923
|
+
// row's original, and in the committed graph after it.
|
|
924
|
+
const owners = Object.fromEntries(["entities", "detection_notebook"].map(inventory => {
|
|
925
|
+
const originals = new Map(written.filter(change => change.inventory === inventory)
|
|
926
|
+
.map(change => [(change.after ?? change.before).uuid, change.before]));
|
|
927
|
+
const current = (uuid) => cachedStatement(database, `SELECT parent_uuid AS parentUUID, type, status FROM ${inventory} WHERE uuid = ?`).get(uuid);
|
|
928
|
+
return [inventory, {
|
|
929
|
+
before: repositoryOwners(uuid => originals.has(uuid) ? originals.get(uuid) : current(uuid)),
|
|
930
|
+
after: repositoryOwners(current),
|
|
931
|
+
}];
|
|
932
|
+
}));
|
|
933
|
+
// What a territory can hold, for the saver's receipt.
|
|
934
|
+
const held = { entities: [], detection_notebook: [] };
|
|
935
|
+
const repositories = new Set();
|
|
936
|
+
for (const transition of stamping) {
|
|
937
|
+
const { inventory, before, after } = transition;
|
|
938
|
+
const uuid = (after ?? before).uuid;
|
|
939
|
+
const repository = activeRepository(before) || activeRepository(after);
|
|
940
|
+
let owned = false;
|
|
941
|
+
if (!repositoryHeadOnly(before, after)) {
|
|
942
|
+
for (const owner of [before && owners[inventory].before(uuid), after && owners[inventory].after(uuid)]) {
|
|
943
|
+
if (owner) {
|
|
944
|
+
add(territory(inventory, owner));
|
|
945
|
+
owned = true;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (repository || owned)
|
|
950
|
+
held[inventory].push(transition);
|
|
951
|
+
if (repository)
|
|
952
|
+
repositories.add(key({ inventory, aspect: "entity", id: uuid }));
|
|
953
|
+
if (activeRepository(before) !== activeRepository(after))
|
|
954
|
+
add(territory(inventory, uuid));
|
|
955
|
+
add({ inventory, aspect: "entity", id: uuid });
|
|
956
|
+
add({ inventory, aspect: "inventory", id: "*" });
|
|
957
|
+
for (const row of [before, after])
|
|
958
|
+
if (row)
|
|
959
|
+
add({ inventory, aspect: "inventory", id: row.rootUUID });
|
|
960
|
+
if (!equal(structure(inventory, before), structure(inventory, after)))
|
|
961
|
+
add({ inventory, aspect: "structure", id: uuid });
|
|
962
|
+
if (!equal(namespace(before), namespace(after)))
|
|
963
|
+
for (const row of [before, after]) {
|
|
964
|
+
if (!row?.parentUUID)
|
|
965
|
+
continue;
|
|
966
|
+
add({ inventory, aspect: "namespace", id: row.parentUUID });
|
|
967
|
+
add({ inventory, aspect: "slot", id: slotKey(row.parentUUID, row.name) });
|
|
968
|
+
}
|
|
969
|
+
// Both the old and the new answer change when membership moves; an
|
|
970
|
+
// answer that stays with the row changes only through its entity key.
|
|
971
|
+
const left = answers(inventory, before), right = answers(inventory, after);
|
|
972
|
+
const kept = new Set(left.map(key).filter(id => right.some(dependency => key(dependency) === id)));
|
|
973
|
+
for (const dependency of [...left, ...right])
|
|
974
|
+
if (!kept.has(key(dependency)))
|
|
975
|
+
add(dependency);
|
|
976
|
+
if (!equal(member(inventory, before), member(inventory, after)))
|
|
977
|
+
add({ inventory, aspect: "repositories", id: "*" });
|
|
978
|
+
if (!equal(container(inventory, before), container(inventory, after)))
|
|
979
|
+
add({ inventory, aspect: "containers", id: "*" });
|
|
980
|
+
}
|
|
981
|
+
if (changed.size === 0)
|
|
982
|
+
return { writes: 0, committed: () => { } };
|
|
983
|
+
const carry = carryTerritories(database, changed, written, outermost);
|
|
984
|
+
const sequence = cachedStatement(database, "UPDATE ground_sequence SET value = value + 1 WHERE singleton = 1 RETURNING value", true).get().value;
|
|
985
|
+
const write = cachedStatement(database, `INSERT INTO ground_revisions(inventory, aspect, identity, revision)
|
|
986
|
+
VALUES (?, ?, ?, ?) ON CONFLICT(inventory, aspect, identity) DO UPDATE SET revision = excluded.revision`);
|
|
987
|
+
for (const dependency of changed.values())
|
|
988
|
+
write.run(dependency.inventory, dependency.aspect, dependency.id, sequence);
|
|
989
|
+
receiptSinks.get(database)?.(receipt(database, sequence, changed, held, repositories));
|
|
990
|
+
return { writes: changed.size, committed: () => carry(sequence) };
|
|
991
|
+
}
|
|
992
|
+
/** On the saver, inside the write: its receipt, with the stamps a warm view
|
|
993
|
+
* consumes and the rows it held read back as stored. */
|
|
994
|
+
function receipt(database, sequence, changed, held, repositories) {
|
|
995
|
+
const stamped = [...changed].filter(([id, { aspect }]) => aspect === "territory" || aspect === "activation" || repositories.has(id)).map(([id]) => id);
|
|
996
|
+
const written = {};
|
|
997
|
+
for (const inventory of stamped.length ? INVENTORIES : []) {
|
|
998
|
+
const changes = held[inventory];
|
|
999
|
+
if (!changes.length)
|
|
1000
|
+
continue;
|
|
1001
|
+
const uuids = [...new Set(changes.map(({ before, after }) => (after ?? before).uuid))];
|
|
1002
|
+
const rows = new Map(cachedStatement(database, `SELECT ${groundRowProjection(inventory)} FROM ${inventory}
|
|
1003
|
+
WHERE uuid IN (SELECT value FROM json_each(?))`).all(JSON.stringify(uuids)).map(row => [row.uuid, row]));
|
|
1004
|
+
const moved = changes.some(({ before, after }) => !before || (after && after.parentUUID !== before.parentUUID));
|
|
1005
|
+
const children = new Map();
|
|
1006
|
+
if (moved) {
|
|
1007
|
+
for (const { parent, uuid } of cachedStatement(database, `SELECT parent_uuid AS parent, uuid FROM ${inventory}
|
|
1008
|
+
WHERE parent_uuid IN (SELECT value FROM json_each(?))`).all(JSON.stringify([...rows.keys()]))) {
|
|
1009
|
+
const listed = children.get(parent);
|
|
1010
|
+
if (listed)
|
|
1011
|
+
listed.push(uuid);
|
|
1012
|
+
else
|
|
1013
|
+
children.set(parent, [uuid]);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
written[inventory] = serialize(uuids.map(uuid => {
|
|
1017
|
+
const row = rows.get(uuid) ?? null;
|
|
1018
|
+
return [uuid, row, row && moved ? children.get(uuid) ?? [] : null];
|
|
1019
|
+
}));
|
|
1020
|
+
}
|
|
1021
|
+
return { incarnation: groundIncarnation(database), sequence, stamped, written };
|
|
1022
|
+
}
|
|
1023
|
+
//# sourceMappingURL=ground-basis.js.map
|