@rocicorp/zero 1.10.0-canary.21 → 1.10.0-canary.23
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/out/analyze-query/src/bin-analyze.js +1 -1
- package/out/analyze-query/src/bin-analyze.js.map +1 -1
- package/out/replicache/src/dag/gc.d.ts.map +1 -1
- package/out/replicache/src/dag/gc.js +2 -1
- package/out/replicache/src/dag/gc.js.map +1 -1
- package/out/replicache/src/dag/invalid-ref-count-error.d.ts +12 -0
- package/out/replicache/src/dag/invalid-ref-count-error.d.ts.map +1 -0
- package/out/replicache/src/dag/invalid-ref-count-error.js +19 -0
- package/out/replicache/src/dag/invalid-ref-count-error.js.map +1 -0
- package/out/replicache/src/dag/store-impl.d.ts +12 -2
- package/out/replicache/src/dag/store-impl.d.ts.map +1 -1
- package/out/replicache/src/dag/store-impl.js +22 -8
- package/out/replicache/src/dag/store-impl.js.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +15 -5
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js +25 -25
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +1 -1
- package/out/replicache/src/kv/idb-store.d.ts +9 -0
- package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store.js +14 -2
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-store.d.ts +1 -0
- package/out/replicache/src/kv/mem-store.d.ts.map +1 -1
- package/out/replicache/src/kv/mem-store.js +3 -0
- package/out/replicache/src/kv/mem-store.js.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.js +1 -1
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +2 -1
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +6 -1
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/kv/store.d.ts +6 -0
- package/out/replicache/src/kv/store.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +10 -5
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +16 -9
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +14 -3
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +104 -9
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/shared/src/arrays.js +1 -15
- package/out/shared/src/arrays.js.map +1 -1
- package/out/shared/src/hash.d.ts +1 -0
- package/out/shared/src/hash.d.ts.map +1 -1
- package/out/shared/src/hash.js +10 -28
- package/out/shared/src/hash.js.map +1 -1
- package/out/shared/src/iterables.d.ts +2 -0
- package/out/shared/src/iterables.d.ts.map +1 -1
- package/out/shared/src/iterables.js +18 -1
- package/out/shared/src/iterables.js.map +1 -1
- package/out/shared/src/json.d.ts.map +1 -1
- package/out/shared/src/json.js +2 -2
- package/out/shared/src/json.js.map +1 -1
- package/out/shared/src/ring-buffer.d.ts +8 -0
- package/out/shared/src/ring-buffer.d.ts.map +1 -1
- package/out/shared/src/ring-buffer.js +22 -1
- package/out/shared/src/ring-buffer.js.map +1 -1
- package/out/shared/src/xxhash32.d.ts +36 -0
- package/out/shared/src/xxhash32.d.ts.map +1 -0
- package/out/shared/src/xxhash32.js +51 -0
- package/out/shared/src/xxhash32.js.map +1 -0
- package/out/z2s/src/compiler.js +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/auth/read-authorizer.js +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +8 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +28 -1
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +11 -3
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.d.ts +18 -2
- package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.js +23 -9
- package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +8 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/mutator.js +2 -0
- package/out/zero-cache/src/server/mutator.js.map +1 -1
- package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
- package/out/zero-cache/src/server/reaper.js +2 -0
- package/out/zero-cache/src/server/reaper.js.map +1 -1
- package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
- package/out/zero-cache/src/server/replicator.js +2 -0
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js +3 -0
- package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +2 -0
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
- package/out/zero-cache/src/services/analyze.js +3 -2
- package/out/zero-cache/src/services/analyze.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +14 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js +15 -3
- package/out/zero-cache/src/services/change-source/common/change-stream-multiplexer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +3 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts +5 -0
- package/out/zero-cache/src/services/mutagen/mutagen.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +7 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +3 -2
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/profz.d.ts +7 -0
- package/out/zero-cache/src/services/profz.d.ts.map +1 -0
- package/out/zero-cache/src/services/profz.js +103 -0
- package/out/zero-cache/src/services/profz.js.map +1 -0
- package/out/zero-cache/src/services/run-ast.d.ts +12 -0
- package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
- package/out/zero-cache/src/services/run-ast.js +27 -15
- package/out/zero-cache/src/services/run-ast.js.map +1 -1
- package/out/zero-cache/src/services/runner.d.ts.map +1 -1
- package/out/zero-cache/src/services/runner.js +1 -1
- package/out/zero-cache/src/services/runner.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +45 -0
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +133 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts +14 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js +30 -4
- package/out/zero-cache/src/services/view-syncer/drain-coordinator.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts +33 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.js +62 -0
- package/out/zero-cache/src/services/view-syncer/hydration-budget.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts +51 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js +78 -0
- package/out/zero-cache/src/services/view-syncer/hydration-circuit-breaker.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +2 -2
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +2 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +33 -18
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-covering.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/query-covering.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +4 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +9 -3
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +11 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +470 -90
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/processes.d.ts +32 -0
- package/out/zero-cache/src/types/processes.d.ts.map +1 -1
- package/out/zero-cache/src/types/processes.js +43 -4
- package/out/zero-cache/src/types/processes.js.map +1 -1
- package/out/zero-cache/src/types/profiler.d.ts +12 -0
- package/out/zero-cache/src/types/profiler.d.ts.map +1 -1
- package/out/zero-cache/src/types/profiler.js +46 -1
- package/out/zero-cache/src/types/profiler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +21 -16
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +10 -1
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/connection-manager.d.ts +20 -0
- package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-manager.js +32 -14
- package/out/zero-client/src/client/connection-manager.js.map +1 -1
- package/out/zero-client/src/client/connection-status-enum.d.ts +2 -0
- package/out/zero-client/src/client/connection-status-enum.d.ts.map +1 -1
- package/out/zero-client/src/client/connection-status-enum.js +3 -1
- package/out/zero-client/src/client/connection-status-enum.js.map +1 -1
- package/out/zero-client/src/client/connection.d.ts +6 -0
- package/out/zero-client/src/client/connection.d.ts.map +1 -1
- package/out/zero-client/src/client/connection.js +2 -1
- package/out/zero-client/src/client/connection.js.map +1 -1
- package/out/zero-client/src/client/context.d.ts +22 -12
- package/out/zero-client/src/client/context.d.ts.map +1 -1
- package/out/zero-client/src/client/context.js +92 -25
- package/out/zero-client/src/client/context.js.map +1 -1
- package/out/zero-client/src/client/custom.js +2 -2
- package/out/zero-client/src/client/custom.js.map +1 -1
- package/out/zero-client/src/client/error.js +1 -1
- package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js +20 -3
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.d.ts.map +1 -1
- package/out/zero-client/src/client/mutator-proxy.js +2 -1
- package/out/zero-client/src/client/mutator-proxy.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +3 -0
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/query-manager.d.ts +2 -0
- package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
- package/out/zero-client/src/client/query-manager.js +48 -9
- package/out/zero-client/src/client/query-manager.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-rep.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-rep.js +1 -1
- package/out/zero-client/src/client/zero-rep.js.map +1 -1
- package/out/zero-client/src/client/zero.d.ts +18 -3
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +61 -14
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/types/query-result.d.ts +2 -0
- package/out/zero-client/src/types/query-result.d.ts.map +1 -1
- package/out/zero-protocol/src/ast.d.ts +64 -1
- package/out/zero-protocol/src/ast.d.ts.map +1 -1
- package/out/zero-protocol/src/ast.js +223 -51
- package/out/zero-protocol/src/ast.js.map +1 -1
- package/out/zero-protocol/src/error.d.ts +1 -1
- package/out/zero-protocol/src/query-hash-visitor.d.ts +81 -0
- package/out/zero-protocol/src/query-hash-visitor.d.ts.map +1 -0
- package/out/zero-protocol/src/query-hash-visitor.js +324 -0
- package/out/zero-protocol/src/query-hash-visitor.js.map +1 -0
- package/out/zero-protocol/src/query-hash.d.ts +7 -2
- package/out/zero-protocol/src/query-hash.d.ts.map +1 -1
- package/out/zero-protocol/src/query-hash.js +12 -9
- package/out/zero-protocol/src/query-hash.js.map +1 -1
- package/out/zero-react/src/use-query.d.ts +5 -4
- package/out/zero-react/src/use-query.d.ts.map +1 -1
- package/out/zero-react/src/use-query.js +86 -9
- package/out/zero-react/src/use-query.js.map +1 -1
- package/out/zero-solid/src/solid-view.d.ts +17 -0
- package/out/zero-solid/src/solid-view.d.ts.map +1 -1
- package/out/zero-solid/src/solid-view.js +39 -1
- package/out/zero-solid/src/solid-view.js.map +1 -1
- package/out/zero-solid/src/use-query.js +5 -3
- package/out/zero-solid/src/use-query.js.map +1 -1
- package/out/zql/src/builder/debug-delegate.d.ts +6 -1
- package/out/zql/src/builder/debug-delegate.d.ts.map +1 -1
- package/out/zql/src/builder/debug-delegate.js +14 -2
- package/out/zql/src/builder/debug-delegate.js.map +1 -1
- package/out/zql/src/ivm/array-view.d.ts +29 -0
- package/out/zql/src/ivm/array-view.d.ts.map +1 -1
- package/out/zql/src/ivm/array-view.js +58 -9
- package/out/zql/src/ivm/array-view.js.map +1 -1
- package/out/zql/src/ivm/cap.d.ts.map +1 -1
- package/out/zql/src/ivm/cap.js +5 -3
- package/out/zql/src/ivm/cap.js.map +1 -1
- package/out/zql/src/ivm/deferred-input.d.ts.map +1 -1
- package/out/zql/src/ivm/deferred-input.js +3 -2
- package/out/zql/src/ivm/deferred-input.js.map +1 -1
- package/out/zql/src/ivm/exists.d.ts +1 -1
- package/out/zql/src/ivm/exists.d.ts.map +1 -1
- package/out/zql/src/ivm/exists.js +1 -1
- package/out/zql/src/ivm/exists.js.map +1 -1
- package/out/zql/src/ivm/fan-in.d.ts +2 -2
- package/out/zql/src/ivm/fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-in.js +6 -5
- package/out/zql/src/ivm/fan-in.js.map +1 -1
- package/out/zql/src/ivm/fan-out.d.ts +2 -2
- package/out/zql/src/ivm/fan-out.d.ts.map +1 -1
- package/out/zql/src/ivm/fan-out.js.map +1 -1
- package/out/zql/src/ivm/filter-operators.d.ts +4 -4
- package/out/zql/src/ivm/filter-operators.d.ts.map +1 -1
- package/out/zql/src/ivm/filter-operators.js +10 -8
- package/out/zql/src/ivm/filter-operators.js.map +1 -1
- package/out/zql/src/ivm/filter.d.ts +2 -2
- package/out/zql/src/ivm/filter.d.ts.map +1 -1
- package/out/zql/src/ivm/filter.js +7 -4
- package/out/zql/src/ivm/filter.js.map +1 -1
- package/out/zql/src/ivm/flipped-join.js +2 -2
- package/out/zql/src/ivm/flipped-join.js.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts.map +1 -1
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +6 -4
- package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +15 -3
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +35 -9
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/ivm/take.d.ts.map +1 -1
- package/out/zql/src/ivm/take.js +3 -4
- package/out/zql/src/ivm/take.js.map +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +9 -7
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/out/zql/src/ivm/view-apply-change.d.ts.map +1 -1
- package/out/zql/src/ivm/view-apply-change.js +3 -3
- package/out/zql/src/ivm/view-apply-change.js.map +1 -1
- package/out/zql/src/ivm/view.d.ts +15 -0
- package/out/zql/src/ivm/view.d.ts.map +1 -1
- package/out/zql/src/mutate/mutator-registry.d.ts.map +1 -1
- package/out/zql/src/mutate/mutator-registry.js +2 -2
- package/out/zql/src/mutate/mutator-registry.js.map +1 -1
- package/out/zql/src/query/query-delegate-base.d.ts +4 -2
- package/out/zql/src/query/query-delegate-base.d.ts.map +1 -1
- package/out/zql/src/query/query-delegate-base.js +69 -36
- package/out/zql/src/query/query-delegate-base.js.map +1 -1
- package/out/zql/src/query/query-delegate.d.ts +33 -6
- package/out/zql/src/query/query-delegate.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.d.ts +25 -14
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +414 -94
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-internals.d.ts +21 -11
- package/out/zql/src/query/query-internals.d.ts.map +1 -1
- package/out/zql/src/query/query-internals.js.map +1 -1
- package/out/zql/src/query/query-transitions.d.ts +171 -0
- package/out/zql/src/query/query-transitions.d.ts.map +1 -0
- package/out/zql/src/query/query-transitions.js +207 -0
- package/out/zql/src/query/query-transitions.js.map +1 -0
- package/out/zql/src/query/query.d.ts +11 -2
- package/out/zql/src/query/query.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.d.ts +3 -2
- package/out/zql/src/query/runnable-query-impl.d.ts.map +1 -1
- package/out/zql/src/query/runnable-query-impl.js +23 -2
- package/out/zql/src/query/runnable-query-impl.js.map +1 -1
- package/out/zql/src/query/typed-view.d.ts +1 -1
- package/out/zql/src/query/typed-view.d.ts.map +1 -1
- package/out/zqlite/src/db.d.ts +9 -1
- package/out/zqlite/src/db.d.ts.map +1 -1
- package/out/zqlite/src/db.js +8 -2
- package/out/zqlite/src/db.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts +1 -0
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +10 -5
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
|
@@ -141,7 +141,7 @@ var { schema, permissions } = await loadSchemaAndPermissions(config.schema.path)
|
|
|
141
141
|
var clientSchema = clientSchemaFrom(schema).clientSchema;
|
|
142
142
|
var sources = /* @__PURE__ */ new Map();
|
|
143
143
|
var clientToServerMapper = clientToServer(schema.tables);
|
|
144
|
-
var debug = new Debug();
|
|
144
|
+
var debug = new Debug(config.outputVendedRows);
|
|
145
145
|
var tableSpecs = computeZqlSpecs(lc, db, { includeBackfillingColumns: false });
|
|
146
146
|
var AnalyzeQueryDelegate = class extends QueryDelegateBase {
|
|
147
147
|
debug = debug;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin-analyze.js","names":[],"sources":["../../../../analyze-query/src/bin-analyze.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport fs from 'node:fs';\nimport {styleText} from 'node:util';\nimport {astToZQL} from '../../ast-to-zql/src/ast-to-zql.ts';\nimport {formatOutput} from '../../ast-to-zql/src/format.ts';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole, createLogContext} from '../../shared/src/logging.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {\n appOptions,\n shardOptions,\n ZERO_ENV_VAR_PREFIX,\n zeroOptions,\n} from '../../zero-cache/src/config/zero-config.ts';\nimport {\n computeZqlSpecs,\n mustGetTableSpec,\n} from '../../zero-cache/src/db/lite-tables.ts';\nimport {\n deployPermissionsOptions,\n loadSchemaAndPermissions,\n} from '../../zero-cache/src/scripts/permissions.ts';\nimport {runAst} from '../../zero-cache/src/services/run-ast.ts';\nimport {pgClient} from '../../zero-cache/src/types/pg.ts';\nimport {getShardID, upstreamSchema} from '../../zero-cache/src/types/shards.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport {type AST} from '../../zero-protocol/src/ast.ts';\nimport {clientSchemaFrom} from '../../zero-schema/src/builder/schema-builder.ts';\nimport {clientToServer} from '../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {\n Debug,\n runtimeDebugFlags,\n} from '../../zql/src/builder/debug-delegate.ts';\nimport type {Source} from '../../zql/src/ivm/source.ts';\nimport {QueryDelegateBase} from '../../zql/src/query/query-delegate-base.ts';\nimport {newQuery} from '../../zql/src/query/query-impl.ts';\nimport {asQueryInternals} from '../../zql/src/query/query-internals.ts';\nimport {type PullRow, type Query} from '../../zql/src/query/query.ts';\nimport {Database} from '../../zqlite/src/db.ts';\nimport {explainQueries} from '../../zqlite/src/explain-queries.ts';\nimport {TableSource} from '../../zqlite/src/table-source.ts';\n\nconst options = {\n schema: deployPermissionsOptions.schema,\n replicaFile: {\n ...zeroOptions.replica.file,\n desc: ['File path to the SQLite replica to test queries against.'],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'AST for the query to be analyzed. Only one of ast/query/hash should be provided.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'Query to be analyzed in the form of: table.where(...).related(...).etc. ',\n 'Only one of ast/query/hash should be provided.',\n ],\n },\n hash: {\n type: v.string().optional(),\n desc: [\n 'Hash of the query to be analyzed. This is used to look up the query in the database. ',\n 'Only one of ast/query/hash should be provided.',\n 'You should run this script from the directory containing your .env file to reduce the amount of',\n 'configuration required. The .env file should contain the connection URL to the CVR database.',\n ],\n },\n applyPermissions: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to apply permissions (from your schema file) to the provided query.',\n ],\n },\n authData: {\n type: v.string().optional(),\n desc: [\n 'JSON encoded payload of the auth data.',\n 'This will be used to fill permission variables if the \"applyPermissions\" option is set',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to output the rows which were read from the replica in order to execute the analyzed query. ',\n 'If the same row is read more than once it will be logged once for each time it was read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to output the rows which would be synced to the client for the analyzed query.',\n ],\n },\n cvr: {\n db: {\n type: v.string().optional(),\n desc: [\n 'Connection URL to the CVR database. If using --hash, either this or --upstream-db',\n 'must be specified.',\n ],\n },\n },\n upstream: {\n db: {\n desc: [\n `Connection URL to the \"upstream\" authoritative postgres database. If using --hash, `,\n 'either this or --cvr-db must be specified.',\n ],\n type: v.string().optional(),\n },\n type: zeroOptions.upstream.type,\n },\n app: appOptions,\n shard: shardOptions,\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\nconst cfg = parseOptions(options, {\n argv: process.argv.slice(2).map(s => s.replaceAll('\\n', ' ')),\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query',\n content: `Analyze a ZQL query and show information about how it runs against a SQLite replica.\n\n analyze-query uses the same environment variables and flags as zero-cache-dev. If run from your development environment, it will pick up your ZERO_REPLICA_FILE, ZERO_SCHEMA_PATH, and other env vars automatically.\n\n If run in another environment (e.g., production) you will have to specify these flags. In particular, you must have a copy of the appropriate Zero schema file to give to the --schema-path flag.`,\n },\n {\n header: 'Examples',\n content: `# In development\n npx analyze-query --query='issue.related(\"comments\").limit(10)'\n npx analyze-query --ast='\\\\{\"table\": \"artist\",\"limit\": 10\\\\}'\n npx analyze-query --hash=1234567890\n\n # In production\n # First copy schema.ts to your production environment, then run:\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --ast='\\\\{\"table\": \"artist\",\"limit\": 10\\\\}'\n\n # cvr-db is required when using the hash option.\n # It is typically the same as your upstream db.\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --cvr-db='postgres://user:pass@host:port/db' \\\\\n --hash=1234567890\n `,\n },\n ],\n});\nconst config = {\n ...cfg,\n cvr: {\n ...cfg.cvr,\n db: cfg.cvr.db ?? cfg.upstream.db,\n },\n};\n\nruntimeDebugFlags.trackRowCountsVended = true;\nruntimeDebugFlags.trackRowsVended = config.outputVendedRows;\n\nconst lc = createLogContext({\n log: config.log,\n});\n\ncolorConsole.warn(\n 'DEPRECATED: `analyze-query` is deprecated. Please migrate to a project-specific analyze command built with `runAnalyzeCLI` from `@rocicorp/zero/analyze`.',\n);\n\nif (!fs.existsSync(config.replicaFile)) {\n colorConsole.error(`Replica file ${config.replicaFile} does not exist`);\n process.exit(1);\n}\nconst db = new Database(lc, config.replicaFile);\n\nconst {schema, permissions} = await loadSchemaAndPermissions(\n config.schema.path,\n);\nconst clientSchema = clientSchemaFrom(schema).clientSchema;\n\nconst sources = new Map<string, TableSource>();\nconst clientToServerMapper = clientToServer(schema.tables);\nconst debug = new Debug();\nconst tableSpecs = computeZqlSpecs(lc, db, {includeBackfillingColumns: false});\n\nclass AnalyzeQueryDelegate extends QueryDelegateBase {\n readonly debug = debug;\n readonly defaultQueryComplete = true;\n\n getSource(serverTableName: string): Source | undefined {\n let source = sources.get(serverTableName);\n if (source) {\n return source;\n }\n const tableSpec = mustGetTableSpec(tableSpecs, serverTableName);\n const {primaryKey} = tableSpec.tableSpec;\n\n source = new TableSource(\n lc,\n config.log,\n db,\n serverTableName,\n tableSpec.zqlSpec,\n primaryKey,\n );\n\n sources.set(serverTableName, source);\n return source;\n }\n}\n\nconst host = new AnalyzeQueryDelegate();\n\nlet result: AnalyzeQueryResult;\n\nif (config.ast) {\n result = await runAst(\n lc,\n clientSchema,\n JSON.parse(config.ast),\n true,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n} else if (config.query) {\n result = await runQuery(config.query);\n} else if (config.hash) {\n result = await runHash(config.hash);\n} else {\n colorConsole.error('No query or AST or hash provided');\n process.exit(1);\n}\n\nfunction runQuery(queryString: string): Promise<AnalyzeQueryResult> {\n const z = {\n query: Object.fromEntries(\n Object.entries(schema.tables).map(([name]) => [\n name,\n newQuery(schema, name),\n ]),\n ),\n };\n\n const f = new Function('z', `return z.query.${queryString};`);\n const q: Query<string, Schema, PullRow<string, Schema>> = f(z);\n\n const ast = asQueryInternals(q).ast;\n return runAst(\n lc,\n clientSchema,\n ast,\n false,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n}\n\nasync function runHash(hash: string) {\n const cvrDB = pgClient(\n lc,\n must(config.cvr.db, 'CVR DB must be provided when using the hash option'),\n 'analyze-query-hash-cvr',\n );\n\n const rows = await cvrDB`select \"clientAST\", \"internal\" from ${cvrDB(\n upstreamSchema(getShardID(config)) + '/cvr',\n )}.\"queries\" where \"queryHash\" = ${must(hash)} limit 1;`;\n await cvrDB.end();\n\n colorConsole.log('ZQL from Hash:');\n const ast = rows[0].clientAST as AST;\n colorConsole.log(await formatOutput(ast.table + astToZQL(ast)));\n\n return runAst(\n lc,\n clientSchema,\n ast,\n true,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n}\n\nif (config.outputSyncedRows) {\n colorConsole.log(styleText(['blue', 'bold'], '=== Synced Rows: ===\\n'));\n for (const [table, rows] of Object.entries(result.syncedRows ?? {})) {\n colorConsole.log(styleText('bold', table + ':'), rows);\n }\n}\n\ncolorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\ncolorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n);\nshowStats();\nif (config.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const source of sources.values()) {\n colorConsole.log(\n styleText('bold', `${source.tableSchema.name}:`),\n debug.getVendedRows()?.[source.tableSchema.name] ?? {},\n );\n }\n}\n\ncolorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n);\nconst nvisitCounts = debug.getNVisitCounts();\nlet totalNVisit = 0;\nfor (const [table, queries] of Object.entries(nvisitCounts)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n}\ncolorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n);\n\ncolorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\nconst fallbackPlans = explainQueries(debug.getVendedRowCounts() ?? {}, db);\nconst capturedPlans = debug.getSQLitePlans();\nconst plans: Record<string, string[]> = {...fallbackPlans, ...capturedPlans};\nfor (const [query, plan] of Object.entries(plans)) {\n colorConsole.log(styleText('bold', 'query'), query);\n colorConsole.log(plan.map((row, i) => colorPlanRow(row, i)).join('\\n'));\n colorConsole.log('\\n');\n}\n\nfunction showStats() {\n let totalRowsConsidered = 0;\n for (const source of sources.values()) {\n const values = Object.values(\n debug.getVendedRowCounts()?.[source.tableSchema.name] ?? {},\n );\n for (const v of values) {\n totalRowsConsidered += v;\n }\n colorConsole.log(\n styleText('bold', source.tableSchema.name + ' vended:'),\n debug.getVendedRowCounts()?.[source.tableSchema.name] ?? {},\n );\n }\n\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsConsidered),\n );\n colorConsole.log(\n styleText('bold', 'time:'),\n colorTime(result.end - result.start),\n 'ms',\n );\n}\n\nfunction colorTime(duration: number) {\n if (duration < 100) {\n return styleText('green', duration.toFixed(2) + 'ms');\n } else if (duration < 1000) {\n return styleText('yellow', duration.toFixed(2) + 'ms');\n }\n return styleText('red', duration.toFixed(2) + 'ms');\n}\n\nfunction colorRowsConsidered(n: number) {\n if (n < 1000) {\n return styleText('green', n.toString());\n } else if (n < 10000) {\n return styleText('yellow', n.toString());\n }\n return styleText('red', n.toString());\n}\n\nfunction colorPlanRow(row: string, i: number) {\n if (row.includes('SCAN')) {\n if (i === 0) {\n return styleText('yellow', row);\n }\n return styleText('red', row);\n }\n return styleText('green', row);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bin-analyze.js","names":[],"sources":["../../../../analyze-query/src/bin-analyze.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport fs from 'node:fs';\nimport {styleText} from 'node:util';\nimport {astToZQL} from '../../ast-to-zql/src/ast-to-zql.ts';\nimport {formatOutput} from '../../ast-to-zql/src/format.ts';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole, createLogContext} from '../../shared/src/logging.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {\n appOptions,\n shardOptions,\n ZERO_ENV_VAR_PREFIX,\n zeroOptions,\n} from '../../zero-cache/src/config/zero-config.ts';\nimport {\n computeZqlSpecs,\n mustGetTableSpec,\n} from '../../zero-cache/src/db/lite-tables.ts';\nimport {\n deployPermissionsOptions,\n loadSchemaAndPermissions,\n} from '../../zero-cache/src/scripts/permissions.ts';\nimport {runAst} from '../../zero-cache/src/services/run-ast.ts';\nimport {pgClient} from '../../zero-cache/src/types/pg.ts';\nimport {getShardID, upstreamSchema} from '../../zero-cache/src/types/shards.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport {type AST} from '../../zero-protocol/src/ast.ts';\nimport {clientSchemaFrom} from '../../zero-schema/src/builder/schema-builder.ts';\nimport {clientToServer} from '../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {\n Debug,\n runtimeDebugFlags,\n} from '../../zql/src/builder/debug-delegate.ts';\nimport type {Source} from '../../zql/src/ivm/source.ts';\nimport {QueryDelegateBase} from '../../zql/src/query/query-delegate-base.ts';\nimport {newQuery} from '../../zql/src/query/query-impl.ts';\nimport {asQueryInternals} from '../../zql/src/query/query-internals.ts';\nimport {type PullRow, type Query} from '../../zql/src/query/query.ts';\nimport {Database} from '../../zqlite/src/db.ts';\nimport {explainQueries} from '../../zqlite/src/explain-queries.ts';\nimport {TableSource} from '../../zqlite/src/table-source.ts';\n\nconst options = {\n schema: deployPermissionsOptions.schema,\n replicaFile: {\n ...zeroOptions.replica.file,\n desc: ['File path to the SQLite replica to test queries against.'],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'AST for the query to be analyzed. Only one of ast/query/hash should be provided.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'Query to be analyzed in the form of: table.where(...).related(...).etc. ',\n 'Only one of ast/query/hash should be provided.',\n ],\n },\n hash: {\n type: v.string().optional(),\n desc: [\n 'Hash of the query to be analyzed. This is used to look up the query in the database. ',\n 'Only one of ast/query/hash should be provided.',\n 'You should run this script from the directory containing your .env file to reduce the amount of',\n 'configuration required. The .env file should contain the connection URL to the CVR database.',\n ],\n },\n applyPermissions: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to apply permissions (from your schema file) to the provided query.',\n ],\n },\n authData: {\n type: v.string().optional(),\n desc: [\n 'JSON encoded payload of the auth data.',\n 'This will be used to fill permission variables if the \"applyPermissions\" option is set',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to output the rows which were read from the replica in order to execute the analyzed query. ',\n 'If the same row is read more than once it will be logged once for each time it was read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to output the rows which would be synced to the client for the analyzed query.',\n ],\n },\n cvr: {\n db: {\n type: v.string().optional(),\n desc: [\n 'Connection URL to the CVR database. If using --hash, either this or --upstream-db',\n 'must be specified.',\n ],\n },\n },\n upstream: {\n db: {\n desc: [\n `Connection URL to the \"upstream\" authoritative postgres database. If using --hash, `,\n 'either this or --cvr-db must be specified.',\n ],\n type: v.string().optional(),\n },\n type: zeroOptions.upstream.type,\n },\n app: appOptions,\n shard: shardOptions,\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\nconst cfg = parseOptions(options, {\n argv: process.argv.slice(2).map(s => s.replaceAll('\\n', ' ')),\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query',\n content: `Analyze a ZQL query and show information about how it runs against a SQLite replica.\n\n analyze-query uses the same environment variables and flags as zero-cache-dev. If run from your development environment, it will pick up your ZERO_REPLICA_FILE, ZERO_SCHEMA_PATH, and other env vars automatically.\n\n If run in another environment (e.g., production) you will have to specify these flags. In particular, you must have a copy of the appropriate Zero schema file to give to the --schema-path flag.`,\n },\n {\n header: 'Examples',\n content: `# In development\n npx analyze-query --query='issue.related(\"comments\").limit(10)'\n npx analyze-query --ast='\\\\{\"table\": \"artist\",\"limit\": 10\\\\}'\n npx analyze-query --hash=1234567890\n\n # In production\n # First copy schema.ts to your production environment, then run:\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --ast='\\\\{\"table\": \"artist\",\"limit\": 10\\\\}'\n\n # cvr-db is required when using the hash option.\n # It is typically the same as your upstream db.\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --cvr-db='postgres://user:pass@host:port/db' \\\\\n --hash=1234567890\n `,\n },\n ],\n});\nconst config = {\n ...cfg,\n cvr: {\n ...cfg.cvr,\n db: cfg.cvr.db ?? cfg.upstream.db,\n },\n};\n\nruntimeDebugFlags.trackRowCountsVended = true;\nruntimeDebugFlags.trackRowsVended = config.outputVendedRows;\n\nconst lc = createLogContext({\n log: config.log,\n});\n\ncolorConsole.warn(\n 'DEPRECATED: `analyze-query` is deprecated. Please migrate to a project-specific analyze command built with `runAnalyzeCLI` from `@rocicorp/zero/analyze`.',\n);\n\nif (!fs.existsSync(config.replicaFile)) {\n colorConsole.error(`Replica file ${config.replicaFile} does not exist`);\n process.exit(1);\n}\nconst db = new Database(lc, config.replicaFile);\n\nconst {schema, permissions} = await loadSchemaAndPermissions(\n config.schema.path,\n);\nconst clientSchema = clientSchemaFrom(schema).clientSchema;\n\nconst sources = new Map<string, TableSource>();\nconst clientToServerMapper = clientToServer(schema.tables);\nconst debug = new Debug(config.outputVendedRows);\nconst tableSpecs = computeZqlSpecs(lc, db, {includeBackfillingColumns: false});\n\nclass AnalyzeQueryDelegate extends QueryDelegateBase {\n readonly debug = debug;\n readonly defaultQueryComplete = true;\n\n getSource(serverTableName: string): Source | undefined {\n let source = sources.get(serverTableName);\n if (source) {\n return source;\n }\n const tableSpec = mustGetTableSpec(tableSpecs, serverTableName);\n const {primaryKey} = tableSpec.tableSpec;\n\n source = new TableSource(\n lc,\n config.log,\n db,\n serverTableName,\n tableSpec.zqlSpec,\n primaryKey,\n );\n\n sources.set(serverTableName, source);\n return source;\n }\n}\n\nconst host = new AnalyzeQueryDelegate();\n\nlet result: AnalyzeQueryResult;\n\nif (config.ast) {\n result = await runAst(\n lc,\n clientSchema,\n JSON.parse(config.ast),\n true,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n} else if (config.query) {\n result = await runQuery(config.query);\n} else if (config.hash) {\n result = await runHash(config.hash);\n} else {\n colorConsole.error('No query or AST or hash provided');\n process.exit(1);\n}\n\nfunction runQuery(queryString: string): Promise<AnalyzeQueryResult> {\n const z = {\n query: Object.fromEntries(\n Object.entries(schema.tables).map(([name]) => [\n name,\n newQuery(schema, name),\n ]),\n ),\n };\n\n const f = new Function('z', `return z.query.${queryString};`);\n const q: Query<string, Schema, PullRow<string, Schema>> = f(z);\n\n const ast = asQueryInternals(q).ast;\n return runAst(\n lc,\n clientSchema,\n ast,\n false,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n}\n\nasync function runHash(hash: string) {\n const cvrDB = pgClient(\n lc,\n must(config.cvr.db, 'CVR DB must be provided when using the hash option'),\n 'analyze-query-hash-cvr',\n );\n\n const rows = await cvrDB`select \"clientAST\", \"internal\" from ${cvrDB(\n upstreamSchema(getShardID(config)) + '/cvr',\n )}.\"queries\" where \"queryHash\" = ${must(hash)} limit 1;`;\n await cvrDB.end();\n\n colorConsole.log('ZQL from Hash:');\n const ast = rows[0].clientAST as AST;\n colorConsole.log(await formatOutput(ast.table + astToZQL(ast)));\n\n return runAst(\n lc,\n clientSchema,\n ast,\n true,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n}\n\nif (config.outputSyncedRows) {\n colorConsole.log(styleText(['blue', 'bold'], '=== Synced Rows: ===\\n'));\n for (const [table, rows] of Object.entries(result.syncedRows ?? {})) {\n colorConsole.log(styleText('bold', table + ':'), rows);\n }\n}\n\ncolorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\ncolorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n);\nshowStats();\nif (config.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const source of sources.values()) {\n colorConsole.log(\n styleText('bold', `${source.tableSchema.name}:`),\n debug.getVendedRows()?.[source.tableSchema.name] ?? {},\n );\n }\n}\n\ncolorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n);\nconst nvisitCounts = debug.getNVisitCounts();\nlet totalNVisit = 0;\nfor (const [table, queries] of Object.entries(nvisitCounts)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n}\ncolorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n);\n\ncolorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\nconst fallbackPlans = explainQueries(debug.getVendedRowCounts() ?? {}, db);\nconst capturedPlans = debug.getSQLitePlans();\nconst plans: Record<string, string[]> = {...fallbackPlans, ...capturedPlans};\nfor (const [query, plan] of Object.entries(plans)) {\n colorConsole.log(styleText('bold', 'query'), query);\n colorConsole.log(plan.map((row, i) => colorPlanRow(row, i)).join('\\n'));\n colorConsole.log('\\n');\n}\n\nfunction showStats() {\n let totalRowsConsidered = 0;\n for (const source of sources.values()) {\n const values = Object.values(\n debug.getVendedRowCounts()?.[source.tableSchema.name] ?? {},\n );\n for (const v of values) {\n totalRowsConsidered += v;\n }\n colorConsole.log(\n styleText('bold', source.tableSchema.name + ' vended:'),\n debug.getVendedRowCounts()?.[source.tableSchema.name] ?? {},\n );\n }\n\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsConsidered),\n );\n colorConsole.log(\n styleText('bold', 'time:'),\n colorTime(result.end - result.start),\n 'ms',\n );\n}\n\nfunction colorTime(duration: number) {\n if (duration < 100) {\n return styleText('green', duration.toFixed(2) + 'ms');\n } else if (duration < 1000) {\n return styleText('yellow', duration.toFixed(2) + 'ms');\n }\n return styleText('red', duration.toFixed(2) + 'ms');\n}\n\nfunction colorRowsConsidered(n: number) {\n if (n < 1000) {\n return styleText('green', n.toString());\n } else if (n < 10000) {\n return styleText('yellow', n.toString());\n }\n return styleText('red', n.toString());\n}\n\nfunction colorPlanRow(row: string, i: number) {\n if (row.includes('SCAN')) {\n if (i === 0) {\n return styleText('yellow', row);\n }\n return styleText('red', row);\n }\n return styleText('green', row);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/dag/gc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAAC,KAAK,IAAI,EAAY,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/dag/gc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAAC,KAAK,IAAI,EAAY,MAAM,YAAY,CAAC;AAGhD,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,EAAE,IAAI,GAAG,SAAS,CAAC;IACtB,GAAG,EAAE,IAAI,GAAG,SAAS,CAAC;CACvB,CAAC;AAIF,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,YAAY,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC9D,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,YAAY,CAAC,SAAS,IAAI,EAAE,GAAG,SAAS,CAAC,CAAC;IACnE;;;;;;;;;;;;;;;;;;OAkBG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;CAC1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,QAAQ,CAAC,UAAU,CAAC,EACjC,SAAS,EAAE,WAAW,CAAC,IAAI,CAAC,EAC5B,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAE5B"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assert, assertNumber } from "../../../shared/src/asserts.js";
|
|
2
2
|
import { isProd } from "../../../shared/src/config.js";
|
|
3
3
|
import "../config.js";
|
|
4
|
+
import { InvalidRefCountError } from "./invalid-ref-count-error.js";
|
|
4
5
|
import { emptyHash } from "../hash.js";
|
|
5
6
|
//#region ../replicache/src/dag/gc.ts
|
|
6
7
|
/**
|
|
@@ -58,7 +59,7 @@ var RefCountUpdates = class {
|
|
|
58
59
|
} while (refCountsUpdated);
|
|
59
60
|
}
|
|
60
61
|
for (const o of this.#oldHeads) await this.#changeRefCount(o, -1);
|
|
61
|
-
|
|
62
|
+
for (const [hash, update] of this.#refCountUpdates) if (!(update >= 0)) throw new InvalidRefCountError(hash, update);
|
|
62
63
|
return this.#refCountUpdates;
|
|
63
64
|
}
|
|
64
65
|
async #changeRefCount(hash, delta) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gc.js","names":["#newHeads","#oldHeads","#putChunks","#delegate","#refsCounted","#refCountUpdates","#loadedRefCountPromises","#isLazyDelegate","#changeRefCount","#ensureRefCountLoaded","#updateRefsCounts","#updateRefCount"],"sources":["../../../../../replicache/src/dag/gc.ts"],"sourcesContent":["import {assert, assertNumber} from '../../../shared/src/asserts.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport {skipGCAsserts} from '../config.ts';\nimport {type Hash, emptyHash} from '../hash.ts';\n\nexport type HeadChange = {\n new: Hash | undefined;\n old: Hash | undefined;\n};\n\ntype LoadedRefCountPromises = Map<Hash, Promise<number>>;\n\nexport interface RefCountUpdatesDelegate {\n getRefCount: (hash: Hash) => MaybePromise<number | undefined>;\n getRefs: (hash: Hash) => MaybePromise<readonly Hash[] | undefined>;\n /**\n * Should be implemented if the store lazily loads refs, returning whether\n * or not the chunks refs have already been counted (i.e. are reflected\n * in `getRefCount`).\n *\n * If defined then:\n * - `getRefs` should return undefined for refs that have not been loaded,\n * but should never return undefined for hashes in `putChunks`.\n * - it is assumed that chunks in `putChunks` may have been reachable before\n * the write, but may not have been counted. This method is used to\n * determine if they have been counted or not. If they have not been\n * counted, and are reachable with the write applied, the returned\n * ref count updates will include updates for counting them.\n *\n * If undefined then:\n * - `getRefs` should never return undefined\n * - it is assumed that the refs of any chunks which were reachable before\n * the write are already counted\n */\n areRefsCounted?: (hash: Hash) => boolean;\n}\n\n/**\n * Computes how ref counts should be updated when a dag write is committed.\n * Does not modify the dag store.\n * @param headChanges Heads that were changed by the dag write.\n * @param putChunks Chunks that were put by the dag write.\n * @param delegate Delegate used for getting ref information from the dag store.\n * @returns Map from chunk Hash to changed ref counts. Chunks with a new ref\n * count of 0 should be deleted. All hashes in `putChunks` will have an entry\n * (which will be zero if the newly put chunk is not reachable from any head).\n */\nexport function computeRefCountUpdates(\n headChanges: Iterable<HeadChange>,\n putChunks: ReadonlySet<Hash>,\n delegate: RefCountUpdatesDelegate,\n): Promise<Map<Hash, number>> {\n return new RefCountUpdates(headChanges, putChunks, delegate).compute();\n}\n\nclass RefCountUpdates {\n readonly #newHeads: Hash[];\n readonly #oldHeads: Hash[];\n readonly #putChunks: ReadonlySet<Hash>;\n readonly #delegate: RefCountUpdatesDelegate;\n readonly #refsCounted: Set<Hash> | null;\n readonly #refCountUpdates: Map<Hash, number>;\n readonly #loadedRefCountPromises: LoadedRefCountPromises;\n readonly #isLazyDelegate: boolean;\n\n constructor(\n headChanges: Iterable<HeadChange>,\n putChunks: ReadonlySet<Hash>,\n delegate: RefCountUpdatesDelegate,\n ) {\n const newHeads: Hash[] = [];\n const oldHeads: Hash[] = [];\n for (const changedHead of headChanges) {\n if (changedHead.old !== changedHead.new) {\n changedHead.old && oldHeads.push(changedHead.old);\n changedHead.new && newHeads.push(changedHead.new);\n }\n }\n this.#newHeads = newHeads;\n this.#oldHeads = oldHeads;\n this.#putChunks = putChunks;\n this.#delegate = delegate;\n this.#refCountUpdates = new Map();\n // This map is used to ensure we do not load the ref count key more than once.\n // Once it is loaded we only operate on a cache of the ref counts.\n this.#loadedRefCountPromises = new Map();\n this.#isLazyDelegate = delegate.areRefsCounted !== undefined;\n this.#refsCounted = this.#isLazyDelegate ? new Set() : null;\n }\n\n async compute(): Promise<Map<Hash, number>> {\n for (const n of this.#newHeads) {\n await this.#changeRefCount(n, 1);\n }\n\n // Now go through the put chunks to ensure each has an entry in\n // refCountUpdates (zero for new chunks which are not reachable from\n // newHeads).\n await Promise.all(\n Array.from(this.#putChunks.values(), hash =>\n this.#ensureRefCountLoaded(hash),\n ),\n );\n\n if (this.#isLazyDelegate) {\n assert(\n this.#delegate.areRefsCounted,\n 'Expected delegate.areRefsCounted to be defined',\n );\n assert(this.#refsCounted, 'Expected refsCounted to be defined');\n let refCountsUpdated;\n do {\n refCountsUpdated = false;\n for (const hash of this.#putChunks.values()) {\n if (\n !this.#delegate.areRefsCounted(hash) &&\n !this.#refsCounted.has(hash) &&\n this.#refCountUpdates.get(hash) !== 0\n ) {\n await this.#updateRefsCounts(hash, 1);\n refCountsUpdated = true;\n break;\n }\n }\n } while (refCountsUpdated);\n }\n\n for (const o of this.#oldHeads) {\n await this.#changeRefCount(o, -1);\n }\n\n
|
|
1
|
+
{"version":3,"file":"gc.js","names":["#newHeads","#oldHeads","#putChunks","#delegate","#refsCounted","#refCountUpdates","#loadedRefCountPromises","#isLazyDelegate","#changeRefCount","#ensureRefCountLoaded","#updateRefsCounts","#updateRefCount"],"sources":["../../../../../replicache/src/dag/gc.ts"],"sourcesContent":["import {assert, assertNumber} from '../../../shared/src/asserts.ts';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport {skipGCAsserts} from '../config.ts';\nimport {type Hash, emptyHash} from '../hash.ts';\nimport {InvalidRefCountError} from './invalid-ref-count-error.ts';\n\nexport type HeadChange = {\n new: Hash | undefined;\n old: Hash | undefined;\n};\n\ntype LoadedRefCountPromises = Map<Hash, Promise<number>>;\n\nexport interface RefCountUpdatesDelegate {\n getRefCount: (hash: Hash) => MaybePromise<number | undefined>;\n getRefs: (hash: Hash) => MaybePromise<readonly Hash[] | undefined>;\n /**\n * Should be implemented if the store lazily loads refs, returning whether\n * or not the chunks refs have already been counted (i.e. are reflected\n * in `getRefCount`).\n *\n * If defined then:\n * - `getRefs` should return undefined for refs that have not been loaded,\n * but should never return undefined for hashes in `putChunks`.\n * - it is assumed that chunks in `putChunks` may have been reachable before\n * the write, but may not have been counted. This method is used to\n * determine if they have been counted or not. If they have not been\n * counted, and are reachable with the write applied, the returned\n * ref count updates will include updates for counting them.\n *\n * If undefined then:\n * - `getRefs` should never return undefined\n * - it is assumed that the refs of any chunks which were reachable before\n * the write are already counted\n */\n areRefsCounted?: (hash: Hash) => boolean;\n}\n\n/**\n * Computes how ref counts should be updated when a dag write is committed.\n * Does not modify the dag store.\n * @param headChanges Heads that were changed by the dag write.\n * @param putChunks Chunks that were put by the dag write.\n * @param delegate Delegate used for getting ref information from the dag store.\n * @returns Map from chunk Hash to changed ref counts. Chunks with a new ref\n * count of 0 should be deleted. All hashes in `putChunks` will have an entry\n * (which will be zero if the newly put chunk is not reachable from any head).\n */\nexport function computeRefCountUpdates(\n headChanges: Iterable<HeadChange>,\n putChunks: ReadonlySet<Hash>,\n delegate: RefCountUpdatesDelegate,\n): Promise<Map<Hash, number>> {\n return new RefCountUpdates(headChanges, putChunks, delegate).compute();\n}\n\nclass RefCountUpdates {\n readonly #newHeads: Hash[];\n readonly #oldHeads: Hash[];\n readonly #putChunks: ReadonlySet<Hash>;\n readonly #delegate: RefCountUpdatesDelegate;\n readonly #refsCounted: Set<Hash> | null;\n readonly #refCountUpdates: Map<Hash, number>;\n readonly #loadedRefCountPromises: LoadedRefCountPromises;\n readonly #isLazyDelegate: boolean;\n\n constructor(\n headChanges: Iterable<HeadChange>,\n putChunks: ReadonlySet<Hash>,\n delegate: RefCountUpdatesDelegate,\n ) {\n const newHeads: Hash[] = [];\n const oldHeads: Hash[] = [];\n for (const changedHead of headChanges) {\n if (changedHead.old !== changedHead.new) {\n changedHead.old && oldHeads.push(changedHead.old);\n changedHead.new && newHeads.push(changedHead.new);\n }\n }\n this.#newHeads = newHeads;\n this.#oldHeads = oldHeads;\n this.#putChunks = putChunks;\n this.#delegate = delegate;\n this.#refCountUpdates = new Map();\n // This map is used to ensure we do not load the ref count key more than once.\n // Once it is loaded we only operate on a cache of the ref counts.\n this.#loadedRefCountPromises = new Map();\n this.#isLazyDelegate = delegate.areRefsCounted !== undefined;\n this.#refsCounted = this.#isLazyDelegate ? new Set() : null;\n }\n\n async compute(): Promise<Map<Hash, number>> {\n for (const n of this.#newHeads) {\n await this.#changeRefCount(n, 1);\n }\n\n // Now go through the put chunks to ensure each has an entry in\n // refCountUpdates (zero for new chunks which are not reachable from\n // newHeads).\n await Promise.all(\n Array.from(this.#putChunks.values(), hash =>\n this.#ensureRefCountLoaded(hash),\n ),\n );\n\n if (this.#isLazyDelegate) {\n assert(\n this.#delegate.areRefsCounted,\n 'Expected delegate.areRefsCounted to be defined',\n );\n assert(this.#refsCounted, 'Expected refsCounted to be defined');\n let refCountsUpdated;\n do {\n refCountsUpdated = false;\n for (const hash of this.#putChunks.values()) {\n if (\n !this.#delegate.areRefsCounted(hash) &&\n !this.#refsCounted.has(hash) &&\n this.#refCountUpdates.get(hash) !== 0\n ) {\n await this.#updateRefsCounts(hash, 1);\n refCountsUpdated = true;\n break;\n }\n }\n } while (refCountsUpdated);\n }\n\n for (const o of this.#oldHeads) {\n await this.#changeRefCount(o, -1);\n }\n\n // A negative count means a chunk that is still referenced had no (or too\n // low a) ref count in the store, so the store is already corrupt. Always\n // check this, even in production: writing the negative count would only\n // spread the corruption, and the typed error lets the store owner recover.\n // Written as `!(update >= 0)` so that NaN is rejected as well.\n for (const [hash, update] of this.#refCountUpdates) {\n if (!(update >= 0)) {\n throw new InvalidRefCountError(hash, update);\n }\n }\n\n return this.#refCountUpdates;\n }\n\n async #changeRefCount(hash: Hash, delta: number): Promise<void> {\n // First make sure that we have the ref count in the cache. This is async\n // because it might need to load the ref count from the store (via the delegate).\n //\n // Once we have loaded the ref count all the updates to it are sync to\n // prevent race conditions.\n await this.#ensureRefCountLoaded(hash);\n if (this.#updateRefCount(hash, delta)) {\n await this.#updateRefsCounts(hash, delta);\n }\n }\n\n async #updateRefsCounts(hash: Hash, delta: number) {\n if (hash === emptyHash) {\n return;\n }\n const refs = await this.#delegate.getRefs(hash);\n if (!skipGCAsserts) {\n assert(\n refs || (this.#isLazyDelegate && !this.#putChunks.has(hash)),\n 'refs must be defined',\n );\n }\n\n if (refs !== undefined) {\n this.#refsCounted?.add(hash);\n const ps = refs.map(ref => this.#changeRefCount(ref, delta));\n await Promise.all(ps);\n }\n }\n\n #ensureRefCountLoaded(hash: Hash): Promise<number> {\n // Only get the ref count once.\n let p = this.#loadedRefCountPromises.get(hash);\n if (p === undefined) {\n p = (async () => {\n const value = (await this.#delegate.getRefCount(hash)) || 0;\n this.#refCountUpdates.set(hash, value);\n return value;\n })();\n this.#loadedRefCountPromises.set(hash, p);\n }\n return p;\n }\n\n #updateRefCount(hash: Hash, delta: number): boolean {\n const oldCount = this.#refCountUpdates.get(hash);\n assertNumber(oldCount);\n this.#refCountUpdates.set(hash, oldCount + delta);\n return (oldCount === 0 && delta === 1) || (oldCount === 1 && delta === -1);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Hash } from '../hash.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a ref count read from the store is not a valid Uint16. This
|
|
4
|
+
* means the store is corrupt (due to some unknown bug) and cannot be repaired.
|
|
5
|
+
*/
|
|
6
|
+
export declare class InvalidRefCountError extends Error {
|
|
7
|
+
name: string;
|
|
8
|
+
readonly hash: Hash;
|
|
9
|
+
readonly value: unknown;
|
|
10
|
+
constructor(hash: Hash, value: unknown);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=invalid-ref-count-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invalid-ref-count-error.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/dag/invalid-ref-count-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,YAAY,CAAC;AAErC;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,IAAI,SAA0B;IAC9B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBACZ,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO;CAOvC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region ../replicache/src/dag/invalid-ref-count-error.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a ref count read from the store is not a valid Uint16. This
|
|
4
|
+
* means the store is corrupt (due to some unknown bug) and cannot be repaired.
|
|
5
|
+
*/
|
|
6
|
+
var InvalidRefCountError = class extends Error {
|
|
7
|
+
name = "InvalidRefCountError";
|
|
8
|
+
hash;
|
|
9
|
+
value;
|
|
10
|
+
constructor(hash, value) {
|
|
11
|
+
super(`Invalid ref count ${String(value)} for ${hash}. We expect the value to be a Uint16`);
|
|
12
|
+
this.hash = hash;
|
|
13
|
+
this.value = value;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { InvalidRefCountError };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=invalid-ref-count-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invalid-ref-count-error.js","names":[],"sources":["../../../../../replicache/src/dag/invalid-ref-count-error.ts"],"sourcesContent":["import type {Hash} from '../hash.ts';\n\n/**\n * Thrown when a ref count read from the store is not a valid Uint16. This\n * means the store is corrupt (due to some unknown bug) and cannot be repaired.\n */\nexport class InvalidRefCountError extends Error {\n name = 'InvalidRefCountError';\n readonly hash: Hash;\n readonly value: unknown;\n constructor(hash: Hash, value: unknown) {\n super(\n `Invalid ref count ${String(value)} for ${hash}. We expect the value to be a Uint16`,\n );\n this.hash = hash;\n this.value = value;\n }\n}\n"],"mappings":";;;;;AAMA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,OAAO;CACP;CACA;CACA,YAAY,MAAY,OAAgB;EACtC,MACE,qBAAqB,OAAO,KAAK,EAAE,OAAO,KAAK,qCACjD;EACA,KAAK,OAAO;EACZ,KAAK,QAAQ;CACf;AACF"}
|
|
@@ -2,10 +2,20 @@ import { type Hash } from '../hash.ts';
|
|
|
2
2
|
import type { Read as KVRead, Store as KVStore, Write as KVWrite } from '../kv/store.ts';
|
|
3
3
|
import { Chunk, type ChunkHasher, type Refs } from './chunk.ts';
|
|
4
4
|
import { type RefCountUpdatesDelegate } from './gc.ts';
|
|
5
|
+
import { InvalidRefCountError } from './invalid-ref-count-error.ts';
|
|
5
6
|
import { type Read, type Store, type Write } from './store.ts';
|
|
7
|
+
/**
|
|
8
|
+
* Called when a write found an invalid ref count in the store. This is the
|
|
9
|
+
* single place where store corruption is detected, so the owner of the store
|
|
10
|
+
* can start recovery no matter which code path performed the write.
|
|
11
|
+
*
|
|
12
|
+
* The callback runs after the write transaction has been released, so it is
|
|
13
|
+
* safe for it to close or drop the underlying kv store.
|
|
14
|
+
*/
|
|
15
|
+
export type OnInvalidRefCount = (e: InvalidRefCountError) => void;
|
|
6
16
|
export declare class StoreImpl implements Store {
|
|
7
17
|
#private;
|
|
8
|
-
constructor(kv: KVStore, chunkHasher: ChunkHasher, assertValidHash: (hash: Hash) => void);
|
|
18
|
+
constructor(kv: KVStore, chunkHasher: ChunkHasher, assertValidHash: (hash: Hash) => void, onInvalidRefCount?: OnInvalidRefCount | undefined);
|
|
9
19
|
read(): Promise<Read>;
|
|
10
20
|
write(): Promise<Write>;
|
|
11
21
|
close(): Promise<void>;
|
|
@@ -24,7 +34,7 @@ export declare class ReadImpl implements Read {
|
|
|
24
34
|
export declare class WriteImpl extends ReadImpl implements Write, RefCountUpdatesDelegate {
|
|
25
35
|
#private;
|
|
26
36
|
protected readonly _tx: KVWrite;
|
|
27
|
-
constructor(kvw: KVWrite, chunkHasher: ChunkHasher, assertValidHash: (hash: Hash) => void);
|
|
37
|
+
constructor(kvw: KVWrite, chunkHasher: ChunkHasher, assertValidHash: (hash: Hash) => void, onInvalidRefCount?: OnInvalidRefCount | undefined);
|
|
28
38
|
createChunk: <V>(data: V, refs: Refs) => Chunk<V>;
|
|
29
39
|
get kvWrite(): KVWrite;
|
|
30
40
|
putChunk(c: Chunk): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-impl.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/dag/store-impl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"store-impl.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/dag/store-impl.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,KAAK,IAAI,EAAa,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EACV,IAAI,IAAI,MAAM,EACd,KAAK,IAAI,OAAO,EAChB,KAAK,IAAI,OAAO,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,EACL,KAAK,WAAW,EAChB,KAAK,IAAI,EAGV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAC,KAAK,uBAAuB,EAAyB,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAC,oBAAoB,EAAC,MAAM,8BAA8B,CAAC;AAElE,OAAO,EAAC,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EAAe,MAAM,YAAY,CAAC;AAE3E;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAElE,qBAAa,SAAU,YAAW,KAAK;;gBAOnC,EAAE,EAAE,OAAO,EACX,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,EACrC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS;IAQ7C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IAS7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAGvB;AAED,qBAAa,QAAS,YAAW,IAAI;IACnC,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;gBAEnC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI;IAK7D,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAIhC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;IAsBtD,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;IAIlC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAStD,OAAO,IAAI,IAAI;IAIf,IAAI,MAAM,IAAI,OAAO,CAEpB;CACF;AAOD,qBAAa,SACX,SAAQ,QACR,YAAW,KAAK,EAAE,uBAAuB;;IAEzC,mBAA2B,GAAG,EAAE,OAAO,CAAC;gBAStC,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,EACrC,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,SAAS;IAOnD,WAAW,GAAI,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,IAAI,KAAG,KAAK,CAAC,CAAC,CAAC,CACH;IAE7C,IAAI,OAAO,IAAI,OAAO,CAErB;IAEK,QAAQ,CAAC,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBvC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BjC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBvB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAgBpD,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;IAgCnD,OAAO,IAAI,IAAI;CAYhB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InvalidRefCountError } from "./invalid-ref-count-error.js";
|
|
2
2
|
import { assertHash } from "../hash.js";
|
|
3
3
|
import { Chunk, assertRefs, createChunk } from "./chunk.js";
|
|
4
4
|
import { computeRefCountUpdates } from "./gc.js";
|
|
@@ -9,16 +9,18 @@ var StoreImpl = class {
|
|
|
9
9
|
#kv;
|
|
10
10
|
#chunkHasher;
|
|
11
11
|
#assertValidHash;
|
|
12
|
-
|
|
12
|
+
#onInvalidRefCount;
|
|
13
|
+
constructor(kv, chunkHasher, assertValidHash, onInvalidRefCount) {
|
|
13
14
|
this.#kv = kv;
|
|
14
15
|
this.#chunkHasher = chunkHasher;
|
|
15
16
|
this.#assertValidHash = assertValidHash;
|
|
17
|
+
this.#onInvalidRefCount = onInvalidRefCount;
|
|
16
18
|
}
|
|
17
19
|
async read() {
|
|
18
20
|
return new ReadImpl(await this.#kv.read(), this.#assertValidHash);
|
|
19
21
|
}
|
|
20
22
|
async write() {
|
|
21
|
-
return new WriteImpl(await this.#kv.write(), this.#chunkHasher, this.#assertValidHash);
|
|
23
|
+
return new WriteImpl(await this.#kv.write(), this.#chunkHasher, this.#assertValidHash, this.#onInvalidRefCount);
|
|
22
24
|
}
|
|
23
25
|
close() {
|
|
24
26
|
return this.#kv.close();
|
|
@@ -68,11 +70,14 @@ var ReadImpl = class {
|
|
|
68
70
|
};
|
|
69
71
|
var WriteImpl = class extends ReadImpl {
|
|
70
72
|
#chunkHasher;
|
|
73
|
+
#onInvalidRefCount;
|
|
74
|
+
#invalidRefCountError;
|
|
71
75
|
#putChunks = /* @__PURE__ */ new Set();
|
|
72
76
|
#changedHeads = /* @__PURE__ */ new Map();
|
|
73
|
-
constructor(kvw, chunkHasher, assertValidHash) {
|
|
77
|
+
constructor(kvw, chunkHasher, assertValidHash, onInvalidRefCount) {
|
|
74
78
|
super(kvw, assertValidHash);
|
|
75
79
|
this.#chunkHasher = chunkHasher;
|
|
80
|
+
this.#onInvalidRefCount = onInvalidRefCount;
|
|
76
81
|
}
|
|
77
82
|
createChunk = (data, refs) => createChunk(data, refs, this.#chunkHasher);
|
|
78
83
|
get kvWrite() {
|
|
@@ -113,15 +118,20 @@ var WriteImpl = class extends ReadImpl {
|
|
|
113
118
|
await p1;
|
|
114
119
|
}
|
|
115
120
|
async commit() {
|
|
116
|
-
|
|
121
|
+
let refCountUpdates;
|
|
122
|
+
try {
|
|
123
|
+
refCountUpdates = await computeRefCountUpdates(this.#changedHeads.values(), this.#putChunks, this);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
if (e instanceof InvalidRefCountError) this.#invalidRefCountError = e;
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
117
128
|
await this.#applyRefCountUpdates(refCountUpdates);
|
|
118
129
|
await this._tx.commit();
|
|
119
130
|
}
|
|
120
131
|
async getRefCount(hash) {
|
|
121
132
|
const value = await this._tx.get(chunkRefCountKey(hash));
|
|
122
133
|
if (value === void 0) return;
|
|
123
|
-
|
|
124
|
-
if (value < 0 || value > 65535 || value !== (value | 0)) throw new Error(`Invalid ref count ${value}. We expect the value to be a Uint16`);
|
|
134
|
+
if (typeof value !== "number" || value < 0 || value > 65535 || value !== (value | 0)) throw new InvalidRefCountError(hash, value);
|
|
125
135
|
return value;
|
|
126
136
|
}
|
|
127
137
|
async getRefs(hash) {
|
|
@@ -148,7 +158,11 @@ var WriteImpl = class extends ReadImpl {
|
|
|
148
158
|
this.#putChunks.delete(hash);
|
|
149
159
|
}
|
|
150
160
|
release() {
|
|
151
|
-
|
|
161
|
+
try {
|
|
162
|
+
this._tx.release();
|
|
163
|
+
} finally {
|
|
164
|
+
if (this.#invalidRefCountError) this.#onInvalidRefCount?.(this.#invalidRefCountError);
|
|
165
|
+
}
|
|
152
166
|
}
|
|
153
167
|
};
|
|
154
168
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-impl.js","names":["#kv","#chunkHasher","#assertValidHash","#putChunks","#changedHeads","#setHead","#applyRefCountUpdates","#removeAllRelatedKeys"],"sources":["../../../../../replicache/src/dag/store-impl.ts"],"sourcesContent":["import {assertNumber} from '../../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {type Hash, assertHash} from '../hash.ts';\nimport type {\n Read as KVRead,\n Store as KVStore,\n Write as KVWrite,\n} from '../kv/store.ts';\nimport {\n Chunk,\n type ChunkHasher,\n type Refs,\n assertRefs,\n createChunk,\n} from './chunk.ts';\nimport {type RefCountUpdatesDelegate, computeRefCountUpdates} from './gc.ts';\nimport {chunkDataKey, chunkMetaKey, chunkRefCountKey, headKey} from './key.ts';\nimport {type Read, type Store, type Write, mustGetChunk} from './store.ts';\n\nexport class StoreImpl implements Store {\n readonly #kv: KVStore;\n readonly #chunkHasher: ChunkHasher;\n readonly #assertValidHash: (hash: Hash) => void;\n\n constructor(\n kv: KVStore,\n chunkHasher: ChunkHasher,\n assertValidHash: (hash: Hash) => void,\n ) {\n this.#kv = kv;\n this.#chunkHasher = chunkHasher;\n this.#assertValidHash = assertValidHash;\n }\n\n async read(): Promise<Read> {\n return new ReadImpl(await this.#kv.read(), this.#assertValidHash);\n }\n\n async write(): Promise<Write> {\n return new WriteImpl(\n await this.#kv.write(),\n this.#chunkHasher,\n this.#assertValidHash,\n );\n }\n\n close(): Promise<void> {\n return this.#kv.close();\n }\n}\n\nexport class ReadImpl implements Read {\n protected readonly _tx: KVRead;\n readonly assertValidHash: (hash: Hash) => void;\n\n constructor(kv: KVRead, assertValidHash: (hash: Hash) => void) {\n this._tx = kv;\n this.assertValidHash = assertValidHash;\n }\n\n hasChunk(hash: Hash): Promise<boolean> {\n return this._tx.has(chunkDataKey(hash));\n }\n\n async getChunk(hash: Hash): Promise<Chunk | undefined> {\n const dataPromise = this._tx.get(chunkDataKey(hash));\n const refsPromise = this._tx.get(chunkMetaKey(hash));\n const data = await dataPromise;\n if (data === undefined) {\n refsPromise.catch(() => {\n // Ignore error since we will return undefined anyway.\n });\n return undefined;\n }\n\n const refsVal = await refsPromise;\n let refs: Refs;\n if (refsVal !== undefined) {\n assertRefs(refsVal);\n refs = refsVal;\n } else {\n refs = [];\n }\n return new Chunk(hash, data, refs);\n }\n\n mustGetChunk(hash: Hash): Promise<Chunk> {\n return mustGetChunk(this, hash);\n }\n\n async getHead(name: string): Promise<Hash | undefined> {\n const data = await this._tx.get(headKey(name));\n if (data === undefined) {\n return undefined;\n }\n assertHash(data);\n return data;\n }\n\n release(): void {\n this._tx.release();\n }\n\n get closed(): boolean {\n return this._tx.closed;\n }\n}\n\ntype HeadChange = {\n new: Hash | undefined;\n old: Hash | undefined;\n};\n\nexport class WriteImpl\n extends ReadImpl\n implements Write, RefCountUpdatesDelegate\n{\n declare protected readonly _tx: KVWrite;\n readonly #chunkHasher: ChunkHasher;\n\n readonly #putChunks = new Set<Hash>();\n readonly #changedHeads = new Map<string, HeadChange>();\n\n constructor(\n kvw: KVWrite,\n chunkHasher: ChunkHasher,\n assertValidHash: (hash: Hash) => void,\n ) {\n super(kvw, assertValidHash);\n this.#chunkHasher = chunkHasher;\n }\n\n createChunk = <V>(data: V, refs: Refs): Chunk<V> =>\n createChunk(data, refs, this.#chunkHasher);\n\n get kvWrite(): KVWrite {\n return this._tx;\n }\n\n async putChunk(c: Chunk): Promise<void> {\n const {hash, data, meta} = c;\n // We never want to write temp hashes to the underlying store.\n this.assertValidHash(hash);\n const key = chunkDataKey(hash);\n // Commit contains InternalValue and Hash which are opaque types.\n const p1 = this._tx.put(key, data as ReadonlyJSONValue);\n let p2;\n if (meta.length > 0) {\n for (const h of meta) {\n this.assertValidHash(h);\n }\n p2 = this._tx.put(chunkMetaKey(hash), meta);\n }\n this.#putChunks.add(hash);\n await p1;\n await p2;\n }\n\n setHead(name: string, hash: Hash): Promise<void> {\n return this.#setHead(name, hash);\n }\n\n removeHead(name: string): Promise<void> {\n return this.#setHead(name, undefined);\n }\n\n async #setHead(name: string, hash: Hash | undefined): Promise<void> {\n const oldHash = await this.getHead(name);\n const hk = headKey(name);\n\n let p1: Promise<void>;\n if (hash === undefined) {\n p1 = this._tx.del(hk);\n } else {\n p1 = this._tx.put(hk, hash);\n }\n\n const v = this.#changedHeads.get(name);\n if (v === undefined) {\n this.#changedHeads.set(name, {new: hash, old: oldHash});\n } else {\n // Keep old if existing\n v.new = hash;\n }\n\n await p1;\n }\n\n async commit(): Promise<void> {\n const refCountUpdates = await computeRefCountUpdates(\n this.#changedHeads.values(),\n this.#putChunks,\n this,\n );\n await this.#applyRefCountUpdates(refCountUpdates);\n await this._tx.commit();\n }\n\n async getRefCount(hash: Hash): Promise<number | undefined> {\n const value = await this._tx.get(chunkRefCountKey(hash));\n if (value === undefined) {\n return undefined;\n }\n assertNumber(value);\n if (value < 0 || value > 0xffff || value !== (value | 0)) {\n throw new Error(\n `Invalid ref count ${value}. We expect the value to be a Uint16`,\n );\n }\n return value;\n }\n\n async getRefs(hash: Hash): Promise<readonly Hash[]> {\n const meta = await this._tx.get(chunkMetaKey(hash));\n if (meta === undefined) {\n return [];\n }\n assertRefs(meta);\n return meta;\n }\n\n async #applyRefCountUpdates(refCountCache: Map<Hash, number>): Promise<void> {\n const ps: Promise<void>[] = [];\n for (const [hash, count] of refCountCache) {\n if (count === 0) {\n ps.push(this.#removeAllRelatedKeys(hash));\n } else {\n const refCountKey = chunkRefCountKey(hash);\n ps.push(this._tx.put(refCountKey, count));\n }\n }\n await Promise.all(ps);\n }\n\n async #removeAllRelatedKeys(hash: Hash): Promise<void> {\n await Promise.all([\n this._tx.del(chunkDataKey(hash)),\n this._tx.del(chunkMetaKey(hash)),\n this._tx.del(chunkRefCountKey(hash)),\n ]);\n\n this.#putChunks.delete(hash);\n }\n\n release(): void {\n this._tx.release();\n }\n}\n"],"mappings":";;;;;;;AAmBA,IAAa,YAAb,MAAwC;CACtC;CACA;CACA;CAEA,YACE,IACA,aACA,iBACA;EACA,KAAKA,MAAM;EACX,KAAKC,eAAe;EACpB,KAAKC,mBAAmB;CAC1B;CAEA,MAAM,OAAsB;EAC1B,OAAO,IAAI,SAAS,MAAM,KAAKF,IAAI,KAAK,GAAG,KAAKE,gBAAgB;CAClE;CAEA,MAAM,QAAwB;EAC5B,OAAO,IAAI,UACT,MAAM,KAAKF,IAAI,MAAM,GACrB,KAAKC,cACL,KAAKC,gBACP;CACF;CAEA,QAAuB;EACrB,OAAO,KAAKF,IAAI,MAAM;CACxB;AACF;AAEA,IAAa,WAAb,MAAsC;CACpC;CACA;CAEA,YAAY,IAAY,iBAAuC;EAC7D,KAAK,MAAM;EACX,KAAK,kBAAkB;CACzB;CAEA,SAAS,MAA8B;EACrC,OAAO,KAAK,IAAI,IAAI,aAAa,IAAI,CAAC;CACxC;CAEA,MAAM,SAAS,MAAwC;EACrD,MAAM,cAAc,KAAK,IAAI,IAAI,aAAa,IAAI,CAAC;EACnD,MAAM,cAAc,KAAK,IAAI,IAAI,aAAa,IAAI,CAAC;EACnD,MAAM,OAAO,MAAM;EACnB,IAAI,SAAS,KAAA,GAAW;GACtB,YAAY,YAAY,CAExB,CAAC;GACD;EACF;EAEA,MAAM,UAAU,MAAM;EACtB,IAAI;EACJ,IAAI,YAAY,KAAA,GAAW;GACzB,WAAW,OAAO;GAClB,OAAO;EACT,OACE,OAAO,CAAC;EAEV,OAAO,IAAI,MAAM,MAAM,MAAM,IAAI;CACnC;CAEA,aAAa,MAA4B;EACvC,OAAO,aAAa,MAAM,IAAI;CAChC;CAEA,MAAM,QAAQ,MAAyC;EACrD,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,IAAI,CAAC;EAC7C,IAAI,SAAS,KAAA,GACX;EAEF,WAAW,IAAI;EACf,OAAO;CACT;CAEA,UAAgB;EACd,KAAK,IAAI,QAAQ;CACnB;CAEA,IAAI,SAAkB;EACpB,OAAO,KAAK,IAAI;CAClB;AACF;AAOA,IAAa,YAAb,cACU,SAEV;CAEE;CAEA,6BAAsB,IAAI,IAAU;CACpC,gCAAyB,IAAI,IAAwB;CAErD,YACE,KACA,aACA,iBACA;EACA,MAAM,KAAK,eAAe;EAC1B,KAAKC,eAAe;CACtB;CAEA,eAAkB,MAAS,SACzB,YAAY,MAAM,MAAM,KAAKA,YAAY;CAE3C,IAAI,UAAmB;EACrB,OAAO,KAAK;CACd;CAEA,MAAM,SAAS,GAAyB;EACtC,MAAM,EAAC,MAAM,MAAM,SAAQ;EAE3B,KAAK,gBAAgB,IAAI;EACzB,MAAM,MAAM,aAAa,IAAI;EAE7B,MAAM,KAAK,KAAK,IAAI,IAAI,KAAK,IAAyB;EACtD,IAAI;EACJ,IAAI,KAAK,SAAS,GAAG;GACnB,KAAK,MAAM,KAAK,MACd,KAAK,gBAAgB,CAAC;GAExB,KAAK,KAAK,IAAI,IAAI,aAAa,IAAI,GAAG,IAAI;EAC5C;EACA,KAAKE,WAAW,IAAI,IAAI;EACxB,MAAM;EACN,MAAM;CACR;CAEA,QAAQ,MAAc,MAA2B;EAC/C,OAAO,KAAKE,SAAS,MAAM,IAAI;CACjC;CAEA,WAAW,MAA6B;EACtC,OAAO,KAAKA,SAAS,MAAM,KAAA,CAAS;CACtC;CAEA,MAAMA,SAAS,MAAc,MAAuC;EAClE,MAAM,UAAU,MAAM,KAAK,QAAQ,IAAI;EACvC,MAAM,KAAK,QAAQ,IAAI;EAEvB,IAAI;EACJ,IAAI,SAAS,KAAA,GACX,KAAK,KAAK,IAAI,IAAI,EAAE;OAEpB,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI;EAG5B,MAAM,IAAI,KAAKD,cAAc,IAAI,IAAI;EACrC,IAAI,MAAM,KAAA,GACR,KAAKA,cAAc,IAAI,MAAM;GAAC,KAAK;GAAM,KAAK;EAAO,CAAC;OAGtD,EAAE,MAAM;EAGV,MAAM;CACR;CAEA,MAAM,SAAwB;EAC5B,MAAM,kBAAkB,MAAM,uBAC5B,KAAKA,cAAc,OAAO,GAC1B,KAAKD,YACL,IACF;EACA,MAAM,KAAKG,sBAAsB,eAAe;EAChD,MAAM,KAAK,IAAI,OAAO;CACxB;CAEA,MAAM,YAAY,MAAyC;EACzD,MAAM,QAAQ,MAAM,KAAK,IAAI,IAAI,iBAAiB,IAAI,CAAC;EACvD,IAAI,UAAU,KAAA,GACZ;EAEF,aAAa,KAAK;EAClB,IAAI,QAAQ,KAAK,QAAQ,SAAU,WAAW,QAAQ,IACpD,MAAM,IAAI,MACR,qBAAqB,MAAM,qCAC7B;EAEF,OAAO;CACT;CAEA,MAAM,QAAQ,MAAsC;EAClD,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI,aAAa,IAAI,CAAC;EAClD,IAAI,SAAS,KAAA,GACX,OAAO,CAAC;EAEV,WAAW,IAAI;EACf,OAAO;CACT;CAEA,MAAMA,sBAAsB,eAAiD;EAC3E,MAAM,KAAsB,CAAC;EAC7B,KAAK,MAAM,CAAC,MAAM,UAAU,eAC1B,IAAI,UAAU,GACZ,GAAG,KAAK,KAAKC,sBAAsB,IAAI,CAAC;OACnC;GACL,MAAM,cAAc,iBAAiB,IAAI;GACzC,GAAG,KAAK,KAAK,IAAI,IAAI,aAAa,KAAK,CAAC;EAC1C;EAEF,MAAM,QAAQ,IAAI,EAAE;CACtB;CAEA,MAAMA,sBAAsB,MAA2B;EACrD,MAAM,QAAQ,IAAI;GAChB,KAAK,IAAI,IAAI,aAAa,IAAI,CAAC;GAC/B,KAAK,IAAI,IAAI,aAAa,IAAI,CAAC;GAC/B,KAAK,IAAI,IAAI,iBAAiB,IAAI,CAAC;EACrC,CAAC;EAED,KAAKJ,WAAW,OAAO,IAAI;CAC7B;CAEA,UAAgB;EACd,KAAK,IAAI,QAAQ;CACnB;AACF"}
|
|
1
|
+
{"version":3,"file":"store-impl.js","names":["#kv","#chunkHasher","#assertValidHash","#onInvalidRefCount","#putChunks","#changedHeads","#setHead","#invalidRefCountError","#applyRefCountUpdates","#removeAllRelatedKeys"],"sources":["../../../../../replicache/src/dag/store-impl.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {type Hash, assertHash} from '../hash.ts';\nimport type {\n Read as KVRead,\n Store as KVStore,\n Write as KVWrite,\n} from '../kv/store.ts';\nimport {\n Chunk,\n type ChunkHasher,\n type Refs,\n assertRefs,\n createChunk,\n} from './chunk.ts';\nimport {type RefCountUpdatesDelegate, computeRefCountUpdates} from './gc.ts';\nimport {InvalidRefCountError} from './invalid-ref-count-error.ts';\nimport {chunkDataKey, chunkMetaKey, chunkRefCountKey, headKey} from './key.ts';\nimport {type Read, type Store, type Write, mustGetChunk} from './store.ts';\n\n/**\n * Called when a write found an invalid ref count in the store. This is the\n * single place where store corruption is detected, so the owner of the store\n * can start recovery no matter which code path performed the write.\n *\n * The callback runs after the write transaction has been released, so it is\n * safe for it to close or drop the underlying kv store.\n */\nexport type OnInvalidRefCount = (e: InvalidRefCountError) => void;\n\nexport class StoreImpl implements Store {\n readonly #kv: KVStore;\n readonly #chunkHasher: ChunkHasher;\n readonly #assertValidHash: (hash: Hash) => void;\n readonly #onInvalidRefCount: OnInvalidRefCount | undefined;\n\n constructor(\n kv: KVStore,\n chunkHasher: ChunkHasher,\n assertValidHash: (hash: Hash) => void,\n onInvalidRefCount?: OnInvalidRefCount | undefined,\n ) {\n this.#kv = kv;\n this.#chunkHasher = chunkHasher;\n this.#assertValidHash = assertValidHash;\n this.#onInvalidRefCount = onInvalidRefCount;\n }\n\n async read(): Promise<Read> {\n return new ReadImpl(await this.#kv.read(), this.#assertValidHash);\n }\n\n async write(): Promise<Write> {\n return new WriteImpl(\n await this.#kv.write(),\n this.#chunkHasher,\n this.#assertValidHash,\n this.#onInvalidRefCount,\n );\n }\n\n close(): Promise<void> {\n return this.#kv.close();\n }\n}\n\nexport class ReadImpl implements Read {\n protected readonly _tx: KVRead;\n readonly assertValidHash: (hash: Hash) => void;\n\n constructor(kv: KVRead, assertValidHash: (hash: Hash) => void) {\n this._tx = kv;\n this.assertValidHash = assertValidHash;\n }\n\n hasChunk(hash: Hash): Promise<boolean> {\n return this._tx.has(chunkDataKey(hash));\n }\n\n async getChunk(hash: Hash): Promise<Chunk | undefined> {\n const dataPromise = this._tx.get(chunkDataKey(hash));\n const refsPromise = this._tx.get(chunkMetaKey(hash));\n const data = await dataPromise;\n if (data === undefined) {\n refsPromise.catch(() => {\n // Ignore error since we will return undefined anyway.\n });\n return undefined;\n }\n\n const refsVal = await refsPromise;\n let refs: Refs;\n if (refsVal !== undefined) {\n assertRefs(refsVal);\n refs = refsVal;\n } else {\n refs = [];\n }\n return new Chunk(hash, data, refs);\n }\n\n mustGetChunk(hash: Hash): Promise<Chunk> {\n return mustGetChunk(this, hash);\n }\n\n async getHead(name: string): Promise<Hash | undefined> {\n const data = await this._tx.get(headKey(name));\n if (data === undefined) {\n return undefined;\n }\n assertHash(data);\n return data;\n }\n\n release(): void {\n this._tx.release();\n }\n\n get closed(): boolean {\n return this._tx.closed;\n }\n}\n\ntype HeadChange = {\n new: Hash | undefined;\n old: Hash | undefined;\n};\n\nexport class WriteImpl\n extends ReadImpl\n implements Write, RefCountUpdatesDelegate\n{\n declare protected readonly _tx: KVWrite;\n readonly #chunkHasher: ChunkHasher;\n readonly #onInvalidRefCount: OnInvalidRefCount | undefined;\n #invalidRefCountError: InvalidRefCountError | undefined;\n\n readonly #putChunks = new Set<Hash>();\n readonly #changedHeads = new Map<string, HeadChange>();\n\n constructor(\n kvw: KVWrite,\n chunkHasher: ChunkHasher,\n assertValidHash: (hash: Hash) => void,\n onInvalidRefCount?: OnInvalidRefCount | undefined,\n ) {\n super(kvw, assertValidHash);\n this.#chunkHasher = chunkHasher;\n this.#onInvalidRefCount = onInvalidRefCount;\n }\n\n createChunk = <V>(data: V, refs: Refs): Chunk<V> =>\n createChunk(data, refs, this.#chunkHasher);\n\n get kvWrite(): KVWrite {\n return this._tx;\n }\n\n async putChunk(c: Chunk): Promise<void> {\n const {hash, data, meta} = c;\n // We never want to write temp hashes to the underlying store.\n this.assertValidHash(hash);\n const key = chunkDataKey(hash);\n // Commit contains InternalValue and Hash which are opaque types.\n const p1 = this._tx.put(key, data as ReadonlyJSONValue);\n let p2;\n if (meta.length > 0) {\n for (const h of meta) {\n this.assertValidHash(h);\n }\n p2 = this._tx.put(chunkMetaKey(hash), meta);\n }\n this.#putChunks.add(hash);\n await p1;\n await p2;\n }\n\n setHead(name: string, hash: Hash): Promise<void> {\n return this.#setHead(name, hash);\n }\n\n removeHead(name: string): Promise<void> {\n return this.#setHead(name, undefined);\n }\n\n async #setHead(name: string, hash: Hash | undefined): Promise<void> {\n const oldHash = await this.getHead(name);\n const hk = headKey(name);\n\n let p1: Promise<void>;\n if (hash === undefined) {\n p1 = this._tx.del(hk);\n } else {\n p1 = this._tx.put(hk, hash);\n }\n\n const v = this.#changedHeads.get(name);\n if (v === undefined) {\n this.#changedHeads.set(name, {new: hash, old: oldHash});\n } else {\n // Keep old if existing\n v.new = hash;\n }\n\n await p1;\n }\n\n async commit(): Promise<void> {\n let refCountUpdates: Map<Hash, number>;\n try {\n refCountUpdates = await computeRefCountUpdates(\n this.#changedHeads.values(),\n this.#putChunks,\n this,\n );\n } catch (e) {\n if (e instanceof InvalidRefCountError) {\n // Reported from release() rather than here: the owner may drop the\n // store in response and the kv transaction still needs to roll back.\n this.#invalidRefCountError = e;\n }\n throw e;\n }\n await this.#applyRefCountUpdates(refCountUpdates);\n await this._tx.commit();\n }\n\n async getRefCount(hash: Hash): Promise<number | undefined> {\n const value = await this._tx.get(chunkRefCountKey(hash));\n if (value === undefined) {\n return undefined;\n }\n if (\n typeof value !== 'number' ||\n value < 0 ||\n value > 0xffff ||\n value !== (value | 0)\n ) {\n throw new InvalidRefCountError(hash, value);\n }\n return value;\n }\n\n async getRefs(hash: Hash): Promise<readonly Hash[]> {\n const meta = await this._tx.get(chunkMetaKey(hash));\n if (meta === undefined) {\n return [];\n }\n assertRefs(meta);\n return meta;\n }\n\n async #applyRefCountUpdates(refCountCache: Map<Hash, number>): Promise<void> {\n const ps: Promise<void>[] = [];\n for (const [hash, count] of refCountCache) {\n if (count === 0) {\n ps.push(this.#removeAllRelatedKeys(hash));\n } else {\n const refCountKey = chunkRefCountKey(hash);\n ps.push(this._tx.put(refCountKey, count));\n }\n }\n await Promise.all(ps);\n }\n\n async #removeAllRelatedKeys(hash: Hash): Promise<void> {\n await Promise.all([\n this._tx.del(chunkDataKey(hash)),\n this._tx.del(chunkMetaKey(hash)),\n this._tx.del(chunkRefCountKey(hash)),\n ]);\n\n this.#putChunks.delete(hash);\n }\n\n release(): void {\n try {\n this._tx.release();\n } finally {\n // Report even if the kv release threw (for example a failed SQLite\n // ROLLBACK): the store is corrupt either way and the owner must still\n // recover. The release error keeps propagating to the caller.\n if (this.#invalidRefCountError) {\n this.#onInvalidRefCount?.(this.#invalidRefCountError);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../../../replicache/src/kv/expo-sqlite/store.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AAExD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,CAAC,EAAE,sBAAsB,GAC5B,aAAa,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../../../replicache/src/kv/expo-sqlite/store.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAGV,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AAExD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,CAAC,EAAE,sBAAsB,GAC5B,aAAa,CAWf"}
|
|
@@ -12,7 +12,7 @@ function dropExpoSQLiteStore(name, opts) {
|
|
|
12
12
|
*/
|
|
13
13
|
function expoSQLiteStoreProvider(opts) {
|
|
14
14
|
return {
|
|
15
|
-
create: (name) => new SQLiteStore(name, (name) => new ExpoSQLiteDatabase(name), opts),
|
|
15
|
+
create: (name) => new SQLiteStore(name, (name) => new ExpoSQLiteDatabase(name), opts, "expo-sqlite"),
|
|
16
16
|
drop: (name) => dropExpoSQLiteStore(name, opts)
|
|
17
17
|
};
|
|
18
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","names":["#statement","#lock","#db","#filename","#statements"],"sources":["../../../../../../replicache/src/kv/expo-sqlite/store.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport {\n deleteDatabaseSync,\n openDatabaseSync,\n type SQLiteDatabase as DB,\n type SQLiteStatement,\n} from 'expo-sqlite';\nimport type {\n PreparedStatement,\n SQLiteDatabase,\n SQLiteStoreOptions,\n} from '../sqlite-store.ts';\nimport {dropStore, SQLiteStore} from '../sqlite-store.ts';\nimport type {StoreProvider} from '../store.ts';\n\nexport type ExpoSQLiteStoreOptions = SQLiteStoreOptions;\n\nexport function dropExpoSQLiteStore(\n name: string,\n opts?: ExpoSQLiteStoreOptions,\n): Promise<void> {\n return dropStore(name, filename => new ExpoSQLiteDatabase(filename), opts);\n}\n\n/**\n * Creates a StoreProvider for SQLite-based stores using expo-sqlite.\n * Supports shared connections between multiple store instances with the same name,\n * providing efficient resource utilization and proper transaction isolation.\n */\nexport function expoSQLiteStoreProvider(\n opts?: ExpoSQLiteStoreOptions,\n): StoreProvider {\n return {\n create: name =>\n new SQLiteStore(name
|
|
1
|
+
{"version":3,"file":"store.js","names":["#statement","#lock","#db","#filename","#statements"],"sources":["../../../../../../replicache/src/kv/expo-sqlite/store.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport {\n deleteDatabaseSync,\n openDatabaseSync,\n type SQLiteDatabase as DB,\n type SQLiteStatement,\n} from 'expo-sqlite';\nimport type {\n PreparedStatement,\n SQLiteDatabase,\n SQLiteStoreOptions,\n} from '../sqlite-store.ts';\nimport {dropStore, SQLiteStore} from '../sqlite-store.ts';\nimport type {StoreProvider} from '../store.ts';\n\nexport type ExpoSQLiteStoreOptions = SQLiteStoreOptions;\n\nexport function dropExpoSQLiteStore(\n name: string,\n opts?: ExpoSQLiteStoreOptions,\n): Promise<void> {\n return dropStore(name, filename => new ExpoSQLiteDatabase(filename), opts);\n}\n\n/**\n * Creates a StoreProvider for SQLite-based stores using expo-sqlite.\n * Supports shared connections between multiple store instances with the same name,\n * providing efficient resource utilization and proper transaction isolation.\n */\nexport function expoSQLiteStoreProvider(\n opts?: ExpoSQLiteStoreOptions,\n): StoreProvider {\n return {\n create: name =>\n new SQLiteStore(\n name,\n name => new ExpoSQLiteDatabase(name),\n opts,\n 'expo-sqlite',\n ),\n drop: name => dropExpoSQLiteStore(name, opts),\n };\n}\n\n/**\n * An expo-sqlite prepared statement is a stateful cursor over a single\n * `sqlite3_stmt`: `executeForRawResultAsync` resets, rebinds and steps the\n * first row, and the returned result's `getAllAsync` then steps the *remaining*\n * rows of whatever the statement is currently bound to. Both are separate\n * native round trips.\n *\n * `SQLiteStore` shares one prepared statement per SQL across all concurrent\n * readers, so without serialization reader B's `executeForRawResultAsync` can\n * rebind the statement inside reader A's await gap, and A's `getAllAsync` then\n * returns B's rows (and B is starved of them). That yields wrong or missing\n * values and, when a ref count delta is computed from such a read and written,\n * a persistently corrupted store.\n *\n * Every call on a statement is therefore run under a per-statement lock so the\n * execute + fetch pair is atomic with respect to other users of that statement.\n */\nclass ExpoSQLitePreparedStatement implements PreparedStatement {\n readonly #statement: SQLiteStatement;\n readonly #lock = new Lock();\n\n constructor(statement: SQLiteStatement) {\n this.#statement = statement;\n }\n\n exec(params: string[]): Promise<void> {\n return this.#lock.withLock(async () => {\n await this.#statement.executeForRawResultAsync(params);\n });\n }\n\n all(params: string[]): Promise<unknown[][]> {\n return this.#lock.withLock(async () => {\n const result = await this.#statement.executeForRawResultAsync(params);\n // Must be awaited inside the lock so that no other caller can reset the\n // statement before all rows have been stepped.\n return (await result.getAllAsync()) as unknown[][];\n });\n }\n}\n\nclass ExpoSQLiteDatabase implements SQLiteDatabase {\n readonly #db: DB;\n readonly #filename: string;\n readonly #statements: Set<SQLiteStatement> = new Set();\n\n constructor(filename: string) {\n this.#filename = filename;\n this.#db = openDatabaseSync(filename);\n }\n\n close(): void {\n for (const stmt of this.#statements) {\n stmt.finalizeSync();\n }\n this.#db.closeSync();\n }\n\n destroy(): void {\n deleteDatabaseSync(this.#filename);\n }\n\n prepare(sql: string): PreparedStatement {\n const statement = this.#db.prepareSync(sql);\n this.#statements.add(statement);\n return new ExpoSQLitePreparedStatement(statement);\n }\n\n execSync(sql: string): void {\n this.#db.execSync(sql);\n }\n}\n"],"mappings":";;;;AAiBA,SAAgB,oBACd,MACA,MACe;CACf,OAAO,UAAU,OAAM,aAAY,IAAI,mBAAmB,QAAQ,GAAG,IAAI;AAC3E;;;;;;AAOA,SAAgB,wBACd,MACe;CACf,OAAO;EACL,SAAQ,SACN,IAAI,YACF,OACA,SAAQ,IAAI,mBAAmB,IAAI,GACnC,MACA,aACF;EACF,OAAM,SAAQ,oBAAoB,MAAM,IAAI;CAC9C;AACF;;;;;;;;;;;;;;;;;;AAmBA,IAAM,8BAAN,MAA+D;CAC7D;CACA,QAAiB,IAAI,KAAK;CAE1B,YAAY,WAA4B;EACtC,KAAKA,aAAa;CACpB;CAEA,KAAK,QAAiC;EACpC,OAAO,KAAKC,MAAM,SAAS,YAAY;GACrC,MAAM,KAAKD,WAAW,yBAAyB,MAAM;EACvD,CAAC;CACH;CAEA,IAAI,QAAwC;EAC1C,OAAO,KAAKC,MAAM,SAAS,YAAY;GAIrC,OAAQ,OAAM,MAHO,KAAKD,WAAW,yBAAyB,MAAM,GAG/C,YAAY;EACnC,CAAC;CACH;AACF;AAEA,IAAM,qBAAN,MAAmD;CACjD;CACA;CACA,8BAA6C,IAAI,IAAI;CAErD,YAAY,UAAkB;EAC5B,KAAKG,YAAY;EACjB,KAAKD,MAAM,iBAAiB,QAAQ;CACtC;CAEA,QAAc;EACZ,KAAK,MAAM,QAAQ,KAAKE,aACtB,KAAK,aAAa;EAEpB,KAAKF,IAAI,UAAU;CACrB;CAEA,UAAgB;EACd,mBAAmB,KAAKC,SAAS;CACnC;CAEA,QAAQ,KAAgC;EACtC,MAAM,YAAY,KAAKD,IAAI,YAAY,GAAG;EAC1C,KAAKE,YAAY,IAAI,SAAS;EAC9B,OAAO,IAAI,4BAA4B,SAAS;CAClD;CAEA,SAAS,KAAmB;EAC1B,KAAKF,IAAI,SAAS,GAAG;CACvB;AACF"}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import type { LogContext } from '@rocicorp/logger';
|
|
2
2
|
import type { Read, Store, Write } from './store.ts';
|
|
3
3
|
/**
|
|
4
|
-
* This store uses an {@link IDBStore} by default. If
|
|
5
|
-
* to open
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* This store uses an {@link IDBStore} by default. If `indexedDB.open` rejects
|
|
5
|
+
* (WebKit's "Unable to open database file on disk" and "Error creating Records
|
|
6
|
+
* table (13) - database or disk is full", or Firefox private browsing), the
|
|
7
|
+
* {@link IDBStore} rejects `read()` and `write()` with an {@link IDBOpenError}
|
|
8
|
+
* and we switch out the implementation to use a {@link MemStore} instead. Any
|
|
9
|
+
* other error, such as a transaction error on a database that did open, is
|
|
10
|
+
* rethrown, so a store that has data is never swapped for an empty one behind
|
|
11
|
+
* the caller's back. Every caller whose call failed with the open error retries
|
|
12
|
+
* on the memory store, so concurrent first calls all succeed and the switch is
|
|
13
|
+
* logged once.
|
|
8
14
|
*
|
|
9
15
|
* The reason this is relatively complicated is that when {@link IDBStore} is
|
|
10
16
|
* created, it calls `openDatabase` synchronously, but that returns a `Promise`
|
|
11
|
-
* that will reject
|
|
17
|
+
* that will reject if the database cannot be opened. We don't await this
|
|
12
18
|
* promise until we call `read` or `write` so we cannot do the switch until
|
|
13
19
|
* then.
|
|
14
20
|
*/
|
|
@@ -19,6 +25,10 @@ export declare class IDBStoreWithMemFallback implements Store {
|
|
|
19
25
|
write(): Promise<Write>;
|
|
20
26
|
close(): Promise<void>;
|
|
21
27
|
get closed(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* `'idb'` until an open failure switches this store to memory, then `'mem'`.
|
|
30
|
+
*/
|
|
31
|
+
get kind(): string | undefined;
|
|
22
32
|
}
|
|
23
33
|
export declare function newIDBStoreWithMemFallback(lc: LogContext, name: string): Store;
|
|
24
34
|
export declare function dropIDBStoreWithMemFallback(name: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idb-store-with-mem-fallback.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/kv/idb-store-with-mem-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"idb-store-with-mem-fallback.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/kv/idb-store-with-mem-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAC,MAAM,YAAY,CAAC;AAEnD;;;;;;;;;;;;;;;;;GAiBG;AAEH,qBAAa,uBAAwB,YAAW,KAAK;;gBAIvC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM;IAMxC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IA0BvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,GAAG,SAAS,CAE7B;CACF;AAED,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,MAAM,GACX,KAAK,CAEP;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOvE"}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { IDBStore } from "./idb-store.js";
|
|
3
|
-
import { localNavigator } from "../../../shared/src/navigator.js";
|
|
1
|
+
import { IDBOpenError, IDBStore } from "./idb-store.js";
|
|
4
2
|
import { MemStore, dropMemStore } from "./mem-store.js";
|
|
5
3
|
//#region ../replicache/src/kv/idb-store-with-mem-fallback.ts
|
|
6
4
|
/**
|
|
7
|
-
* This store uses an {@link IDBStore} by default. If
|
|
8
|
-
* to open
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* This store uses an {@link IDBStore} by default. If `indexedDB.open` rejects
|
|
6
|
+
* (WebKit's "Unable to open database file on disk" and "Error creating Records
|
|
7
|
+
* table (13) - database or disk is full", or Firefox private browsing), the
|
|
8
|
+
* {@link IDBStore} rejects `read()` and `write()` with an {@link IDBOpenError}
|
|
9
|
+
* and we switch out the implementation to use a {@link MemStore} instead. Any
|
|
10
|
+
* other error, such as a transaction error on a database that did open, is
|
|
11
|
+
* rethrown, so a store that has data is never swapped for an empty one behind
|
|
12
|
+
* the caller's back. Every caller whose call failed with the open error retries
|
|
13
|
+
* on the memory store, so concurrent first calls all succeed and the switch is
|
|
14
|
+
* logged once.
|
|
11
15
|
*
|
|
12
16
|
* The reason this is relatively complicated is that when {@link IDBStore} is
|
|
13
17
|
* created, it calls `openDatabase` synchronously, but that returns a `Promise`
|
|
14
|
-
* that will reject
|
|
18
|
+
* that will reject if the database cannot be opened. We don't await this
|
|
15
19
|
* promise until we call `read` or `write` so we cannot do the switch until
|
|
16
20
|
* then.
|
|
17
21
|
*/
|
|
@@ -34,9 +38,9 @@ var IDBStoreWithMemFallback = class {
|
|
|
34
38
|
try {
|
|
35
39
|
return await f(this.#store);
|
|
36
40
|
} catch (e) {
|
|
37
|
-
if (
|
|
41
|
+
if (e instanceof IDBOpenError) {
|
|
38
42
|
if (this.#store instanceof IDBStore) {
|
|
39
|
-
this.#lc.
|
|
43
|
+
this.#lc.warn?.("Switching to MemStore because IndexedDB failed to open", e);
|
|
40
44
|
this.#store = new MemStore(this.#name);
|
|
41
45
|
}
|
|
42
46
|
return f(this.#store);
|
|
@@ -50,25 +54,21 @@ var IDBStoreWithMemFallback = class {
|
|
|
50
54
|
get closed() {
|
|
51
55
|
return this.#store.closed;
|
|
52
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* `'idb'` until an open failure switches this store to memory, then `'mem'`.
|
|
59
|
+
*/
|
|
60
|
+
get kind() {
|
|
61
|
+
return this.#store.kind;
|
|
62
|
+
}
|
|
53
63
|
};
|
|
54
|
-
function isFirefoxPrivateBrowsingError(e) {
|
|
55
|
-
return isFirefox() && e instanceof DOMException && e.name === "InvalidStateError" && e.message === "A mutation operation was attempted on a database that did not allow mutations.";
|
|
56
|
-
}
|
|
57
|
-
function isFirefox() {
|
|
58
|
-
return localNavigator?.userAgent?.includes("Firefox") ?? false;
|
|
59
|
-
}
|
|
60
64
|
function newIDBStoreWithMemFallback(lc, name) {
|
|
61
|
-
|
|
62
|
-
return new IDBStore(name);
|
|
65
|
+
return new IDBStoreWithMemFallback(lc, name);
|
|
63
66
|
}
|
|
64
67
|
function dropIDBStoreWithMemFallback(name) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
if (isFirefoxPrivateBrowsingError(e)) return dropMemStore(name);
|
|
70
|
-
}
|
|
71
|
-
return promiseVoid;
|
|
68
|
+
return dropIDBStore(name).catch((e) => {
|
|
69
|
+
if (e instanceof DOMException) return dropMemStore(name);
|
|
70
|
+
throw e;
|
|
71
|
+
});
|
|
72
72
|
}
|
|
73
73
|
function dropIDBStore(name) {
|
|
74
74
|
return new Promise((resolve, reject) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idb-store-with-mem-fallback.js","names":["#lc","#name","#store","#withBrainTransplant"],"sources":["../../../../../replicache/src/kv/idb-store-with-mem-fallback.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {
|
|
1
|
+
{"version":3,"file":"idb-store-with-mem-fallback.js","names":["#lc","#name","#store","#withBrainTransplant"],"sources":["../../../../../replicache/src/kv/idb-store-with-mem-fallback.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {IDBOpenError, IDBStore} from './idb-store.ts';\nimport {MemStore, dropMemStore} from './mem-store.ts';\nimport type {Read, Store, Write} from './store.ts';\n\n/**\n * This store uses an {@link IDBStore} by default. If `indexedDB.open` rejects\n * (WebKit's \"Unable to open database file on disk\" and \"Error creating Records\n * table (13) - database or disk is full\", or Firefox private browsing), the\n * {@link IDBStore} rejects `read()` and `write()` with an {@link IDBOpenError}\n * and we switch out the implementation to use a {@link MemStore} instead. Any\n * other error, such as a transaction error on a database that did open, is\n * rethrown, so a store that has data is never swapped for an empty one behind\n * the caller's back. Every caller whose call failed with the open error retries\n * on the memory store, so concurrent first calls all succeed and the switch is\n * logged once.\n *\n * The reason this is relatively complicated is that when {@link IDBStore} is\n * created, it calls `openDatabase` synchronously, but that returns a `Promise`\n * that will reject if the database cannot be opened. We don't await this\n * promise until we call `read` or `write` so we cannot do the switch until\n * then.\n */\n\nexport class IDBStoreWithMemFallback implements Store {\n readonly #lc: LogContext;\n readonly #name: string;\n #store: Store;\n constructor(lc: LogContext, name: string) {\n this.#lc = lc;\n this.#name = name;\n this.#store = new IDBStore(name);\n }\n\n read(): Promise<Read> {\n return this.#withBrainTransplant(s => s.read());\n }\n\n write(): Promise<Write> {\n return this.#withBrainTransplant(s => s.write());\n }\n\n async #withBrainTransplant<T extends Read>(\n f: (store: Store) => Promise<T>,\n ): Promise<T> {\n try {\n return await f(this.#store);\n } catch (e) {\n if (e instanceof IDBOpenError) {\n // It is possible that we end up with multiple pending read/write and\n // they all reject. Make sure we only replace the implementation once.\n if (this.#store instanceof IDBStore) {\n this.#lc.warn?.(\n 'Switching to MemStore because IndexedDB failed to open',\n e,\n );\n this.#store = new MemStore(this.#name);\n }\n return f(this.#store);\n }\n throw e;\n }\n }\n\n close(): Promise<void> {\n return this.#store.close();\n }\n\n get closed(): boolean {\n return this.#store.closed;\n }\n\n /**\n * `'idb'` until an open failure switches this store to memory, then `'mem'`.\n */\n get kind(): string | undefined {\n return this.#store.kind;\n }\n}\n\nexport function newIDBStoreWithMemFallback(\n lc: LogContext,\n name: string,\n): Store {\n return new IDBStoreWithMemFallback(lc, name);\n}\n\nexport function dropIDBStoreWithMemFallback(name: string): Promise<void> {\n return dropIDBStore(name).catch((e: unknown) => {\n if (e instanceof DOMException) {\n return dropMemStore(name);\n }\n throw e;\n });\n}\n\nfunction dropIDBStore(name: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const req = indexedDB.deleteDatabase(name);\n req.onsuccess = () => resolve();\n req.onerror = () => reject(req.error);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwBA,IAAa,0BAAb,MAAsD;CACpD;CACA;CACA;CACA,YAAY,IAAgB,MAAc;EACxC,KAAKA,MAAM;EACX,KAAKC,QAAQ;EACb,KAAKC,SAAS,IAAI,SAAS,IAAI;CACjC;CAEA,OAAsB;EACpB,OAAO,KAAKC,sBAAqB,MAAK,EAAE,KAAK,CAAC;CAChD;CAEA,QAAwB;EACtB,OAAO,KAAKA,sBAAqB,MAAK,EAAE,MAAM,CAAC;CACjD;CAEA,MAAMA,qBACJ,GACY;EACZ,IAAI;GACF,OAAO,MAAM,EAAE,KAAKD,MAAM;EAC5B,SAAS,GAAG;GACV,IAAI,aAAa,cAAc;IAG7B,IAAI,KAAKA,kBAAkB,UAAU;KACnC,KAAKF,IAAI,OACP,0DACA,CACF;KACA,KAAKE,SAAS,IAAI,SAAS,KAAKD,KAAK;IACvC;IACA,OAAO,EAAE,KAAKC,MAAM;GACtB;GACA,MAAM;EACR;CACF;CAEA,QAAuB;EACrB,OAAO,KAAKA,OAAO,MAAM;CAC3B;CAEA,IAAI,SAAkB;EACpB,OAAO,KAAKA,OAAO;CACrB;;;;CAKA,IAAI,OAA2B;EAC7B,OAAO,KAAKA,OAAO;CACrB;AACF;AAEA,SAAgB,2BACd,IACA,MACO;CACP,OAAO,IAAI,wBAAwB,IAAI,IAAI;AAC7C;AAEA,SAAgB,4BAA4B,MAA6B;CACvE,OAAO,aAAa,IAAI,EAAE,OAAO,MAAe;EAC9C,IAAI,aAAa,cACf,OAAO,aAAa,IAAI;EAE1B,MAAM;CACR,CAAC;AACH;AAEA,SAAS,aAAa,MAA6B;CACjD,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,MAAM,UAAU,eAAe,IAAI;EACzC,IAAI,kBAAkB,QAAQ;EAC9B,IAAI,gBAAgB,OAAO,IAAI,KAAK;CACtC,CAAC;AACH"}
|