@rocicorp/zero 1.12.0-canary.12 → 1.12.0-canary.13
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/replicache/src/btree/diff.d.ts.map +1 -1
- package/out/replicache/src/btree/diff.js +1 -2
- package/out/replicache/src/btree/diff.js.map +1 -1
- package/out/replicache/src/btree/read.d.ts +31 -1
- package/out/replicache/src/btree/read.d.ts.map +1 -1
- package/out/replicache/src/btree/read.js +74 -19
- package/out/replicache/src/btree/read.js.map +1 -1
- package/out/replicache/src/btree/splice.d.ts.map +1 -1
- package/out/replicache/src/btree/splice.js +2 -1
- package/out/replicache/src/btree/splice.js.map +1 -1
- package/out/replicache/src/btree/write.d.ts.map +1 -1
- package/out/replicache/src/btree/write.js +2 -1
- package/out/replicache/src/btree/write.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +1 -0
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +13 -5
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/scan-iterator.js +1 -1
- package/out/replicache/src/sync/request-id.js +2 -2
- package/out/replicache/src/sync/request-id.js.map +1 -1
- package/out/shared/src/bigint-json.d.ts.map +1 -1
- package/out/shared/src/bigint-json.js +1 -0
- package/out/shared/src/bigint-json.js.map +1 -1
- package/out/shared/src/btree-set.d.ts +21 -0
- package/out/shared/src/btree-set.d.ts.map +1 -1
- package/out/shared/src/btree-set.js +54 -0
- package/out/shared/src/btree-set.js.map +1 -1
- package/out/shared/src/headers.js +20 -0
- package/out/shared/src/headers.js.map +1 -0
- package/out/shared/src/natural-runs.d.ts +191 -0
- package/out/shared/src/natural-runs.d.ts.map +1 -0
- package/out/shared/src/natural-runs.js +337 -0
- package/out/shared/src/natural-runs.js.map +1 -0
- package/out/shared/src/random-uint64.d.ts.map +1 -1
- package/out/shared/src/random-uint64.js +2 -2
- package/out/shared/src/random-uint64.js.map +1 -1
- package/out/shared/src/random-values.d.ts +19 -1
- package/out/shared/src/random-values.d.ts.map +1 -1
- package/out/shared/src/random-values.js +38 -4
- package/out/shared/src/random-values.js.map +1 -1
- package/out/shared/src/tuple-map.d.ts +22 -0
- package/out/shared/src/tuple-map.d.ts.map +1 -0
- package/out/shared/src/tuple-map.js +48 -0
- package/out/shared/src/tuple-map.js.map +1 -0
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/auth/auth.d.ts.map +1 -1
- package/out/zero-cache/src/auth/auth.js +5 -0
- package/out/zero-cache/src/auth/auth.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +21 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +47 -9
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js +2 -2
- package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
- package/out/zero-cache/src/db/pg-to-lite.d.ts.map +1 -1
- package/out/zero-cache/src/db/pg-to-lite.js +3 -2
- package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
- package/out/zero-cache/src/db/pg-type-parser.js +55 -2
- package/out/zero-cache/src/db/pg-type-parser.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +5 -2
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/inspector-delegate.js +6 -29
- package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
- package/out/zero-cache/src/server/main.js +5 -0
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/replicator.js +3 -2
- package/out/zero-cache/src/server/replicator.js.map +1 -1
- package/out/zero-cache/src/server/syncer.js +5 -4
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/worker-dispatcher.js +2 -1
- package/out/zero-cache/src/server/worker-dispatcher.js.map +1 -1
- package/out/zero-cache/src/services/admin-auth.d.ts +22 -0
- package/out/zero-cache/src/services/admin-auth.d.ts.map +1 -0
- package/out/zero-cache/src/services/admin-auth.js +42 -0
- package/out/zero-cache/src/services/admin-auth.js.map +1 -0
- package/out/zero-cache/src/services/auth-attempt-counter.d.ts +33 -0
- package/out/zero-cache/src/services/auth-attempt-counter.d.ts.map +1 -0
- package/out/zero-cache/src/services/auth-attempt-counter.js +61 -0
- package/out/zero-cache/src/services/auth-attempt-counter.js.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +110 -13
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +13 -7
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +38 -5
- package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js +10 -1
- package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +4 -0
- package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +10 -3
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +23 -2
- 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.js +2 -2
- 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.js +10 -3
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/heapz.d.ts.map +1 -1
- package/out/zero-cache/src/services/heapz.js +4 -5
- package/out/zero-cache/src/services/heapz.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/error.d.ts +5 -0
- package/out/zero-cache/src/services/mutagen/error.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/error.js +7 -1
- package/out/zero-cache/src/services/mutagen/error.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js +1 -1
- package/out/zero-cache/src/services/mutagen/mutagen.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/plannerz.d.ts.map +1 -1
- package/out/zero-cache/src/services/plannerz.js +3 -7
- package/out/zero-cache/src/services/plannerz.js.map +1 -1
- package/out/zero-cache/src/services/profz.d.ts.map +1 -1
- package/out/zero-cache/src/services/profz.js +4 -13
- package/out/zero-cache/src/services/profz.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +29 -2
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +3 -3
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/statz.d.ts.map +1 -1
- package/out/zero-cache/src/services/statz.js +2 -7
- package/out/zero-cache/src/services/statz.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +22 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.js +40 -2
- package/out/zero-cache/src/services/view-syncer/connection-context-manager.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +54 -17
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +11 -0
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-auth-limiter.js +107 -0
- package/out/zero-cache/src/services/view-syncer/inspect-auth-limiter.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +11 -6
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +2 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js +16 -14
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/cvr.js +4 -1
- package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/init.js +3 -0
- package/out/zero-cache/src/services/view-syncer/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +89 -51
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/client-group-log-id.d.ts +9 -0
- package/out/zero-cache/src/types/client-group-log-id.d.ts.map +1 -0
- package/out/zero-cache/src/types/client-group-log-id.js +16 -0
- package/out/zero-cache/src/types/client-group-log-id.js.map +1 -0
- package/out/zero-cache/src/types/lite.d.ts.map +1 -1
- package/out/zero-cache/src/types/lite.js +1 -0
- package/out/zero-cache/src/types/lite.js.map +1 -1
- package/out/zero-cache/src/types/pg.d.ts +13 -2
- package/out/zero-cache/src/types/pg.d.ts.map +1 -1
- package/out/zero-cache/src/types/pg.js +2 -2
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/poke-chunk.d.ts +25 -4
- package/out/zero-cache/src/types/poke-chunk.d.ts.map +1 -1
- package/out/zero-cache/src/types/poke-chunk.js +46 -5
- package/out/zero-cache/src/types/poke-chunk.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +10 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/rehome.d.ts +10 -0
- package/out/zero-cache/src/workers/rehome.d.ts.map +1 -1
- package/out/zero-cache/src/workers/rehome.js +4 -2
- package/out/zero-cache/src/workers/rehome.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +3 -2
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +15 -15
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/inspector/inspector.d.ts +4 -0
- package/out/zero-client/src/client/inspector/inspector.d.ts.map +1 -1
- package/out/zero-client/src/client/inspector/inspector.js +4 -0
- package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.d.ts +10 -2
- package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
- package/out/zero-client/src/client/inspector/lazy-inspector.js +38 -31
- package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
- package/out/zero-client/src/client/inspector/query.d.ts +2 -2
- package/out/zero-client/src/client/inspector/query.d.ts.map +1 -1
- package/out/zero-client/src/client/inspector/query.js +4 -4
- package/out/zero-client/src/client/inspector/query.js.map +1 -1
- package/out/zero-client/src/client/ivm-branch.d.ts +3 -0
- package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
- package/out/zero-client/src/client/ivm-branch.js +10 -5
- package/out/zero-client/src/client/ivm-branch.js.map +1 -1
- package/out/zero-client/src/client/reconnect-backoff.d.ts +31 -0
- package/out/zero-client/src/client/reconnect-backoff.d.ts.map +1 -0
- package/out/zero-client/src/client/reconnect-backoff.js +44 -0
- package/out/zero-client/src/client/reconnect-backoff.js.map +1 -0
- 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 +7 -0
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/util/nanoid.js +2 -2
- package/out/zero-client/src/util/nanoid.js.map +1 -1
- package/out/zero-protocol/src/up.d.ts +42 -42
- package/out/zero-server/src/process-mutations.d.ts +10 -0
- package/out/zero-server/src/process-mutations.d.ts.map +1 -1
- package/out/zero-server/src/process-mutations.js +39 -6
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +89 -6
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +37 -3
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.js","names":["ErrorKind.Unauthorized","ErrorOrigin.ZeroCache"],"sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import {pid} from 'node:process';\nimport type {LogContext} from '@rocicorp/logger';\nimport {WebSocket, WebSocketServer, type ServerOptions} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {resolver} from '../../../shared/src/resolver.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {\n isProtocolError,\n ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {resolveAuth, type Auth, type ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateNativeHistogram,\n getOrCreateUpDownCounter,\n} from '../observability/metrics.ts';\nimport {\n recordConnectionAttempted,\n recordConnectionSuccess,\n setActiveClientGroupsGetter,\n} from '../server/anonymous-otel-start.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {ServiceRunner} from '../services/runner.ts';\nimport type {\n ActivityBasedService,\n Service,\n SingletonService,\n} from '../services/service.ts';\nimport type {ConnectionContextManager} from '../services/view-syncer/connection-context-manager.ts';\nimport {\n DrainCoordinator,\n TARGET_UTILIZATION,\n} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {ClientGroupStatusMessage, Worker} from '../types/processes.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {installWebSocketReceiver} from '../types/websocket-handoff.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {Connection, sendError} from './connection.ts';\nimport {expiredAffinityCookieHeader, rehome} from './rehome.ts';\nimport {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type ReplicaReadyState = {\n readonly watermark: string;\n readonly replicaReadyTimeMs: number;\n};\n\nexport type ServingLagViewSyncer = Pick<\n ViewSyncer,\n 'createdAtMs' | 'servedVersion' | 'servingLagEligible'\n>;\n\nexport type ServingLagStats = {\n readonly activeClientGroups: number;\n readonly laggingClientGroups: number;\n readonly minMs: number;\n readonly p50Ms: number;\n readonly p75Ms: number;\n readonly p99Ms: number;\n readonly maxMs: number;\n};\n\ntype ServingLagDistribution = ServingLagStats & {\n readonly lagsMs: readonly number[];\n};\n\nexport type PipelineDedupViewSyncer = Pick<\n ViewSyncer,\n 'pipelineHashes' | 'clientSchemaKey'\n>;\n\nexport type PipelineDedupStats = {\n readonly clientTotal: number;\n readonly internalTotal: number;\n // (clientSchemaKey, transformationHash) -> occurrence count across client\n // groups, for client/custom queries.\n readonly clientHashes: ReadonlyMap<\n string,\n {count: number; queryName: string | undefined}\n >;\n readonly internalUnique: number;\n readonly clientSchemas: number;\n};\n\nexport function computePipelineDedupStats(\n viewSyncers: Iterable<PipelineDedupViewSyncer>,\n): PipelineDedupStats {\n let clientTotal = 0;\n let internalTotal = 0;\n const clientHashes = new Map<\n string,\n {count: number; queryName: string | undefined}\n >();\n const internalHashes = new Set<string>();\n const schemaKeys = new Set<string>();\n let vsIndex = 0;\n for (const vs of viewSyncers) {\n const schemaKey = vs.clientSchemaKey;\n if (schemaKey !== undefined) {\n schemaKeys.add(schemaKey);\n }\n // Without a client schema the pipeline's dedup identity is unknown, so key\n // it uniquely per view-syncer; otherwise schema-less pipelines from\n // unrelated client groups would collapse under a shared `undefined/<hash>`\n // key and overstate deduplication.\n const dedupSchemaKey = schemaKey ?? `#vs${vsIndex}`;\n vsIndex++;\n for (const {\n transformationHash,\n internal,\n queryName,\n } of vs.pipelineHashes()) {\n // Pipelines only share when both the client schema (which determines\n // row keys) and the transformed AST are identical.\n const key = `${dedupSchemaKey}/${transformationHash}`;\n if (internal) {\n internalTotal++;\n internalHashes.add(key);\n } else {\n clientTotal++;\n const entry = clientHashes.get(key);\n if (entry) {\n entry.count++;\n } else {\n clientHashes.set(key, {count: 1, queryName});\n }\n }\n }\n }\n return {\n clientTotal,\n internalTotal,\n clientHashes,\n internalUnique: internalHashes.size,\n clientSchemas: schemaKeys.size,\n };\n}\n\nexport const MAX_REPLICA_READY_STATES = 10_000;\nconst VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS = 60_000;\nconst SHARED_ADVANCE_ELIGIBILITY_LOG_INTERVAL_MS = 5 * 60_000;\n\nconst PROTOCOL_VERSION_ATTRIBUTE = 'protocol.version';\nconst FAILURE_REASON_ATTRIBUTE = 'reason';\n\nfunction boundReplicaReadyStates(\n replicaReadyStates: ReplicaReadyState[],\n): void {\n if (replicaReadyStates.length > MAX_REPLICA_READY_STATES) {\n replicaReadyStates.splice(\n 0,\n replicaReadyStates.length - MAX_REPLICA_READY_STATES,\n );\n }\n}\n\nfunction pruneReplicaReadyStates(\n replicaReadyStates: ReplicaReadyState[],\n firstNeededIndex: number,\n): void {\n if (firstNeededIndex > 0) {\n replicaReadyStates.splice(0, firstNeededIndex);\n }\n\n boundReplicaReadyStates(replicaReadyStates);\n}\n\nfunction lowerBoundReplicaReadyTimeMs(\n replicaReadyStates: readonly ReplicaReadyState[],\n replicaReadyTimeMs: number,\n): number {\n let low = 0;\n let high = replicaReadyStates.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (replicaReadyStates[mid].replicaReadyTimeMs < replicaReadyTimeMs) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n}\n\nfunction upperBoundWatermark(\n replicaReadyStates: readonly ReplicaReadyState[],\n watermark: string,\n): number {\n let low = 0;\n let high = replicaReadyStates.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (replicaReadyStates[mid].watermark <= watermark) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n}\n\nfunction findFirstUnservedIndex(\n replicaReadyStates: readonly ReplicaReadyState[],\n viewSyncer: ServingLagViewSyncer,\n): number {\n const firstReadyAfterCreation = lowerBoundReplicaReadyTimeMs(\n replicaReadyStates,\n viewSyncer.createdAtMs,\n );\n const firstAfterServedVersion =\n viewSyncer.servedVersion === null\n ? 0\n : upperBoundWatermark(replicaReadyStates, viewSyncer.servedVersion);\n\n const firstUnservedIndex = Math.max(\n firstReadyAfterCreation,\n firstAfterServedVersion,\n );\n return firstUnservedIndex < replicaReadyStates.length\n ? firstUnservedIndex\n : -1;\n}\n\nfunction percentileNearestRank(\n sortedValues: readonly number[],\n percentile: number,\n): number {\n if (sortedValues.length === 0) {\n return 0;\n }\n const index = Math.min(\n sortedValues.length - 1,\n Math.max(0, Math.ceil((percentile / 100) * sortedValues.length) - 1),\n );\n return sortedValues[index];\n}\n\nfunction computeServingLagDistributionMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): ServingLagDistribution {\n const lags: number[] = [];\n let laggingClientGroups = 0;\n let firstNeededIndex = replicaReadyStates.length;\n\n for (const viewSyncer of viewSyncers) {\n if (!viewSyncer.servingLagEligible) {\n continue;\n }\n\n const firstUnservedIndex = findFirstUnservedIndex(\n replicaReadyStates,\n viewSyncer,\n );\n if (firstUnservedIndex === -1) {\n lags.push(0);\n continue;\n }\n\n firstNeededIndex = Math.min(firstNeededIndex, firstUnservedIndex);\n const lagMs = Math.max(\n 0,\n now - replicaReadyStates[firstUnservedIndex].replicaReadyTimeMs,\n );\n lags.push(lagMs);\n if (lagMs > 0) {\n laggingClientGroups++;\n }\n }\n\n pruneReplicaReadyStates(replicaReadyStates, firstNeededIndex);\n\n lags.sort((a, b) => a - b);\n return {\n activeClientGroups: lags.length,\n laggingClientGroups,\n // The percentile fields are for the legacy serving_lag_stats gauge.\n // The native view_syncer_lag histogram records lagsMs and computes\n // percentiles in Prometheus/AMP.\n minMs: lags[0] ?? 0,\n p50Ms: percentileNearestRank(lags, 50),\n p75Ms: percentileNearestRank(lags, 75),\n p99Ms: percentileNearestRank(lags, 99),\n maxMs: lags.at(-1) ?? 0,\n lagsMs: lags,\n };\n}\n\nexport function computeServingLagStatsMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): ServingLagStats {\n const distribution = computeServingLagDistributionMs(\n now,\n replicaReadyStates,\n viewSyncers,\n );\n return {\n activeClientGroups: distribution.activeClientGroups,\n laggingClientGroups: distribution.laggingClientGroups,\n minMs: distribution.minMs,\n p50Ms: distribution.p50Ms,\n p75Ms: distribution.p75Ms,\n p99Ms: distribution.p99Ms,\n maxMs: distribution.maxMs,\n };\n}\n\nexport function computeMaxServingLagMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): number {\n return computeServingLagStatsMs(now, replicaReadyStates, viewSyncers).maxMs;\n}\n\nfunction getWebSocketServerOptions(config: ZeroConfig): ServerOptions {\n const options: ServerOptions = {\n noServer: true,\n maxPayload: config.websocketMaxPayloadBytes,\n };\n\n if (config.websocketCompression) {\n options.perMessageDeflate = true;\n\n if (config.websocketCompressionOptions) {\n try {\n const compressionOptions = JSON.parse(\n config.websocketCompressionOptions,\n );\n options.perMessageDeflate = compressionOptions;\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n }\n }\n\n return options;\n}\n\n/**\n * The Syncer worker receives websocket handoffs for \"/sync\" connections\n * from the Dispatcher in the main thread, and creates websocket\n * {@link Connection}s with a corresponding {@link ViewSyncer}, {@link Mutagen},\n * and {@link Subscription} to version notifications from the Replicator\n * worker.\n */\nexport class Syncer implements SingletonService {\n readonly id = `syncer-${pid}`;\n readonly #lc: LogContext;\n readonly #viewSyncers: ServiceRunner<ViewSyncer & ActivityBasedService>;\n readonly #mutagens: ServiceRunner<Mutagen & Service> | undefined;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #pendingConnections = new Map<string, number>();\n readonly #clientGroupGenerations = new Map<string, number>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n #draining = false;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n readonly #replicaReadyStates: ReplicaReadyState[] = [];\n readonly #webSocketOpenConnections = getOrCreateUpDownCounter(\n 'sync',\n 'websocket.open_connections',\n 'Open client WebSocket connections.',\n );\n readonly #webSocketConnectionAttempts = getOrCreateCounter(\n 'sync',\n 'websocket.connection_attempts',\n 'Client WebSocket connection attempts.',\n );\n readonly #webSocketConnectionSuccesses = getOrCreateCounter(\n 'sync',\n 'websocket.connection_successes',\n 'Client WebSocket connections successfully initialized.',\n );\n readonly #webSocketConnectionFailures = getOrCreateCounter(\n 'sync',\n 'websocket.connection_failures',\n 'Client WebSocket connection attempts that failed before initialization.',\n );\n readonly #viewSyncerLag = getOrCreateNativeHistogram(\n 'sync',\n 'view_syncer_lag',\n {\n description:\n 'Lag from replica-ready change to ViewSyncer output for active ' +\n 'client groups. A change is output after IVM advancement, CVR flush, ' +\n 'and pokeEnd.',\n unit: 's',\n },\n );\n #servingLagDistributionCache: ServingLagDistribution | undefined;\n #servingLagDistributionCacheClearQueued = false;\n #viewSyncerLagSampleInterval: ReturnType<typeof setInterval> | undefined;\n #eligibilityLogInterval: ReturnType<typeof setInterval> | undefined;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: ((id: string) => Mutagen & Service) | undefined,\n pusherFactory:\n | ((\n id: string,\n connContextManager: ConnectionContextManager,\n ) => Pusher & Service)\n | undefined,\n parent: Worker,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n ) {\n this.#config = config;\n this.#validateLegacyJWT = validateLegacyJWT;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent, state =>\n this.#recordReplicaReadyState(state),\n );\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#parent = parent;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator),\n v => v.keepalive(),\n id => {\n if ((this.#pendingConnections.get(id) ?? 0) === 0) {\n const generation = this.#clientGroupGenerations.get(id);\n this.#parent.send<ClientGroupStatusMessage>([\n 'clientGroupStatus',\n {\n clientGroupID: id,\n active: false,\n ...(generation !== undefined ? {generation} : {}),\n },\n ]);\n this.#clientGroupGenerations.delete(id);\n }\n },\n );\n if (mutagenFactory) {\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n }\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(\n lc,\n id =>\n pusherFactory(\n id,\n this.#viewSyncers.getService(id).connContextManager,\n ),\n p => p.hasRefs(),\n );\n }\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n const expiredCookie = expiredAffinityCookieHeader(config);\n if (expiredCookie) {\n // ws exposes handshake headers through this event. Set-Cookie must be\n // part of the 101 response, before receiveConnection sends Rehome.\n this.#wss.on('headers', headers => {\n if (this.#draining) {\n headers.push(expiredCookie);\n }\n });\n }\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#receiveConnection,\n this.#parent,\n params => {\n this.#releasePending(params.clientGroupID, params.generation);\n },\n params => {\n this.#pendingConnections.set(\n params.clientGroupID,\n (this.#pendingConnections.get(params.clientGroupID) ?? 0) + 1,\n );\n if (params.generation !== undefined) {\n const current = this.#clientGroupGenerations.get(\n params.clientGroupID,\n );\n if (current === undefined || params.generation > current) {\n this.#clientGroupGenerations.set(\n params.clientGroupID,\n params.generation,\n );\n }\n }\n },\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n\n getOrCreateGauge(\n 'sync',\n 'active-client-groups',\n 'Number of active client groups',\n ).addCallback(result => result.observe(this.#viewSyncers.size));\n\n getOrCreateGauge(\n 'sync',\n 'queries',\n 'Active queries (pipelines) across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.queryCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge('sync', 'drain-time', {\n description:\n 'How long a graceful shutdown would take to release every client ' +\n 'group, pacing their disconnects by their hydration times as drain() ' +\n 'does. A pod drains its workers in parallel, so its drain takes the ' +\n 'longest of theirs.',\n unit: 's',\n }).addCallback(result => {\n let hydrationTimeMs = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n hydrationTimeMs += vs.hydrationTimeMs;\n }\n result.observe(hydrationTimeMs / TARGET_UTILIZATION / 1000);\n });\n\n getOrCreateGauge(\n 'sync',\n 'rows',\n 'Tracked rows across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.rowCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge('sync', 'serving_lag', {\n description:\n 'Maximum time active ViewSyncer client groups have had unserved ' +\n 'replica changes. A change is served after IVM advancement, CVR flush, ' +\n 'and pokeEnd.',\n unit: 'millisecond',\n }).addCallback(result => {\n result.observe(this.#computeServingLagDistribution().maxMs);\n });\n\n getOrCreateGauge('sync', 'serving_lag_stats', {\n description:\n 'Distribution of time active ViewSyncer client groups have had ' +\n 'unserved replica changes. A change is served after IVM advancement, ' +\n 'CVR flush, and pokeEnd.',\n unit: 'millisecond',\n }).addCallback(result => {\n const stats = this.#computeServingLagDistribution();\n result.observe(stats.minMs, {stat: 'min'});\n result.observe(stats.p50Ms, {stat: 'p50'});\n result.observe(stats.p75Ms, {stat: 'p75'});\n result.observe(stats.p99Ms, {stat: 'p99'});\n result.observe(stats.maxMs, {stat: 'max'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'serving_lagging_client_groups',\n 'Number of active client groups with unserved replica changes',\n ).addCallback(result => {\n result.observe(this.#computeServingLagDistribution().laggingClientGroups);\n });\n\n getOrCreateGauge(\n 'sync',\n 'pipelines_total',\n 'Query pipelines across all client groups on this worker, by query ' +\n 'type. The ratio to sync.pipelines_unique is the dedup factor ' +\n 'available to shared pipeline advancement.',\n ).addCallback(result => {\n const stats = this.#computePipelineDedupStats();\n result.observe(stats.clientTotal, {type: 'client'});\n result.observe(stats.internalTotal, {type: 'internal'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'pipelines_unique',\n 'Distinct (clientSchema, transformationHash) pipelines across all ' +\n 'client groups on this worker, by query type',\n ).addCallback(result => {\n const stats = this.#computePipelineDedupStats();\n result.observe(stats.clientHashes.size, {type: 'client'});\n result.observe(stats.internalUnique, {type: 'internal'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'client_schemas',\n 'Distinct client schemas across all client groups on this worker',\n ).addCallback(result => {\n result.observe(this.#computePipelineDedupStats().clientSchemas);\n });\n\n this.#viewSyncerLagSampleInterval = setInterval(\n () => this.#recordViewSyncerLagSamples(),\n VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS,\n );\n this.#viewSyncerLagSampleInterval.unref?.();\n\n this.#eligibilityLogInterval = setInterval(\n () => this.#logSharedAdvanceEligibility(),\n SHARED_ADVANCE_ELIGIBILITY_LOG_INTERVAL_MS,\n );\n this.#eligibilityLogInterval.unref?.();\n }\n\n #computeServingLagDistribution(): ServingLagDistribution {\n if (this.#servingLagDistributionCache) {\n return this.#servingLagDistributionCache;\n }\n\n const distribution = computeServingLagDistributionMs(\n Date.now(),\n this.#replicaReadyStates,\n this.#viewSyncers.getServices(),\n );\n this.#servingLagDistributionCache = distribution;\n if (!this.#servingLagDistributionCacheClearQueued) {\n this.#servingLagDistributionCacheClearQueued = true;\n queueMicrotask(() => {\n this.#servingLagDistributionCache = undefined;\n this.#servingLagDistributionCacheClearQueued = false;\n });\n }\n return distribution;\n }\n\n #recordViewSyncerLagSamples(): void {\n const {lagsMs} = this.#computeServingLagDistribution();\n for (const lagMs of lagsMs) {\n this.#viewSyncerLag.recordMs(lagMs);\n }\n }\n\n #computePipelineDedupStats(): PipelineDedupStats {\n return computePipelineDedupStats(this.#viewSyncers.getServices());\n }\n\n #logSharedAdvanceEligibility(): void {\n const stats = this.#computePipelineDedupStats();\n if (stats.clientTotal === 0) {\n return;\n }\n const unique = stats.clientHashes.size;\n const topDuplicated = [...stats.clientHashes.entries()]\n .filter(([, {count}]) => count > 1)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, 10)\n .map(([key, {count, queryName}]) => ({key, count, queryName}));\n this.#lc.info?.('shared-advance-eligibility', {\n clientPipelines: stats.clientTotal,\n uniqueClientPipelines: unique,\n dedupFactor: Number((stats.clientTotal / Math.max(unique, 1)).toFixed(2)),\n internalPipelines: stats.internalTotal,\n clientSchemas: stats.clientSchemas,\n topDuplicated,\n });\n }\n\n #recordReplicaReadyState(state: ReplicaState): void {\n if (\n state.watermark === undefined ||\n state.replicaReadyTimeMs === undefined\n ) {\n return;\n }\n const last = this.#replicaReadyStates.at(-1);\n if (last && last.watermark >= state.watermark) {\n return;\n }\n this.#replicaReadyStates.push({\n watermark: state.watermark,\n replicaReadyTimeMs: state.replicaReadyTimeMs,\n });\n if (this.#viewSyncers.size === 0) {\n this.#replicaReadyStates.length = 0;\n return;\n }\n boundReplicaReadyStates(this.#replicaReadyStates);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\n const metricAttributes = {\n [PROTOCOL_VERSION_ATTRIBUTE]: params.protocolVersion,\n };\n let connectionResultRecorded = false;\n const recordConnectionFailure = (reason: string) => {\n if (connectionResultRecorded) {\n return;\n }\n connectionResultRecorded = true;\n this.#webSocketConnectionFailures.add(1, {\n ...metricAttributes,\n [FAILURE_REASON_ATTRIBUTE]: reason,\n });\n };\n const recordConnectionSuccessMetric = () => {\n if (connectionResultRecorded) {\n return;\n }\n connectionResultRecorded = true;\n this.#webSocketConnectionSuccesses.add(1, metricAttributes);\n };\n\n this.#webSocketConnectionAttempts.add(1, metricAttributes);\n this.#webSocketOpenConnections.add(1, metricAttributes);\n ws.once('close', () => {\n this.#webSocketOpenConnections.add(-1, metricAttributes);\n });\n\n this.#lc.debug?.(\n 'creating connection',\n params.clientGroupID,\n params.clientID,\n );\n recordConnectionAttempted();\n const {clientID, clientGroupID, auth, userID} = params;\n const hasProvidedAuth = auth !== undefined && auth !== '';\n const incomingUserID = userID ?? null;\n\n if (hasProvidedAuth) {\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n\n const hasPushOrMutate =\n this.#config?.push?.url !== undefined ||\n this.#config?.mutate?.url !== undefined;\n const hasQueries =\n this.#config?.query?.url !== undefined ||\n this.#config?.getQueries?.url !== undefined;\n\n // must either have one of the token options set or have custom mutations & queries enabled\n const hasExactlyOneTokenOption = tokenOptions.length === 1;\n const hasCustomEndpoints = hasPushOrMutate && hasQueries;\n if (!hasExactlyOneTokenOption && !hasCustomEndpoints) {\n recordConnectionFailure('configuration');\n throw new Error(\n 'Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: ' +\n JSON.stringify(tokenOptions) +\n '. You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.',\n );\n }\n }\n\n let initialAuth: Auth | undefined;\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS.\n try {\n initialAuth = await resolveAuth(\n this.#lc\n .withContext('clientGroupID', clientGroupID)\n .withContext('clientID', clientID),\n // no previous auth, since this is a new connection, and resolveAuth is\n // connection scoped, not client group scoped\n undefined,\n incomingUserID,\n auth,\n this.#validateLegacyJWT,\n );\n } catch (e) {\n if (isProtocolError(e)) {\n this.#lc.warn?.(\n 'Rejecting sync connection during initial auth resolution',\n {\n clientGroupID,\n clientID,\n incomingUserID,\n hasProvidedAuth,\n errorKind: e.message,\n },\n );\n recordConnectionFailure('auth');\n sendError(this.#lc, ws, e.errorBody);\n ws.close(3000, e.errorBody.message);\n return;\n }\n recordConnectionFailure('internal');\n throw e;\n }\n\n // Resolving auth can take a while (e.g. a remote JWKS fetch), during\n // which the client may have disconnected. A Connection created for a\n // closed socket never receives the 'close' event and is thus never\n // cleaned up, leaking its timer, its registrations in the connection\n // context manager and its refs on the mutagen and pusher services.\n if (ws.readyState !== WebSocket.OPEN) {\n this.#lc.debug?.('websocket closed while resolving auth', {\n clientGroupID,\n clientID,\n readyState: ws.readyState,\n });\n recordConnectionFailure('closed_during_auth');\n return;\n }\n\n // Authentication may yield while a drain starts. Do not create services\n // or replace an existing client connection after that transition.\n if (this.#draining) {\n recordConnectionFailure('draining');\n rehome(this.#lc, ws);\n return;\n }\n\n const viewSyncer = this.#viewSyncers.getService(clientGroupID);\n const connContextManager = viewSyncer.connContextManager;\n const group = connContextManager.getGroupState();\n\n // TODO(0xcadams): we only check for user ID mismatch here if the group is\n // already validated. This prevents wrong-user reconnects from evicting a\n // healthy connection, but it does not protect against same-user reconnects\n // with an invalid opaque token. The long-term fix is to keep the replacement\n // connection pending until its auth is fully validated, and only then replace\n // the existing socket.\n if (\n group.pinnedUser !== undefined &&\n group.pinnedUser.id !== incomingUserID\n ) {\n const error = new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Client groups are pinned to a single userID. Connection userID does not match existing client group userID.',\n origin: ErrorOrigin.ZeroCache,\n });\n recordConnectionFailure('user_mismatch');\n sendError(this.#lc, ws, error.errorBody);\n ws.close(3000, error.message);\n return;\n }\n\n // Check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n if (existing.wsID === params.wsID) {\n // Clients generate a new wsID per socket, so this means the same\n // connect request was delivered twice (e.g. duplicated upstream).\n this.#lc.warn?.(\n `duplicate connection for client ${clientID} with the same wsID ${params.wsID}, closing existing connection`,\n {clientGroupID},\n );\n }\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\n }\n\n connContextManager.registerConnection(\n {clientID, wsID: params.wsID},\n params,\n initialAuth,\n );\n\n const mutagen = this.#mutagens?.getService(clientGroupID);\n const pusher = this.#pushers?.getService(clientGroupID);\n // a new connection is using the mutagen and pusher. Bump their ref counts.\n mutagen?.ref();\n pusher?.ref();\n\n let connection: Connection;\n try {\n connection = new Connection(\n this.#lc,\n params,\n ws,\n this.#config.allowLegacyQueries,\n new SyncerWsMessageHandler(\n this.#lc,\n params,\n connContextManager,\n viewSyncer,\n mutagen,\n pusher,\n ),\n () => {\n connContextManager.closeConnection({\n clientID,\n wsID: params.wsID,\n });\n if (this.#connections.get(clientID) === connection) {\n this.#connections.delete(clientID);\n }\n // Connection is closed. We can unref the mutagen and pusher.\n // If their ref counts are zero, they will stop themselves and set themselves invalid.\n mutagen?.unref();\n pusher?.unref();\n },\n );\n } catch (e) {\n recordConnectionFailure('internal');\n connContextManager.closeConnection({clientID, wsID: params.wsID});\n mutagen?.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n if (connection.init()) {\n recordConnectionSuccessMetric();\n recordConnectionSuccess();\n } else {\n recordConnectionFailure('protocol_version');\n }\n\n if (params.initConnectionMsg) {\n this.#lc.debug?.(\n 'handling init connection message from sec header',\n params.clientGroupID,\n params.clientID,\n );\n await connection.handleInitConnection(\n JSON.stringify(params.initConnectionMsg),\n );\n }\n };\n\n readonly #receiveConnection = async (\n ws: WebSocket,\n params: ConnectParams,\n ) => {\n try {\n if (this.#draining) {\n rehome(this.#lc, ws);\n return;\n }\n await this.#createConnection(ws, params);\n } finally {\n this.#releasePending(params.clientGroupID, params.generation);\n }\n };\n\n #releasePending(clientGroupID: string, generation: number | undefined): void {\n const remaining = (this.#pendingConnections.get(clientGroupID) ?? 1) - 1;\n if (remaining <= 0) {\n this.#pendingConnections.delete(clientGroupID);\n if (!this.#viewSyncers.hasService(clientGroupID)) {\n const gen =\n this.#clientGroupGenerations.get(clientGroupID) ?? generation;\n this.#parent.send<ClientGroupStatusMessage>([\n 'clientGroupStatus',\n {\n clientGroupID,\n active: false,\n ...(gen !== undefined ? {generation: gen} : {}),\n },\n ]);\n this.#clientGroupGenerations.delete(clientGroupID);\n }\n } else {\n this.#pendingConnections.set(clientGroupID, remaining);\n }\n }\n\n run() {\n return this.#stopped.promise;\n }\n\n /**\n * Graceful shutdown involves shutting down view syncers one at a time, pausing\n * for the duration of view syncer's hydration between each one. This paces the\n * disconnects to avoid creating a backlog of hydrations in the receiving server\n * when the clients reconnect.\n */\n async drain() {\n this.#draining = true;\n const start = Date.now();\n this.#lc.info?.(`draining ${this.#viewSyncers.size} view-syncers`);\n\n this.#drainCoordinator.drainNextIn(0);\n\n while (this.#viewSyncers.size) {\n await this.#drainCoordinator.forceDrainTimeout;\n\n // Pick an arbitrary view syncer to force drain.\n for (const vs of this.#viewSyncers.getServices()) {\n this.#lc.debug?.(`draining view-syncer ${vs.id} (forced)`);\n // When this drain or an elective drain completes, the forceDrainTimeout will\n // resolve after the next drain interval.\n void vs.stop();\n break;\n }\n }\n this.#lc.info?.(`finished draining (${Date.now() - start} ms)`);\n }\n\n stop() {\n clearInterval(this.#viewSyncerLagSampleInterval);\n clearInterval(this.#eligibilityLogInterval);\n this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"syncer.js","names":["ErrorKind.Unauthorized","ErrorOrigin.ZeroCache"],"sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import {pid} from 'node:process';\nimport type {LogContext} from '@rocicorp/logger';\nimport {WebSocket, WebSocketServer, type ServerOptions} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {resolver} from '../../../shared/src/resolver.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {\n isProtocolError,\n ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {resolveAuth, type Auth, type ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.ts';\nimport {\n getOrCreateCounter,\n getOrCreateGauge,\n getOrCreateNativeHistogram,\n getOrCreateUpDownCounter,\n} from '../observability/metrics.ts';\nimport {\n recordConnectionAttempted,\n recordConnectionSuccess,\n setActiveClientGroupsGetter,\n} from '../server/anonymous-otel-start.ts';\nimport type {Mutagen} from '../services/mutagen/mutagen.ts';\nimport type {Pusher} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {ServiceRunner} from '../services/runner.ts';\nimport type {\n ActivityBasedService,\n Service,\n SingletonService,\n} from '../services/service.ts';\nimport type {ConnectionContextManager} from '../services/view-syncer/connection-context-manager.ts';\nimport {\n DrainCoordinator,\n TARGET_UTILIZATION,\n} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport {clientGroupLogID} from '../types/client-group-log-id.ts';\nimport type {ClientGroupStatusMessage, Worker} from '../types/processes.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {installWebSocketReceiver} from '../types/websocket-handoff.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {Connection, sendError} from './connection.ts';\nimport {expiredAffinityCookieHeader, rehome} from './rehome.ts';\nimport {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type ReplicaReadyState = {\n readonly watermark: string;\n readonly replicaReadyTimeMs: number;\n};\n\nexport type ServingLagViewSyncer = Pick<\n ViewSyncer,\n 'createdAtMs' | 'servedVersion' | 'servingLagEligible'\n>;\n\nexport type ServingLagStats = {\n readonly activeClientGroups: number;\n readonly laggingClientGroups: number;\n readonly minMs: number;\n readonly p50Ms: number;\n readonly p75Ms: number;\n readonly p99Ms: number;\n readonly maxMs: number;\n};\n\ntype ServingLagDistribution = ServingLagStats & {\n readonly lagsMs: readonly number[];\n};\n\nexport type PipelineDedupViewSyncer = Pick<\n ViewSyncer,\n 'pipelineHashes' | 'clientSchemaKey'\n>;\n\nexport type PipelineDedupStats = {\n readonly clientTotal: number;\n readonly internalTotal: number;\n // (clientSchemaKey, transformationHash) -> occurrence count across client\n // groups, for client/custom queries.\n readonly clientHashes: ReadonlyMap<\n string,\n {count: number; queryName: string | undefined}\n >;\n readonly internalUnique: number;\n readonly clientSchemas: number;\n};\n\nexport function computePipelineDedupStats(\n viewSyncers: Iterable<PipelineDedupViewSyncer>,\n): PipelineDedupStats {\n let clientTotal = 0;\n let internalTotal = 0;\n const clientHashes = new Map<\n string,\n {count: number; queryName: string | undefined}\n >();\n const internalHashes = new Set<string>();\n const schemaKeys = new Set<string>();\n let vsIndex = 0;\n for (const vs of viewSyncers) {\n const schemaKey = vs.clientSchemaKey;\n if (schemaKey !== undefined) {\n schemaKeys.add(schemaKey);\n }\n // Without a client schema the pipeline's dedup identity is unknown, so key\n // it uniquely per view-syncer; otherwise schema-less pipelines from\n // unrelated client groups would collapse under a shared `undefined/<hash>`\n // key and overstate deduplication.\n const dedupSchemaKey = schemaKey ?? `#vs${vsIndex}`;\n vsIndex++;\n for (const {\n transformationHash,\n internal,\n queryName,\n } of vs.pipelineHashes()) {\n // Pipelines only share when both the client schema (which determines\n // row keys) and the transformed AST are identical.\n const key = `${dedupSchemaKey}/${transformationHash}`;\n if (internal) {\n internalTotal++;\n internalHashes.add(key);\n } else {\n clientTotal++;\n const entry = clientHashes.get(key);\n if (entry) {\n entry.count++;\n } else {\n clientHashes.set(key, {count: 1, queryName});\n }\n }\n }\n }\n return {\n clientTotal,\n internalTotal,\n clientHashes,\n internalUnique: internalHashes.size,\n clientSchemas: schemaKeys.size,\n };\n}\n\nexport const MAX_REPLICA_READY_STATES = 10_000;\nconst VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS = 60_000;\nconst SHARED_ADVANCE_ELIGIBILITY_LOG_INTERVAL_MS = 5 * 60_000;\n\nconst PROTOCOL_VERSION_ATTRIBUTE = 'protocol.version';\nconst FAILURE_REASON_ATTRIBUTE = 'reason';\n\nfunction boundReplicaReadyStates(\n replicaReadyStates: ReplicaReadyState[],\n): void {\n if (replicaReadyStates.length > MAX_REPLICA_READY_STATES) {\n replicaReadyStates.splice(\n 0,\n replicaReadyStates.length - MAX_REPLICA_READY_STATES,\n );\n }\n}\n\nfunction pruneReplicaReadyStates(\n replicaReadyStates: ReplicaReadyState[],\n firstNeededIndex: number,\n): void {\n if (firstNeededIndex > 0) {\n replicaReadyStates.splice(0, firstNeededIndex);\n }\n\n boundReplicaReadyStates(replicaReadyStates);\n}\n\nfunction lowerBoundReplicaReadyTimeMs(\n replicaReadyStates: readonly ReplicaReadyState[],\n replicaReadyTimeMs: number,\n): number {\n let low = 0;\n let high = replicaReadyStates.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (replicaReadyStates[mid].replicaReadyTimeMs < replicaReadyTimeMs) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n}\n\nfunction upperBoundWatermark(\n replicaReadyStates: readonly ReplicaReadyState[],\n watermark: string,\n): number {\n let low = 0;\n let high = replicaReadyStates.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (replicaReadyStates[mid].watermark <= watermark) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n}\n\nfunction findFirstUnservedIndex(\n replicaReadyStates: readonly ReplicaReadyState[],\n viewSyncer: ServingLagViewSyncer,\n): number {\n const firstReadyAfterCreation = lowerBoundReplicaReadyTimeMs(\n replicaReadyStates,\n viewSyncer.createdAtMs,\n );\n const firstAfterServedVersion =\n viewSyncer.servedVersion === null\n ? 0\n : upperBoundWatermark(replicaReadyStates, viewSyncer.servedVersion);\n\n const firstUnservedIndex = Math.max(\n firstReadyAfterCreation,\n firstAfterServedVersion,\n );\n return firstUnservedIndex < replicaReadyStates.length\n ? firstUnservedIndex\n : -1;\n}\n\nfunction percentileNearestRank(\n sortedValues: readonly number[],\n percentile: number,\n): number {\n if (sortedValues.length === 0) {\n return 0;\n }\n const index = Math.min(\n sortedValues.length - 1,\n Math.max(0, Math.ceil((percentile / 100) * sortedValues.length) - 1),\n );\n return sortedValues[index];\n}\n\nfunction computeServingLagDistributionMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): ServingLagDistribution {\n const lags: number[] = [];\n let laggingClientGroups = 0;\n let firstNeededIndex = replicaReadyStates.length;\n\n for (const viewSyncer of viewSyncers) {\n if (!viewSyncer.servingLagEligible) {\n continue;\n }\n\n const firstUnservedIndex = findFirstUnservedIndex(\n replicaReadyStates,\n viewSyncer,\n );\n if (firstUnservedIndex === -1) {\n lags.push(0);\n continue;\n }\n\n firstNeededIndex = Math.min(firstNeededIndex, firstUnservedIndex);\n const lagMs = Math.max(\n 0,\n now - replicaReadyStates[firstUnservedIndex].replicaReadyTimeMs,\n );\n lags.push(lagMs);\n if (lagMs > 0) {\n laggingClientGroups++;\n }\n }\n\n pruneReplicaReadyStates(replicaReadyStates, firstNeededIndex);\n\n lags.sort((a, b) => a - b);\n return {\n activeClientGroups: lags.length,\n laggingClientGroups,\n // The percentile fields are for the legacy serving_lag_stats gauge.\n // The native view_syncer_lag histogram records lagsMs and computes\n // percentiles in Prometheus/AMP.\n minMs: lags[0] ?? 0,\n p50Ms: percentileNearestRank(lags, 50),\n p75Ms: percentileNearestRank(lags, 75),\n p99Ms: percentileNearestRank(lags, 99),\n maxMs: lags.at(-1) ?? 0,\n lagsMs: lags,\n };\n}\n\nexport function computeServingLagStatsMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): ServingLagStats {\n const distribution = computeServingLagDistributionMs(\n now,\n replicaReadyStates,\n viewSyncers,\n );\n return {\n activeClientGroups: distribution.activeClientGroups,\n laggingClientGroups: distribution.laggingClientGroups,\n minMs: distribution.minMs,\n p50Ms: distribution.p50Ms,\n p75Ms: distribution.p75Ms,\n p99Ms: distribution.p99Ms,\n maxMs: distribution.maxMs,\n };\n}\n\nexport function computeMaxServingLagMs(\n now: number,\n replicaReadyStates: ReplicaReadyState[],\n viewSyncers: Iterable<ServingLagViewSyncer>,\n): number {\n return computeServingLagStatsMs(now, replicaReadyStates, viewSyncers).maxMs;\n}\n\nfunction getWebSocketServerOptions(config: ZeroConfig): ServerOptions {\n const options: ServerOptions = {\n noServer: true,\n maxPayload: config.websocketMaxPayloadBytes,\n };\n\n if (config.websocketCompression) {\n options.perMessageDeflate = true;\n\n if (config.websocketCompressionOptions) {\n try {\n const compressionOptions = JSON.parse(\n config.websocketCompressionOptions,\n );\n options.perMessageDeflate = compressionOptions;\n } catch (e) {\n throw new Error(\n `Failed to parse ZERO_WEBSOCKET_COMPRESSION_OPTIONS: ${String(e)}. Expected valid JSON.`,\n );\n }\n }\n }\n\n return options;\n}\n\n/**\n * The Syncer worker receives websocket handoffs for \"/sync\" connections\n * from the Dispatcher in the main thread, and creates websocket\n * {@link Connection}s with a corresponding {@link ViewSyncer}, {@link Mutagen},\n * and {@link Subscription} to version notifications from the Replicator\n * worker.\n */\nexport class Syncer implements SingletonService {\n readonly id = `syncer-${pid}`;\n readonly #lc: LogContext;\n readonly #viewSyncers: ServiceRunner<ViewSyncer & ActivityBasedService>;\n readonly #mutagens: ServiceRunner<Mutagen & Service> | undefined;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #pendingConnections = new Map<string, number>();\n readonly #clientGroupGenerations = new Map<string, number>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n #draining = false;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n readonly #validateLegacyJWT: ValidateLegacyJWT | undefined;\n readonly #replicaReadyStates: ReplicaReadyState[] = [];\n readonly #webSocketOpenConnections = getOrCreateUpDownCounter(\n 'sync',\n 'websocket.open_connections',\n 'Open client WebSocket connections.',\n );\n readonly #webSocketConnectionAttempts = getOrCreateCounter(\n 'sync',\n 'websocket.connection_attempts',\n 'Client WebSocket connection attempts.',\n );\n readonly #webSocketConnectionSuccesses = getOrCreateCounter(\n 'sync',\n 'websocket.connection_successes',\n 'Client WebSocket connections successfully initialized.',\n );\n readonly #webSocketConnectionFailures = getOrCreateCounter(\n 'sync',\n 'websocket.connection_failures',\n 'Client WebSocket connection attempts that failed before initialization.',\n );\n readonly #viewSyncerLag = getOrCreateNativeHistogram(\n 'sync',\n 'view_syncer_lag',\n {\n description:\n 'Lag from replica-ready change to ViewSyncer output for active ' +\n 'client groups. A change is output after IVM advancement, CVR flush, ' +\n 'and pokeEnd.',\n unit: 's',\n },\n );\n #servingLagDistributionCache: ServingLagDistribution | undefined;\n #servingLagDistributionCacheClearQueued = false;\n #viewSyncerLagSampleInterval: ReturnType<typeof setInterval> | undefined;\n #eligibilityLogInterval: ReturnType<typeof setInterval> | undefined;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: ((id: string) => Mutagen & Service) | undefined,\n pusherFactory:\n | ((\n id: string,\n connContextManager: ConnectionContextManager,\n ) => Pusher & Service)\n | undefined,\n parent: Worker,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n ) {\n this.#config = config;\n this.#validateLegacyJWT = validateLegacyJWT;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent, state =>\n this.#recordReplicaReadyState(state),\n );\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#parent = parent;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator),\n v => v.keepalive(),\n id => {\n if ((this.#pendingConnections.get(id) ?? 0) === 0) {\n const generation = this.#clientGroupGenerations.get(id);\n this.#parent.send<ClientGroupStatusMessage>([\n 'clientGroupStatus',\n {\n clientGroupID: id,\n active: false,\n ...(generation !== undefined ? {generation} : {}),\n },\n ]);\n this.#clientGroupGenerations.delete(id);\n }\n },\n );\n if (mutagenFactory) {\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n }\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(\n lc,\n id =>\n pusherFactory(\n id,\n this.#viewSyncers.getService(id).connContextManager,\n ),\n p => p.hasRefs(),\n );\n }\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n const expiredCookie = expiredAffinityCookieHeader(config);\n if (expiredCookie) {\n // ws exposes handshake headers through this event. Set-Cookie must be\n // part of the 101 response, before receiveConnection sends Rehome.\n this.#wss.on('headers', headers => {\n if (this.#draining) {\n headers.push(expiredCookie);\n }\n });\n }\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#receiveConnection,\n this.#parent,\n params => {\n this.#releasePending(params.clientGroupID, params.generation);\n },\n params => {\n this.#pendingConnections.set(\n params.clientGroupID,\n (this.#pendingConnections.get(params.clientGroupID) ?? 0) + 1,\n );\n if (params.generation !== undefined) {\n const current = this.#clientGroupGenerations.get(\n params.clientGroupID,\n );\n if (current === undefined || params.generation > current) {\n this.#clientGroupGenerations.set(\n params.clientGroupID,\n params.generation,\n );\n }\n }\n },\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n\n getOrCreateGauge(\n 'sync',\n 'active-client-groups',\n 'Number of active client groups',\n ).addCallback(result => result.observe(this.#viewSyncers.size));\n\n getOrCreateGauge(\n 'sync',\n 'queries',\n 'Active queries (pipelines) across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.queryCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge('sync', 'drain-time', {\n description:\n 'How long a graceful shutdown would take to release every client ' +\n 'group, pacing their disconnects by their hydration times as drain() ' +\n 'does. A pod drains its workers in parallel, so its drain takes the ' +\n 'longest of theirs.',\n unit: 's',\n }).addCallback(result => {\n let hydrationTimeMs = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n hydrationTimeMs += vs.hydrationTimeMs;\n }\n result.observe(hydrationTimeMs / TARGET_UTILIZATION / 1000);\n });\n\n getOrCreateGauge(\n 'sync',\n 'rows',\n 'Tracked rows across all client groups',\n ).addCallback(result => {\n let total = 0;\n for (const vs of this.#viewSyncers.getServices()) {\n total += vs.rowCount;\n }\n result.observe(total);\n });\n\n getOrCreateGauge('sync', 'serving_lag', {\n description:\n 'Maximum time active ViewSyncer client groups have had unserved ' +\n 'replica changes. A change is served after IVM advancement, CVR flush, ' +\n 'and pokeEnd.',\n unit: 'millisecond',\n }).addCallback(result => {\n result.observe(this.#computeServingLagDistribution().maxMs);\n });\n\n getOrCreateGauge('sync', 'serving_lag_stats', {\n description:\n 'Distribution of time active ViewSyncer client groups have had ' +\n 'unserved replica changes. A change is served after IVM advancement, ' +\n 'CVR flush, and pokeEnd.',\n unit: 'millisecond',\n }).addCallback(result => {\n const stats = this.#computeServingLagDistribution();\n result.observe(stats.minMs, {stat: 'min'});\n result.observe(stats.p50Ms, {stat: 'p50'});\n result.observe(stats.p75Ms, {stat: 'p75'});\n result.observe(stats.p99Ms, {stat: 'p99'});\n result.observe(stats.maxMs, {stat: 'max'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'serving_lagging_client_groups',\n 'Number of active client groups with unserved replica changes',\n ).addCallback(result => {\n result.observe(this.#computeServingLagDistribution().laggingClientGroups);\n });\n\n getOrCreateGauge(\n 'sync',\n 'pipelines_total',\n 'Query pipelines across all client groups on this worker, by query ' +\n 'type. The ratio to sync.pipelines_unique is the dedup factor ' +\n 'available to shared pipeline advancement.',\n ).addCallback(result => {\n const stats = this.#computePipelineDedupStats();\n result.observe(stats.clientTotal, {type: 'client'});\n result.observe(stats.internalTotal, {type: 'internal'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'pipelines_unique',\n 'Distinct (clientSchema, transformationHash) pipelines across all ' +\n 'client groups on this worker, by query type',\n ).addCallback(result => {\n const stats = this.#computePipelineDedupStats();\n result.observe(stats.clientHashes.size, {type: 'client'});\n result.observe(stats.internalUnique, {type: 'internal'});\n });\n\n getOrCreateGauge(\n 'sync',\n 'client_schemas',\n 'Distinct client schemas across all client groups on this worker',\n ).addCallback(result => {\n result.observe(this.#computePipelineDedupStats().clientSchemas);\n });\n\n this.#viewSyncerLagSampleInterval = setInterval(\n () => this.#recordViewSyncerLagSamples(),\n VIEW_SYNCER_LAG_SAMPLE_INTERVAL_MS,\n );\n this.#viewSyncerLagSampleInterval.unref?.();\n\n this.#eligibilityLogInterval = setInterval(\n () => this.#logSharedAdvanceEligibility(),\n SHARED_ADVANCE_ELIGIBILITY_LOG_INTERVAL_MS,\n );\n this.#eligibilityLogInterval.unref?.();\n }\n\n #computeServingLagDistribution(): ServingLagDistribution {\n if (this.#servingLagDistributionCache) {\n return this.#servingLagDistributionCache;\n }\n\n const distribution = computeServingLagDistributionMs(\n Date.now(),\n this.#replicaReadyStates,\n this.#viewSyncers.getServices(),\n );\n this.#servingLagDistributionCache = distribution;\n if (!this.#servingLagDistributionCacheClearQueued) {\n this.#servingLagDistributionCacheClearQueued = true;\n queueMicrotask(() => {\n this.#servingLagDistributionCache = undefined;\n this.#servingLagDistributionCacheClearQueued = false;\n });\n }\n return distribution;\n }\n\n #recordViewSyncerLagSamples(): void {\n const {lagsMs} = this.#computeServingLagDistribution();\n for (const lagMs of lagsMs) {\n this.#viewSyncerLag.recordMs(lagMs);\n }\n }\n\n #computePipelineDedupStats(): PipelineDedupStats {\n return computePipelineDedupStats(this.#viewSyncers.getServices());\n }\n\n #logSharedAdvanceEligibility(): void {\n const stats = this.#computePipelineDedupStats();\n if (stats.clientTotal === 0) {\n return;\n }\n const unique = stats.clientHashes.size;\n const topDuplicated = [...stats.clientHashes.entries()]\n .filter(([, {count}]) => count > 1)\n .sort((a, b) => b[1].count - a[1].count)\n .slice(0, 10)\n .map(([key, {count, queryName}]) => ({key, count, queryName}));\n this.#lc.info?.('shared-advance-eligibility', {\n clientPipelines: stats.clientTotal,\n uniqueClientPipelines: unique,\n dedupFactor: Number((stats.clientTotal / Math.max(unique, 1)).toFixed(2)),\n internalPipelines: stats.internalTotal,\n clientSchemas: stats.clientSchemas,\n topDuplicated,\n });\n }\n\n #recordReplicaReadyState(state: ReplicaState): void {\n if (\n state.watermark === undefined ||\n state.replicaReadyTimeMs === undefined\n ) {\n return;\n }\n const last = this.#replicaReadyStates.at(-1);\n if (last && last.watermark >= state.watermark) {\n return;\n }\n this.#replicaReadyStates.push({\n watermark: state.watermark,\n replicaReadyTimeMs: state.replicaReadyTimeMs,\n });\n if (this.#viewSyncers.size === 0) {\n this.#replicaReadyStates.length = 0;\n return;\n }\n boundReplicaReadyStates(this.#replicaReadyStates);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\n const metricAttributes = {\n [PROTOCOL_VERSION_ATTRIBUTE]: params.protocolVersion,\n };\n let connectionResultRecorded = false;\n const recordConnectionFailure = (reason: string) => {\n if (connectionResultRecorded) {\n return;\n }\n connectionResultRecorded = true;\n this.#webSocketConnectionFailures.add(1, {\n ...metricAttributes,\n [FAILURE_REASON_ATTRIBUTE]: reason,\n });\n };\n const recordConnectionSuccessMetric = () => {\n if (connectionResultRecorded) {\n return;\n }\n connectionResultRecorded = true;\n this.#webSocketConnectionSuccesses.add(1, metricAttributes);\n };\n\n this.#webSocketConnectionAttempts.add(1, metricAttributes);\n this.#webSocketOpenConnections.add(1, metricAttributes);\n ws.once('close', () => {\n this.#webSocketOpenConnections.add(-1, metricAttributes);\n });\n\n this.#lc.debug?.(\n 'creating connection',\n clientGroupLogID(params.clientGroupID),\n params.clientID,\n );\n recordConnectionAttempted();\n const {clientID, clientGroupID, auth, userID} = params;\n const hasProvidedAuth = auth !== undefined && auth !== '';\n const incomingUserID = userID ?? null;\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n const hasPushOrMutate =\n this.#config?.push?.url !== undefined ||\n this.#config?.mutate?.url !== undefined;\n const hasQueries =\n this.#config?.query?.url !== undefined ||\n this.#config?.getQueries?.url !== undefined;\n const hasCustomEndpoints = hasPushOrMutate && hasQueries;\n\n // More than one token option is a misconfiguration under which no\n // validator is built, so it is refused whether or not a token was sent.\n // A token otherwise requires exactly one token option, or custom\n // mutations & queries to be enabled instead.\n if (\n tokenOptions.length > 1 ||\n (hasProvidedAuth && tokenOptions.length === 0 && !hasCustomEndpoints)\n ) {\n recordConnectionFailure('configuration');\n throw new Error(\n 'Exactly one of jwk, secret, or jwksUrl must be set in order to verify tokens but actually the following were set: ' +\n JSON.stringify(tokenOptions) +\n '. You may also set both ZERO_MUTATE_URL and ZERO_QUERY_URL to enable custom mutations and queries without passing token verification options.',\n );\n }\n\n let initialAuth: Auth | undefined;\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS.\n try {\n initialAuth = await resolveAuth(\n this.#lc\n .withContext('clientGroupID', clientGroupLogID(clientGroupID))\n .withContext('clientID', clientID),\n // no previous auth, since this is a new connection, and resolveAuth is\n // connection scoped, not client group scoped\n undefined,\n incomingUserID,\n auth,\n this.#validateLegacyJWT,\n );\n } catch (e) {\n if (isProtocolError(e)) {\n this.#lc.warn?.(\n 'Rejecting sync connection during initial auth resolution',\n {\n clientGroupID: clientGroupLogID(clientGroupID),\n clientID,\n incomingUserID,\n hasProvidedAuth,\n errorKind: e.message,\n },\n );\n recordConnectionFailure('auth');\n sendError(this.#lc, ws, e.errorBody);\n ws.close(3000, e.errorBody.message);\n return;\n }\n recordConnectionFailure('internal');\n throw e;\n }\n\n // Resolving auth can take a while (e.g. a remote JWKS fetch), during\n // which the client may have disconnected. A Connection created for a\n // closed socket never receives the 'close' event and is thus never\n // cleaned up, leaking its timer, its registrations in the connection\n // context manager and its refs on the mutagen and pusher services.\n if (ws.readyState !== WebSocket.OPEN) {\n this.#lc.debug?.('websocket closed while resolving auth', {\n clientGroupID: clientGroupLogID(clientGroupID),\n clientID,\n readyState: ws.readyState,\n });\n recordConnectionFailure('closed_during_auth');\n return;\n }\n\n // Authentication may yield while a drain starts. Do not create services\n // or replace an existing client connection after that transition.\n if (this.#draining) {\n recordConnectionFailure('draining');\n rehome(this.#lc, ws);\n return;\n }\n\n const viewSyncer = this.#viewSyncers.getService(clientGroupID);\n const connContextManager = viewSyncer.connContextManager;\n const group = connContextManager.getGroupState();\n\n // TODO(0xcadams): we only check for user ID mismatch here if the group is\n // already validated. This prevents wrong-user reconnects from evicting a\n // healthy connection, but it does not protect against same-user reconnects\n // with an invalid opaque token. The long-term fix is to keep the replacement\n // connection pending until its auth is fully validated, and only then replace\n // the existing socket.\n if (\n group.pinnedUser !== undefined &&\n group.pinnedUser.id !== incomingUserID\n ) {\n const error = new ProtocolError({\n kind: ErrorKind.Unauthorized,\n message:\n 'Client groups are pinned to a single userID. Connection userID does not match existing client group userID.',\n origin: ErrorOrigin.ZeroCache,\n });\n recordConnectionFailure('user_mismatch');\n sendError(this.#lc, ws, error.errorBody);\n ws.close(3000, error.message);\n return;\n }\n\n // Check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n if (existing.wsID === params.wsID) {\n // Clients generate a new wsID per socket, so this means the same\n // connect request was delivered twice (e.g. duplicated upstream).\n this.#lc.warn?.(\n `duplicate connection for client ${clientID} with the same wsID ${params.wsID}, closing existing connection`,\n {clientGroupID: clientGroupLogID(clientGroupID)},\n );\n }\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\n }\n\n connContextManager.registerConnection(\n {clientID, wsID: params.wsID},\n params,\n initialAuth,\n );\n\n const mutagen = this.#mutagens?.getService(clientGroupID);\n const pusher = this.#pushers?.getService(clientGroupID);\n // a new connection is using the mutagen and pusher. Bump their ref counts.\n mutagen?.ref();\n pusher?.ref();\n\n let connection: Connection;\n try {\n connection = new Connection(\n this.#lc,\n params,\n ws,\n this.#config.allowLegacyQueries,\n new SyncerWsMessageHandler(\n this.#lc,\n params,\n connContextManager,\n viewSyncer,\n mutagen,\n pusher,\n ),\n () => {\n connContextManager.closeConnection({\n clientID,\n wsID: params.wsID,\n });\n if (this.#connections.get(clientID) === connection) {\n this.#connections.delete(clientID);\n }\n // Connection is closed. We can unref the mutagen and pusher.\n // If their ref counts are zero, they will stop themselves and set themselves invalid.\n mutagen?.unref();\n pusher?.unref();\n },\n );\n } catch (e) {\n recordConnectionFailure('internal');\n connContextManager.closeConnection({clientID, wsID: params.wsID});\n mutagen?.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n if (connection.init()) {\n recordConnectionSuccessMetric();\n recordConnectionSuccess();\n } else {\n recordConnectionFailure('protocol_version');\n }\n\n if (params.initConnectionMsg) {\n this.#lc.debug?.(\n 'handling init connection message from sec header',\n clientGroupLogID(params.clientGroupID),\n params.clientID,\n );\n await connection.handleInitConnection(\n JSON.stringify(params.initConnectionMsg),\n );\n }\n };\n\n readonly #receiveConnection = async (\n ws: WebSocket,\n params: ConnectParams,\n ) => {\n try {\n if (this.#draining) {\n rehome(this.#lc, ws);\n return;\n }\n await this.#createConnection(ws, params);\n } finally {\n this.#releasePending(params.clientGroupID, params.generation);\n }\n };\n\n #releasePending(clientGroupID: string, generation: number | undefined): void {\n const remaining = (this.#pendingConnections.get(clientGroupID) ?? 1) - 1;\n if (remaining <= 0) {\n this.#pendingConnections.delete(clientGroupID);\n if (!this.#viewSyncers.hasService(clientGroupID)) {\n const gen =\n this.#clientGroupGenerations.get(clientGroupID) ?? generation;\n this.#parent.send<ClientGroupStatusMessage>([\n 'clientGroupStatus',\n {\n clientGroupID,\n active: false,\n ...(gen !== undefined ? {generation: gen} : {}),\n },\n ]);\n this.#clientGroupGenerations.delete(clientGroupID);\n }\n } else {\n this.#pendingConnections.set(clientGroupID, remaining);\n }\n }\n\n run() {\n return this.#stopped.promise;\n }\n\n /**\n * Graceful shutdown involves shutting down view syncers one at a time, pausing\n * for the duration of view syncer's hydration between each one. This paces the\n * disconnects to avoid creating a backlog of hydrations in the receiving server\n * when the clients reconnect.\n */\n async drain() {\n this.#draining = true;\n const start = Date.now();\n this.#lc.info?.(`draining ${this.#viewSyncers.size} view-syncers`);\n\n this.#drainCoordinator.drainNextIn(0);\n\n while (this.#viewSyncers.size) {\n await this.#drainCoordinator.forceDrainTimeout;\n\n // Pick an arbitrary view syncer to force drain.\n for (const vs of this.#viewSyncers.getServices()) {\n this.#lc.debug?.(\n `draining view-syncer ${clientGroupLogID(vs.id)} (forced)`,\n );\n // When this drain or an elective drain completes, the forceDrainTimeout will\n // resolve after the next drain interval.\n void vs.stop();\n break;\n }\n }\n this.#lc.info?.(`finished draining (${Date.now() - start} ms)`);\n }\n\n stop() {\n clearInterval(this.#viewSyncerLagSampleInterval);\n clearInterval(this.#eligibilityLogInterval);\n this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n}\n"],"mappings":"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"}
|
|
@@ -37,6 +37,10 @@ export declare class Inspector {
|
|
|
37
37
|
* environments. Call this first from Node contexts to establish the
|
|
38
38
|
* session.
|
|
39
39
|
*
|
|
40
|
+
* The server authenticates each connection separately. An accepted
|
|
41
|
+
* password is remembered by this inspector and presented again when the
|
|
42
|
+
* connection is re-established.
|
|
43
|
+
*
|
|
40
44
|
* Returns `true` if the password is accepted (or the server runs in a
|
|
41
45
|
* development mode that bypasses the check), `false` otherwise.
|
|
42
46
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../../../../../zero-client/src/client/inspector/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,sCAAsC,CAAC;AAC9D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,6CAA6C,CAAC;AAErF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6CAA6C,CAAC;AAC/E,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AACjE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,EAEV,iBAAiB,EACjB,OAAO,EAEP,GAAG,EACJ,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAC,iBAAiB,EAAC,CAAC;AAGhC,MAAM,MAAM,IAAI,GAAG,cAAc,qBAAqB,CAAC,CAAC;AAExD,qBAAa,SAAS;;IAMpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAElC,YACE,GAAG,EAAE,GAAG,EACR,iBAAiB,EAAE,iBAAiB,EACpC,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAgCpC;IAEK,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAEhC;IAEK,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAEjC;IAEK,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAI5C;IAEK,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErC;IAEK,YAAY,CAChB,KAAK,EAAE,QAAQ,EACf,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAM7B;IAED;;;;OAIG;IACG,gBAAgB,CACpB,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAM7B;IAED;;;OAGG;IACG,iBAAiB,CACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,CAAC,iBAAiB,CAAC,EACtC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAO7B;IAED
|
|
1
|
+
{"version":3,"file":"inspector.d.ts","sourceRoot":"","sources":["../../../../../../zero-client/src/client/inspector/inspector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,sCAAsC,CAAC;AAC9D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,6CAA6C,CAAC;AAErF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6CAA6C,CAAC;AAC/E,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AACjE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,EAEV,iBAAiB,EACjB,OAAO,EAEP,GAAG,EACJ,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAC,iBAAiB,EAAC,CAAC;AAGhC,MAAM,MAAM,IAAI,GAAG,cAAc,qBAAqB,CAAC,CAAC;AAExD,qBAAa,SAAS;;IAMpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAElC,YACE,GAAG,EAAE,GAAG,EACR,iBAAiB,EAAE,iBAAiB,EACpC,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAgCpC;IAEK,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAEhC;IAEK,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAEjC;IAEK,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAI5C;IAEK,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErC;IAEK,YAAY,CAChB,KAAK,EAAE,QAAQ,EACf,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAM7B;IAED;;;;OAIG;IACG,gBAAgB,CACpB,GAAG,EAAE,GAAG,EACR,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAM7B;IAED;;;OAGG;IACG,iBAAiB,CACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,CAAC,iBAAiB,CAAC,EACtC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAO7B;IAED;;;;;;;;;;;;;OAaG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAErD;IAED;;OAEG;IACH,mBAAmB,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAExD;CACF"}
|
|
@@ -71,6 +71,10 @@ var Inspector = class {
|
|
|
71
71
|
* environments. Call this first from Node contexts to establish the
|
|
72
72
|
* session.
|
|
73
73
|
*
|
|
74
|
+
* The server authenticates each connection separately. An accepted
|
|
75
|
+
* password is remembered by this inspector and presented again when the
|
|
76
|
+
* connection is re-established.
|
|
77
|
+
*
|
|
74
78
|
* Returns `true` if the password is accepted (or the server runs in a
|
|
75
79
|
* development mode that bypasses the check), `false` otherwise.
|
|
76
80
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspector.js","names":[],"sources":["../../../../../../zero-client/src/client/inspector/inspector.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../../../shared/src/json.ts';\nimport type {\n AnalyzeQueryResult,\n PlanDebugEventJSON,\n} from '../../../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../../../zero-protocol/src/ast.ts';\nimport type {AnalyzeQueryOptions} from '../../../../zero-protocol/src/inspect-up.ts';\nimport {formatPlannerEvents} from '../../../../zql/src/planner/planner-debug.ts';\nimport type {QueryDelegate} from '../../../../zql/src/query/query-delegate.ts';\nimport type {AnyQuery} from '../../../../zql/src/query/query.ts';\nimport type {ClientGroup} from './client-group.ts';\nimport {Client} from './client.ts';\nimport type {\n ExtendedInspectorDelegate,\n InspectorDelegate,\n Metrics,\n QueryClientMetrics,\n Rep,\n} from './lazy-inspector.ts';\n\nexport type {InspectorDelegate};\n\n// oxlint-disable-next-line consistent-type-imports\nexport type Lazy = typeof import('./lazy-inspector.ts');\n\nexport class Inspector {\n readonly #delegate: ExtendedInspectorDelegate;\n readonly #deletedQueries = new Map<\n string,\n {readonly ast: AST; readonly metrics: QueryClientMetrics}\n >();\n readonly client: Client;\n readonly clientGroup: ClientGroup;\n\n constructor(\n rep: Rep,\n inspectorDelegate: InspectorDelegate,\n queryDelegate: QueryDelegate,\n getSocket: () => Promise<WebSocket>,\n ) {\n const deletedQueries = this.#deletedQueries;\n inspectorDelegate.setQueryEvictedCallback((hash, ast, metrics) => {\n deletedQueries.set(hash, {ast, metrics});\n });\n this.#delegate = {\n getQueryMetrics(hash) {\n return (\n inspectorDelegate.getQueryMetrics(hash) ??\n deletedQueries.get(hash)?.metrics\n );\n },\n getAST(queryID) {\n return (\n inspectorDelegate.getAST(queryID) ?? deletedQueries.get(queryID)?.ast\n );\n },\n mapClientASTToServer:\n inspectorDelegate.mapClientASTToServer.bind(inspectorDelegate),\n get metrics() {\n return inspectorDelegate.metrics;\n },\n setQueryEvictedCallback() {},\n queryDelegate,\n rep,\n getSocket,\n lazy: import('./lazy-inspector.ts'),\n };\n\n this.client = new Client(this.#delegate, rep.clientID, rep.clientGroupID);\n this.clientGroup = this.client.clientGroup;\n }\n\n async metrics(): Promise<Metrics> {\n return (await this.#delegate.lazy).inspectorMetrics(this.#delegate);\n }\n\n async clients(): Promise<Client[]> {\n return (await this.#delegate.lazy).inspectorClients(this.#delegate);\n }\n\n async clientsWithQueries(): Promise<Client[]> {\n return (await this.#delegate.lazy).inspectorClientsWithQueries(\n this.#delegate,\n );\n }\n\n async serverVersion(): Promise<string> {\n return (await this.#delegate.lazy).serverVersion(this.#delegate);\n }\n\n async analyzeQuery(\n query: AnyQuery,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeQuery(\n this.#delegate,\n query,\n options,\n );\n }\n\n /**\n * Analyze a query specified by a server-side AST. Unlike {@link analyzeQuery}\n * the AST is sent to the server verbatim with no client-to-server name\n * mapping; callers should provide an AST already in the server shape.\n */\n async analyzeServerAST(\n ast: AST,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeServerAST(\n this.#delegate,\n ast,\n options,\n );\n }\n\n /**\n * Analyze a server-registered named (custom) query. The server resolves\n * the name and args to an AST using its registered custom-query handler.\n */\n async analyzeNamedQuery(\n name: string,\n args: ReadonlyArray<ReadonlyJSONValue>,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeNamedQuery(\n this.#delegate,\n name,\n args,\n options,\n );\n }\n\n /**\n * Authenticate with the server's admin password. Other inspector RPCs\n * (e.g. {@link analyzeQuery}) fall back to an interactive HTML password\n * prompt when authentication is needed, which is unavailable in non-DOM\n * environments. Call this first from Node contexts to establish the\n * session.\n *\n * Returns `true` if the password is accepted (or the server runs in a\n * development mode that bypasses the check), `false` otherwise.\n */\n async authenticate(password: string): Promise<boolean> {\n return (await this.#delegate.lazy).authenticate(this.#delegate, password);\n }\n\n /**\n * Format planner debug events as a human-readable string.\n */\n formatPlannerEvents(events: PlanDebugEventJSON[]): string {\n return formatPlannerEvents(events);\n }\n}\n"],"mappings":";;;AAyBA,IAAa,YAAb,MAAuB;CACrB;CACA,kCAA2B,IAAI,IAG7B;CACF;CACA;CAEA,YACE,KACA,mBACA,eACA,WACA;EACA,MAAM,iBAAiB,KAAK;EAC5B,kBAAkB,yBAAyB,MAAM,KAAK,YAAY;GAChE,eAAe,IAAI,MAAM;IAAC;IAAK;GAAO,CAAC;EACzC,CAAC;EACD,KAAK,YAAY;GACf,gBAAgB,MAAM;IACpB,OACE,kBAAkB,gBAAgB,IAAI,KACtC,eAAe,IAAI,IAAI,CAAC,EAAE;GAE9B;GACA,OAAO,SAAS;IACd,OACE,kBAAkB,OAAO,OAAO,KAAK,eAAe,IAAI,OAAO,CAAC,EAAE;GAEtE;GACA,sBACE,kBAAkB,qBAAqB,KAAK,iBAAiB;GAC/D,IAAI,UAAU;IACZ,OAAO,kBAAkB;GAC3B;GACA,0BAA0B,CAAC;GAC3B;GACA;GACA;GACA,MAAM,OAAO;EACf;EAEA,KAAK,SAAS,IAAI,OAAO,KAAK,WAAW,IAAI,UAAU,IAAI,aAAa;EACxE,KAAK,cAAc,KAAK,OAAO;CACjC;CAEA,MAAM,UAA4B;EAChC,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,iBAAiB,KAAK,SAAS;CACpE;CAEA,MAAM,UAA6B;EACjC,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,iBAAiB,KAAK,SAAS;CACpE;CAEA,MAAM,qBAAwC;EAC5C,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,4BACjC,KAAK,SACP;CACF;CAEA,MAAM,gBAAiC;EACrC,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,cAAc,KAAK,SAAS;CACjE;CAEA,MAAM,aACJ,OACA,SAC6B;EAC7B,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,aACjC,KAAK,WACL,OACA,OACF;CACF;;;;;;CAOA,MAAM,iBACJ,KACA,SAC6B;EAC7B,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,iBACjC,KAAK,WACL,KACA,OACF;CACF;;;;;CAMA,MAAM,kBACJ,MACA,MACA,SAC6B;EAC7B,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,kBACjC,KAAK,WACL,MACA,MACA,OACF;CACF
|
|
1
|
+
{"version":3,"file":"inspector.js","names":[],"sources":["../../../../../../zero-client/src/client/inspector/inspector.ts"],"sourcesContent":["import type {ReadonlyJSONValue} from '../../../../shared/src/json.ts';\nimport type {\n AnalyzeQueryResult,\n PlanDebugEventJSON,\n} from '../../../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../../../zero-protocol/src/ast.ts';\nimport type {AnalyzeQueryOptions} from '../../../../zero-protocol/src/inspect-up.ts';\nimport {formatPlannerEvents} from '../../../../zql/src/planner/planner-debug.ts';\nimport type {QueryDelegate} from '../../../../zql/src/query/query-delegate.ts';\nimport type {AnyQuery} from '../../../../zql/src/query/query.ts';\nimport type {ClientGroup} from './client-group.ts';\nimport {Client} from './client.ts';\nimport type {\n ExtendedInspectorDelegate,\n InspectorDelegate,\n Metrics,\n QueryClientMetrics,\n Rep,\n} from './lazy-inspector.ts';\n\nexport type {InspectorDelegate};\n\n// oxlint-disable-next-line consistent-type-imports\nexport type Lazy = typeof import('./lazy-inspector.ts');\n\nexport class Inspector {\n readonly #delegate: ExtendedInspectorDelegate;\n readonly #deletedQueries = new Map<\n string,\n {readonly ast: AST; readonly metrics: QueryClientMetrics}\n >();\n readonly client: Client;\n readonly clientGroup: ClientGroup;\n\n constructor(\n rep: Rep,\n inspectorDelegate: InspectorDelegate,\n queryDelegate: QueryDelegate,\n getSocket: () => Promise<WebSocket>,\n ) {\n const deletedQueries = this.#deletedQueries;\n inspectorDelegate.setQueryEvictedCallback((hash, ast, metrics) => {\n deletedQueries.set(hash, {ast, metrics});\n });\n this.#delegate = {\n getQueryMetrics(hash) {\n return (\n inspectorDelegate.getQueryMetrics(hash) ??\n deletedQueries.get(hash)?.metrics\n );\n },\n getAST(queryID) {\n return (\n inspectorDelegate.getAST(queryID) ?? deletedQueries.get(queryID)?.ast\n );\n },\n mapClientASTToServer:\n inspectorDelegate.mapClientASTToServer.bind(inspectorDelegate),\n get metrics() {\n return inspectorDelegate.metrics;\n },\n setQueryEvictedCallback() {},\n queryDelegate,\n rep,\n getSocket,\n lazy: import('./lazy-inspector.ts'),\n };\n\n this.client = new Client(this.#delegate, rep.clientID, rep.clientGroupID);\n this.clientGroup = this.client.clientGroup;\n }\n\n async metrics(): Promise<Metrics> {\n return (await this.#delegate.lazy).inspectorMetrics(this.#delegate);\n }\n\n async clients(): Promise<Client[]> {\n return (await this.#delegate.lazy).inspectorClients(this.#delegate);\n }\n\n async clientsWithQueries(): Promise<Client[]> {\n return (await this.#delegate.lazy).inspectorClientsWithQueries(\n this.#delegate,\n );\n }\n\n async serverVersion(): Promise<string> {\n return (await this.#delegate.lazy).serverVersion(this.#delegate);\n }\n\n async analyzeQuery(\n query: AnyQuery,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeQuery(\n this.#delegate,\n query,\n options,\n );\n }\n\n /**\n * Analyze a query specified by a server-side AST. Unlike {@link analyzeQuery}\n * the AST is sent to the server verbatim with no client-to-server name\n * mapping; callers should provide an AST already in the server shape.\n */\n async analyzeServerAST(\n ast: AST,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeServerAST(\n this.#delegate,\n ast,\n options,\n );\n }\n\n /**\n * Analyze a server-registered named (custom) query. The server resolves\n * the name and args to an AST using its registered custom-query handler.\n */\n async analyzeNamedQuery(\n name: string,\n args: ReadonlyArray<ReadonlyJSONValue>,\n options?: AnalyzeQueryOptions,\n ): Promise<AnalyzeQueryResult> {\n return (await this.#delegate.lazy).analyzeNamedQuery(\n this.#delegate,\n name,\n args,\n options,\n );\n }\n\n /**\n * Authenticate with the server's admin password. Other inspector RPCs\n * (e.g. {@link analyzeQuery}) fall back to an interactive HTML password\n * prompt when authentication is needed, which is unavailable in non-DOM\n * environments. Call this first from Node contexts to establish the\n * session.\n *\n * The server authenticates each connection separately. An accepted\n * password is remembered by this inspector and presented again when the\n * connection is re-established.\n *\n * Returns `true` if the password is accepted (or the server runs in a\n * development mode that bypasses the check), `false` otherwise.\n */\n async authenticate(password: string): Promise<boolean> {\n return (await this.#delegate.lazy).authenticate(this.#delegate, password);\n }\n\n /**\n * Format planner debug events as a human-readable string.\n */\n formatPlannerEvents(events: PlanDebugEventJSON[]): string {\n return formatPlannerEvents(events);\n }\n}\n"],"mappings":";;;AAyBA,IAAa,YAAb,MAAuB;CACrB;CACA,kCAA2B,IAAI,IAG7B;CACF;CACA;CAEA,YACE,KACA,mBACA,eACA,WACA;EACA,MAAM,iBAAiB,KAAK;EAC5B,kBAAkB,yBAAyB,MAAM,KAAK,YAAY;GAChE,eAAe,IAAI,MAAM;IAAC;IAAK;GAAO,CAAC;EACzC,CAAC;EACD,KAAK,YAAY;GACf,gBAAgB,MAAM;IACpB,OACE,kBAAkB,gBAAgB,IAAI,KACtC,eAAe,IAAI,IAAI,CAAC,EAAE;GAE9B;GACA,OAAO,SAAS;IACd,OACE,kBAAkB,OAAO,OAAO,KAAK,eAAe,IAAI,OAAO,CAAC,EAAE;GAEtE;GACA,sBACE,kBAAkB,qBAAqB,KAAK,iBAAiB;GAC/D,IAAI,UAAU;IACZ,OAAO,kBAAkB;GAC3B;GACA,0BAA0B,CAAC;GAC3B;GACA;GACA;GACA,MAAM,OAAO;EACf;EAEA,KAAK,SAAS,IAAI,OAAO,KAAK,WAAW,IAAI,UAAU,IAAI,aAAa;EACxE,KAAK,cAAc,KAAK,OAAO;CACjC;CAEA,MAAM,UAA4B;EAChC,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,iBAAiB,KAAK,SAAS;CACpE;CAEA,MAAM,UAA6B;EACjC,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,iBAAiB,KAAK,SAAS;CACpE;CAEA,MAAM,qBAAwC;EAC5C,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,4BACjC,KAAK,SACP;CACF;CAEA,MAAM,gBAAiC;EACrC,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,cAAc,KAAK,SAAS;CACjE;CAEA,MAAM,aACJ,OACA,SAC6B;EAC7B,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,aACjC,KAAK,WACL,OACA,OACF;CACF;;;;;;CAOA,MAAM,iBACJ,KACA,SAC6B;EAC7B,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,iBACjC,KAAK,WACL,KACA,OACF;CACF;;;;;CAMA,MAAM,kBACJ,MACA,MACA,SAC6B;EAC7B,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,kBACjC,KAAK,WACL,MACA,MACA,OACF;CACF;;;;;;;;;;;;;;;CAgBA,MAAM,aAAa,UAAoC;EACrD,QAAQ,MAAM,KAAK,UAAU,KAAA,CAAM,aAAa,KAAK,WAAW,QAAQ;CAC1E;;;;CAKA,oBAAoB,QAAsC;EACxD,OAAO,oBAAoB,MAAM;CACnC;AACF"}
|
|
@@ -14,12 +14,11 @@ import type { MutatorDefs } from '../replicache-types.ts';
|
|
|
14
14
|
import { Client } from './client.ts';
|
|
15
15
|
import { type Lazy } from './inspector.ts';
|
|
16
16
|
import { Query } from './query.ts';
|
|
17
|
-
export type GetWebSocket = () => Promise<WebSocket>;
|
|
18
17
|
export type Metrics = {
|
|
19
18
|
readonly [K in keyof (ClientMetricMap & ServerMetricMap)]: ReadonlyTDigest;
|
|
20
19
|
};
|
|
21
20
|
type DistributiveOmit<T, K extends string> = T extends object ? Omit<T, K> : never;
|
|
22
|
-
export declare function rpc<T extends InspectDownBody>(
|
|
21
|
+
export declare function rpc<T extends InspectDownBody>(delegate: ExtendedInspectorDelegate, arg: DistributiveOmit<InspectUpBody, 'id'>, downSchema: valita.Type<T>): Promise<T['value']>;
|
|
23
22
|
export declare function mergeMetrics(clientMetrics: QueryClientMetrics | undefined, serverMetrics: QueryServerMetricsJSON | null | undefined): ClientMetrics & ServerMetrics;
|
|
24
23
|
export declare function inspectorMetrics(delegate: ExtendedInspectorDelegate): Promise<Metrics>;
|
|
25
24
|
export declare function inspectorClients(delegate: ExtendedInspectorDelegate): Promise<Client[]>;
|
|
@@ -39,6 +38,9 @@ export declare function analyzeNamedQuery(delegate: ExtendedInspectorDelegate, n
|
|
|
39
38
|
* is accepted (or the server is in a development mode that bypasses the
|
|
40
39
|
* check). Use this from Node contexts where the default interactive HTML
|
|
41
40
|
* prompt used by other inspector RPCs is not available.
|
|
41
|
+
*
|
|
42
|
+
* The server authenticates each connection separately. An accepted password
|
|
43
|
+
* is remembered and presented again when the connection is re-established.
|
|
42
44
|
*/
|
|
43
45
|
export declare function authenticate(delegate: ExtendedInspectorDelegate, password: string): Promise<boolean>;
|
|
44
46
|
export interface InspectorDelegate {
|
|
@@ -53,6 +55,12 @@ export interface ExtendedInspectorDelegate extends InspectorDelegate {
|
|
|
53
55
|
readonly getSocket: () => Promise<WebSocket>;
|
|
54
56
|
readonly queryDelegate: QueryDelegate;
|
|
55
57
|
lazy: Promise<Lazy>;
|
|
58
|
+
/**
|
|
59
|
+
* The admin password the server last accepted, if any. Authentication is
|
|
60
|
+
* per connection, so it is presented again after a reconnect before the
|
|
61
|
+
* user is prompted.
|
|
62
|
+
*/
|
|
63
|
+
adminPassword?: string | undefined;
|
|
56
64
|
}
|
|
57
65
|
export type Rep = ReplicacheImpl<MutatorDefs>;
|
|
58
66
|
export type QueryClientMetrics = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-inspector.d.ts","sourceRoot":"","sources":["../../../../../../zero-client/src/client/inspector/lazy-inspector.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,+CAA+C,CAAC;AAGlF,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAU,KAAK,eAAe,EAAC,MAAM,mCAAmC,CAAC;AAChF,OAAO,KAAK,MAAM,MAAM,kCAAkC,CAAC;AAC3D,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,uDAAuD,CAAC;AAC9F,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,sCAAsC,CAAC;AAC9D,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,uCAAuC,CAAC;AAC/D,OAAO,EAOL,KAAK,eAAe,EAEpB,KAAK,kBAAkB,IAAI,sBAAsB,EAClD,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACd,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAChB,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6CAA6C,CAAC;AAE/E,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AAGjE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAEnC,OAAO,EAAC,KAAK,IAAI,EAAC,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AAEjC,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"lazy-inspector.d.ts","sourceRoot":"","sources":["../../../../../../zero-client/src/client/inspector/lazy-inspector.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,22 +14,28 @@ import "./inspector.js";
|
|
|
14
14
|
import { createHTMLPasswordPrompt } from "./html-dialog-prompt.js";
|
|
15
15
|
import { Query } from "./query.js";
|
|
16
16
|
//#region ../zero-client/src/client/inspector/lazy-inspector.ts
|
|
17
|
-
async function rpc(
|
|
17
|
+
async function rpc(delegate, arg, downSchema) {
|
|
18
|
+
const socket = await delegate.getSocket();
|
|
18
19
|
try {
|
|
19
20
|
return await rpcNoAuthTry(socket, arg, downSchema);
|
|
20
21
|
} catch (e) {
|
|
21
|
-
if (e instanceof UnauthenticatedError)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
throw e;
|
|
22
|
+
if (!(e instanceof UnauthenticatedError)) throw e;
|
|
23
|
+
}
|
|
24
|
+
const remembered = delegate.adminPassword;
|
|
25
|
+
if (remembered !== void 0 && await authenticateSocket(socket, remembered)) return rpcNoAuthTry(socket, arg, downSchema);
|
|
26
|
+
delegate.adminPassword = void 0;
|
|
27
|
+
const password = await createHTMLPasswordPrompt("Enter password:");
|
|
28
|
+
if (password && await authenticateSocket(socket, password)) {
|
|
29
|
+
delegate.adminPassword = password;
|
|
30
|
+
return rpcNoAuthTry(socket, arg, downSchema);
|
|
32
31
|
}
|
|
32
|
+
throw new Error("Authentication failed");
|
|
33
|
+
}
|
|
34
|
+
function authenticateSocket(socket, password) {
|
|
35
|
+
return rpcNoAuthTry(socket, {
|
|
36
|
+
op: "authenticate",
|
|
37
|
+
value: password
|
|
38
|
+
}, inspectAuthenticatedDownSchema);
|
|
33
39
|
}
|
|
34
40
|
function rpcNoAuthTry(socket, arg, downSchema) {
|
|
35
41
|
return new Promise((resolve, reject) => {
|
|
@@ -108,7 +114,7 @@ function convertServerMetrics(metrics) {
|
|
|
108
114
|
}
|
|
109
115
|
async function inspectorMetrics(delegate) {
|
|
110
116
|
const clientMetrics = delegate.metrics;
|
|
111
|
-
const serverMetricsJSON = await rpc(
|
|
117
|
+
const serverMetricsJSON = await rpc(delegate, { op: "metrics" }, inspectMetricsDownSchema);
|
|
112
118
|
return {
|
|
113
119
|
...clientMetrics ?? newClientMetrics(),
|
|
114
120
|
"query-materialization-server": TDigest.fromJSON(serverMetricsJSON["query-materialization-server"]),
|
|
@@ -177,8 +183,8 @@ function clientRows(delegate, clientID, tableName) {
|
|
|
177
183
|
return rows;
|
|
178
184
|
});
|
|
179
185
|
}
|
|
180
|
-
|
|
181
|
-
return rpc(
|
|
186
|
+
function serverVersion(delegate) {
|
|
187
|
+
return rpc(delegate, { op: "version" }, inspectVersionDownSchema);
|
|
182
188
|
}
|
|
183
189
|
function clientQueries(delegate, clientID) {
|
|
184
190
|
return queries(delegate, {
|
|
@@ -187,32 +193,31 @@ function clientQueries(delegate, clientID) {
|
|
|
187
193
|
});
|
|
188
194
|
}
|
|
189
195
|
async function queries(delegate, arg) {
|
|
190
|
-
const queries = (await rpc(
|
|
196
|
+
const queries = (await rpc(delegate, arg, inspectQueriesDownSchema)).map((row) => new Query(row, delegate));
|
|
191
197
|
queries.sort((a, b) => (b.hydrateServer ?? 0) - (a.hydrateServer ?? 0));
|
|
192
198
|
return queries;
|
|
193
199
|
}
|
|
194
|
-
|
|
200
|
+
function analyzeQuery(delegate, query, options) {
|
|
195
201
|
const qi = asQueryInternals(query);
|
|
196
202
|
const { customQueryID } = qi;
|
|
197
|
-
|
|
198
|
-
name: customQueryID.name,
|
|
199
|
-
args: customQueryID.args
|
|
200
|
-
} : { ast: delegate.mapClientASTToServer(qi.ast) };
|
|
201
|
-
return rpc(await delegate.getSocket(), {
|
|
203
|
+
return rpc(delegate, {
|
|
202
204
|
op: "analyze-query",
|
|
203
|
-
...
|
|
205
|
+
...customQueryID ? {
|
|
206
|
+
name: customQueryID.name,
|
|
207
|
+
args: customQueryID.args
|
|
208
|
+
} : { ast: delegate.mapClientASTToServer(qi.ast) },
|
|
204
209
|
options
|
|
205
210
|
}, inspectAnalyzeQueryDownSchema);
|
|
206
211
|
}
|
|
207
|
-
|
|
208
|
-
return rpc(
|
|
212
|
+
function analyzeServerAST(delegate, ast, options) {
|
|
213
|
+
return rpc(delegate, {
|
|
209
214
|
op: "analyze-query",
|
|
210
215
|
ast,
|
|
211
216
|
options
|
|
212
217
|
}, inspectAnalyzeQueryDownSchema);
|
|
213
218
|
}
|
|
214
|
-
|
|
215
|
-
return rpc(
|
|
219
|
+
function analyzeNamedQuery(delegate, name, args, options) {
|
|
220
|
+
return rpc(delegate, {
|
|
216
221
|
op: "analyze-query",
|
|
217
222
|
name,
|
|
218
223
|
args,
|
|
@@ -224,12 +229,14 @@ async function analyzeNamedQuery(delegate, name, args, options) {
|
|
|
224
229
|
* is accepted (or the server is in a development mode that bypasses the
|
|
225
230
|
* check). Use this from Node contexts where the default interactive HTML
|
|
226
231
|
* prompt used by other inspector RPCs is not available.
|
|
232
|
+
*
|
|
233
|
+
* The server authenticates each connection separately. An accepted password
|
|
234
|
+
* is remembered and presented again when the connection is re-established.
|
|
227
235
|
*/
|
|
228
236
|
async function authenticate(delegate, password) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}, inspectAuthenticatedDownSchema);
|
|
237
|
+
const ok = await authenticateSocket(await delegate.getSocket(), password);
|
|
238
|
+
if (ok) delegate.adminPassword = password;
|
|
239
|
+
return ok;
|
|
233
240
|
}
|
|
234
241
|
var UnauthenticatedError = class extends Error {};
|
|
235
242
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-inspector.js","names":["valita.test"],"sources":["../../../../../../zero-client/src/client/inspector/lazy-inspector.ts"],"sourcesContent":["import type {BTreeRead} from '../../../../replicache/src/btree/read.ts';\nimport type {Read} from '../../../../replicache/src/dag/store.ts';\nimport {readFromHash} from '../../../../replicache/src/db/read.ts';\nimport * as FormatVersion from '../../../../replicache/src/format-version-enum.ts';\nimport {getClientGroup} from '../../../../replicache/src/persist/client-groups.ts';\nimport {\n getClient,\n getClients,\n type ClientMap,\n} from '../../../../replicache/src/persist/clients.ts';\nimport type {ReplicacheImpl} from '../../../../replicache/src/replicache-impl.ts';\nimport {withRead} from '../../../../replicache/src/with-transactions.ts';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../../../shared/src/json.ts';\nimport {TDigest, type ReadonlyTDigest} from '../../../../shared/src/tdigest.ts';\nimport * as valita from '../../../../shared/src/valita.ts';\nimport type {AnalyzeQueryResult} from '../../../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../../zero-protocol/src/data.ts';\nimport {\n inspectAnalyzeQueryDownSchema,\n inspectAuthenticatedDownSchema,\n inspectErrorDownSchema,\n inspectMetricsDownSchema,\n inspectQueriesDownSchema,\n inspectVersionDownSchema,\n type InspectDownBody,\n type InspectQueryRow,\n type QueryServerMetrics as QueryServerMetricsJSON,\n} from '../../../../zero-protocol/src/inspect-down.ts';\nimport type {\n AnalyzeQueryOptions,\n InspectUpBody,\n} from '../../../../zero-protocol/src/inspect-up.ts';\nimport type {\n ClientMetricMap,\n ServerMetricMap,\n} from '../../../../zql/src/query/metrics-delegate.ts';\nimport type {QueryDelegate} from '../../../../zql/src/query/query-delegate.ts';\nimport {asQueryInternals} from '../../../../zql/src/query/query-internals.ts';\nimport type {AnyQuery} from '../../../../zql/src/query/query.ts';\nimport {nanoid} from '../../util/nanoid.ts';\nimport {ENTITIES_KEY_PREFIX} from '../keys.ts';\nimport type {MutatorDefs} from '../replicache-types.ts';\nimport {Client} from './client.ts';\nimport {createHTMLPasswordPrompt} from './html-dialog-prompt.ts';\nimport {type Lazy} from './inspector.ts';\nimport {Query} from './query.ts';\n\nexport type GetWebSocket = () => Promise<WebSocket>;\n\nexport type Metrics = {\n readonly [K in keyof (ClientMetricMap & ServerMetricMap)]: ReadonlyTDigest;\n};\n\ntype DistributiveOmit<T, K extends string> = T extends object\n ? Omit<T, K>\n : never;\n\nexport async function rpc<T extends InspectDownBody>(\n socket: WebSocket,\n arg: DistributiveOmit<InspectUpBody, 'id'>,\n downSchema: valita.Type<T>,\n): Promise<T['value']> {\n try {\n return await rpcNoAuthTry(socket, arg, downSchema);\n } catch (e) {\n if (e instanceof UnauthenticatedError) {\n const password = await createHTMLPasswordPrompt('Enter password:');\n if (password) {\n // Do authenticate rpc\n const authRes = await rpcNoAuthTry(\n socket,\n {op: 'authenticate', value: password},\n inspectAuthenticatedDownSchema,\n );\n if (authRes) {\n // If authentication is successful, retry the original RPC\n return rpcNoAuthTry(socket, arg, downSchema);\n }\n }\n throw new Error('Authentication failed');\n }\n throw e;\n }\n}\n\nfunction rpcNoAuthTry<T extends InspectDownBody>(\n socket: WebSocket,\n arg: DistributiveOmit<InspectUpBody, 'id'>,\n downSchema: valita.Type<T>,\n): Promise<T['value']> {\n return new Promise((resolve, reject) => {\n const id = nanoid();\n const f = (ev: MessageEvent) => {\n if (typeof ev.data !== 'string') {\n // Binary messages (such as poke chunks) are not inspector messages.\n return;\n }\n let msg: unknown;\n try {\n msg = JSON.parse(ev.data);\n } catch {\n return;\n }\n if (!Array.isArray(msg) || msg[0] !== 'inspect') {\n return;\n }\n const body = msg[1];\n if (body.id !== id) {\n return;\n }\n const errorRes = valita.test(body, inspectErrorDownSchema);\n if (errorRes.ok) {\n reject(new Error(errorRes.value.value));\n } else {\n const res = valita.test(body, downSchema);\n if (res.ok) {\n resolve(res.value.value);\n } else {\n // Check if we got un authenticated/false response\n const authRes = valita.test(body, inspectAuthenticatedDownSchema);\n if (authRes.ok) {\n // Handle authenticated response\n assert(\n authRes.value.value === false,\n 'Expected unauthenticated response',\n );\n reject(new UnauthenticatedError());\n } else {\n reject(res.error);\n }\n }\n }\n socket.removeEventListener('message', f);\n };\n socket.addEventListener('message', f);\n socket.send(JSON.stringify(['inspect', {...arg, id}]));\n });\n} // T extends forces T to be resolved\n\nexport function mergeMetrics(\n clientMetrics: QueryClientMetrics | undefined,\n serverMetrics: QueryServerMetricsJSON | null | undefined,\n): ClientMetrics & ServerMetrics {\n const cm = clientMetrics ?? newClientMetrics();\n return {\n ...convertClientMetrics(cm),\n ...(serverMetrics\n ? convertServerMetrics(serverMetrics)\n : newServerMetrics()),\n };\n}\n\nfunction newClientMetrics(): QueryClientMetrics {\n return {\n 'query-materialization-client': undefined,\n 'query-materialization-end-to-end': undefined,\n 'query-update-client': new TDigest(),\n };\n}\n\nfunction convertClientMetrics(metrics: QueryClientMetrics): ClientMetrics {\n const hydrateDigest = new TDigest();\n if (metrics['query-materialization-client'] !== undefined) {\n hydrateDigest.add(metrics['query-materialization-client']);\n }\n const totalDigest = new TDigest();\n if (metrics['query-materialization-end-to-end'] !== undefined) {\n totalDigest.add(metrics['query-materialization-end-to-end']);\n }\n return {\n 'query-materialization-client': hydrateDigest,\n 'query-materialization-end-to-end': totalDigest,\n 'query-update-client': metrics['query-update-client'] as TDigest,\n };\n}\n\nfunction newServerMetrics(): ServerMetrics {\n return {\n 'query-materialization-server': new TDigest(),\n 'query-update-server': new TDigest(),\n };\n}\n\nfunction convertServerMetrics(metrics: QueryServerMetricsJSON): ServerMetrics {\n const hydrateMs = metrics['query-hydration-server-ms'];\n const hydrateDigest = new TDigest();\n if (hydrateMs !== undefined) {\n hydrateDigest.add(hydrateMs);\n }\n return {\n 'query-materialization-server': hydrateDigest,\n 'query-update-server': TDigest.fromJSON(metrics['query-update-server']),\n };\n}\n\nexport async function inspectorMetrics(\n delegate: ExtendedInspectorDelegate,\n): Promise<Metrics> {\n const clientMetrics = delegate.metrics;\n const serverMetricsJSON = await rpc(\n await delegate.getSocket(),\n {op: 'metrics'},\n inspectMetricsDownSchema,\n );\n return {\n ...(clientMetrics ?? newClientMetrics()),\n 'query-materialization-server': TDigest.fromJSON(\n serverMetricsJSON['query-materialization-server'],\n ),\n 'query-update-server': TDigest.fromJSON(\n serverMetricsJSON['query-update-server'],\n ),\n };\n}\n\nexport function inspectorClients(\n delegate: ExtendedInspectorDelegate,\n): Promise<Client[]> {\n return withDagRead(delegate, dagRead => clients(delegate, dagRead));\n}\n\nexport function inspectorClientsWithQueries(\n delegate: ExtendedInspectorDelegate,\n): Promise<Client[]> {\n return withDagRead(delegate, dagRead =>\n clientsWithQueries(delegate, dagRead),\n );\n}\n\nasync function withDagRead<T>(\n delegate: ExtendedInspectorDelegate,\n f: (dagRead: Read) => Promise<T>,\n): Promise<T> {\n const {rep} = delegate;\n await rep.refresh();\n await rep.persist();\n return withRead(rep.perdag, f);\n}\n\nasync function getBTree(dagRead: Read, clientID: string): Promise<BTreeRead> {\n const client = await getClient(clientID, dagRead);\n assert(client, `Client not found: ${clientID}`);\n const {clientGroupID} = client;\n const clientGroup = await getClientGroup(clientGroupID, dagRead);\n assert(clientGroup, `Client group not found: ${clientGroupID}`);\n const dbRead = await readFromHash(\n clientGroup.headHash,\n dagRead,\n FormatVersion.Latest,\n );\n return dbRead.map;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\ntype MapEntry<T extends ReadonlyMap<any, any>> =\n T extends ReadonlyMap<infer K, infer V> ? readonly [K, V] : never;\n\nasync function clients(\n delegate: ExtendedInspectorDelegate,\n dagRead: Read,\n predicate: (entry: MapEntry<ClientMap>) => boolean = () => true,\n): Promise<Client[]> {\n const clients = await getClients(dagRead);\n return [...clients.entries()]\n .filter(predicate)\n .map(\n ([clientID, {clientGroupID}]) =>\n new Client(delegate, clientID, clientGroupID),\n );\n}\n\nasync function clientsWithQueries(\n delegate: ExtendedInspectorDelegate,\n dagRead: Read,\n predicate: (entry: MapEntry<ClientMap>) => boolean = () => true,\n): Promise<Client[]> {\n const allClients = await clients(delegate, dagRead, predicate);\n const clientsWithQueries: Client[] = [];\n await Promise.all(\n allClients.map(async client => {\n const queries = await client.queries();\n if (queries.length > 0) {\n clientsWithQueries.push(client);\n }\n }),\n );\n return clientsWithQueries;\n}\n\nexport async function clientGroupClients(\n delegate: ExtendedInspectorDelegate,\n clientGroupID: Promise<string> | string,\n): Promise<Client[]> {\n const id = await clientGroupID;\n return withDagRead(delegate, dagRead =>\n clients(delegate, dagRead, ([_, v]) => v.clientGroupID === id),\n );\n}\n\nexport async function clientGroupClientsWithQueries(\n delegate: ExtendedInspectorDelegate,\n clientGroupID: Promise<string> | string,\n): Promise<Client[]> {\n const id = await clientGroupID;\n return withDagRead(delegate, dagRead =>\n clientsWithQueries(delegate, dagRead, ([_, v]) => v.clientGroupID === id),\n );\n}\n\nexport function clientGroupQueries(\n delegate: ExtendedInspectorDelegate,\n): Promise<Query[]> {\n return queries(delegate, {op: 'queries'});\n}\nexport function clientMap(\n delegate: ExtendedInspectorDelegate,\n clientID: string,\n): Promise<Map<string, ReadonlyJSONValue>> {\n return withDagRead(delegate, async dagRead => {\n const tree = await getBTree(dagRead, clientID);\n const map = new Map<string, ReadonlyJSONValue>();\n for await (const [key, value] of tree.scan('', true)) {\n map.set(key, value);\n }\n return map;\n });\n}\n\nexport function clientRows(\n delegate: ExtendedInspectorDelegate,\n clientID: string,\n tableName: string,\n): Promise<Row[]> {\n return withDagRead(delegate, async dagRead => {\n const prefix = ENTITIES_KEY_PREFIX + tableName + '/';\n const tree = await getBTree(dagRead, clientID);\n const rows: Row[] = [];\n for await (const [key, value] of tree.scan(prefix, false)) {\n if (!key.startsWith(prefix)) {\n break;\n }\n rows.push(value as Row);\n }\n return rows;\n });\n}\n\nexport async function serverVersion(\n delegate: ExtendedInspectorDelegate,\n): Promise<string> {\n return rpc(\n await delegate.getSocket(),\n {op: 'version'},\n inspectVersionDownSchema,\n );\n}\n\nexport function clientQueries(\n delegate: ExtendedInspectorDelegate,\n clientID: string,\n): Promise<Query[]> {\n return queries(delegate, {op: 'queries', clientID});\n}\n\nasync function queries(\n delegate: ExtendedInspectorDelegate,\n arg: {op: 'queries'; clientID?: string},\n): Promise<Query[]> {\n const rows: InspectQueryRow[] = await rpc(\n await delegate.getSocket(),\n arg,\n inspectQueriesDownSchema,\n );\n const queries = rows.map(row => new Query(row, delegate, delegate.getSocket));\n queries.sort((a, b) => (b.hydrateServer ?? 0) - (a.hydrateServer ?? 0));\n return queries;\n}\n\nexport async function analyzeQuery(\n delegate: ExtendedInspectorDelegate,\n query: AnyQuery,\n options?: AnalyzeQueryOptions,\n): Promise<AnalyzeQueryResult> {\n const qi = asQueryInternals(query);\n const {customQueryID} = qi;\n const queryParameters = customQueryID\n ? {name: customQueryID.name, args: customQueryID.args}\n : {ast: delegate.mapClientASTToServer(qi.ast)};\n\n return rpc(\n await delegate.getSocket(),\n {\n op: 'analyze-query',\n ...queryParameters,\n options,\n },\n inspectAnalyzeQueryDownSchema,\n );\n}\n\nexport async function analyzeServerAST(\n delegate: ExtendedInspectorDelegate,\n ast: AST,\n options?: AnalyzeQueryOptions,\n): Promise<AnalyzeQueryResult> {\n return rpc(\n await delegate.getSocket(),\n {op: 'analyze-query', ast, options},\n inspectAnalyzeQueryDownSchema,\n );\n}\n\nexport async function analyzeNamedQuery(\n delegate: ExtendedInspectorDelegate,\n name: string,\n args: ReadonlyArray<ReadonlyJSONValue>,\n options?: AnalyzeQueryOptions,\n): Promise<AnalyzeQueryResult> {\n return rpc(\n await delegate.getSocket(),\n {op: 'analyze-query', name, args, options},\n inspectAnalyzeQueryDownSchema,\n );\n}\n\n/**\n * Sends an `authenticate` op to the server. Returns `true` if the password\n * is accepted (or the server is in a development mode that bypasses the\n * check). Use this from Node contexts where the default interactive HTML\n * prompt used by other inspector RPCs is not available.\n */\nexport async function authenticate(\n delegate: ExtendedInspectorDelegate,\n password: string,\n): Promise<boolean> {\n return rpc(\n await delegate.getSocket(),\n {op: 'authenticate', value: password},\n inspectAuthenticatedDownSchema,\n );\n}\n\nclass UnauthenticatedError extends Error {}\n\nexport interface InspectorDelegate {\n getQueryMetrics(hash: string): QueryClientMetrics | undefined;\n getAST(queryID: string): AST | undefined;\n readonly metrics: ClientMetrics;\n mapClientASTToServer(ast: AST): AST;\n setQueryEvictedCallback(\n cb: (hash: string, ast: AST, metrics: QueryClientMetrics) => void,\n ): void;\n}\n\nexport interface ExtendedInspectorDelegate extends InspectorDelegate {\n readonly rep: Rep;\n readonly getSocket: () => Promise<WebSocket>;\n readonly queryDelegate: QueryDelegate;\n lazy: Promise<Lazy>;\n}\n\nexport type Rep = ReplicacheImpl<MutatorDefs>;\n\nexport type QueryClientMetrics = {\n readonly 'query-materialization-client': number | undefined;\n readonly 'query-materialization-end-to-end': number | undefined;\n readonly 'query-update-client': ReadonlyTDigest;\n};\n\nexport type ClientMetrics = {\n readonly [K in keyof ClientMetricMap]: ReadonlyTDigest;\n};\n\nexport type ServerMetrics = {\n readonly [K in keyof ServerMetricMap]: ReadonlyTDigest;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"lazy-inspector.js","names":["valita.test"],"sources":["../../../../../../zero-client/src/client/inspector/lazy-inspector.ts"],"sourcesContent":["import type {BTreeRead} from '../../../../replicache/src/btree/read.ts';\nimport type {Read} from '../../../../replicache/src/dag/store.ts';\nimport {readFromHash} from '../../../../replicache/src/db/read.ts';\nimport * as FormatVersion from '../../../../replicache/src/format-version-enum.ts';\nimport {getClientGroup} from '../../../../replicache/src/persist/client-groups.ts';\nimport {\n getClient,\n getClients,\n type ClientMap,\n} from '../../../../replicache/src/persist/clients.ts';\nimport type {ReplicacheImpl} from '../../../../replicache/src/replicache-impl.ts';\nimport {withRead} from '../../../../replicache/src/with-transactions.ts';\nimport {assert} from '../../../../shared/src/asserts.ts';\nimport type {ReadonlyJSONValue} from '../../../../shared/src/json.ts';\nimport {TDigest, type ReadonlyTDigest} from '../../../../shared/src/tdigest.ts';\nimport * as valita from '../../../../shared/src/valita.ts';\nimport type {AnalyzeQueryResult} from '../../../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../../zero-protocol/src/data.ts';\nimport {\n inspectAnalyzeQueryDownSchema,\n inspectAuthenticatedDownSchema,\n inspectErrorDownSchema,\n inspectMetricsDownSchema,\n inspectQueriesDownSchema,\n inspectVersionDownSchema,\n type InspectDownBody,\n type InspectQueryRow,\n type QueryServerMetrics as QueryServerMetricsJSON,\n} from '../../../../zero-protocol/src/inspect-down.ts';\nimport type {\n AnalyzeQueryOptions,\n InspectUpBody,\n} from '../../../../zero-protocol/src/inspect-up.ts';\nimport type {\n ClientMetricMap,\n ServerMetricMap,\n} from '../../../../zql/src/query/metrics-delegate.ts';\nimport type {QueryDelegate} from '../../../../zql/src/query/query-delegate.ts';\nimport {asQueryInternals} from '../../../../zql/src/query/query-internals.ts';\nimport type {AnyQuery} from '../../../../zql/src/query/query.ts';\nimport {nanoid} from '../../util/nanoid.ts';\nimport {ENTITIES_KEY_PREFIX} from '../keys.ts';\nimport type {MutatorDefs} from '../replicache-types.ts';\nimport {Client} from './client.ts';\nimport {createHTMLPasswordPrompt} from './html-dialog-prompt.ts';\nimport {type Lazy} from './inspector.ts';\nimport {Query} from './query.ts';\n\nexport type Metrics = {\n readonly [K in keyof (ClientMetricMap & ServerMetricMap)]: ReadonlyTDigest;\n};\n\ntype DistributiveOmit<T, K extends string> = T extends object\n ? Omit<T, K>\n : never;\n\nexport async function rpc<T extends InspectDownBody>(\n delegate: ExtendedInspectorDelegate,\n arg: DistributiveOmit<InspectUpBody, 'id'>,\n downSchema: valita.Type<T>,\n): Promise<T['value']> {\n const socket = await delegate.getSocket();\n try {\n return await rpcNoAuthTry(socket, arg, downSchema);\n } catch (e) {\n if (!(e instanceof UnauthenticatedError)) {\n throw e;\n }\n }\n\n // The server authenticates each connection separately. Before asking for\n // the password again, retry the one accepted on an earlier connection.\n const remembered = delegate.adminPassword;\n if (\n remembered !== undefined &&\n (await authenticateSocket(socket, remembered))\n ) {\n return rpcNoAuthTry(socket, arg, downSchema);\n }\n delegate.adminPassword = undefined;\n\n const password = await createHTMLPasswordPrompt('Enter password:');\n if (password && (await authenticateSocket(socket, password))) {\n delegate.adminPassword = password;\n // If authentication is successful, retry the original RPC\n return rpcNoAuthTry(socket, arg, downSchema);\n }\n throw new Error('Authentication failed');\n}\n\nfunction authenticateSocket(\n socket: WebSocket,\n password: string,\n): Promise<boolean> {\n return rpcNoAuthTry(\n socket,\n {op: 'authenticate', value: password},\n inspectAuthenticatedDownSchema,\n );\n}\n\nfunction rpcNoAuthTry<T extends InspectDownBody>(\n socket: WebSocket,\n arg: DistributiveOmit<InspectUpBody, 'id'>,\n downSchema: valita.Type<T>,\n): Promise<T['value']> {\n return new Promise((resolve, reject) => {\n const id = nanoid();\n const f = (ev: MessageEvent) => {\n if (typeof ev.data !== 'string') {\n // Binary messages (such as poke chunks) are not inspector messages.\n return;\n }\n let msg: unknown;\n try {\n msg = JSON.parse(ev.data);\n } catch {\n return;\n }\n if (!Array.isArray(msg) || msg[0] !== 'inspect') {\n return;\n }\n const body = msg[1];\n if (body.id !== id) {\n return;\n }\n const errorRes = valita.test(body, inspectErrorDownSchema);\n if (errorRes.ok) {\n reject(new Error(errorRes.value.value));\n } else {\n const res = valita.test(body, downSchema);\n if (res.ok) {\n resolve(res.value.value);\n } else {\n // Check if we got un authenticated/false response\n const authRes = valita.test(body, inspectAuthenticatedDownSchema);\n if (authRes.ok) {\n // Handle authenticated response\n assert(\n authRes.value.value === false,\n 'Expected unauthenticated response',\n );\n reject(new UnauthenticatedError());\n } else {\n reject(res.error);\n }\n }\n }\n socket.removeEventListener('message', f);\n };\n socket.addEventListener('message', f);\n socket.send(JSON.stringify(['inspect', {...arg, id}]));\n });\n} // T extends forces T to be resolved\n\nexport function mergeMetrics(\n clientMetrics: QueryClientMetrics | undefined,\n serverMetrics: QueryServerMetricsJSON | null | undefined,\n): ClientMetrics & ServerMetrics {\n const cm = clientMetrics ?? newClientMetrics();\n return {\n ...convertClientMetrics(cm),\n ...(serverMetrics\n ? convertServerMetrics(serverMetrics)\n : newServerMetrics()),\n };\n}\n\nfunction newClientMetrics(): QueryClientMetrics {\n return {\n 'query-materialization-client': undefined,\n 'query-materialization-end-to-end': undefined,\n 'query-update-client': new TDigest(),\n };\n}\n\nfunction convertClientMetrics(metrics: QueryClientMetrics): ClientMetrics {\n const hydrateDigest = new TDigest();\n if (metrics['query-materialization-client'] !== undefined) {\n hydrateDigest.add(metrics['query-materialization-client']);\n }\n const totalDigest = new TDigest();\n if (metrics['query-materialization-end-to-end'] !== undefined) {\n totalDigest.add(metrics['query-materialization-end-to-end']);\n }\n return {\n 'query-materialization-client': hydrateDigest,\n 'query-materialization-end-to-end': totalDigest,\n 'query-update-client': metrics['query-update-client'] as TDigest,\n };\n}\n\nfunction newServerMetrics(): ServerMetrics {\n return {\n 'query-materialization-server': new TDigest(),\n 'query-update-server': new TDigest(),\n };\n}\n\nfunction convertServerMetrics(metrics: QueryServerMetricsJSON): ServerMetrics {\n const hydrateMs = metrics['query-hydration-server-ms'];\n const hydrateDigest = new TDigest();\n if (hydrateMs !== undefined) {\n hydrateDigest.add(hydrateMs);\n }\n return {\n 'query-materialization-server': hydrateDigest,\n 'query-update-server': TDigest.fromJSON(metrics['query-update-server']),\n };\n}\n\nexport async function inspectorMetrics(\n delegate: ExtendedInspectorDelegate,\n): Promise<Metrics> {\n const clientMetrics = delegate.metrics;\n const serverMetricsJSON = await rpc(\n delegate,\n {op: 'metrics'},\n inspectMetricsDownSchema,\n );\n return {\n ...(clientMetrics ?? newClientMetrics()),\n 'query-materialization-server': TDigest.fromJSON(\n serverMetricsJSON['query-materialization-server'],\n ),\n 'query-update-server': TDigest.fromJSON(\n serverMetricsJSON['query-update-server'],\n ),\n };\n}\n\nexport function inspectorClients(\n delegate: ExtendedInspectorDelegate,\n): Promise<Client[]> {\n return withDagRead(delegate, dagRead => clients(delegate, dagRead));\n}\n\nexport function inspectorClientsWithQueries(\n delegate: ExtendedInspectorDelegate,\n): Promise<Client[]> {\n return withDagRead(delegate, dagRead =>\n clientsWithQueries(delegate, dagRead),\n );\n}\n\nasync function withDagRead<T>(\n delegate: ExtendedInspectorDelegate,\n f: (dagRead: Read) => Promise<T>,\n): Promise<T> {\n const {rep} = delegate;\n await rep.refresh();\n await rep.persist();\n return withRead(rep.perdag, f);\n}\n\nasync function getBTree(dagRead: Read, clientID: string): Promise<BTreeRead> {\n const client = await getClient(clientID, dagRead);\n assert(client, `Client not found: ${clientID}`);\n const {clientGroupID} = client;\n const clientGroup = await getClientGroup(clientGroupID, dagRead);\n assert(clientGroup, `Client group not found: ${clientGroupID}`);\n const dbRead = await readFromHash(\n clientGroup.headHash,\n dagRead,\n FormatVersion.Latest,\n );\n return dbRead.map;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\ntype MapEntry<T extends ReadonlyMap<any, any>> =\n T extends ReadonlyMap<infer K, infer V> ? readonly [K, V] : never;\n\nasync function clients(\n delegate: ExtendedInspectorDelegate,\n dagRead: Read,\n predicate: (entry: MapEntry<ClientMap>) => boolean = () => true,\n): Promise<Client[]> {\n const clients = await getClients(dagRead);\n return [...clients.entries()]\n .filter(predicate)\n .map(\n ([clientID, {clientGroupID}]) =>\n new Client(delegate, clientID, clientGroupID),\n );\n}\n\nasync function clientsWithQueries(\n delegate: ExtendedInspectorDelegate,\n dagRead: Read,\n predicate: (entry: MapEntry<ClientMap>) => boolean = () => true,\n): Promise<Client[]> {\n const allClients = await clients(delegate, dagRead, predicate);\n const clientsWithQueries: Client[] = [];\n await Promise.all(\n allClients.map(async client => {\n const queries = await client.queries();\n if (queries.length > 0) {\n clientsWithQueries.push(client);\n }\n }),\n );\n return clientsWithQueries;\n}\n\nexport async function clientGroupClients(\n delegate: ExtendedInspectorDelegate,\n clientGroupID: Promise<string> | string,\n): Promise<Client[]> {\n const id = await clientGroupID;\n return withDagRead(delegate, dagRead =>\n clients(delegate, dagRead, ([_, v]) => v.clientGroupID === id),\n );\n}\n\nexport async function clientGroupClientsWithQueries(\n delegate: ExtendedInspectorDelegate,\n clientGroupID: Promise<string> | string,\n): Promise<Client[]> {\n const id = await clientGroupID;\n return withDagRead(delegate, dagRead =>\n clientsWithQueries(delegate, dagRead, ([_, v]) => v.clientGroupID === id),\n );\n}\n\nexport function clientGroupQueries(\n delegate: ExtendedInspectorDelegate,\n): Promise<Query[]> {\n return queries(delegate, {op: 'queries'});\n}\nexport function clientMap(\n delegate: ExtendedInspectorDelegate,\n clientID: string,\n): Promise<Map<string, ReadonlyJSONValue>> {\n return withDagRead(delegate, async dagRead => {\n const tree = await getBTree(dagRead, clientID);\n const map = new Map<string, ReadonlyJSONValue>();\n for await (const [key, value] of tree.scan('', true)) {\n map.set(key, value);\n }\n return map;\n });\n}\n\nexport function clientRows(\n delegate: ExtendedInspectorDelegate,\n clientID: string,\n tableName: string,\n): Promise<Row[]> {\n return withDagRead(delegate, async dagRead => {\n const prefix = ENTITIES_KEY_PREFIX + tableName + '/';\n const tree = await getBTree(dagRead, clientID);\n const rows: Row[] = [];\n for await (const [key, value] of tree.scan(prefix, false)) {\n if (!key.startsWith(prefix)) {\n break;\n }\n rows.push(value as Row);\n }\n return rows;\n });\n}\n\nexport function serverVersion(\n delegate: ExtendedInspectorDelegate,\n): Promise<string> {\n return rpc(delegate, {op: 'version'}, inspectVersionDownSchema);\n}\n\nexport function clientQueries(\n delegate: ExtendedInspectorDelegate,\n clientID: string,\n): Promise<Query[]> {\n return queries(delegate, {op: 'queries', clientID});\n}\n\nasync function queries(\n delegate: ExtendedInspectorDelegate,\n arg: {op: 'queries'; clientID?: string},\n): Promise<Query[]> {\n const rows: InspectQueryRow[] = await rpc(\n delegate,\n arg,\n inspectQueriesDownSchema,\n );\n const queries = rows.map(row => new Query(row, delegate));\n queries.sort((a, b) => (b.hydrateServer ?? 0) - (a.hydrateServer ?? 0));\n return queries;\n}\n\nexport function analyzeQuery(\n delegate: ExtendedInspectorDelegate,\n query: AnyQuery,\n options?: AnalyzeQueryOptions,\n): Promise<AnalyzeQueryResult> {\n const qi = asQueryInternals(query);\n const {customQueryID} = qi;\n const queryParameters = customQueryID\n ? {name: customQueryID.name, args: customQueryID.args}\n : {ast: delegate.mapClientASTToServer(qi.ast)};\n\n return rpc(\n delegate,\n {\n op: 'analyze-query',\n ...queryParameters,\n options,\n },\n inspectAnalyzeQueryDownSchema,\n );\n}\n\nexport function analyzeServerAST(\n delegate: ExtendedInspectorDelegate,\n ast: AST,\n options?: AnalyzeQueryOptions,\n): Promise<AnalyzeQueryResult> {\n return rpc(\n delegate,\n {op: 'analyze-query', ast, options},\n inspectAnalyzeQueryDownSchema,\n );\n}\n\nexport function analyzeNamedQuery(\n delegate: ExtendedInspectorDelegate,\n name: string,\n args: ReadonlyArray<ReadonlyJSONValue>,\n options?: AnalyzeQueryOptions,\n): Promise<AnalyzeQueryResult> {\n return rpc(\n delegate,\n {op: 'analyze-query', name, args, options},\n inspectAnalyzeQueryDownSchema,\n );\n}\n\n/**\n * Sends an `authenticate` op to the server. Returns `true` if the password\n * is accepted (or the server is in a development mode that bypasses the\n * check). Use this from Node contexts where the default interactive HTML\n * prompt used by other inspector RPCs is not available.\n *\n * The server authenticates each connection separately. An accepted password\n * is remembered and presented again when the connection is re-established.\n */\nexport async function authenticate(\n delegate: ExtendedInspectorDelegate,\n password: string,\n): Promise<boolean> {\n const ok = await authenticateSocket(await delegate.getSocket(), password);\n if (ok) {\n delegate.adminPassword = password;\n }\n return ok;\n}\n\nclass UnauthenticatedError extends Error {}\n\nexport interface InspectorDelegate {\n getQueryMetrics(hash: string): QueryClientMetrics | undefined;\n getAST(queryID: string): AST | undefined;\n readonly metrics: ClientMetrics;\n mapClientASTToServer(ast: AST): AST;\n setQueryEvictedCallback(\n cb: (hash: string, ast: AST, metrics: QueryClientMetrics) => void,\n ): void;\n}\n\nexport interface ExtendedInspectorDelegate extends InspectorDelegate {\n readonly rep: Rep;\n readonly getSocket: () => Promise<WebSocket>;\n readonly queryDelegate: QueryDelegate;\n lazy: Promise<Lazy>;\n /**\n * The admin password the server last accepted, if any. Authentication is\n * per connection, so it is presented again after a reconnect before the\n * user is prompted.\n */\n adminPassword?: string | undefined;\n}\n\nexport type Rep = ReplicacheImpl<MutatorDefs>;\n\nexport type QueryClientMetrics = {\n readonly 'query-materialization-client': number | undefined;\n readonly 'query-materialization-end-to-end': number | undefined;\n readonly 'query-update-client': ReadonlyTDigest;\n};\n\nexport type ClientMetrics = {\n readonly [K in keyof ClientMetricMap]: ReadonlyTDigest;\n};\n\nexport type ServerMetrics = {\n readonly [K in keyof ServerMetricMap]: ReadonlyTDigest;\n};\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import type { AnalyzeQueryResult } from '../../../../zero-protocol/src/analyze-q
|
|
|
3
3
|
import { type InspectQueryRow } from '../../../../zero-protocol/src/inspect-down.ts';
|
|
4
4
|
import type { AnalyzeQueryOptions } from '../../../../zero-protocol/src/inspect-up.ts';
|
|
5
5
|
import { type TTL } from '../../../../zql/src/query/ttl.ts';
|
|
6
|
-
import { type ExtendedInspectorDelegate, type
|
|
6
|
+
import { type ExtendedInspectorDelegate, type Metrics } from './lazy-inspector.ts';
|
|
7
7
|
export declare class Query {
|
|
8
8
|
#private;
|
|
9
9
|
readonly name: string | null;
|
|
@@ -25,7 +25,7 @@ export declare class Query {
|
|
|
25
25
|
readonly updateClientP95: number | null;
|
|
26
26
|
readonly updateServerP50: number | null;
|
|
27
27
|
readonly updateServerP95: number | null;
|
|
28
|
-
constructor(row: InspectQueryRow, delegate: ExtendedInspectorDelegate
|
|
28
|
+
constructor(row: InspectQueryRow, delegate: ExtendedInspectorDelegate);
|
|
29
29
|
analyze(options?: AnalyzeQueryOptions): Promise<AnalyzeQueryResult>;
|
|
30
30
|
}
|
|
31
31
|
//# sourceMappingURL=query.d.ts.map
|