@rocicorp/zero 1.11.0-canary.13 → 1.11.0-canary.15
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/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts +8 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.js +25 -25
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/otel/src/log-options.d.ts +16 -0
- package/out/otel/src/log-options.d.ts.map +1 -1
- package/out/otel/src/log-options.js +54 -1
- package/out/otel/src/log-options.js.map +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
- package/out/replicache/src/get-kv-store-provider.js +4 -4
- package/out/replicache/src/get-kv-store-provider.js.map +1 -1
- package/out/replicache/src/kv/idb-store.d.ts +10 -1
- package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store.js +18 -2
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
- package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
- package/out/replicache/src/kv/mem-fallback-store.js +177 -0
- package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
- package/out/replicache/src/kv/op-sqlite/store.js +3 -3
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +9 -0
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +86 -6
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/new-client-channel.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +17 -3
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +81 -11
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
- package/out/replicache/src/persist/persist.d.ts.map +1 -1
- package/out/replicache/src/persist/persist.js +6 -3
- package/out/replicache/src/persist/persist.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +43 -3
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +135 -17
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/storage-failure.d.ts +37 -0
- package/out/replicache/src/storage-failure.d.ts.map +1 -0
- package/out/replicache/src/storage-failure.js +31 -0
- package/out/replicache/src/storage-failure.js.map +1 -0
- package/out/shared/src/random-values.js +12 -1
- package/out/shared/src/random-values.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/zero.js +2 -1
- package/out/zero-cache/src/config/normalize.d.ts.map +1 -1
- package/out/zero-cache/src/config/normalize.js +1 -1
- package/out/zero-cache/src/config/normalize.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +26 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +36 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
- package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
- package/out/zero-cache/src/observability/log-throttle.js +76 -0
- package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
- package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
- package/out/zero-cache/src/scripts/permissions.d.ts.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 +2 -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 +20 -2
- package/out/zero-cache/src/server/syncer.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 +27 -15
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +15 -3
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +11 -2
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +11 -2
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +7 -6
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +20 -4
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +26 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +88 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js +9 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +29 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +34 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/plannerz.d.ts +159 -0
- package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
- package/out/zero-cache/src/services/plannerz.js +263 -0
- package/out/zero-cache/src/services/plannerz.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +44 -12
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +4 -3
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +36 -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 +362 -20
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +11 -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 +7 -1
- 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.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +49 -3
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +29 -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/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +8 -23
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.d.ts +1 -0
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-client/src/mod.js +1 -0
- package/out/zero-protocol/src/down.d.ts +2 -2
- package/out/zero-react/src/zero.js +1 -0
- package/out/zero-solid/src/zero.js +1 -0
- package/out/zql/src/builder/builder.d.ts +6 -0
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +4 -3
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +13 -0
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +1 -1
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/planner/planner-builder.d.ts +12 -1
- package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
- package/out/zql/src/planner/planner-builder.js +8 -1
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts +43 -0
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +31 -4
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-graph.d.ts +10 -0
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +14 -0
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-warnings.d.ts +83 -0
- package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
- package/out/zql/src/planner/planner-warnings.js +78 -0
- package/out/zql/src/planner/planner-warnings.js.map +1 -0
- package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
- package/out/zql/src/query/measure-push-operator.js +30 -8
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/out/zqlite/src/pending-delta.d.ts +110 -0
- package/out/zqlite/src/pending-delta.d.ts.map +1 -0
- package/out/zqlite/src/pending-delta.js +278 -0
- package/out/zqlite/src/pending-delta.js.map +1 -0
- package/out/zqlite/src/query-delegate.d.ts.map +1 -1
- package/out/zqlite/src/query-delegate.js +5 -1
- package/out/zqlite/src/query-delegate.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +52 -1
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js +2 -26
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
- package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
- package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
- package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
- package/out/zqlite/src/table-source.d.ts +68 -0
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +206 -7
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -35
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +0 -156
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +0 -15
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js.map +0 -1
|
@@ -9,7 +9,7 @@ import { parseOptions } from "../../shared/src/options.js";
|
|
|
9
9
|
import { ZERO_ENV_VAR_PREFIX } from "../../zero-cache/src/config/zero-config.js";
|
|
10
10
|
import { Console } from "node:console";
|
|
11
11
|
import { styleText } from "node:util";
|
|
12
|
-
import { WebSocket
|
|
12
|
+
import { WebSocket } from "ws";
|
|
13
13
|
//#region ../analyze-query/src/analyze-cli.ts
|
|
14
14
|
var options = {
|
|
15
15
|
zeroCacheURL: {
|
|
@@ -223,7 +223,7 @@ function resolveHandshakeHeaders(config) {
|
|
|
223
223
|
return headers;
|
|
224
224
|
}
|
|
225
225
|
function installWebSocketHeaderShim(headers) {
|
|
226
|
-
class HeaderInjectingWebSocket extends WebSocket
|
|
226
|
+
class HeaderInjectingWebSocket extends WebSocket {
|
|
227
227
|
constructor(url, protocols) {
|
|
228
228
|
super(url, protocols, { headers });
|
|
229
229
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-cli.js","names":[],"sources":["../../../../analyze-query/src/analyze-cli.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport {Console} from 'node:console';\nimport {styleText} from 'node:util';\nimport type {LogSink} from '@rocicorp/logger';\nimport {WebSocket as NodeWebSocket} from 'ws';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole} from '../../shared/src/logging.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../../zero-cache/src/config/zero-config.ts';\nimport {Zero} from '../../zero-client/src/client/zero.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {formatPlannerEvents} from '../../zql/src/planner/planner-debug.ts';\nimport {createBuilder} from '../../zql/src/query/create-builder.ts';\nimport type {AnyQuery} from '../../zql/src/query/query.ts';\nimport type {SchemaQuery} from '../../zql/src/query/schema-query.ts';\n\nexport type AnalyzeCLIOptions = {\n schema: Schema;\n /** Defaults to `process.argv.slice(2)`. */\n argv?: readonly string[] | undefined;\n};\n\nconst options = {\n zeroCacheURL: {\n type: v.string().optional(),\n desc: [\n 'URL of the remote zero-cache to analyze against.',\n 'Accepts http(s):// or ws(s):// (ws(s) is the transport actually used).',\n ],\n },\n adminPassword: {\n type: v.string().optional(),\n desc: [\n 'Admin password for zero-cache.',\n 'Required when the server is configured with one; ignored in dev mode.',\n ],\n },\n authToken: {\n type: v.string().optional(),\n desc: [\n 'Raw JWT forwarded to zero-cache.',\n 'Used server-side to fill permission variables for the query.',\n ],\n },\n cookie: {\n type: v.string().optional(),\n desc: [\n 'Cookie header value sent on the WebSocket upgrade request,',\n 'e.g. `session=abc; foo=bar`. Use this when zero-cache is behind',\n 'a proxy that resolves auth via cookies. Merged with --headers-json',\n '(--cookie wins on conflict).',\n ],\n },\n headersJson: {\n type: v.string().optional(),\n desc: [\n 'JSON object of arbitrary headers to send on the WebSocket upgrade',\n 'request, e.g. `{\"x-api-key\":\"...\"}`. Escape hatch for exotic auth',\n 'schemes; prefer --auth-token or --cookie when possible.',\n ],\n },\n userId: {\n type: v.string().optional(),\n desc: [\n 'Optional userID to report to zero-cache.',\n 'Omit for anonymous analysis.',\n ],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded AST. Exactly one of --ast / --query / --query-name is required.',\n 'The AST is sent to the server verbatim — provide it in server (post-mapping) form.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'ZQL query in chain form, e.g. `issue.related(\"comments\").limit(10)`.',\n 'Evaluated against the schema you pass to runAnalyzeCLI.',\n ],\n },\n queryName: {\n type: v.string().optional(),\n desc: [\n 'Name of a server-registered custom (named) query.',\n 'The server resolves the name + args via its registered query handler.',\n ],\n },\n queryArgs: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded array of arguments for --query-name. Defaults to `[]`.',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Include the rows read from the replica to execute the query.',\n 'Each row appears once per read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: ['Include the rows that would be synced to the client.'],\n },\n joinPlans: {\n type: v.boolean().default(false),\n desc: ['Include join planner diagnostics.'],\n },\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\ntype QueryPlan =\n | {kind: 'ast'; ast: AST}\n | {kind: 'zql'; text: string}\n | {kind: 'named'; name: string; args: ReadonlyArray<unknown>};\n\n// Route all Zero client log output to stderr so stdout contains only the\n// analyze result. Shell redirection (`2>/dev/null`) can then cleanly silence\n// logs without affecting output.\nconst stderrConsole = new Console({\n stdout: process.stderr,\n stderr: process.stderr,\n});\nconst stderrLogSink: LogSink = {\n log(level, context, ...args) {\n const ctx = context\n ? Object.entries(context).map(([k, v]) =>\n v === undefined ? k : `${k}=${v}`,\n )\n : [];\n stderrConsole[level](...ctx, ...args);\n },\n};\n\n/**\n * Entry point for a user's `cli.ts`. Parses argv, connects to a remote\n * zero-cache by standing up an in-process Zero client (in-memory storage,\n * no subscriptions), calls the inspector's `analyze-query` RPC, and\n * renders the result. Intended to be called as:\n *\n * ```ts\n * import {schema} from './schema.ts';\n * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';\n * await runAnalyzeCLI({schema});\n * ```\n *\n * Exits the process with code 1 on error.\n */\nexport async function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void> {\n const argv = (opts.argv ?? process.argv.slice(2)).map(s =>\n s.replaceAll('\\n', ' '),\n );\n\n const config = parseOptions(options, {\n argv,\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query (remote)',\n content: `Analyze a ZQL query against a remote zero-cache.\n\n Connects to zero-cache's inspector protocol and reports the server-observed\n row scans, SQLite query plans, and timings.`,\n },\n {\n header: 'Examples',\n content: ` node cli.ts --zero-cache-url=https://zero.example.com \\\\\n --admin-password=\"$ZERO_ADMIN_PASSWORD\" \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n node cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --ast='\\\\{\"table\": \"issue\", \"limit\": 5\\\\}'\n\n node cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --query-name=issueList --query-args='[]'`,\n },\n ],\n });\n\n if (!config.zeroCacheURL) {\n colorConsole.error('--zero-cache-url is required. See --help for usage.');\n process.exit(1);\n }\n\n const plan = buildQueryPlan(config);\n\n const handshakeHeaders = resolveHandshakeHeaders(config);\n if (Object.keys(handshakeHeaders).length > 0) {\n installWebSocketHeaderShim(handshakeHeaders);\n }\n\n // zero-client and replicache reference a build-time `TESTING` global that\n // bundlers replace with a boolean literal; under node there's no replacement,\n // so provide a runtime default.\n (globalThis as {TESTING?: boolean}).TESTING ??= false;\n\n const z = new Zero({\n schema: opts.schema,\n server: config.zeroCacheURL,\n auth: config.authToken,\n userID: config.userId,\n kvStore: 'mem',\n logLevel: config.log.level,\n logSink: stderrLogSink,\n });\n\n let result: AnalyzeQueryResult;\n try {\n const authOk = await z.inspector.authenticate(config.adminPassword ?? '');\n if (!authOk) {\n throw new Error(\n 'admin password rejected (or --admin-password is required)',\n );\n }\n\n const rpcOptions = {\n vendedRows: config.outputVendedRows,\n syncedRows: config.outputSyncedRows,\n joinPlans: config.joinPlans,\n };\n\n if (plan.kind === 'ast') {\n result = await z.inspector.analyzeServerAST(plan.ast, rpcOptions);\n } else if (plan.kind === 'named') {\n result = await z.inspector.analyzeNamedQuery(\n plan.name,\n plan.args as ReadonlyArray<never>,\n rpcOptions,\n );\n } else {\n const built = buildZqlQuery(plan.text, createBuilder(opts.schema));\n result = await z.inspector.analyzeQuery(built, rpcOptions);\n }\n } catch (e) {\n colorConsole.error(e instanceof Error ? e.message : String(e));\n await z.close().catch(() => {});\n process.exit(1);\n }\n\n renderResult(result, {\n outputSyncedRows: config.outputSyncedRows,\n outputVendedRows: config.outputVendedRows,\n });\n\n await z.close();\n}\n\nfunction buildQueryPlan(config: {\n ast?: string | undefined;\n query?: string | undefined;\n queryName?: string | undefined;\n queryArgs?: string | undefined;\n}): QueryPlan {\n const selectors = [\n config.ast !== undefined && 'ast',\n config.query !== undefined && 'query',\n config.queryName !== undefined && 'queryName',\n ].filter(Boolean) as string[];\n\n if (selectors.length === 0) {\n colorConsole.error(\n 'Exactly one of --ast / --query / --query-name is required.',\n );\n process.exit(1);\n }\n if (selectors.length > 1) {\n colorConsole.error(\n `Only one of --ast / --query / --query-name may be provided; got: ${selectors.join(', ')}`,\n );\n process.exit(1);\n }\n\n if (config.ast !== undefined) {\n return {kind: 'ast', ast: JSON.parse(config.ast) as AST};\n }\n if (config.query !== undefined) {\n return {kind: 'zql', text: config.query};\n }\n const args = config.queryArgs\n ? (JSON.parse(config.queryArgs) as ReadonlyArray<unknown>)\n : [];\n return {kind: 'named', name: config.queryName as string, args};\n}\n\nfunction buildZqlQuery(\n queryString: string,\n builder: SchemaQuery<Schema>,\n): AnyQuery {\n const f = new Function('builder', `return builder.${queryString};`);\n return f(builder) as AnyQuery;\n}\n\nfunction resolveHandshakeHeaders(config: {\n cookie?: string | undefined;\n headersJson?: string | undefined;\n}): Record<string, string> {\n const headers: Record<string, string> = {};\n if (config.headersJson !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(config.headersJson);\n } catch (e) {\n colorConsole.error(\n `--headers-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,\n );\n process.exit(1);\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n colorConsole.error('--headers-json must be a JSON object.');\n process.exit(1);\n }\n for (const [k, val] of Object.entries(parsed)) {\n if (typeof val !== 'string') {\n colorConsole.error(\n `--headers-json values must be strings; got ${typeof val} for \"${k}\".`,\n );\n process.exit(1);\n }\n headers[k] = val;\n }\n }\n if (config.cookie !== undefined) {\n headers.cookie = config.cookie;\n }\n return headers;\n}\n\nfunction installWebSocketHeaderShim(headers: Record<string, string>): void {\n class HeaderInjectingWebSocket extends NodeWebSocket {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols, {headers});\n }\n }\n (globalThis as {WebSocket?: unknown}).WebSocket = HeaderInjectingWebSocket;\n}\n\nfunction renderResult(\n result: AnalyzeQueryResult,\n opts: {outputSyncedRows: boolean; outputVendedRows: boolean},\n) {\n if (opts.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\n colorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\n colorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n );\n\n const readRowCountsByQuery = result.readRowCountsByQuery ?? {};\n let totalRowsRead = 0;\n for (const table of Object.keys(readRowCountsByQuery).sort()) {\n const counts = readRowCountsByQuery[table];\n for (const n of Object.values(counts)) {\n totalRowsRead += n;\n }\n colorConsole.log(styleText('bold', `${table} vended:`), counts);\n }\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsRead),\n );\n const duration = result.elapsed ?? result.end - result.start;\n colorConsole.log(styleText('bold', 'time:'), colorTime(duration), 'ms');\n\n if (opts.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const [table, rows] of Object.entries(result.readRows ?? {})) {\n colorConsole.log(styleText('bold', `${table}:`), rows);\n }\n }\n\n colorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n );\n const dbScansByQuery = result.dbScansByQuery ?? {};\n let totalNVisit = 0;\n for (const [table, queries] of Object.entries(dbScansByQuery)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n }\n colorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n );\n\n colorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\n const plans = result.sqlitePlans ?? {};\n for (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\n if (result.warnings.length > 0) {\n colorConsole.log(styleText(['yellow', 'bold'], '=== Warnings: ===\\n'));\n for (const w of result.warnings) {\n colorConsole.log(styleText('yellow', w));\n }\n }\n\n if (result.joinPlans && result.joinPlans.length > 0) {\n colorConsole.log(styleText(['blue', 'bold'], '\\n=== Join Plans: ===\\n'));\n colorConsole.log(formatPlannerEvents(result.joinPlans));\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":"analyze-cli.js","names":[],"sources":["../../../../analyze-query/src/analyze-cli.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport {Console} from 'node:console';\nimport {styleText} from 'node:util';\nimport type {LogSink} from '@rocicorp/logger';\nimport {WebSocket as NodeWebSocket} from 'ws';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole} from '../../shared/src/logging.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../../zero-cache/src/config/zero-config.ts';\nimport {Zero} from '../../zero-client/src/client/zero.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {formatPlannerEvents} from '../../zql/src/planner/planner-debug.ts';\nimport {createBuilder} from '../../zql/src/query/create-builder.ts';\nimport type {AnyQuery} from '../../zql/src/query/query.ts';\nimport type {SchemaQuery} from '../../zql/src/query/schema-query.ts';\n\nexport type AnalyzeCLIOptions = {\n schema: Schema;\n /** Defaults to `process.argv.slice(2)`. */\n argv?: readonly string[] | undefined;\n};\n\nconst options = {\n zeroCacheURL: {\n type: v.string().optional(),\n desc: [\n 'URL of the remote zero-cache to analyze against.',\n 'Accepts http(s):// or ws(s):// (ws(s) is the transport actually used).',\n ],\n },\n adminPassword: {\n type: v.string().optional(),\n desc: [\n 'Admin password for zero-cache.',\n 'Required when the server is configured with one; ignored in dev mode.',\n ],\n },\n authToken: {\n type: v.string().optional(),\n desc: [\n 'Raw JWT forwarded to zero-cache.',\n 'Used server-side to fill permission variables for the query.',\n ],\n },\n cookie: {\n type: v.string().optional(),\n desc: [\n 'Cookie header value sent on the WebSocket upgrade request,',\n 'e.g. `session=abc; foo=bar`. Use this when zero-cache is behind',\n 'a proxy that resolves auth via cookies. Merged with --headers-json',\n '(--cookie wins on conflict).',\n ],\n },\n headersJson: {\n type: v.string().optional(),\n desc: [\n 'JSON object of arbitrary headers to send on the WebSocket upgrade',\n 'request, e.g. `{\"x-api-key\":\"...\"}`. Escape hatch for exotic auth',\n 'schemes; prefer --auth-token or --cookie when possible.',\n ],\n },\n userId: {\n type: v.string().optional(),\n desc: [\n 'Optional userID to report to zero-cache.',\n 'Omit for anonymous analysis.',\n ],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded AST. Exactly one of --ast / --query / --query-name is required.',\n 'The AST is sent to the server verbatim — provide it in server (post-mapping) form.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'ZQL query in chain form, e.g. `issue.related(\"comments\").limit(10)`.',\n 'Evaluated against the schema you pass to runAnalyzeCLI.',\n ],\n },\n queryName: {\n type: v.string().optional(),\n desc: [\n 'Name of a server-registered custom (named) query.',\n 'The server resolves the name + args via its registered query handler.',\n ],\n },\n queryArgs: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded array of arguments for --query-name. Defaults to `[]`.',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Include the rows read from the replica to execute the query.',\n 'Each row appears once per read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: ['Include the rows that would be synced to the client.'],\n },\n joinPlans: {\n type: v.boolean().default(false),\n desc: ['Include join planner diagnostics.'],\n },\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\ntype QueryPlan =\n | {kind: 'ast'; ast: AST}\n | {kind: 'zql'; text: string}\n | {kind: 'named'; name: string; args: ReadonlyArray<unknown>};\n\n// Route all Zero client log output to stderr so stdout contains only the\n// analyze result. Shell redirection (`2>/dev/null`) can then cleanly silence\n// logs without affecting output.\nconst stderrConsole = new Console({\n stdout: process.stderr,\n stderr: process.stderr,\n});\nconst stderrLogSink: LogSink = {\n log(level, context, ...args) {\n const ctx = context\n ? Object.entries(context).map(([k, v]) =>\n v === undefined ? k : `${k}=${v}`,\n )\n : [];\n stderrConsole[level](...ctx, ...args);\n },\n};\n\n/**\n * Entry point for a user's `cli.ts`. Parses argv, connects to a remote\n * zero-cache by standing up an in-process Zero client (in-memory storage,\n * no subscriptions), calls the inspector's `analyze-query` RPC, and\n * renders the result. Intended to be called as:\n *\n * ```ts\n * import {schema} from './schema.ts';\n * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';\n * await runAnalyzeCLI({schema});\n * ```\n *\n * Exits the process with code 1 on error.\n */\nexport async function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void> {\n const argv = (opts.argv ?? process.argv.slice(2)).map(s =>\n s.replaceAll('\\n', ' '),\n );\n\n const config = parseOptions(options, {\n argv,\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query (remote)',\n content: `Analyze a ZQL query against a remote zero-cache.\n\n Connects to zero-cache's inspector protocol and reports the server-observed\n row scans, SQLite query plans, and timings.`,\n },\n {\n header: 'Examples',\n content: ` node cli.ts --zero-cache-url=https://zero.example.com \\\\\n --admin-password=\"$ZERO_ADMIN_PASSWORD\" \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n node cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --ast='\\\\{\"table\": \"issue\", \"limit\": 5\\\\}'\n\n node cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --query-name=issueList --query-args='[]'`,\n },\n ],\n });\n\n if (!config.zeroCacheURL) {\n colorConsole.error('--zero-cache-url is required. See --help for usage.');\n process.exit(1);\n }\n\n const plan = buildQueryPlan(config);\n\n const handshakeHeaders = resolveHandshakeHeaders(config);\n if (Object.keys(handshakeHeaders).length > 0) {\n installWebSocketHeaderShim(handshakeHeaders);\n }\n\n // zero-client and replicache reference a build-time `TESTING` global that\n // bundlers replace with a boolean literal; under node there's no replacement,\n // so provide a runtime default.\n (globalThis as {TESTING?: boolean}).TESTING ??= false;\n\n const z = new Zero({\n schema: opts.schema,\n server: config.zeroCacheURL,\n auth: config.authToken,\n userID: config.userId,\n kvStore: 'mem',\n logLevel: config.log.level,\n logSink: stderrLogSink,\n });\n\n let result: AnalyzeQueryResult;\n try {\n const authOk = await z.inspector.authenticate(config.adminPassword ?? '');\n if (!authOk) {\n throw new Error(\n 'admin password rejected (or --admin-password is required)',\n );\n }\n\n const rpcOptions = {\n vendedRows: config.outputVendedRows,\n syncedRows: config.outputSyncedRows,\n joinPlans: config.joinPlans,\n };\n\n if (plan.kind === 'ast') {\n result = await z.inspector.analyzeServerAST(plan.ast, rpcOptions);\n } else if (plan.kind === 'named') {\n result = await z.inspector.analyzeNamedQuery(\n plan.name,\n plan.args as ReadonlyArray<never>,\n rpcOptions,\n );\n } else {\n const built = buildZqlQuery(plan.text, createBuilder(opts.schema));\n result = await z.inspector.analyzeQuery(built, rpcOptions);\n }\n } catch (e) {\n colorConsole.error(e instanceof Error ? e.message : String(e));\n await z.close().catch(() => {});\n process.exit(1);\n }\n\n renderResult(result, {\n outputSyncedRows: config.outputSyncedRows,\n outputVendedRows: config.outputVendedRows,\n });\n\n await z.close();\n}\n\nfunction buildQueryPlan(config: {\n ast?: string | undefined;\n query?: string | undefined;\n queryName?: string | undefined;\n queryArgs?: string | undefined;\n}): QueryPlan {\n const selectors = [\n config.ast !== undefined && 'ast',\n config.query !== undefined && 'query',\n config.queryName !== undefined && 'queryName',\n ].filter(Boolean) as string[];\n\n if (selectors.length === 0) {\n colorConsole.error(\n 'Exactly one of --ast / --query / --query-name is required.',\n );\n process.exit(1);\n }\n if (selectors.length > 1) {\n colorConsole.error(\n `Only one of --ast / --query / --query-name may be provided; got: ${selectors.join(', ')}`,\n );\n process.exit(1);\n }\n\n if (config.ast !== undefined) {\n return {kind: 'ast', ast: JSON.parse(config.ast) as AST};\n }\n if (config.query !== undefined) {\n return {kind: 'zql', text: config.query};\n }\n const args = config.queryArgs\n ? (JSON.parse(config.queryArgs) as ReadonlyArray<unknown>)\n : [];\n return {kind: 'named', name: config.queryName as string, args};\n}\n\nfunction buildZqlQuery(\n queryString: string,\n builder: SchemaQuery<Schema>,\n): AnyQuery {\n const f = new Function('builder', `return builder.${queryString};`);\n return f(builder) as AnyQuery;\n}\n\nfunction resolveHandshakeHeaders(config: {\n cookie?: string | undefined;\n headersJson?: string | undefined;\n}): Record<string, string> {\n const headers: Record<string, string> = {};\n if (config.headersJson !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(config.headersJson);\n } catch (e) {\n colorConsole.error(\n `--headers-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,\n );\n process.exit(1);\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n colorConsole.error('--headers-json must be a JSON object.');\n process.exit(1);\n }\n for (const [k, val] of Object.entries(parsed)) {\n if (typeof val !== 'string') {\n colorConsole.error(\n `--headers-json values must be strings; got ${typeof val} for \"${k}\".`,\n );\n process.exit(1);\n }\n headers[k] = val;\n }\n }\n if (config.cookie !== undefined) {\n headers.cookie = config.cookie;\n }\n return headers;\n}\n\nfunction installWebSocketHeaderShim(headers: Record<string, string>): void {\n class HeaderInjectingWebSocket extends NodeWebSocket {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols, {headers});\n }\n }\n (globalThis as {WebSocket?: unknown}).WebSocket = HeaderInjectingWebSocket;\n}\n\nfunction renderResult(\n result: AnalyzeQueryResult,\n opts: {outputSyncedRows: boolean; outputVendedRows: boolean},\n) {\n if (opts.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\n colorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\n colorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n );\n\n const readRowCountsByQuery = result.readRowCountsByQuery ?? {};\n let totalRowsRead = 0;\n for (const table of Object.keys(readRowCountsByQuery).sort()) {\n const counts = readRowCountsByQuery[table];\n for (const n of Object.values(counts)) {\n totalRowsRead += n;\n }\n colorConsole.log(styleText('bold', `${table} vended:`), counts);\n }\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsRead),\n );\n const duration = result.elapsed ?? result.end - result.start;\n colorConsole.log(styleText('bold', 'time:'), colorTime(duration), 'ms');\n\n if (opts.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const [table, rows] of Object.entries(result.readRows ?? {})) {\n colorConsole.log(styleText('bold', `${table}:`), rows);\n }\n }\n\n colorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n );\n const dbScansByQuery = result.dbScansByQuery ?? {};\n let totalNVisit = 0;\n for (const [table, queries] of Object.entries(dbScansByQuery)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n }\n colorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n );\n\n colorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\n const plans = result.sqlitePlans ?? {};\n for (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\n if (result.warnings.length > 0) {\n colorConsole.log(styleText(['yellow', 'bold'], '=== Warnings: ===\\n'));\n for (const w of result.warnings) {\n colorConsole.log(styleText('yellow', w));\n }\n }\n\n if (result.joinPlans && result.joinPlans.length > 0) {\n colorConsole.log(styleText(['blue', 'bold'], '\\n=== Join Plans: ===\\n'));\n colorConsole.log(formatPlannerEvents(result.joinPlans));\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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"}
|
|
@@ -9,5 +9,12 @@ import type { AST } from '../../zero-protocol/src/ast.ts';
|
|
|
9
9
|
* console.log(astToZQL(ast)); // outputs: .where('id', '=', 123)
|
|
10
10
|
* ```
|
|
11
11
|
*/
|
|
12
|
-
export declare function astToZQL(ast: AST): string;
|
|
12
|
+
export declare function astToZQL(ast: AST, options?: AstToZQLOptions): string;
|
|
13
|
+
export type AstToZQLOptions = {
|
|
14
|
+
/**
|
|
15
|
+
* Renders every literal value, and the `start` row, as `?` so that the
|
|
16
|
+
* output can be logged without leaking row data or query arguments.
|
|
17
|
+
*/
|
|
18
|
+
readonly redactLiterals?: boolean | undefined;
|
|
19
|
+
};
|
|
13
20
|
//# sourceMappingURL=ast-to-zql.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-to-zql.d.ts","sourceRoot":"","sources":["../../../../ast-to-zql/src/ast-to-zql.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,GAAG,EAWJ,MAAM,gCAAgC,CAAC;AAGxC;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"ast-to-zql.d.ts","sourceRoot":"","sources":["../../../../ast-to-zql/src/ast-to-zql.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,GAAG,EAWJ,MAAM,gCAAgC,CAAC;AAGxC;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,GAAE,eAAoB,GAAG,MAAM,CAyCxE;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/C,CAAC"}
|
|
@@ -13,53 +13,53 @@ import { SUBQ_PREFIX } from "../../zero-protocol/src/ast.js";
|
|
|
13
13
|
* console.log(astToZQL(ast)); // outputs: .where('id', '=', 123)
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
|
-
function astToZQL(ast) {
|
|
16
|
+
function astToZQL(ast, options = {}) {
|
|
17
17
|
let code = "";
|
|
18
|
-
if (ast.where) code += transformCondition(ast.where, ".where", /* @__PURE__ */ new Set());
|
|
18
|
+
if (ast.where) code += transformCondition(ast.where, ".where", /* @__PURE__ */ new Set(), options);
|
|
19
19
|
if (ast.related && ast.related.length > 0) for (const related of ast.related) if (related.hidden) {
|
|
20
20
|
const nestedRelated = related.subquery.related?.[0];
|
|
21
|
-
if (nestedRelated) code += transformRelated(nestedRelated);
|
|
22
|
-
} else code += transformRelated(related);
|
|
21
|
+
if (nestedRelated) code += transformRelated(nestedRelated, options);
|
|
22
|
+
} else code += transformRelated(related, options);
|
|
23
23
|
if (ast.orderBy && ast.orderBy.length > 0) code += transformOrder(ast.orderBy);
|
|
24
24
|
if (ast.limit !== void 0) code += `.limit(${ast.limit})`;
|
|
25
25
|
if (ast.start) {
|
|
26
26
|
const { row, exclusive } = ast.start;
|
|
27
|
-
code += `.start(${JSON.stringify(row)}${exclusive ? "" : ", { inclusive: true }"})`;
|
|
27
|
+
code += `.start(${options.redactLiterals ? "?" : JSON.stringify(row)}${exclusive ? "" : ", { inclusive: true }"})`;
|
|
28
28
|
}
|
|
29
29
|
return code;
|
|
30
30
|
}
|
|
31
|
-
function transformCondition(condition, prefix, args) {
|
|
31
|
+
function transformCondition(condition, prefix, args, options) {
|
|
32
32
|
switch (condition.type) {
|
|
33
|
-
case "simple": return transformSimpleCondition(condition, prefix);
|
|
33
|
+
case "simple": return transformSimpleCondition(condition, prefix, options);
|
|
34
34
|
case "and":
|
|
35
|
-
case "or": return transformLogicalCondition(condition, prefix, args);
|
|
36
|
-
case "correlatedSubquery": return transformExistsCondition(condition, prefix, args);
|
|
35
|
+
case "or": return transformLogicalCondition(condition, prefix, args, options);
|
|
36
|
+
case "correlatedSubquery": return transformExistsCondition(condition, prefix, args, options);
|
|
37
37
|
default: unreachable(condition);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
function transformSimpleCondition(condition, prefix) {
|
|
40
|
+
function transformSimpleCondition(condition, prefix, options) {
|
|
41
41
|
const { left, op, right } = condition;
|
|
42
|
-
const leftCode = transformValuePosition(left);
|
|
43
|
-
const rightCode = transformValuePosition(right);
|
|
42
|
+
const leftCode = transformValuePosition(left, options);
|
|
43
|
+
const rightCode = transformValuePosition(right, options);
|
|
44
44
|
if (op === "=") return `${prefix}(${leftCode}, ${rightCode})`;
|
|
45
45
|
return `${prefix}(${leftCode}, '${op}', ${rightCode})`;
|
|
46
46
|
}
|
|
47
|
-
function transformLogicalCondition(condition, prefix, args) {
|
|
47
|
+
function transformLogicalCondition(condition, prefix, args, options) {
|
|
48
48
|
const { type, conditions } = condition;
|
|
49
|
-
if (conditions.length === 1) return transformCondition(conditions[0], prefix, args);
|
|
49
|
+
if (conditions.length === 1) return transformCondition(conditions[0], prefix, args, options);
|
|
50
50
|
if (type === "and") {
|
|
51
|
-
const parts = conditions.map((c) => transformCondition(c, prefix, args));
|
|
51
|
+
const parts = conditions.map((c) => transformCondition(c, prefix, args, options));
|
|
52
52
|
if (prefix === ".where") return parts.join("");
|
|
53
53
|
args.add("and");
|
|
54
54
|
return "and(" + parts.join(", ") + ")";
|
|
55
55
|
}
|
|
56
56
|
args = /* @__PURE__ */ new Set();
|
|
57
|
-
const conditionsCode = conditions.map((c) => transformCondition(c, "cmp", args)).join(", ");
|
|
57
|
+
const conditionsCode = conditions.map((c) => transformCondition(c, "cmp", args, options)).join(", ");
|
|
58
58
|
args.add("cmp");
|
|
59
59
|
args.add(type);
|
|
60
60
|
return `.where(({${toSorted(args).join(", ")}}) => ${type}(${conditionsCode}))`;
|
|
61
61
|
}
|
|
62
|
-
function transformExistsCondition(condition, prefix, args) {
|
|
62
|
+
function transformExistsCondition(condition, prefix, args, options) {
|
|
63
63
|
const { related, op } = condition;
|
|
64
64
|
const relationship = extractRelationshipName(related);
|
|
65
65
|
const nextSubquery = getNextExistsSubquery(related);
|
|
@@ -74,15 +74,15 @@ function transformExistsCondition(condition, prefix, args) {
|
|
|
74
74
|
args.add("exists");
|
|
75
75
|
return `exists('${relationship}'${optionsStr})`;
|
|
76
76
|
}
|
|
77
|
-
if (prefix === ".where") return `.whereExists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr})`;
|
|
77
|
+
if (prefix === ".where") return `.whereExists('${relationship}', q => q${astToZQL(nextSubquery, options)}${optionsStr})`;
|
|
78
78
|
args.add("exists");
|
|
79
|
-
return `exists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr})`;
|
|
79
|
+
return `exists('${relationship}', q => q${astToZQL(nextSubquery, options)}${optionsStr})`;
|
|
80
80
|
}
|
|
81
81
|
if (hasSubQueryProps) {
|
|
82
|
-
if (prefix === ".where") return `.where(({exists, not}) => not(exists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr})))`;
|
|
82
|
+
if (prefix === ".where") return `.where(({exists, not}) => not(exists('${relationship}', q => q${astToZQL(nextSubquery, options)}${optionsStr})))`;
|
|
83
83
|
args.add("not");
|
|
84
84
|
args.add("exists");
|
|
85
|
-
return `not(exists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr}))`;
|
|
85
|
+
return `not(exists('${relationship}', q => q${astToZQL(nextSubquery, options)}${optionsStr}))`;
|
|
86
86
|
}
|
|
87
87
|
if (prefix === ".where") return `.where(({exists, not}) => not(exists('${relationship}'${optionsStr})))`;
|
|
88
88
|
args.add("not");
|
|
@@ -97,11 +97,11 @@ function extractRelationshipName(related) {
|
|
|
97
97
|
const alias = must(related.subquery.alias);
|
|
98
98
|
return alias.startsWith("zsubq_") ? alias.substring(SUBQ_PREFIX.length) : alias;
|
|
99
99
|
}
|
|
100
|
-
function transformRelated(related) {
|
|
100
|
+
function transformRelated(related, options) {
|
|
101
101
|
const { alias } = related.subquery;
|
|
102
102
|
if (!alias) return "";
|
|
103
103
|
let code = `.related('${alias}'`;
|
|
104
|
-
if (related.subquery.where || related.subquery.related && related.subquery.related.length > 0 || related.subquery.orderBy || related.subquery.limit) code += ", q => q" + astToZQL(related.subquery);
|
|
104
|
+
if (related.subquery.where || related.subquery.related && related.subquery.related.length > 0 || related.subquery.orderBy || related.subquery.limit) code += ", q => q" + astToZQL(related.subquery, options);
|
|
105
105
|
code += ")";
|
|
106
106
|
return code;
|
|
107
107
|
}
|
|
@@ -110,9 +110,9 @@ function transformOrder(orderBy) {
|
|
|
110
110
|
for (const [field, direction] of orderBy) code += `.orderBy('${field}', '${direction}')`;
|
|
111
111
|
return code;
|
|
112
112
|
}
|
|
113
|
-
function transformValuePosition(value) {
|
|
113
|
+
function transformValuePosition(value, options) {
|
|
114
114
|
switch (value.type) {
|
|
115
|
-
case "literal": return transformLiteral(value);
|
|
115
|
+
case "literal": return options.redactLiterals ? "?" : transformLiteral(value);
|
|
116
116
|
case "column": return `'${value.name}'`;
|
|
117
117
|
case "static": return transformParameter(value);
|
|
118
118
|
default: unreachable(value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-to-zql.js","names":[],"sources":["../../../../ast-to-zql/src/ast-to-zql.ts"],"sourcesContent":["import {unreachable} from '../../shared/src/asserts.ts';\nimport {toSorted} from '../../shared/src/iterables.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n Conjunction,\n CorrelatedSubquery,\n CorrelatedSubqueryCondition,\n Disjunction,\n LiteralReference,\n Ordering,\n Parameter,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport {SUBQ_PREFIX} from '../../zero-protocol/src/ast.ts';\n\n/**\n * Converts an AST to the equivalent query builder code.\n * This is useful for debugging and understanding queries.\n *\n * @example\n * ```\n * const ast = query.issue.where('id', '=', 123)[astSymbol];\n * console.log(astToZQL(ast)); // outputs: .where('id', '=', 123)\n * ```\n */\nexport function astToZQL(ast: AST): string {\n let code = '';\n\n // Handle where conditions\n if (ast.where) {\n code += transformCondition(ast.where, '.where', new Set());\n }\n\n // Handle related subqueries\n if (ast.related && ast.related.length > 0) {\n for (const related of ast.related) {\n if (related.hidden) {\n const nestedRelated = related.subquery.related?.[0];\n if (nestedRelated) {\n code += transformRelated(nestedRelated);\n }\n } else {\n code += transformRelated(related);\n }\n }\n }\n\n // Handle orderBy\n if (ast.orderBy && ast.orderBy.length > 0) {\n code += transformOrder(ast.orderBy);\n }\n\n // Handle limit\n if (ast.limit !== undefined) {\n code += `.limit(${ast.limit})`;\n }\n\n // Handle start\n if (ast.start) {\n const {row, exclusive} = ast.start;\n code += `.start(${JSON.stringify(row)}${\n exclusive ? '' : ', { inclusive: true }'\n })`;\n }\n\n return code;\n}\n\ntype Args = Set<string>;\n\ntype Prefix = '.where' | 'cmp';\n\nfunction transformCondition(\n condition: Condition,\n prefix: Prefix,\n args: Args,\n): string {\n switch (condition.type) {\n case 'simple':\n return transformSimpleCondition(condition, prefix);\n case 'and':\n case 'or':\n return transformLogicalCondition(condition, prefix, args);\n case 'correlatedSubquery':\n return transformExistsCondition(condition, prefix, args);\n default:\n unreachable(condition);\n }\n}\n\nfunction transformSimpleCondition(\n condition: SimpleCondition,\n prefix: Prefix,\n): string {\n const {left, op, right} = condition;\n\n const leftCode = transformValuePosition(left);\n const rightCode = transformValuePosition(right);\n\n // Handle the shorthand form for equals\n if (op === '=') {\n return `${prefix}(${leftCode}, ${rightCode})`;\n }\n\n return `${prefix}(${leftCode}, '${op}', ${rightCode})`;\n}\n\nfunction transformLogicalCondition(\n condition: Conjunction | Disjunction,\n prefix: Prefix,\n args: Args,\n): string {\n const {type, conditions} = condition;\n\n // For single condition, no need for logical operator\n if (conditions.length === 1) {\n return transformCondition(conditions[0], prefix, args);\n }\n\n // Generate multiple where calls for top-level AND conditions\n if (type === 'and') {\n const parts = conditions.map(c => transformCondition(c, prefix, args));\n // Simply concatenate the where conditions\n if (prefix === '.where') {\n return parts.join('');\n }\n args.add('and');\n return 'and(' + parts.join(', ') + ')';\n }\n\n args = new Set<string>();\n\n // Handle nested conditions with a callback for OR conditions and nested ANDs/ORs\n const conditionsCode = conditions\n .map(c => transformCondition(c, 'cmp', args))\n .join(', ');\n\n args.add('cmp');\n args.add(type);\n const argsCode = toSorted(args).join(', ');\n\n return `.where(({${argsCode}}) => ${type}(${conditionsCode}))`;\n}\n\nfunction transformExistsCondition(\n condition: CorrelatedSubqueryCondition,\n prefix: '.where' | 'cmp',\n args: Set<string>,\n): string {\n const {related, op} = condition;\n const relationship = extractRelationshipName(related);\n\n const nextSubquery = getNextExistsSubquery(related);\n\n // Check if subquery has additional properties\n const hasSubQueryProps =\n nextSubquery.where ||\n (nextSubquery.related && nextSubquery.related.length > 0) ||\n nextSubquery.orderBy ||\n nextSubquery.limit;\n\n // Build options string for flip and scalar\n const optionParts: string[] = [];\n if (condition.flip !== undefined) {\n optionParts.push(`flip: ${condition.flip}`);\n }\n if (condition.scalar !== undefined) {\n optionParts.push(`scalar: ${condition.scalar}`);\n }\n const optionsStr =\n optionParts.length > 0 ? `, {${optionParts.join(', ')}}` : '';\n\n if (op === 'EXISTS') {\n if (!hasSubQueryProps) {\n if (prefix === '.where') {\n return `.whereExists('${relationship}'${optionsStr})`;\n }\n args.add('exists');\n return `exists('${relationship}'${optionsStr})`;\n }\n\n if (prefix === '.where') {\n return `.whereExists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr})`;\n }\n prefix satisfies 'cmp';\n args.add('exists');\n return `exists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr})`;\n }\n\n op satisfies 'NOT EXISTS';\n\n if (hasSubQueryProps) {\n if (prefix === '.where') {\n return `.where(({exists, not}) => not(exists('${relationship}', q => q${astToZQL(\n nextSubquery,\n )}${optionsStr})))`;\n }\n prefix satisfies 'cmp';\n args.add('not');\n args.add('exists');\n return `not(exists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr}))`;\n }\n\n if (prefix === '.where') {\n return `.where(({exists, not}) => not(exists('${relationship}'${optionsStr})))`;\n }\n args.add('not');\n args.add('exists');\n\n return `not(exists('${relationship}'${optionsStr})))`;\n}\n\n// If the `exists` is applied against a junction edge, both hops will have the same alias and both hops will be exists conditions.\nfunction getNextExistsSubquery(related: CorrelatedSubquery): AST {\n if (\n related.subquery.where?.type === 'correlatedSubquery' &&\n related.subquery.where.related.subquery.alias?.includes(\n SUBQ_PREFIX + 'zhidden_',\n )\n ) {\n return getNextExistsSubquery(related.subquery.where.related);\n }\n\n return related.subquery;\n}\n\nfunction extractRelationshipName(related: CorrelatedSubquery): string {\n const alias = must(related.subquery.alias);\n return alias.startsWith(SUBQ_PREFIX)\n ? alias.substring(SUBQ_PREFIX.length)\n : alias;\n}\n\nfunction transformRelated(related: CorrelatedSubquery): string {\n const {alias} = related.subquery;\n if (!alias) return '';\n\n const relationship = alias;\n let code = `.related('${relationship}'`;\n\n // If the subquery has additional filters or configurations\n if (\n related.subquery.where ||\n (related.subquery.related && related.subquery.related.length > 0) ||\n related.subquery.orderBy ||\n related.subquery.limit\n ) {\n code += ', q => q' + astToZQL(related.subquery);\n }\n\n code += ')';\n return code;\n}\n\nfunction transformOrder(orderBy: Ordering): string {\n let code = '';\n for (const [field, direction] of orderBy) {\n code += `.orderBy('${field}', '${direction}')`;\n }\n return code;\n}\n\nfunction transformValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'literal':\n return transformLiteral(value);\n case 'column':\n return `'${value.name}'`;\n case 'static':\n return transformParameter(value);\n default:\n unreachable(value);\n }\n}\n\nfunction transformLiteral(literal: LiteralReference): string {\n if (literal.value === null) {\n return 'null';\n }\n if (Array.isArray(literal.value)) {\n return JSON.stringify(literal.value);\n }\n if (typeof literal.value === 'string') {\n return `'${literal.value.replace(/'/g, \"\\\\'\")}'`;\n }\n return String(literal.value);\n}\n\nfunction transformParameter(param: Parameter): string {\n const fieldStr = Array.isArray(param.field)\n ? `[${param.field.map(f => `'${f}'`).join(', ')}]`\n : `'${param.field}'`;\n\n return `authParam(${fieldStr})`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ast-to-zql.js","names":[],"sources":["../../../../ast-to-zql/src/ast-to-zql.ts"],"sourcesContent":["import {unreachable} from '../../shared/src/asserts.ts';\nimport {toSorted} from '../../shared/src/iterables.ts';\nimport {must} from '../../shared/src/must.ts';\nimport type {\n AST,\n Condition,\n Conjunction,\n CorrelatedSubquery,\n CorrelatedSubqueryCondition,\n Disjunction,\n LiteralReference,\n Ordering,\n Parameter,\n SimpleCondition,\n ValuePosition,\n} from '../../zero-protocol/src/ast.ts';\nimport {SUBQ_PREFIX} from '../../zero-protocol/src/ast.ts';\n\n/**\n * Converts an AST to the equivalent query builder code.\n * This is useful for debugging and understanding queries.\n *\n * @example\n * ```\n * const ast = query.issue.where('id', '=', 123)[astSymbol];\n * console.log(astToZQL(ast)); // outputs: .where('id', '=', 123)\n * ```\n */\nexport function astToZQL(ast: AST, options: AstToZQLOptions = {}): string {\n let code = '';\n\n // Handle where conditions\n if (ast.where) {\n code += transformCondition(ast.where, '.where', new Set(), options);\n }\n\n // Handle related subqueries\n if (ast.related && ast.related.length > 0) {\n for (const related of ast.related) {\n if (related.hidden) {\n const nestedRelated = related.subquery.related?.[0];\n if (nestedRelated) {\n code += transformRelated(nestedRelated, options);\n }\n } else {\n code += transformRelated(related, options);\n }\n }\n }\n\n // Handle orderBy\n if (ast.orderBy && ast.orderBy.length > 0) {\n code += transformOrder(ast.orderBy);\n }\n\n // Handle limit\n if (ast.limit !== undefined) {\n code += `.limit(${ast.limit})`;\n }\n\n // Handle start\n if (ast.start) {\n const {row, exclusive} = ast.start;\n code += `.start(${options.redactLiterals ? '?' : JSON.stringify(row)}${\n exclusive ? '' : ', { inclusive: true }'\n })`;\n }\n\n return code;\n}\n\nexport type AstToZQLOptions = {\n /**\n * Renders every literal value, and the `start` row, as `?` so that the\n * output can be logged without leaking row data or query arguments.\n */\n readonly redactLiterals?: boolean | undefined;\n};\n\ntype Args = Set<string>;\n\ntype Prefix = '.where' | 'cmp';\n\nfunction transformCondition(\n condition: Condition,\n prefix: Prefix,\n args: Args,\n options: AstToZQLOptions,\n): string {\n switch (condition.type) {\n case 'simple':\n return transformSimpleCondition(condition, prefix, options);\n case 'and':\n case 'or':\n return transformLogicalCondition(condition, prefix, args, options);\n case 'correlatedSubquery':\n return transformExistsCondition(condition, prefix, args, options);\n default:\n unreachable(condition);\n }\n}\n\nfunction transformSimpleCondition(\n condition: SimpleCondition,\n prefix: Prefix,\n options: AstToZQLOptions,\n): string {\n const {left, op, right} = condition;\n\n const leftCode = transformValuePosition(left, options);\n const rightCode = transformValuePosition(right, options);\n\n // Handle the shorthand form for equals\n if (op === '=') {\n return `${prefix}(${leftCode}, ${rightCode})`;\n }\n\n return `${prefix}(${leftCode}, '${op}', ${rightCode})`;\n}\n\nfunction transformLogicalCondition(\n condition: Conjunction | Disjunction,\n prefix: Prefix,\n args: Args,\n options: AstToZQLOptions,\n): string {\n const {type, conditions} = condition;\n\n // For single condition, no need for logical operator\n if (conditions.length === 1) {\n return transformCondition(conditions[0], prefix, args, options);\n }\n\n // Generate multiple where calls for top-level AND conditions\n if (type === 'and') {\n const parts = conditions.map(c =>\n transformCondition(c, prefix, args, options),\n );\n // Simply concatenate the where conditions\n if (prefix === '.where') {\n return parts.join('');\n }\n args.add('and');\n return 'and(' + parts.join(', ') + ')';\n }\n\n args = new Set<string>();\n\n // Handle nested conditions with a callback for OR conditions and nested ANDs/ORs\n const conditionsCode = conditions\n .map(c => transformCondition(c, 'cmp', args, options))\n .join(', ');\n\n args.add('cmp');\n args.add(type);\n const argsCode = toSorted(args).join(', ');\n\n return `.where(({${argsCode}}) => ${type}(${conditionsCode}))`;\n}\n\nfunction transformExistsCondition(\n condition: CorrelatedSubqueryCondition,\n prefix: '.where' | 'cmp',\n args: Set<string>,\n options: AstToZQLOptions,\n): string {\n const {related, op} = condition;\n const relationship = extractRelationshipName(related);\n\n const nextSubquery = getNextExistsSubquery(related);\n\n // Check if subquery has additional properties\n const hasSubQueryProps =\n nextSubquery.where ||\n (nextSubquery.related && nextSubquery.related.length > 0) ||\n nextSubquery.orderBy ||\n nextSubquery.limit;\n\n // Build options string for flip and scalar\n const optionParts: string[] = [];\n if (condition.flip !== undefined) {\n optionParts.push(`flip: ${condition.flip}`);\n }\n if (condition.scalar !== undefined) {\n optionParts.push(`scalar: ${condition.scalar}`);\n }\n const optionsStr =\n optionParts.length > 0 ? `, {${optionParts.join(', ')}}` : '';\n\n if (op === 'EXISTS') {\n if (!hasSubQueryProps) {\n if (prefix === '.where') {\n return `.whereExists('${relationship}'${optionsStr})`;\n }\n args.add('exists');\n return `exists('${relationship}'${optionsStr})`;\n }\n\n if (prefix === '.where') {\n return `.whereExists('${relationship}', q => q${astToZQL(nextSubquery, options)}${optionsStr})`;\n }\n prefix satisfies 'cmp';\n args.add('exists');\n return `exists('${relationship}', q => q${astToZQL(nextSubquery, options)}${optionsStr})`;\n }\n\n op satisfies 'NOT EXISTS';\n\n if (hasSubQueryProps) {\n if (prefix === '.where') {\n return `.where(({exists, not}) => not(exists('${relationship}', q => q${astToZQL(\n nextSubquery,\n options,\n )}${optionsStr})))`;\n }\n prefix satisfies 'cmp';\n args.add('not');\n args.add('exists');\n return `not(exists('${relationship}', q => q${astToZQL(nextSubquery, options)}${optionsStr}))`;\n }\n\n if (prefix === '.where') {\n return `.where(({exists, not}) => not(exists('${relationship}'${optionsStr})))`;\n }\n args.add('not');\n args.add('exists');\n\n return `not(exists('${relationship}'${optionsStr})))`;\n}\n\n// If the `exists` is applied against a junction edge, both hops will have the same alias and both hops will be exists conditions.\nfunction getNextExistsSubquery(related: CorrelatedSubquery): AST {\n if (\n related.subquery.where?.type === 'correlatedSubquery' &&\n related.subquery.where.related.subquery.alias?.includes(\n SUBQ_PREFIX + 'zhidden_',\n )\n ) {\n return getNextExistsSubquery(related.subquery.where.related);\n }\n\n return related.subquery;\n}\n\nfunction extractRelationshipName(related: CorrelatedSubquery): string {\n const alias = must(related.subquery.alias);\n return alias.startsWith(SUBQ_PREFIX)\n ? alias.substring(SUBQ_PREFIX.length)\n : alias;\n}\n\nfunction transformRelated(\n related: CorrelatedSubquery,\n options: AstToZQLOptions,\n): string {\n const {alias} = related.subquery;\n if (!alias) return '';\n\n const relationship = alias;\n let code = `.related('${relationship}'`;\n\n // If the subquery has additional filters or configurations\n if (\n related.subquery.where ||\n (related.subquery.related && related.subquery.related.length > 0) ||\n related.subquery.orderBy ||\n related.subquery.limit\n ) {\n code += ', q => q' + astToZQL(related.subquery, options);\n }\n\n code += ')';\n return code;\n}\n\nfunction transformOrder(orderBy: Ordering): string {\n let code = '';\n for (const [field, direction] of orderBy) {\n code += `.orderBy('${field}', '${direction}')`;\n }\n return code;\n}\n\nfunction transformValuePosition(\n value: ValuePosition,\n options: AstToZQLOptions,\n): string {\n switch (value.type) {\n case 'literal':\n return options.redactLiterals ? '?' : transformLiteral(value);\n case 'column':\n return `'${value.name}'`;\n case 'static':\n return transformParameter(value);\n default:\n unreachable(value);\n }\n}\n\nfunction transformLiteral(literal: LiteralReference): string {\n if (literal.value === null) {\n return 'null';\n }\n if (Array.isArray(literal.value)) {\n return JSON.stringify(literal.value);\n }\n if (typeof literal.value === 'string') {\n return `'${literal.value.replace(/'/g, \"\\\\'\")}'`;\n }\n return String(literal.value);\n}\n\nfunction transformParameter(param: Parameter): string {\n const fieldStr = Array.isArray(param.field)\n ? `[${param.field.map(f => `'${f}'`).join(', ')}]`\n : `'${param.field}'`;\n\n return `authParam(${fieldStr})`;\n}\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,SAAgB,SAAS,KAAU,UAA2B,CAAC,GAAW;CACxE,IAAI,OAAO;CAGX,IAAI,IAAI,OACN,QAAQ,mBAAmB,IAAI,OAAO,0BAAU,IAAI,IAAI,GAAG,OAAO;CAIpE,IAAI,IAAI,WAAW,IAAI,QAAQ,SAAS,GACtC,KAAK,MAAM,WAAW,IAAI,SACxB,IAAI,QAAQ,QAAQ;EAClB,MAAM,gBAAgB,QAAQ,SAAS,UAAU;EACjD,IAAI,eACF,QAAQ,iBAAiB,eAAe,OAAO;CAEnD,OACE,QAAQ,iBAAiB,SAAS,OAAO;CAM/C,IAAI,IAAI,WAAW,IAAI,QAAQ,SAAS,GACtC,QAAQ,eAAe,IAAI,OAAO;CAIpC,IAAI,IAAI,UAAU,KAAA,GAChB,QAAQ,UAAU,IAAI,MAAM;CAI9B,IAAI,IAAI,OAAO;EACb,MAAM,EAAC,KAAK,cAAa,IAAI;EAC7B,QAAQ,UAAU,QAAQ,iBAAiB,MAAM,KAAK,UAAU,GAAG,IACjE,YAAY,KAAK,wBAClB;CACH;CAEA,OAAO;AACT;AAcA,SAAS,mBACP,WACA,QACA,MACA,SACQ;CACR,QAAQ,UAAU,MAAlB;EACE,KAAK,UACH,OAAO,yBAAyB,WAAW,QAAQ,OAAO;EAC5D,KAAK;EACL,KAAK,MACH,OAAO,0BAA0B,WAAW,QAAQ,MAAM,OAAO;EACnE,KAAK,sBACH,OAAO,yBAAyB,WAAW,QAAQ,MAAM,OAAO;EAClE,SACE,YAAY,SAAS;CACzB;AACF;AAEA,SAAS,yBACP,WACA,QACA,SACQ;CACR,MAAM,EAAC,MAAM,IAAI,UAAS;CAE1B,MAAM,WAAW,uBAAuB,MAAM,OAAO;CACrD,MAAM,YAAY,uBAAuB,OAAO,OAAO;CAGvD,IAAI,OAAO,KACT,OAAO,GAAG,OAAO,GAAG,SAAS,IAAI,UAAU;CAG7C,OAAO,GAAG,OAAO,GAAG,SAAS,KAAK,GAAG,KAAK,UAAU;AACtD;AAEA,SAAS,0BACP,WACA,QACA,MACA,SACQ;CACR,MAAM,EAAC,MAAM,eAAc;CAG3B,IAAI,WAAW,WAAW,GACxB,OAAO,mBAAmB,WAAW,IAAI,QAAQ,MAAM,OAAO;CAIhE,IAAI,SAAS,OAAO;EAClB,MAAM,QAAQ,WAAW,KAAI,MAC3B,mBAAmB,GAAG,QAAQ,MAAM,OAAO,CAC7C;EAEA,IAAI,WAAW,UACb,OAAO,MAAM,KAAK,EAAE;EAEtB,KAAK,IAAI,KAAK;EACd,OAAO,SAAS,MAAM,KAAK,IAAI,IAAI;CACrC;CAEA,uBAAO,IAAI,IAAY;CAGvB,MAAM,iBAAiB,WACpB,KAAI,MAAK,mBAAmB,GAAG,OAAO,MAAM,OAAO,CAAC,EACpD,KAAK,IAAI;CAEZ,KAAK,IAAI,KAAK;CACd,KAAK,IAAI,IAAI;CAGb,OAAO,YAFU,SAAS,IAAI,EAAE,KAAK,IAElB,EAAS,QAAQ,KAAK,GAAG,eAAe;AAC7D;AAEA,SAAS,yBACP,WACA,QACA,MACA,SACQ;CACR,MAAM,EAAC,SAAS,OAAM;CACtB,MAAM,eAAe,wBAAwB,OAAO;CAEpD,MAAM,eAAe,sBAAsB,OAAO;CAGlD,MAAM,mBACJ,aAAa,SACZ,aAAa,WAAW,aAAa,QAAQ,SAAS,KACvD,aAAa,WACb,aAAa;CAGf,MAAM,cAAwB,CAAC;CAC/B,IAAI,UAAU,SAAS,KAAA,GACrB,YAAY,KAAK,SAAS,UAAU,MAAM;CAE5C,IAAI,UAAU,WAAW,KAAA,GACvB,YAAY,KAAK,WAAW,UAAU,QAAQ;CAEhD,MAAM,aACJ,YAAY,SAAS,IAAI,MAAM,YAAY,KAAK,IAAI,EAAE,KAAK;CAE7D,IAAI,OAAO,UAAU;EACnB,IAAI,CAAC,kBAAkB;GACrB,IAAI,WAAW,UACb,OAAO,iBAAiB,aAAa,GAAG,WAAW;GAErD,KAAK,IAAI,QAAQ;GACjB,OAAO,WAAW,aAAa,GAAG,WAAW;EAC/C;EAEA,IAAI,WAAW,UACb,OAAO,iBAAiB,aAAa,WAAW,SAAS,cAAc,OAAO,IAAI,WAAW;EAG/F,KAAK,IAAI,QAAQ;EACjB,OAAO,WAAW,aAAa,WAAW,SAAS,cAAc,OAAO,IAAI,WAAW;CACzF;CAIA,IAAI,kBAAkB;EACpB,IAAI,WAAW,UACb,OAAO,yCAAyC,aAAa,WAAW,SACtE,cACA,OACF,IAAI,WAAW;EAGjB,KAAK,IAAI,KAAK;EACd,KAAK,IAAI,QAAQ;EACjB,OAAO,eAAe,aAAa,WAAW,SAAS,cAAc,OAAO,IAAI,WAAW;CAC7F;CAEA,IAAI,WAAW,UACb,OAAO,yCAAyC,aAAa,GAAG,WAAW;CAE7E,KAAK,IAAI,KAAK;CACd,KAAK,IAAI,QAAQ;CAEjB,OAAO,eAAe,aAAa,GAAG,WAAW;AACnD;AAGA,SAAS,sBAAsB,SAAkC;CAC/D,IACE,QAAQ,SAAS,OAAO,SAAS,wBACjC,QAAQ,SAAS,MAAM,QAAQ,SAAS,OAAO,SAAA,gBAE/C,GAEA,OAAO,sBAAsB,QAAQ,SAAS,MAAM,OAAO;CAG7D,OAAO,QAAQ;AACjB;AAEA,SAAS,wBAAwB,SAAqC;CACpE,MAAM,QAAQ,KAAK,QAAQ,SAAS,KAAK;CACzC,OAAO,MAAM,WAAA,QAAsB,IAC/B,MAAM,UAAU,YAAY,MAAM,IAClC;AACN;AAEA,SAAS,iBACP,SACA,SACQ;CACR,MAAM,EAAC,UAAS,QAAQ;CACxB,IAAI,CAAC,OAAO,OAAO;CAGnB,IAAI,OAAO,aAAa,MAAa;CAGrC,IACE,QAAQ,SAAS,SAChB,QAAQ,SAAS,WAAW,QAAQ,SAAS,QAAQ,SAAS,KAC/D,QAAQ,SAAS,WACjB,QAAQ,SAAS,OAEjB,QAAQ,aAAa,SAAS,QAAQ,UAAU,OAAO;CAGzD,QAAQ;CACR,OAAO;AACT;AAEA,SAAS,eAAe,SAA2B;CACjD,IAAI,OAAO;CACX,KAAK,MAAM,CAAC,OAAO,cAAc,SAC/B,QAAQ,aAAa,MAAM,MAAM,UAAU;CAE7C,OAAO;AACT;AAEA,SAAS,uBACP,OACA,SACQ;CACR,QAAQ,MAAM,MAAd;EACE,KAAK,WACH,OAAO,QAAQ,iBAAiB,MAAM,iBAAiB,KAAK;EAC9D,KAAK,UACH,OAAO,IAAI,MAAM,KAAK;EACxB,KAAK,UACH,OAAO,mBAAmB,KAAK;EACjC,SACE,YAAY,KAAK;CACrB;AACF;AAEA,SAAS,iBAAiB,SAAmC;CAC3D,IAAI,QAAQ,UAAU,MACpB,OAAO;CAET,IAAI,MAAM,QAAQ,QAAQ,KAAK,GAC7B,OAAO,KAAK,UAAU,QAAQ,KAAK;CAErC,IAAI,OAAO,QAAQ,UAAU,UAC3B,OAAO,IAAI,QAAQ,MAAM,QAAQ,MAAM,KAAK,EAAE;CAEhD,OAAO,OAAO,QAAQ,KAAK;AAC7B;AAEA,SAAS,mBAAmB,OAA0B;CAKpD,OAAO,aAJU,MAAM,QAAQ,MAAM,KAAK,IACtC,IAAI,MAAM,MAAM,KAAI,MAAK,IAAI,EAAE,EAAE,EAAE,KAAK,IAAI,EAAE,KAC9C,IAAI,MAAM,MAAM,GAES;AAC/B"}
|
|
@@ -15,6 +15,22 @@ export declare const logOptions: {
|
|
|
15
15
|
type: v.Type<number>;
|
|
16
16
|
desc: string[];
|
|
17
17
|
};
|
|
18
|
+
slowAdvanceThreshold: {
|
|
19
|
+
type: v.Type<number>;
|
|
20
|
+
desc: string[];
|
|
21
|
+
};
|
|
22
|
+
planWarningRowThreshold: {
|
|
23
|
+
type: v.Type<number>;
|
|
24
|
+
desc: string[];
|
|
25
|
+
};
|
|
26
|
+
planWarningCostThreshold: {
|
|
27
|
+
type: v.Type<number>;
|
|
28
|
+
desc: string[];
|
|
29
|
+
};
|
|
30
|
+
queryStatsIntervalSeconds: {
|
|
31
|
+
type: v.Type<number>;
|
|
32
|
+
desc: string[];
|
|
33
|
+
};
|
|
18
34
|
ivmSampling: {
|
|
19
35
|
type: v.Type<number>;
|
|
20
36
|
desc: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-options.d.ts","sourceRoot":"","sources":["../../../../otel/src/log-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAC9D,OAAO,KAAK,CAAC,MAAM,4BAA4B,CAAC;AAEhD,eAAO,MAAM,QAAQ,6CAAmD,CAAC;AAEzE,eAAO,MAAM,UAAU;IACrB,KAAK;IAEL,MAAM;QACJ,IAAI;QACJ,IAAI;;IAMN,gBAAgB;QACd,IAAI;QACJ,IAAI;;IAKN,oBAAoB;QAClB,IAAI;QACJ,IAAI;;
|
|
1
|
+
{"version":3,"file":"log-options.d.ts","sourceRoot":"","sources":["../../../../otel/src/log-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAC9D,OAAO,KAAK,CAAC,MAAM,4BAA4B,CAAC;AAEhD,eAAO,MAAM,QAAQ,6CAAmD,CAAC;AAEzE,eAAO,MAAM,UAAU;IACrB,KAAK;IAEL,MAAM;QACJ,IAAI;QACJ,IAAI;;IAMN,gBAAgB;QACd,IAAI;QACJ,IAAI;;IAKN,oBAAoB;QAClB,IAAI;QACJ,IAAI;;IASN,oBAAoB;QAClB,IAAI;QACJ,IAAI;;IAYN,uBAAuB;QACrB,IAAI;QACJ,IAAI;;IAWN,wBAAwB;QACtB,IAAI;QACJ,IAAI;;IAON,yBAAyB;QACvB,IAAI;QACJ,IAAI;;IAaN,WAAW;QACT,IAAI;QACJ,IAAI;;CAIP,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC"}
|
|
@@ -13,7 +13,60 @@ var logOptions = {
|
|
|
13
13
|
},
|
|
14
14
|
slowHydrateThreshold: {
|
|
15
15
|
type: valita_exports.number().default(100),
|
|
16
|
-
desc: [
|
|
16
|
+
desc: [
|
|
17
|
+
`The number of milliseconds a query hydration must take to print a slow warning.`,
|
|
18
|
+
``,
|
|
19
|
+
`The warning logs the query with its literal values redacted, and is logged`,
|
|
20
|
+
`at most once every 5 minutes per query shape (the query with its values`,
|
|
21
|
+
`redacted), with a count of the slow hydrations suppressed in between.`
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
slowAdvanceThreshold: {
|
|
25
|
+
type: valita_exports.number().default(100),
|
|
26
|
+
desc: [
|
|
27
|
+
`The number of milliseconds a query must take to process one advancement`,
|
|
28
|
+
`(the changes of a replicated transaction, or a batch of them) to print a`,
|
|
29
|
+
`slow warning.`,
|
|
30
|
+
``,
|
|
31
|
+
`Like {bold slowHydrateThreshold}, the warning logs the query with its values`,
|
|
32
|
+
`redacted, at most once every 5 minutes per query shape. When an advancement`,
|
|
33
|
+
`times out and resets the pipelines, the queries that took the most time are`,
|
|
34
|
+
`logged regardless of this threshold.`
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
planWarningRowThreshold: {
|
|
38
|
+
type: valita_exports.number().default(1e4),
|
|
39
|
+
desc: [
|
|
40
|
+
`Log a warning when the query planner estimates that one read of a table`,
|
|
41
|
+
`scans or sorts at least this many rows: a read that scans the whole`,
|
|
42
|
+
`table because no index covers the columns it looks rows up by, or that`,
|
|
43
|
+
`sorts every matching row because no index covers the ordering.`,
|
|
44
|
+
``,
|
|
45
|
+
`The warning is logged at most once an hour per query shape (the query`,
|
|
46
|
+
`with its values redacted). Set to 0 to disable. Requires the query planner.`
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
planWarningCostThreshold: {
|
|
50
|
+
type: valita_exports.number().default(1e6),
|
|
51
|
+
desc: [
|
|
52
|
+
`Log a warning when the best plan the query planner finds for a query is`,
|
|
53
|
+
`still estimated to process at least this many rows. Throttled like`,
|
|
54
|
+
`{bold planWarningRowThreshold}. Set to 0 to disable.`
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
queryStatsIntervalSeconds: {
|
|
58
|
+
type: valita_exports.number().default(60),
|
|
59
|
+
desc: [
|
|
60
|
+
`How often each view-syncer worker logs the work done per query shape (the`,
|
|
61
|
+
`query with its values redacted): the number of hydrations and`,
|
|
62
|
+
`advancements, the time they took, and the rows they read and output.`,
|
|
63
|
+
``,
|
|
64
|
+
`Unlike the slow query warnings, every query is counted, so the logs can be`,
|
|
65
|
+
`summed across workers and over time to rank queries by the time they take.`,
|
|
66
|
+
`Logged at the info level as {bold query-stats} events, for the 100 shapes`,
|
|
67
|
+
`that took the most time in the interval, plus a {bold query-stats-summary}`,
|
|
68
|
+
`event with the totals. Set to 0 to disable.`
|
|
69
|
+
]
|
|
17
70
|
},
|
|
18
71
|
ivmSampling: {
|
|
19
72
|
type: valita_exports.number().default(5e3),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-options.js","names":[],"sources":["../../../../otel/src/log-options.ts"],"sourcesContent":["import {type Config} from '../../shared/src/options-types.ts';\nimport * as v from '../../shared/src/valita.ts';\n\nexport const logLevel = v.literalUnion('debug', 'info', 'warn', 'error');\n\nexport const logOptions = {\n level: logLevel.default('info'),\n\n format: {\n type: v.literalUnion('text', 'json').default('text'),\n desc: [\n `Use {bold text} for developer-friendly console logging`,\n `and {bold json} for consumption by structured-logging services`,\n ],\n },\n\n slowRowThreshold: {\n type: v.number().default(2),\n desc: [\n `The number of ms a row must take to fetch from table-source before it is considered slow.`,\n ],\n },\n\n slowHydrateThreshold: {\n type: v.number().default(100),\n desc: [\n `The number of milliseconds a query hydration must take to print a slow warning.`,\n ],\n },\n\n ivmSampling: {\n type: v.number().default(5000),\n desc: [\n `How often to collect IVM metrics. 1 out of N requests will be sampled where N is this value.`,\n ],\n },\n};\n\nexport type LogConfig = Config<typeof logOptions>;\n"],"mappings":";;AAGA,IAAa,WAAW,aAAe,SAAS,QAAQ,QAAQ,OAAO;AAEvE,IAAa,aAAa;CACxB,OAAO,SAAS,QAAQ,MAAM;CAE9B,QAAQ;EACN,MAAM,aAAe,QAAQ,MAAM,EAAE,QAAQ,MAAM;EACnD,MAAM,CACJ,0DACA,gEACF;CACF;CAEA,kBAAkB;EAChB,MAAM,eAAE,OAAO,EAAE,QAAQ,CAAC;EAC1B,MAAM,CACJ,2FACF;CACF;CAEA,sBAAsB;EACpB,MAAM,eAAE,OAAO,EAAE,QAAQ,GAAG;EAC5B,MAAM,
|
|
1
|
+
{"version":3,"file":"log-options.js","names":[],"sources":["../../../../otel/src/log-options.ts"],"sourcesContent":["import {type Config} from '../../shared/src/options-types.ts';\nimport * as v from '../../shared/src/valita.ts';\n\nexport const logLevel = v.literalUnion('debug', 'info', 'warn', 'error');\n\nexport const logOptions = {\n level: logLevel.default('info'),\n\n format: {\n type: v.literalUnion('text', 'json').default('text'),\n desc: [\n `Use {bold text} for developer-friendly console logging`,\n `and {bold json} for consumption by structured-logging services`,\n ],\n },\n\n slowRowThreshold: {\n type: v.number().default(2),\n desc: [\n `The number of ms a row must take to fetch from table-source before it is considered slow.`,\n ],\n },\n\n slowHydrateThreshold: {\n type: v.number().default(100),\n desc: [\n `The number of milliseconds a query hydration must take to print a slow warning.`,\n ``,\n `The warning logs the query with its literal values redacted, and is logged`,\n `at most once every 5 minutes per query shape (the query with its values`,\n `redacted), with a count of the slow hydrations suppressed in between.`,\n ],\n },\n\n slowAdvanceThreshold: {\n type: v.number().default(100),\n desc: [\n `The number of milliseconds a query must take to process one advancement`,\n `(the changes of a replicated transaction, or a batch of them) to print a`,\n `slow warning.`,\n ``,\n `Like {bold slowHydrateThreshold}, the warning logs the query with its values`,\n `redacted, at most once every 5 minutes per query shape. When an advancement`,\n `times out and resets the pipelines, the queries that took the most time are`,\n `logged regardless of this threshold.`,\n ],\n },\n\n planWarningRowThreshold: {\n type: v.number().default(10_000),\n desc: [\n `Log a warning when the query planner estimates that one read of a table`,\n `scans or sorts at least this many rows: a read that scans the whole`,\n `table because no index covers the columns it looks rows up by, or that`,\n `sorts every matching row because no index covers the ordering.`,\n ``,\n `The warning is logged at most once an hour per query shape (the query`,\n `with its values redacted). Set to 0 to disable. Requires the query planner.`,\n ],\n },\n\n planWarningCostThreshold: {\n type: v.number().default(1_000_000),\n desc: [\n `Log a warning when the best plan the query planner finds for a query is`,\n `still estimated to process at least this many rows. Throttled like`,\n `{bold planWarningRowThreshold}. Set to 0 to disable.`,\n ],\n },\n\n queryStatsIntervalSeconds: {\n type: v.number().default(60),\n desc: [\n `How often each view-syncer worker logs the work done per query shape (the`,\n `query with its values redacted): the number of hydrations and`,\n `advancements, the time they took, and the rows they read and output.`,\n ``,\n `Unlike the slow query warnings, every query is counted, so the logs can be`,\n `summed across workers and over time to rank queries by the time they take.`,\n `Logged at the info level as {bold query-stats} events, for the 100 shapes`,\n `that took the most time in the interval, plus a {bold query-stats-summary}`,\n `event with the totals. Set to 0 to disable.`,\n ],\n },\n\n ivmSampling: {\n type: v.number().default(5000),\n desc: [\n `How often to collect IVM metrics. 1 out of N requests will be sampled where N is this value.`,\n ],\n },\n};\n\nexport type LogConfig = Config<typeof logOptions>;\n"],"mappings":";;AAGA,IAAa,WAAW,aAAe,SAAS,QAAQ,QAAQ,OAAO;AAEvE,IAAa,aAAa;CACxB,OAAO,SAAS,QAAQ,MAAM;CAE9B,QAAQ;EACN,MAAM,aAAe,QAAQ,MAAM,EAAE,QAAQ,MAAM;EACnD,MAAM,CACJ,0DACA,gEACF;CACF;CAEA,kBAAkB;EAChB,MAAM,eAAE,OAAO,EAAE,QAAQ,CAAC;EAC1B,MAAM,CACJ,2FACF;CACF;CAEA,sBAAsB;EACpB,MAAM,eAAE,OAAO,EAAE,QAAQ,GAAG;EAC5B,MAAM;GACJ;GACA;GACA;GACA;GACA;EACF;CACF;CAEA,sBAAsB;EACpB,MAAM,eAAE,OAAO,EAAE,QAAQ,GAAG;EAC5B,MAAM;GACJ;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF;CAEA,yBAAyB;EACvB,MAAM,eAAE,OAAO,EAAE,QAAQ,GAAM;EAC/B,MAAM;GACJ;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF;CAEA,0BAA0B;EACxB,MAAM,eAAE,OAAO,EAAE,QAAQ,GAAS;EAClC,MAAM;GACJ;GACA;GACA;EACF;CACF;CAEA,2BAA2B;EACzB,MAAM,eAAE,OAAO,EAAE,QAAQ,EAAE;EAC3B,MAAM;GACJ;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF;CAEA,aAAa;EACX,MAAM,eAAE,OAAO,EAAE,QAAQ,GAAI;EAC7B,MAAM,CACJ,8FACF;CACF;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { LogContext } from '@rocicorp/logger';
|
|
2
2
|
import type { StoreProvider } from './kv/store.ts';
|
|
3
|
-
export declare function getKVStoreProvider(
|
|
3
|
+
export declare function getKVStoreProvider(_lc: LogContext, kvStore: 'mem' | 'idb' | StoreProvider | undefined): StoreProvider;
|
|
4
4
|
//# sourceMappingURL=get-kv-store-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-kv-store-provider.d.ts","sourceRoot":"","sources":["../../../../replicache/src/get-kv-store-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"get-kv-store-provider.d.ts","sourceRoot":"","sources":["../../../../replicache/src/get-kv-store-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,eAAe,CAAC;AAEjD,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,KAAK,GAAG,KAAK,GAAG,aAAa,GAAG,SAAS,GACjD,aAAa,CAgBf"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { IDBStore, dropIDBStore } from "./kv/idb-store.js";
|
|
1
2
|
import { MemStore, dropMemStore } from "./kv/mem-store.js";
|
|
2
|
-
import { dropIDBStoreWithMemFallback, newIDBStoreWithMemFallback } from "./kv/idb-store-with-mem-fallback.js";
|
|
3
3
|
//#region ../replicache/src/get-kv-store-provider.ts
|
|
4
|
-
function getKVStoreProvider(
|
|
4
|
+
function getKVStoreProvider(_lc, kvStore) {
|
|
5
5
|
switch (kvStore) {
|
|
6
6
|
case "idb":
|
|
7
7
|
case void 0: return {
|
|
8
|
-
create: (name) =>
|
|
9
|
-
drop:
|
|
8
|
+
create: (name) => new IDBStore(name),
|
|
9
|
+
drop: dropIDBStore
|
|
10
10
|
};
|
|
11
11
|
case "mem": return {
|
|
12
12
|
create: (name) => new MemStore(name),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-kv-store-provider.js","names":[],"sources":["../../../../replicache/src/get-kv-store-provider.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {
|
|
1
|
+
{"version":3,"file":"get-kv-store-provider.js","names":[],"sources":["../../../../replicache/src/get-kv-store-provider.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {dropIDBStore, IDBStore} from './kv/idb-store.ts';\nimport {dropMemStore, MemStore} from './kv/mem-store.ts';\nimport type {StoreProvider} from './kv/store.ts';\n\nexport function getKVStoreProvider(\n _lc: LogContext,\n kvStore: 'mem' | 'idb' | StoreProvider | undefined,\n): StoreProvider {\n switch (kvStore) {\n case 'idb':\n case undefined:\n return {\n create: name => new IDBStore(name),\n drop: dropIDBStore,\n };\n case 'mem':\n return {\n create: name => new MemStore(name),\n drop: name => dropMemStore(name),\n };\n default:\n return kvStore;\n }\n}\n"],"mappings":";;;AAKA,SAAgB,mBACd,KACA,SACe;CACf,QAAQ,SAAR;EACE,KAAK;EACL,KAAK,KAAA,GACH,OAAO;GACL,SAAQ,SAAQ,IAAI,SAAS,IAAI;GACjC,MAAM;EACR;EACF,KAAK,OACH,OAAO;GACL,SAAQ,SAAQ,IAAI,SAAS,IAAI;GACjC,OAAM,SAAQ,aAAa,IAAI;EACjC;EACF,SACE,OAAO;CACX;AACF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StorageFailureError } from '../storage-failure.ts';
|
|
1
2
|
import type { Read, Store, Write } from './store.ts';
|
|
2
3
|
export declare class IDBStore implements Store {
|
|
3
4
|
#private;
|
|
@@ -20,8 +21,16 @@ export declare class IDBNotFoundError extends Error {
|
|
|
20
21
|
* `read()` and `write()` reject with this error when the initial
|
|
21
22
|
* `indexedDB.open` failed. The browser's error is the `cause`. A failure after
|
|
22
23
|
* the database opened, such as a transaction error, is not wrapped.
|
|
24
|
+
*
|
|
25
|
+
* It is a `cannot-open` storage failure, or `full` when the browser says so
|
|
26
|
+
* (WebKit's IndexedDB is SQLite underneath and reports `database or disk is
|
|
27
|
+
* full`).
|
|
23
28
|
*/
|
|
24
|
-
export declare class IDBOpenError extends
|
|
29
|
+
export declare class IDBOpenError extends StorageFailureError {
|
|
25
30
|
name: string;
|
|
31
|
+
constructor(message: string, options: {
|
|
32
|
+
cause: unknown;
|
|
33
|
+
});
|
|
26
34
|
}
|
|
35
|
+
export declare function dropIDBStore(name: string): Promise<void>;
|
|
27
36
|
//# sourceMappingURL=idb-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idb-store.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/kv/idb-store.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAC,MAAM,YAAY,CAAC;AAUnD,qBAAa,QAAS,YAAW,KAAK;;IAKpC,YAAY,IAAI,EAAE,MAAM,EAKvB;IAED,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAKpB;IAED,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAKtB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAM3B;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;CA4DF;AAyHD;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,IAAI,SAAsB;CAC3B;
|
|
1
|
+
{"version":3,"file":"idb-store.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/kv/idb-store.ts"],"names":[],"mappings":"AAQA,OAAO,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAC,MAAM,YAAY,CAAC;AAUnD,qBAAa,QAAS,YAAW,KAAK;;IAKpC,YAAY,IAAI,EAAE,MAAM,EAKvB;IAED,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAKpB;IAED,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAKtB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAM3B;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;CA4DF;AAyHD;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,IAAI,SAAsB;CAC3B;AAID;;;;;;;;GAQG;AACH,qBAAa,YAAa,SAAQ,mBAAmB;IACnD,IAAI,SAAkB;IAEtB,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE;QAAC,KAAK,EAAE,OAAO,CAAA;KAAC,EAMrD;CACF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxD"}
|
|
@@ -2,6 +2,7 @@ import { assertNotNull } from "../../../shared/src/asserts.js";
|
|
|
2
2
|
import { mustGetBrowserGlobal } from "../../../shared/src/browser-env.js";
|
|
3
3
|
import { promiseVoid } from "../../../shared/src/resolved-promises.js";
|
|
4
4
|
import { deepFreezeAllowUndefined } from "../frozen-json.js";
|
|
5
|
+
import { StorageFailureError } from "../storage-failure.js";
|
|
5
6
|
import { storeIsClosedRejection, transactionIsClosedRejection } from "./throw-if-closed.js";
|
|
6
7
|
import { WriteImplBase, deleteSentinel } from "./write-impl-base.js";
|
|
7
8
|
import { resolver } from "@rocicorp/resolver";
|
|
@@ -157,15 +158,30 @@ function openDatabase(name) {
|
|
|
157
158
|
var IDBNotFoundError = class extends Error {
|
|
158
159
|
name = "IDBNotFoundError";
|
|
159
160
|
};
|
|
161
|
+
var DISK_FULL = /database or disk is full/;
|
|
160
162
|
/**
|
|
161
163
|
* `read()` and `write()` reject with this error when the initial
|
|
162
164
|
* `indexedDB.open` failed. The browser's error is the `cause`. A failure after
|
|
163
165
|
* the database opened, such as a transaction error, is not wrapped.
|
|
166
|
+
*
|
|
167
|
+
* It is a `cannot-open` storage failure, or `full` when the browser says so
|
|
168
|
+
* (WebKit's IndexedDB is SQLite underneath and reports `database or disk is
|
|
169
|
+
* full`).
|
|
164
170
|
*/
|
|
165
|
-
var IDBOpenError = class extends
|
|
171
|
+
var IDBOpenError = class extends StorageFailureError {
|
|
166
172
|
name = "IDBOpenError";
|
|
173
|
+
constructor(message, options) {
|
|
174
|
+
super(DISK_FULL.test(String(options.cause)) ? "full" : "cannot-open", message, options);
|
|
175
|
+
}
|
|
167
176
|
};
|
|
177
|
+
function dropIDBStore(name) {
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const req = indexedDB.deleteDatabase(name);
|
|
180
|
+
req.onsuccess = () => resolve();
|
|
181
|
+
req.onerror = () => reject(req.error);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
168
184
|
//#endregion
|
|
169
|
-
export { IDBNotFoundError,
|
|
185
|
+
export { IDBNotFoundError, IDBStore, dropIDBStore };
|
|
170
186
|
|
|
171
187
|
//# sourceMappingURL=idb-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idb-store.js","names":["#db","#closed","#withReopen","#idbDeleted","#tx"],"sources":["../../../../../replicache/src/kv/idb-store.ts"],"sourcesContent":["import {resolver} from '@rocicorp/resolver';\nimport {assertNotNull} from '../../../shared/src/asserts.ts';\nimport {mustGetBrowserGlobal} from '../../../shared/src/browser-env.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {\n type FrozenJSONValue,\n deepFreezeAllowUndefined,\n} from '../frozen-json.ts';\nimport type {Read, Store, Write} from './store.ts';\nimport {\n storeIsClosedRejection,\n transactionIsClosedRejection,\n} from './throw-if-closed.ts';\nimport {WriteImplBase, deleteSentinel} from './write-impl-base.ts';\n\nconst RELAXED = {durability: 'relaxed'} as const;\nconst OBJECT_STORE = 'chunks';\n\nexport class IDBStore implements Store {\n #db: Promise<IDBDatabase>;\n #closed = false;\n #idbDeleted = false;\n\n constructor(name: string) {\n this.#db = openDatabase(name);\n // read() and write() surface an open failure as an IDBOpenError. Don't\n // leave an unhandled rejection when neither is called.\n this.#db.catch(() => undefined);\n }\n\n read(): Promise<Read> {\n if (this.#closed) {\n return storeIsClosedRejection();\n }\n return this.#withReopen(readImpl);\n }\n\n write(): Promise<Write> {\n if (this.#closed) {\n return storeIsClosedRejection();\n }\n return this.#withReopen(writeImpl);\n }\n\n async close(): Promise<void> {\n if (!this.#idbDeleted) {\n const db = await this.#db;\n db.close();\n }\n this.#closed = true;\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n get kind(): string {\n return 'idb';\n }\n\n async #withReopen<R>(fn: (db: IDBDatabase) => R): Promise<R> {\n // Tries to reopen an IndexedDB, and rejects if the database needs\n // upgrading (is missing for whatever reason).\n const reopenExistingDB = async (name: string): Promise<IDBDatabase> => {\n const {promise, resolve, reject} = resolver<IDBDatabase>();\n const req = indexedDB.open(name);\n\n req.onupgradeneeded = () => {\n const tx = req.transaction;\n assertNotNull(tx);\n tx.abort();\n this.#idbDeleted = true;\n reject(\n new IDBNotFoundError(\n `Expected IndexedDB not found: ${name}. This likely means that the user deleted IndexedDB instances while the app was running. This is non-fatal. The app will continue running in memory until reload.`,\n ),\n );\n };\n\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n\n const db = await promise;\n db.onversionchange = () => db.close();\n return db;\n };\n\n // We abstract on `readImpl` to work around an issue in Safari. Safari does\n // not allow any microtask between a transaction is created until it is\n // first used. We used to use `await read()` here instead of `await\n // this._db` but then there is a microtask between the creation of the\n // transaction and the return of this function. By doing `await this._db`\n // here we only await the db and no await is involved with the transaction.\n // See https://github.com/jakearchibald/idb-keyval/commit/1af0a00b1a70a678d2f9cf5e74c55a22e57324c5#r55989916\n const db = await this.#db;\n\n try {\n return fn(db);\n } catch (e: unknown) {\n if (!this.#closed && e instanceof DOMException) {\n if (e.name === 'InvalidStateError') {\n this.#db = reopenExistingDB(db.name);\n const reopened = await this.#db;\n return fn(reopened);\n } else if (e.name === 'NotFoundError') {\n // This edge-case can happen if the db has been deleted and the\n // user/developer has DevTools open in certain browsers.\n // See discussion at https://github.com/rocicorp/replicache-internal/pull/216\n this.#idbDeleted = true;\n mustGetBrowserGlobal('indexedDB').deleteDatabase(db.name);\n throw new IDBNotFoundError(\n `Expected IndexedDB ${db.name} missing object store. Deleting db. This is non-fatal, the app will continue working in memory until it is reloaded.`,\n );\n }\n }\n throw e;\n }\n }\n}\n\nclass ReadImpl implements Read {\n readonly #tx: IDBTransaction;\n #closed = false;\n\n constructor(tx: IDBTransaction) {\n this.#tx = tx;\n }\n\n has(key: string): Promise<boolean> {\n if (this.#closed) {\n return transactionIsClosedRejection();\n }\n return new Promise((resolve, reject) => {\n const req = objectStore(this.#tx).count(key);\n req.onsuccess = () => resolve(req.result > 0);\n req.onerror = () => reject(req.error);\n });\n }\n\n get(key: string): Promise<FrozenJSONValue | undefined> {\n if (this.#closed) {\n return transactionIsClosedRejection();\n }\n return new Promise((resolve, reject) => {\n const req = objectStore(this.#tx).get(key);\n req.onsuccess = () => resolve(deepFreezeAllowUndefined(req.result));\n req.onerror = () => reject(req.error);\n });\n }\n\n release(): void {\n this.#closed = true;\n // Do nothing. We rely on IDB locking.\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\nclass WriteImpl extends WriteImplBase {\n readonly #tx: IDBTransaction;\n #closed = false;\n\n constructor(tx: IDBTransaction) {\n super(new ReadImpl(tx));\n this.#tx = tx;\n }\n\n commit(): Promise<void> {\n if (this.#closed) {\n return transactionIsClosedRejection();\n }\n if (this._pending.size === 0) {\n return promiseVoid;\n }\n\n return new Promise((resolve, reject) => {\n const tx = this.#tx;\n const store = objectStore(tx);\n for (const [key, val] of this._pending) {\n if (val === deleteSentinel) {\n store.delete(key);\n } else {\n store.put(val, key);\n }\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n }\n\n release(): void {\n // We rely on IDB locking so no need to do anything here.\n this.#closed = true;\n super.release();\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\nfunction writeImpl(db: IDBDatabase): Write {\n const tx = db.transaction(OBJECT_STORE, 'readwrite', RELAXED);\n return new WriteImpl(tx);\n}\n\nfunction readImpl(db: IDBDatabase): Read {\n const tx = db.transaction(OBJECT_STORE, 'readonly');\n return new ReadImpl(tx);\n}\n\nfunction objectStore(tx: IDBTransaction): IDBObjectStore {\n return tx.objectStore(OBJECT_STORE);\n}\n\nfunction openDatabase(name: string): Promise<IDBDatabase> {\n const idb = mustGetBrowserGlobal('indexedDB');\n return new Promise((resolve, reject) => {\n const req = idb.open(name);\n req.onupgradeneeded = () => {\n req.result.createObjectStore(OBJECT_STORE);\n };\n req.onsuccess = () => {\n const db = req.result;\n // Another tab/process wants to modify the db, so release it.\n db.onversionchange = () => db.close();\n resolve(db);\n };\n req.onerror = () =>\n reject(\n new IDBOpenError(`Failed to open IndexedDB ${name}`, {\n cause: req.error,\n }),\n );\n });\n}\n\n/**\n * This error is thrown when we detect that the IndexedDB has been removed. This\n * does not normally happen but can happen during development if the user has\n * DevTools open and deletes the IndexedDB from there.\n */\nexport class IDBNotFoundError extends Error {\n name = 'IDBNotFoundError';\n}\n\n/**\n * `read()` and `write()` reject with this error when the initial\n * `indexedDB.open` failed. The browser's error is the `cause`. A failure after\n * the database opened, such as a transaction error, is not wrapped.\n */\nexport class IDBOpenError extends Error {\n name = 'IDBOpenError';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"idb-store.js","names":["#db","#closed","#withReopen","#idbDeleted","#tx"],"sources":["../../../../../replicache/src/kv/idb-store.ts"],"sourcesContent":["import {resolver} from '@rocicorp/resolver';\nimport {assertNotNull} from '../../../shared/src/asserts.ts';\nimport {mustGetBrowserGlobal} from '../../../shared/src/browser-env.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {\n type FrozenJSONValue,\n deepFreezeAllowUndefined,\n} from '../frozen-json.ts';\nimport {StorageFailureError} from '../storage-failure.ts';\nimport type {Read, Store, Write} from './store.ts';\nimport {\n storeIsClosedRejection,\n transactionIsClosedRejection,\n} from './throw-if-closed.ts';\nimport {WriteImplBase, deleteSentinel} from './write-impl-base.ts';\n\nconst RELAXED = {durability: 'relaxed'} as const;\nconst OBJECT_STORE = 'chunks';\n\nexport class IDBStore implements Store {\n #db: Promise<IDBDatabase>;\n #closed = false;\n #idbDeleted = false;\n\n constructor(name: string) {\n this.#db = openDatabase(name);\n // read() and write() surface an open failure as an IDBOpenError. Don't\n // leave an unhandled rejection when neither is called.\n this.#db.catch(() => undefined);\n }\n\n read(): Promise<Read> {\n if (this.#closed) {\n return storeIsClosedRejection();\n }\n return this.#withReopen(readImpl);\n }\n\n write(): Promise<Write> {\n if (this.#closed) {\n return storeIsClosedRejection();\n }\n return this.#withReopen(writeImpl);\n }\n\n async close(): Promise<void> {\n if (!this.#idbDeleted) {\n const db = await this.#db;\n db.close();\n }\n this.#closed = true;\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n\n get kind(): string {\n return 'idb';\n }\n\n async #withReopen<R>(fn: (db: IDBDatabase) => R): Promise<R> {\n // Tries to reopen an IndexedDB, and rejects if the database needs\n // upgrading (is missing for whatever reason).\n const reopenExistingDB = async (name: string): Promise<IDBDatabase> => {\n const {promise, resolve, reject} = resolver<IDBDatabase>();\n const req = indexedDB.open(name);\n\n req.onupgradeneeded = () => {\n const tx = req.transaction;\n assertNotNull(tx);\n tx.abort();\n this.#idbDeleted = true;\n reject(\n new IDBNotFoundError(\n `Expected IndexedDB not found: ${name}. This likely means that the user deleted IndexedDB instances while the app was running. This is non-fatal. The app will continue running in memory until reload.`,\n ),\n );\n };\n\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n\n const db = await promise;\n db.onversionchange = () => db.close();\n return db;\n };\n\n // We abstract on `readImpl` to work around an issue in Safari. Safari does\n // not allow any microtask between a transaction is created until it is\n // first used. We used to use `await read()` here instead of `await\n // this._db` but then there is a microtask between the creation of the\n // transaction and the return of this function. By doing `await this._db`\n // here we only await the db and no await is involved with the transaction.\n // See https://github.com/jakearchibald/idb-keyval/commit/1af0a00b1a70a678d2f9cf5e74c55a22e57324c5#r55989916\n const db = await this.#db;\n\n try {\n return fn(db);\n } catch (e: unknown) {\n if (!this.#closed && e instanceof DOMException) {\n if (e.name === 'InvalidStateError') {\n this.#db = reopenExistingDB(db.name);\n const reopened = await this.#db;\n return fn(reopened);\n } else if (e.name === 'NotFoundError') {\n // This edge-case can happen if the db has been deleted and the\n // user/developer has DevTools open in certain browsers.\n // See discussion at https://github.com/rocicorp/replicache-internal/pull/216\n this.#idbDeleted = true;\n mustGetBrowserGlobal('indexedDB').deleteDatabase(db.name);\n throw new IDBNotFoundError(\n `Expected IndexedDB ${db.name} missing object store. Deleting db. This is non-fatal, the app will continue working in memory until it is reloaded.`,\n );\n }\n }\n throw e;\n }\n }\n}\n\nclass ReadImpl implements Read {\n readonly #tx: IDBTransaction;\n #closed = false;\n\n constructor(tx: IDBTransaction) {\n this.#tx = tx;\n }\n\n has(key: string): Promise<boolean> {\n if (this.#closed) {\n return transactionIsClosedRejection();\n }\n return new Promise((resolve, reject) => {\n const req = objectStore(this.#tx).count(key);\n req.onsuccess = () => resolve(req.result > 0);\n req.onerror = () => reject(req.error);\n });\n }\n\n get(key: string): Promise<FrozenJSONValue | undefined> {\n if (this.#closed) {\n return transactionIsClosedRejection();\n }\n return new Promise((resolve, reject) => {\n const req = objectStore(this.#tx).get(key);\n req.onsuccess = () => resolve(deepFreezeAllowUndefined(req.result));\n req.onerror = () => reject(req.error);\n });\n }\n\n release(): void {\n this.#closed = true;\n // Do nothing. We rely on IDB locking.\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\nclass WriteImpl extends WriteImplBase {\n readonly #tx: IDBTransaction;\n #closed = false;\n\n constructor(tx: IDBTransaction) {\n super(new ReadImpl(tx));\n this.#tx = tx;\n }\n\n commit(): Promise<void> {\n if (this.#closed) {\n return transactionIsClosedRejection();\n }\n if (this._pending.size === 0) {\n return promiseVoid;\n }\n\n return new Promise((resolve, reject) => {\n const tx = this.#tx;\n const store = objectStore(tx);\n for (const [key, val] of this._pending) {\n if (val === deleteSentinel) {\n store.delete(key);\n } else {\n store.put(val, key);\n }\n }\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n });\n }\n\n release(): void {\n // We rely on IDB locking so no need to do anything here.\n this.#closed = true;\n super.release();\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\nfunction writeImpl(db: IDBDatabase): Write {\n const tx = db.transaction(OBJECT_STORE, 'readwrite', RELAXED);\n return new WriteImpl(tx);\n}\n\nfunction readImpl(db: IDBDatabase): Read {\n const tx = db.transaction(OBJECT_STORE, 'readonly');\n return new ReadImpl(tx);\n}\n\nfunction objectStore(tx: IDBTransaction): IDBObjectStore {\n return tx.objectStore(OBJECT_STORE);\n}\n\nfunction openDatabase(name: string): Promise<IDBDatabase> {\n const idb = mustGetBrowserGlobal('indexedDB');\n return new Promise((resolve, reject) => {\n const req = idb.open(name);\n req.onupgradeneeded = () => {\n req.result.createObjectStore(OBJECT_STORE);\n };\n req.onsuccess = () => {\n const db = req.result;\n // Another tab/process wants to modify the db, so release it.\n db.onversionchange = () => db.close();\n resolve(db);\n };\n req.onerror = () =>\n reject(\n new IDBOpenError(`Failed to open IndexedDB ${name}`, {\n cause: req.error,\n }),\n );\n });\n}\n\n/**\n * This error is thrown when we detect that the IndexedDB has been removed. This\n * does not normally happen but can happen during development if the user has\n * DevTools open and deletes the IndexedDB from there.\n */\nexport class IDBNotFoundError extends Error {\n name = 'IDBNotFoundError';\n}\n\nconst DISK_FULL = /database or disk is full/;\n\n/**\n * `read()` and `write()` reject with this error when the initial\n * `indexedDB.open` failed. The browser's error is the `cause`. A failure after\n * the database opened, such as a transaction error, is not wrapped.\n *\n * It is a `cannot-open` storage failure, or `full` when the browser says so\n * (WebKit's IndexedDB is SQLite underneath and reports `database or disk is\n * full`).\n */\nexport class IDBOpenError extends StorageFailureError {\n name = 'IDBOpenError';\n\n constructor(message: string, options: {cause: unknown}) {\n super(\n DISK_FULL.test(String(options.cause)) ? 'full' : 'cannot-open',\n message,\n options,\n );\n }\n}\n\nexport function 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":"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"}
|