@rocicorp/zero 1.2.0-canary.10 → 1.2.0-canary.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/analyze-query/src/bin-analyze.js +25 -25
- package/out/analyze-query/src/bin-analyze.js.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.js +2 -1
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/replicache/src/btree/node.d.ts.map +1 -1
- package/out/replicache/src/btree/node.js +2 -2
- package/out/replicache/src/btree/node.js.map +1 -1
- package/out/replicache/src/connection-loop.js +3 -3
- package/out/replicache/src/connection-loop.js.map +1 -1
- package/out/replicache/src/deleted-clients.d.ts +0 -4
- package/out/replicache/src/deleted-clients.d.ts.map +1 -1
- package/out/replicache/src/deleted-clients.js +1 -1
- package/out/replicache/src/deleted-clients.js.map +1 -1
- package/out/replicache/src/hash.d.ts.map +1 -1
- package/out/replicache/src/hash.js.map +1 -1
- package/out/replicache/src/process-scheduler.d.ts.map +1 -1
- package/out/replicache/src/process-scheduler.js.map +1 -1
- package/out/replicache/src/request-idle.js +1 -1
- package/out/replicache/src/request-idle.js.map +1 -1
- package/out/replicache/src/sync/patch.d.ts +1 -1
- package/out/replicache/src/sync/patch.d.ts.map +1 -1
- package/out/replicache/src/sync/patch.js +1 -1
- package/out/replicache/src/sync/patch.js.map +1 -1
- package/out/shared/src/arrays.d.ts.map +1 -1
- package/out/shared/src/arrays.js +1 -2
- package/out/shared/src/arrays.js.map +1 -1
- package/out/shared/src/bigint-json.js +1 -1
- package/out/shared/src/bigint-json.js.map +1 -1
- package/out/shared/src/btree-set.js +1 -1
- package/out/shared/src/btree-set.js.map +1 -1
- package/out/shared/src/iterables.d.ts +7 -0
- package/out/shared/src/iterables.d.ts.map +1 -1
- package/out/shared/src/iterables.js +10 -1
- package/out/shared/src/iterables.js.map +1 -1
- package/out/shared/src/logging.d.ts.map +1 -1
- package/out/shared/src/logging.js +10 -9
- package/out/shared/src/logging.js.map +1 -1
- package/out/shared/src/options.js +1 -1
- package/out/shared/src/options.js.map +1 -1
- package/out/shared/src/tdigest-schema.d.ts.map +1 -1
- package/out/shared/src/tdigest-schema.js.map +1 -1
- package/out/shared/src/tdigest.d.ts.map +1 -1
- package/out/shared/src/tdigest.js +7 -7
- package/out/shared/src/tdigest.js.map +1 -1
- package/out/shared/src/valita.d.ts.map +1 -1
- package/out/shared/src/valita.js +1 -1
- package/out/shared/src/valita.js.map +1 -1
- package/out/z2s/src/sql.d.ts.map +1 -1
- package/out/z2s/src/sql.js +2 -1
- package/out/z2s/src/sql.js.map +1 -1
- package/out/zero/package.js +5 -6
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/pg.js +1 -1
- package/out/zero/src/server.js +1 -1
- package/out/zero-cache/src/auth/load-permissions.js +1 -1
- package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +8 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +16 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +2 -0
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +1 -1
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/db/migration.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration.js +1 -1
- package/out/zero-cache/src/db/migration.js.map +1 -1
- package/out/zero-cache/src/db/pg-copy-binary.d.ts +101 -0
- package/out/zero-cache/src/db/pg-copy-binary.d.ts.map +1 -0
- package/out/zero-cache/src/db/pg-copy-binary.js +381 -0
- package/out/zero-cache/src/db/pg-copy-binary.js.map +1 -0
- package/out/zero-cache/src/db/warmup.d.ts.map +1 -1
- package/out/zero-cache/src/db/warmup.js +3 -1
- package/out/zero-cache/src/db/warmup.js.map +1 -1
- package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +1 -1
- package/out/zero-cache/src/server/anonymous-otel-start.js +2 -1
- package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
- package/out/zero-cache/src/server/main.js +1 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +8 -10
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
- package/out/zero-cache/src/server/worker-urls.js +2 -1
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/change-source/change-source.d.ts +4 -0
- package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +7 -4
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +9 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +85 -5
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -3
- 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 +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-schema.js +4 -3
- package/out/zero-cache/src/services/view-syncer/client-schema.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +2 -2
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +4 -3
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +3 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +2 -2
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/lite.d.ts.map +1 -1
- package/out/zero-cache/src/types/lite.js +3 -2
- package/out/zero-cache/src/types/lite.js.map +1 -1
- package/out/zero-cache/src/types/pg-types.js +4 -1
- package/out/zero-cache/src/types/pg-types.js.map +1 -1
- package/out/zero-cache/src/types/pg.d.ts.map +1 -1
- package/out/zero-cache/src/types/pg.js +6 -1
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
- package/out/zero-cache/src/types/subscription.js +2 -2
- package/out/zero-cache/src/types/subscription.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +2 -2
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js +18 -2
- package/out/zero-cache/src/workers/syncer-ws-message-handler.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +5 -5
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/http-string.d.ts.map +1 -1
- package/out/zero-client/src/client/http-string.js.map +1 -1
- package/out/zero-client/src/client/metrics.d.ts.map +1 -1
- package/out/zero-client/src/client/metrics.js +2 -1
- package/out/zero-client/src/client/metrics.js.map +1 -1
- package/out/zero-client/src/client/server-option.js +1 -1
- package/out/zero-client/src/client/server-option.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.d.ts.map +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js +1 -1
- package/out/zero-client/src/client/zero-poke-handler.js.map +1 -1
- package/out/zero-pg/src/mod.js +1 -1
- package/out/zero-protocol/src/ast.d.ts.map +1 -1
- package/out/zero-protocol/src/ast.js.map +1 -1
- package/out/zero-protocol/src/primary-key.d.ts.map +1 -1
- package/out/zero-protocol/src/primary-key.js.map +1 -1
- package/out/zero-protocol/src/push.d.ts.map +1 -1
- package/out/zero-protocol/src/push.js.map +1 -1
- package/out/zero-schema/src/name-mapper.js +1 -1
- package/out/zero-schema/src/name-mapper.js.map +1 -1
- package/out/zero-server/src/mod.js +1 -1
- package/out/zero-server/src/process-mutations.d.ts.map +1 -1
- package/out/zero-server/src/process-mutations.js +2 -1
- package/out/zero-server/src/process-mutations.js.map +1 -1
- package/out/zero-server/src/push-processor.d.ts +1 -0
- package/out/zero-server/src/push-processor.d.ts.map +1 -1
- package/out/zero-server/src/push-processor.js +3 -2
- package/out/zero-server/src/push-processor.js.map +1 -1
- package/out/zql/src/builder/like.js +2 -1
- package/out/zql/src/builder/like.js.map +1 -1
- package/out/zql/src/ivm/data.d.ts.map +1 -1
- package/out/zql/src/ivm/data.js +6 -15
- package/out/zql/src/ivm/data.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +2 -4
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/query/complete-ordering.js +1 -1
- package/out/zql/src/query/complete-ordering.js.map +1 -1
- package/out/zql/src/query/query-impl.d.ts.map +1 -1
- package/out/zql/src/query/query-impl.js +2 -2
- package/out/zql/src/query/query-impl.js.map +1 -1
- package/out/zql/src/query/query-registry.d.ts.map +1 -1
- package/out/zql/src/query/query-registry.js +2 -1
- package/out/zql/src/query/query-registry.js.map +1 -1
- package/out/zql/src/query/ttl.js +1 -1
- package/out/zql/src/query/ttl.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +1 -1
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +1 -1
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +5 -6
|
@@ -53,7 +53,7 @@ var Syncer = class {
|
|
|
53
53
|
subscribeTo(lc, parent);
|
|
54
54
|
this.#lc = lc;
|
|
55
55
|
this.#viewSyncers = new ServiceRunner(lc, (id) => viewSyncerFactory(id, notifier.subscribe(), this.#drainCoordinator, this.#validateLegacyJWT()), (v) => v.keepalive());
|
|
56
|
-
this.#mutagens = new ServiceRunner(lc, mutagenFactory, (m) => m.hasRefs());
|
|
56
|
+
if (mutagenFactory) this.#mutagens = new ServiceRunner(lc, mutagenFactory, (m) => m.hasRefs());
|
|
57
57
|
if (pusherFactory) this.#pushers = new ServiceRunner(lc, pusherFactory, (p) => p.hasRefs());
|
|
58
58
|
this.#parent = parent;
|
|
59
59
|
this.#wss = new WebSocketServer(getWebSocketServerOptions(config));
|
|
@@ -82,19 +82,19 @@ var Syncer = class {
|
|
|
82
82
|
this.#lc.debug?.(`client ${clientID} already connected, closing existing connection`);
|
|
83
83
|
existing.close(`replaced by ${params.wsID}`);
|
|
84
84
|
}
|
|
85
|
-
const mutagen = this.#mutagens
|
|
85
|
+
const mutagen = this.#mutagens?.getService(clientGroupID);
|
|
86
86
|
const pusher = this.#pushers?.getService(clientGroupID);
|
|
87
|
-
mutagen
|
|
87
|
+
mutagen?.ref();
|
|
88
88
|
pusher?.ref();
|
|
89
89
|
let connection;
|
|
90
90
|
try {
|
|
91
91
|
connection = new Connection(this.#lc, params, ws, new SyncerWsMessageHandler(this.#lc, params, viewSyncer, mutagen, pusher), () => {
|
|
92
92
|
if (this.#connections.get(clientID) === connection) this.#connections.delete(clientID);
|
|
93
|
-
mutagen
|
|
93
|
+
mutagen?.unref();
|
|
94
94
|
pusher?.unref();
|
|
95
95
|
});
|
|
96
96
|
} catch (e) {
|
|
97
|
-
mutagen
|
|
97
|
+
mutagen?.unref();
|
|
98
98
|
pusher?.unref();
|
|
99
99
|
throw e;
|
|
100
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.js","names":["#lc","#viewSyncers","#mutagens","#pushers","#connections","#drainCoordinator","#parent","#wss","#stopped","#config","#validateLegacyJWT","#createConnection"],"sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {pid} from 'node:process';\nimport type {MessagePort} from 'node:worker_threads';\nimport {WebSocketServer, type ServerOptions, type WebSocket} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {type ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.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 {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {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 {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type SyncerWorkerData = {\n replicatorPort: MessagePort;\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>;\n readonly #pushers: ServiceRunner<Pusher & Service> | undefined;\n readonly #connections = new Map<string, Connection>();\n readonly #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: (id: string) => Mutagen & Service,\n pusherFactory: ((id: string) => Pusher & Service) | undefined,\n parent: Worker,\n ) {\n this.#config = config;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent);\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id =>\n viewSyncerFactory(\n id,\n notifier.subscribe(),\n this.#drainCoordinator,\n this.#validateLegacyJWT(),\n ),\n v => v.keepalive(),\n );\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(lc, pusherFactory, p => p.hasRefs());\n }\n this.#parent = parent;\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#createConnection,\n this.#parent,\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\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\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 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 const viewSyncer = this.#viewSyncers.getService(clientGroupID);\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS\n const authResult = await viewSyncer.initAuthSession(userID, auth);\n if (!authResult.ok) {\n sendError(this.#lc, ws, authResult.error);\n ws.close(3000, authResult.error.message);\n return;\n }\n\n // Only check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\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 new SyncerWsMessageHandler(\n this.#lc,\n params,\n viewSyncer,\n mutagen,\n pusher,\n ),\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 mutagen.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n connection.init() && recordConnectionSuccess();\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 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 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 this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n\n /** @deprecated used in JWT validation */\n #validateLegacyJWT(): ValidateLegacyJWT | undefined {\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n if (tokenOptions.length !== 1) {\n return undefined;\n }\n\n return async (token, {userID}) => {\n const decoded = await verifyToken(this.#config.auth, token, {\n subject: userID,\n ...(this.#config.auth?.issuer && {issuer: this.#config.auth.issuer}),\n ...(this.#config.auth?.audience && {\n audience: this.#config.auth.audience,\n }),\n });\n return {\n type: 'jwt',\n raw: token,\n decoded,\n };\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"syncer.js","names":["#lc","#viewSyncers","#mutagens","#pushers","#connections","#drainCoordinator","#parent","#wss","#stopped","#config","#validateLegacyJWT","#createConnection"],"sources":["../../../../../zero-cache/src/workers/syncer.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {pid} from 'node:process';\nimport type {MessagePort} from 'node:worker_threads';\nimport {WebSocketServer, type ServerOptions, type WebSocket} from 'ws';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport {type ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport {type ZeroConfig} from '../config/zero-config.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 {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport type {ViewSyncer} from '../services/view-syncer/view-syncer.ts';\nimport type {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 {createNotifierFrom, subscribeTo} from './replicator.ts';\nimport {SyncerWsMessageHandler} from './syncer-ws-message-handler.ts';\n\nexport type SyncerWorkerData = {\n replicatorPort: MessagePort;\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 #drainCoordinator = new DrainCoordinator();\n readonly #parent: Worker;\n readonly #wss: WebSocketServer;\n readonly #stopped = resolver();\n readonly #config: ZeroConfig;\n\n constructor(\n lc: LogContext,\n config: ZeroConfig,\n viewSyncerFactory: (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n validateLegacyJWT: ValidateLegacyJWT | undefined,\n ) => ViewSyncer & ActivityBasedService,\n mutagenFactory: ((id: string) => Mutagen & Service) | undefined,\n pusherFactory: ((id: string) => Pusher & Service) | undefined,\n parent: Worker,\n ) {\n this.#config = config;\n // Relays notifications from the parent thread subscription\n // to ViewSyncers within this thread.\n const notifier = createNotifierFrom(lc, parent);\n subscribeTo(lc, parent);\n\n this.#lc = lc;\n this.#viewSyncers = new ServiceRunner(\n lc,\n id =>\n viewSyncerFactory(\n id,\n notifier.subscribe(),\n this.#drainCoordinator,\n this.#validateLegacyJWT(),\n ),\n v => v.keepalive(),\n );\n if (mutagenFactory) {\n this.#mutagens = new ServiceRunner(lc, mutagenFactory, m => m.hasRefs());\n }\n if (pusherFactory) {\n this.#pushers = new ServiceRunner(lc, pusherFactory, p => p.hasRefs());\n }\n this.#parent = parent;\n this.#wss = new WebSocketServer(getWebSocketServerOptions(config));\n\n installWebSocketReceiver(\n lc,\n this.#wss,\n this.#createConnection,\n this.#parent,\n );\n\n setActiveClientGroupsGetter(() => this.#viewSyncers.size);\n }\n\n readonly #createConnection = async (ws: WebSocket, params: ConnectParams) => {\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\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 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 const viewSyncer = this.#viewSyncers.getService(clientGroupID);\n\n // Verify JWT BEFORE touching existing connections - prevents unauthenticated\n // attackers from force-disconnecting legitimate users via DoS\n const authResult = await viewSyncer.initAuthSession(userID, auth);\n if (!authResult.ok) {\n sendError(this.#lc, ws, authResult.error);\n ws.close(3000, authResult.error.message);\n return;\n }\n\n // Only check for and close existing connections AFTER auth is validated\n const existing = this.#connections.get(clientID);\n if (existing) {\n this.#lc.debug?.(\n `client ${clientID} already connected, closing existing connection`,\n );\n existing.close(`replaced by ${params.wsID}`);\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 new SyncerWsMessageHandler(\n this.#lc,\n params,\n viewSyncer,\n mutagen,\n pusher,\n ),\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 mutagen?.unref();\n pusher?.unref();\n throw e;\n }\n\n this.#connections.set(clientID, connection);\n\n connection.init() && recordConnectionSuccess();\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 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 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 this.#wss.close();\n this.#stopped.resolve();\n return promiseVoid;\n }\n\n /** @deprecated used in JWT validation */\n #validateLegacyJWT(): ValidateLegacyJWT | undefined {\n const tokenOptions = tokenConfigOptions(this.#config.auth ?? {});\n if (tokenOptions.length !== 1) {\n return undefined;\n }\n\n return async (token, {userID}) => {\n const decoded = await verifyToken(this.#config.auth, token, {\n subject: userID,\n ...(this.#config.auth?.issuer && {issuer: this.#config.auth.issuer}),\n ...(this.#config.auth?.audience && {\n audience: this.#config.auth.audience,\n }),\n });\n return {\n type: 'jwt',\n raw: token,\n decoded,\n };\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-string.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/http-string.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAAG,OAAO,EAAE,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC;AAEnD,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,CAEpD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,GAAG,UAAU,CAEtD;
|
|
1
|
+
{"version":3,"file":"http-string.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/http-string.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,UAAU,GAAG,OAAO,EAAE,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC;AAEnD,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,QAAQ,CAEpD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,GAAG,UAAU,CAEtD;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,CAEvE;AAID,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,QAAQ,CAEnE;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,UAAU,GAAG,QAAQ,EACxD,GAAG,EAAE,CAAC,EACN,QAAQ,EAAE,IAAI,MAAM,EAAE,GACrB,CAAC,CAEH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-string.js","names":[],"sources":["../../../../../zero-client/src/client/http-string.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\n\nexport type HTTPString = `http${'' | 's'}://${string}`;\n\nexport type WSString = `ws${'' | 's'}://${string}`;\n\nexport function toWSString(url: HTTPString): WSString {\n return ('ws' + url.slice(4)) as WSString;\n}\n\nexport function toHTTPString(url: WSString): HTTPString {\n return ('http' + url.slice(2)) as HTTPString;\n}\n\nexport function assertHTTPString(url: string): asserts url is HTTPString {\n assert(
|
|
1
|
+
{"version":3,"file":"http-string.js","names":[],"sources":["../../../../../zero-client/src/client/http-string.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\n\nexport type HTTPString = `http${'' | 's'}://${string}`;\n\nexport type WSString = `ws${'' | 's'}://${string}`;\n\nexport function toWSString(url: HTTPString): WSString {\n return ('ws' + url.slice(4)) as WSString;\n}\n\nexport function toHTTPString(url: WSString): HTTPString {\n return ('http' + url.slice(2)) as HTTPString;\n}\n\nconst httpsRe = /^https?:\\/\\//;\n\nexport function assertHTTPString(url: string): asserts url is HTTPString {\n assert(httpsRe.test(url), () => `Expected HTTP(S) URL, got \"${url}\"`);\n}\n\nconst wssRe = /^wss?:\\/\\//;\n\nexport function assertWSString(url: string): asserts url is WSString {\n assert(wssRe.test(url), () => `Expected WS(S) URL, got \"${url}\"`);\n}\n\nexport function appendPath<T extends HTTPString | WSString>(\n url: T,\n toAppend: `/${string}`,\n): T {\n return (url + (url.endsWith('/') ? toAppend.substring(1) : toAppend)) as T;\n}\n"],"mappings":";AAMA,SAAgB,WAAW,KAA2B;AACpD,QAAQ,OAAO,IAAI,MAAM,EAAE;;AAmB7B,SAAgB,WACd,KACA,UACG;AACH,QAAQ,OAAO,IAAI,SAAS,IAAI,GAAG,SAAS,UAAU,EAAE,GAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,YAAY,CAAC;AAOpB,eAAO,MAAM,qBAAqB,QAAa,CAAC;AAEhD,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AASxC,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,2CAEzD;
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAGL,KAAK,SAAS,EAEf,MAAM,YAAY,CAAC;AAOpB,eAAO,MAAM,qBAAqB,QAAa,CAAC;AAEhD,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AASxC,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,2CAEzD;AAWD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAcnE;AAED,KAAK,eAAe,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,CAAC,IAAI,CAAC,CAAC;AAEjE,MAAM,MAAM,oBAAoB,GAAG;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,EAAE,EAAE,UAAU,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,qBAAa,aAAa;;gBAOZ,IAAI,EAAE,oBAAoB;IAiCtC,QAAQ,CAAC,eAAe,QAEtB;IAMF,QAAQ,CAAC,gBAAgB,QAKvB;IAqCF,YAAY,CAAC,eAAe,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM;IAOlE,gCAAgC;IAmBhC,eAAe,CAAC,MAAM,EAAE,SAAS;IAOjC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAM;IAIvB,KAAK;IA4BX,IAAI;CAaL;AAMD,6DAA6D;AAC7D,MAAM,MAAM,MAAM,GAAG;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AACF;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAMvC,KAAK,SAAS,GAAG;IACf,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC,GAAG,SAAS,CAAC;CACxD,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAM,YAAW,SAAS;;gBAIzB,IAAI,EAAE,MAAM;IAIxB,GAAG,CAAC,KAAK,EAAE,MAAM;IAIjB,GAAG;IAIH,KAAK;IAIL,KAAK;;;;CASN;AAMD;;;;;;;;;;;;GAYG;AACH,qBAAa,KAAM,YAAW,SAAS;;gBAKzB,MAAM,EAAE,MAAM,EAAE,YAAY,UAAQ;IAKhD,GAAG,CAAC,KAAK,EAAE,MAAM;IAIjB,GAAG;IAIH,KAAK;IAIL,KAAK;;;;CAYN"}
|
|
@@ -7,8 +7,9 @@ var REPORT_INTERVAL_MS = 5e3;
|
|
|
7
7
|
function getLastConnectErrorValue(reason) {
|
|
8
8
|
return `${isServerError(reason) ? "server_" : "client_"}${camelToSnake(reason.kind)}`;
|
|
9
9
|
}
|
|
10
|
+
var upperCasesRegExp = /\.?(?=[A-Z])/;
|
|
10
11
|
function camelToSnake(kind) {
|
|
11
|
-
return kind.split(
|
|
12
|
+
return kind.split(upperCasesRegExp).join("_").toLowerCase();
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
15
|
* Returns whether an error should be reported in metrics and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","names":["#reportIntervalMs","#host","#reporter","#lc","#setNotConnectedReason","#timerID","#register","#notConnected","#timeToConnectMsV2","#lastConnectErrorV2","#totalTimeToConnectMs","#metrics","#name","#value","#prefix","#clearOnFlush","#current"],"sources":["../../../../../zero-client/src/client/metrics.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport {ClientErrorKind} from './client-error-kind.ts';\nimport {\n isClientError,\n isServerError,\n type ZeroError,\n type ZeroErrorKind,\n} from './error.ts';\nimport {MetricName} from './metric-name.ts';\n\n// This value is used to indicate that the client's last connection attempt\n// failed. We don't make this -1 because we want to stack this never connected\n// state in a graph on top of actual connection times, so it should be greater\n// than any other value.\nexport const DID_NOT_CONNECT_VALUE = 100 * 1000;\n\nexport const REPORT_INTERVAL_MS = 5_000;\n\ntype NotConnectedReason =\n | 'init'\n | 'error'\n | 'hidden'\n | 'hidden_was_init'\n | 'hidden_was_error';\n\nexport function getLastConnectErrorValue(reason: ZeroError) {\n return `${isServerError(reason) ? 'server_' : 'client_'}${camelToSnake(reason.kind)}` as const;\n}\n\n// camelToSnake is used to convert a ZeroErrorKind into a suitable\n// metric name, eg AuthInvalidated => auth_invalidated. It converts\n// both PascalCase and camelCase to snake_case.\nfunction camelToSnake(kind: ZeroErrorKind): string {\n return kind\n .split(/\\.?(?=[A-Z])/)\n .join('_')\n .toLowerCase();\n}\n\n/**\n * Returns whether an error should be reported in metrics and\n * increment the connect error count.\n *\n * Returns `true` for all server errors and client errors that represent actual\n * connection problems. Returns `false` for expected client-side disconnections\n * (user disconnect, client closed, hidden tab, clean/abrupt close).\n */\nexport function shouldReportConnectError(reason: ZeroError): boolean {\n if (!isClientError(reason)) {\n return true;\n }\n switch (reason.kind) {\n case ClientErrorKind.Hidden:\n case ClientErrorKind.ClientClosed:\n case ClientErrorKind.UserDisconnect:\n case ClientErrorKind.CleanClose:\n case ClientErrorKind.AbruptClose:\n return false;\n default:\n return true;\n }\n}\n\ntype MetricsReporter = (metrics: Series[]) => MaybePromise<void>;\n\nexport type MetricManagerOptions = {\n reportIntervalMs: number;\n host: string;\n source: string;\n reporter: MetricsReporter;\n lc: LogContext;\n};\n\n/**\n * MetricManager keeps track of the set of metrics in use and flushes them\n * to a format suitable for reporting.\n */\nexport class MetricManager {\n #reportIntervalMs: number;\n #host: string;\n #reporter: MetricsReporter;\n #lc: LogContext;\n #timerID: ReturnType<typeof setInterval> | null;\n\n constructor(opts: MetricManagerOptions) {\n this.#reportIntervalMs = opts.reportIntervalMs;\n this.#host = opts.host;\n this.#reporter = opts.reporter;\n this.#lc = opts.lc;\n\n this.tags.push(`source:${opts.source}`);\n\n this.timeToConnectMs.set(DID_NOT_CONNECT_VALUE);\n this.#setNotConnectedReason('init');\n\n this.#timerID = setInterval(() => {\n void this.flush();\n }, this.#reportIntervalMs);\n }\n\n #metrics: Flushable[] = [];\n\n // timeToConnectMs measures the time from the call to connect() to receiving\n // the 'connected' ws message. We record the DID_NOT_CONNECT_VALUE if the previous\n // connection attempt failed for any reason.\n //\n // We set the gauge using #connectStart as follows:\n // - #connectStart is undefined if we are disconnected or connected; it is\n // defined only in the Connecting state, as a number representing the timestamp\n // at which we started connecting.\n // - #connectStart is set to the current time when connect() is called.\n // - When we receive the 'connected' message we record the time to connect and\n // set #connectStart to undefined.\n // - If disconnect() is called with a defined #connectStart then we record\n // DID_NOT_CONNECT_VALUE and set #connectStart to undefined.\n //\n // TODO: this should be folded into the ConnectionManager.\n readonly timeToConnectMs = this.#register(\n new Gauge(MetricName.TimeToConnectMs),\n );\n\n // lastConnectError records the last error that occurred when connecting,\n // if any. It is cleared when connecting successfully or when reported, so this\n // state only gets reported if there was a failure during the reporting period and\n // we are still not connected.\n readonly lastConnectError = this.#register(\n new State(\n MetricName.LastConnectError,\n true, // clearOnFlush\n ),\n );\n\n // notConnected records the reason why the client is not currently connected.\n // It is cleared when the client successfully connects.\n readonly #notConnected = this.#register(new State(MetricName.NotConnected));\n\n // The time from the call to connect() to receiving the 'connected' ws message\n // for the current connection. Cleared when the client is not connected.\n // TODO: Not actually currently cleared on disconnect untill there is a\n // connect error, or client reports disconnected and waiting for visible.\n // Should have a value iff _notConnected has no value.\n readonly #timeToConnectMsV2 = this.#register(\n new Gauge(MetricName.TimeToConnectMsV2),\n );\n\n // lastConnectErrorV2 records the last error that occurred when connecting,\n // if any. It is cleared when the client successfully connects or\n // stops trying to connect due to being hidden.\n // Should have a value iff notConnected state is NotConnectedReason.Error.\n readonly #lastConnectErrorV2 = this.#register(\n new State(MetricName.LastConnectErrorV2),\n );\n\n // The total time it took to connect across retries for the current\n // connection. Cleared when the client is not connected.\n // TODO: Not actually currently cleared on disconnect until there is a\n // connect error, or client reports disconnected and waiting for visible.\n // See Zero.#totalToConnectStart for details of how this total is computed.\n // Should have a value iff _notConnected has no value.\n readonly #totalTimeToConnectMs = this.#register(\n new Gauge(MetricName.TotalTimeToConnectMs),\n );\n\n #setNotConnectedReason(reason: NotConnectedReason) {\n this.#notConnected.set(reason);\n }\n\n setConnected(timeToConnectMs: number, totalTimeToConnectMs: number) {\n this.#notConnected.clear();\n this.#lastConnectErrorV2.clear();\n this.#timeToConnectMsV2.set(timeToConnectMs);\n this.#totalTimeToConnectMs.set(totalTimeToConnectMs);\n }\n\n setDisconnectedWaitingForVisible() {\n this.#timeToConnectMsV2.clear();\n this.#totalTimeToConnectMs.clear();\n this.#lastConnectErrorV2.clear();\n let notConnectedReason: NotConnectedReason;\n switch (this.#notConnected.get()) {\n case 'init':\n notConnectedReason = 'hidden_was_init';\n break;\n case 'error':\n notConnectedReason = 'hidden_was_error';\n break;\n default:\n notConnectedReason = 'hidden';\n break;\n }\n this.#setNotConnectedReason(notConnectedReason);\n }\n\n setConnectError(reason: ZeroError) {\n this.#timeToConnectMsV2.clear();\n this.#totalTimeToConnectMs.clear();\n this.#setNotConnectedReason('error');\n this.#lastConnectErrorV2.set(getLastConnectErrorValue(reason));\n }\n\n /**\n * Tags to include in all metrics.\n */\n readonly tags: string[] = [];\n\n // Flushes all metrics to an array of time series (plural), one Series\n // per metric.\n async flush() {\n const lc = this.#lc;\n if (this.#timerID === null) {\n lc.error?.('MetricManager.flush() called but already stopped');\n return;\n }\n const allSeries: Series[] = [];\n for (const metric of this.#metrics) {\n const series = metric.flush();\n if (series !== undefined) {\n allSeries.push({\n ...series,\n host: this.#host,\n tags: this.tags,\n });\n }\n }\n if (allSeries.length === 0) {\n lc?.debug?.('No metrics to report');\n return;\n }\n try {\n await this.#reporter(allSeries);\n } catch (e) {\n lc?.error?.('Error reporting metrics', e);\n }\n }\n\n stop() {\n if (this.#timerID === null) {\n this.#lc.error?.('MetricManager.stop() called but already stopped');\n return;\n }\n clearInterval(this.#timerID);\n this.#timerID = null;\n }\n\n #register<M extends Flushable>(metric: M) {\n this.#metrics.push(metric);\n return metric;\n }\n}\n\n// These two types are influenced by Datadog's API's needs. We could change what\n// we use internally if necessary, but we'd just have to convert to/from before\n// sending to DD. So for convenience we go with their format.\n\n/** Series is a time series of points for a single metric. */\nexport type Series = {\n host: string;\n metric: string; // We call this 'name' bc 'metric' is overloaded in code.\n points: Point[];\n tags?: string[];\n};\n/**\n * A point is a second-resolution timestamp and a set of values for that\n * timestamp. A point represents exactly one second in time and the values\n * are those recorded for that second. The first element of this array\n * is the timestamp and the second element is an array of values.\n */\nexport type Point = [number, number[]];\n\nfunction makePoint(ts: number, value: number): Point {\n return [ts, [value]];\n}\n\ntype Flushable = {\n flush(): Pick<Series, 'metric' | 'points'> | undefined;\n};\n\n/**\n * Gauge is a metric type that represents a single value that can go up and\n * down. It's typically used to track discrete values or counts eg the number\n * of active users, number of connections, cpu load, etc. A gauge retains\n * its value when flushed.\n *\n * We use a Gauge to sample at the client. If we are interested in tracking\n * a metric value *per client*, the client can note the latest value in\n * a Gauge metric. The metric is periodically reported via Reporter. On the\n * server, we graph the value of the metric rolled up over the periodic\n * reporting period, that is, counted over a span of time equal to the\n * reporting period. The result is ~one point per client per reporting\n * period.\n */\nexport class Gauge implements Flushable {\n readonly #name: string;\n #value: number | undefined = undefined;\n\n constructor(name: string) {\n this.#name = name;\n }\n\n set(value: number) {\n this.#value = value;\n }\n\n get() {\n return this.#value;\n }\n\n clear() {\n this.#value = undefined;\n }\n\n flush() {\n if (this.#value === undefined) {\n return undefined;\n }\n // Gauge reports the timestamp at flush time, not at the point the value was\n // recorded.\n const points = [makePoint(t(), this.#value)];\n return {metric: this.#name, points};\n }\n}\n\nfunction t() {\n return Math.round(Date.now() / 1000);\n}\n\n/**\n * State is a metric type that represents a specific state that the system is\n * in, for example the state of a connection which may be 'open' or 'closed'.\n * The state is given a name/prefix at construction time (eg 'connection') and\n * then can be set to a specific state (eg 'open'). The prefix is prepended to\n * the set state (eg, 'connection_open') and a value of 1 is reported.\n * Unset/cleared states are not reported.\n *\n * Example:\n * const s = new State('connection');\n * s.set('open');\n * s.flush(); // returns {metric: 'connection_open', points: [[now(), [1]]]}\n */\nexport class State implements Flushable {\n readonly #prefix: string;\n readonly #clearOnFlush: boolean;\n #current: string | undefined = undefined;\n\n constructor(prefix: string, clearOnFlush = false) {\n this.#prefix = prefix;\n this.#clearOnFlush = clearOnFlush;\n }\n\n set(state: string) {\n this.#current = state;\n }\n\n get() {\n return this.#current;\n }\n\n clear() {\n this.#current = undefined;\n }\n\n flush() {\n if (this.#current === undefined) {\n return undefined;\n }\n const gauge = new Gauge([this.#prefix, this.#current].join('_'));\n gauge.set(1);\n const series = gauge.flush();\n if (this.#clearOnFlush) {\n this.clear();\n }\n return series;\n }\n}\n"],"mappings":";;;;AAeA,IAAa,wBAAwB,MAAM;AAE3C,IAAa,qBAAqB;AASlC,SAAgB,yBAAyB,QAAmB;AAC1D,QAAO,GAAG,cAAc,OAAO,GAAG,YAAY,YAAY,aAAa,OAAO,KAAK;;AAMrF,SAAS,aAAa,MAA6B;AACjD,QAAO,KACJ,MAAM,eAAe,CACrB,KAAK,IAAI,CACT,aAAa;;;;;;;;;;AAWlB,SAAgB,yBAAyB,QAA4B;AACnE,KAAI,CAAC,cAAc,OAAO,CACxB,QAAO;AAET,SAAQ,OAAO,MAAf;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH,QAAO;EACT,QACE,QAAO;;;;;;;AAkBb,IAAa,gBAAb,MAA2B;CACzB;CACA;CACA;CACA;CACA;CAEA,YAAY,MAA4B;AACtC,QAAA,mBAAyB,KAAK;AAC9B,QAAA,OAAa,KAAK;AAClB,QAAA,WAAiB,KAAK;AACtB,QAAA,KAAW,KAAK;AAEhB,OAAK,KAAK,KAAK,UAAU,KAAK,SAAS;AAEvC,OAAK,gBAAgB,IAAI,sBAAsB;AAC/C,QAAA,sBAA4B,OAAO;AAEnC,QAAA,UAAgB,kBAAkB;AAC3B,QAAK,OAAO;KAChB,MAAA,iBAAuB;;CAG5B,WAAwB,EAAE;CAiB1B,kBAA2B,MAAA,SACzB,IAAI,MAAM,gBAA2B,CACtC;CAMD,mBAA4B,MAAA,SAC1B,IAAI,MACF,kBACA,KACD,CACF;CAID,gBAAyB,MAAA,SAAe,IAAI,MAAM,aAAwB,CAAC;CAO3E,qBAA8B,MAAA,SAC5B,IAAI,MAAM,kBAA6B,CACxC;CAMD,sBAA+B,MAAA,SAC7B,IAAI,MAAM,mBAA8B,CACzC;CAQD,wBAAiC,MAAA,SAC/B,IAAI,MAAM,qBAAgC,CAC3C;CAED,uBAAuB,QAA4B;AACjD,QAAA,aAAmB,IAAI,OAAO;;CAGhC,aAAa,iBAAyB,sBAA8B;AAClE,QAAA,aAAmB,OAAO;AAC1B,QAAA,mBAAyB,OAAO;AAChC,QAAA,kBAAwB,IAAI,gBAAgB;AAC5C,QAAA,qBAA2B,IAAI,qBAAqB;;CAGtD,mCAAmC;AACjC,QAAA,kBAAwB,OAAO;AAC/B,QAAA,qBAA2B,OAAO;AAClC,QAAA,mBAAyB,OAAO;EAChC,IAAI;AACJ,UAAQ,MAAA,aAAmB,KAAK,EAAhC;GACE,KAAK;AACH,yBAAqB;AACrB;GACF,KAAK;AACH,yBAAqB;AACrB;GACF;AACE,yBAAqB;AACrB;;AAEJ,QAAA,sBAA4B,mBAAmB;;CAGjD,gBAAgB,QAAmB;AACjC,QAAA,kBAAwB,OAAO;AAC/B,QAAA,qBAA2B,OAAO;AAClC,QAAA,sBAA4B,QAAQ;AACpC,QAAA,mBAAyB,IAAI,yBAAyB,OAAO,CAAC;;;;;CAMhE,OAA0B,EAAE;CAI5B,MAAM,QAAQ;EACZ,MAAM,KAAK,MAAA;AACX,MAAI,MAAA,YAAkB,MAAM;AAC1B,MAAG,QAAQ,mDAAmD;AAC9D;;EAEF,MAAM,YAAsB,EAAE;AAC9B,OAAK,MAAM,UAAU,MAAA,SAAe;GAClC,MAAM,SAAS,OAAO,OAAO;AAC7B,OAAI,WAAW,KAAA,EACb,WAAU,KAAK;IACb,GAAG;IACH,MAAM,MAAA;IACN,MAAM,KAAK;IACZ,CAAC;;AAGN,MAAI,UAAU,WAAW,GAAG;AAC1B,OAAI,QAAQ,uBAAuB;AACnC;;AAEF,MAAI;AACF,SAAM,MAAA,SAAe,UAAU;WACxB,GAAG;AACV,OAAI,QAAQ,2BAA2B,EAAE;;;CAI7C,OAAO;AACL,MAAI,MAAA,YAAkB,MAAM;AAC1B,SAAA,GAAS,QAAQ,kDAAkD;AACnE;;AAEF,gBAAc,MAAA,QAAc;AAC5B,QAAA,UAAgB;;CAGlB,UAA+B,QAAW;AACxC,QAAA,QAAc,KAAK,OAAO;AAC1B,SAAO;;;AAuBX,SAAS,UAAU,IAAY,OAAsB;AACnD,QAAO,CAAC,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;AAqBtB,IAAa,QAAb,MAAwC;CACtC;CACA,SAA6B,KAAA;CAE7B,YAAY,MAAc;AACxB,QAAA,OAAa;;CAGf,IAAI,OAAe;AACjB,QAAA,QAAc;;CAGhB,MAAM;AACJ,SAAO,MAAA;;CAGT,QAAQ;AACN,QAAA,QAAc,KAAA;;CAGhB,QAAQ;AACN,MAAI,MAAA,UAAgB,KAAA,EAClB;EAIF,MAAM,SAAS,CAAC,UAAU,GAAG,EAAE,MAAA,MAAY,CAAC;AAC5C,SAAO;GAAC,QAAQ,MAAA;GAAY;GAAO;;;AAIvC,SAAS,IAAI;AACX,QAAO,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;;;;;;;;;;;;;;;AAgBtC,IAAa,QAAb,MAAwC;CACtC;CACA;CACA,WAA+B,KAAA;CAE/B,YAAY,QAAgB,eAAe,OAAO;AAChD,QAAA,SAAe;AACf,QAAA,eAAqB;;CAGvB,IAAI,OAAe;AACjB,QAAA,UAAgB;;CAGlB,MAAM;AACJ,SAAO,MAAA;;CAGT,QAAQ;AACN,QAAA,UAAgB,KAAA;;CAGlB,QAAQ;AACN,MAAI,MAAA,YAAkB,KAAA,EACpB;EAEF,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAA,QAAc,MAAA,QAAc,CAAC,KAAK,IAAI,CAAC;AAChE,QAAM,IAAI,EAAE;EACZ,MAAM,SAAS,MAAM,OAAO;AAC5B,MAAI,MAAA,aACF,MAAK,OAAO;AAEd,SAAO"}
|
|
1
|
+
{"version":3,"file":"metrics.js","names":["#reportIntervalMs","#host","#reporter","#lc","#setNotConnectedReason","#timerID","#register","#notConnected","#timeToConnectMsV2","#lastConnectErrorV2","#totalTimeToConnectMs","#metrics","#name","#value","#prefix","#clearOnFlush","#current"],"sources":["../../../../../zero-client/src/client/metrics.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport type {MaybePromise} from '../../../shared/src/types.ts';\nimport {ClientErrorKind} from './client-error-kind.ts';\nimport {\n isClientError,\n isServerError,\n type ZeroError,\n type ZeroErrorKind,\n} from './error.ts';\nimport {MetricName} from './metric-name.ts';\n\n// This value is used to indicate that the client's last connection attempt\n// failed. We don't make this -1 because we want to stack this never connected\n// state in a graph on top of actual connection times, so it should be greater\n// than any other value.\nexport const DID_NOT_CONNECT_VALUE = 100 * 1000;\n\nexport const REPORT_INTERVAL_MS = 5_000;\n\ntype NotConnectedReason =\n | 'init'\n | 'error'\n | 'hidden'\n | 'hidden_was_init'\n | 'hidden_was_error';\n\nexport function getLastConnectErrorValue(reason: ZeroError) {\n return `${isServerError(reason) ? 'server_' : 'client_'}${camelToSnake(reason.kind)}` as const;\n}\n\nconst upperCasesRegExp = /\\.?(?=[A-Z])/;\n\n// camelToSnake is used to convert a ZeroErrorKind into a suitable\n// metric name, eg AuthInvalidated => auth_invalidated. It converts\n// both PascalCase and camelCase to snake_case.\nfunction camelToSnake(kind: ZeroErrorKind): string {\n return kind.split(upperCasesRegExp).join('_').toLowerCase();\n}\n\n/**\n * Returns whether an error should be reported in metrics and\n * increment the connect error count.\n *\n * Returns `true` for all server errors and client errors that represent actual\n * connection problems. Returns `false` for expected client-side disconnections\n * (user disconnect, client closed, hidden tab, clean/abrupt close).\n */\nexport function shouldReportConnectError(reason: ZeroError): boolean {\n if (!isClientError(reason)) {\n return true;\n }\n switch (reason.kind) {\n case ClientErrorKind.Hidden:\n case ClientErrorKind.ClientClosed:\n case ClientErrorKind.UserDisconnect:\n case ClientErrorKind.CleanClose:\n case ClientErrorKind.AbruptClose:\n return false;\n default:\n return true;\n }\n}\n\ntype MetricsReporter = (metrics: Series[]) => MaybePromise<void>;\n\nexport type MetricManagerOptions = {\n reportIntervalMs: number;\n host: string;\n source: string;\n reporter: MetricsReporter;\n lc: LogContext;\n};\n\n/**\n * MetricManager keeps track of the set of metrics in use and flushes them\n * to a format suitable for reporting.\n */\nexport class MetricManager {\n #reportIntervalMs: number;\n #host: string;\n #reporter: MetricsReporter;\n #lc: LogContext;\n #timerID: ReturnType<typeof setInterval> | null;\n\n constructor(opts: MetricManagerOptions) {\n this.#reportIntervalMs = opts.reportIntervalMs;\n this.#host = opts.host;\n this.#reporter = opts.reporter;\n this.#lc = opts.lc;\n\n this.tags.push(`source:${opts.source}`);\n\n this.timeToConnectMs.set(DID_NOT_CONNECT_VALUE);\n this.#setNotConnectedReason('init');\n\n this.#timerID = setInterval(() => {\n void this.flush();\n }, this.#reportIntervalMs);\n }\n\n #metrics: Flushable[] = [];\n\n // timeToConnectMs measures the time from the call to connect() to receiving\n // the 'connected' ws message. We record the DID_NOT_CONNECT_VALUE if the previous\n // connection attempt failed for any reason.\n //\n // We set the gauge using #connectStart as follows:\n // - #connectStart is undefined if we are disconnected or connected; it is\n // defined only in the Connecting state, as a number representing the timestamp\n // at which we started connecting.\n // - #connectStart is set to the current time when connect() is called.\n // - When we receive the 'connected' message we record the time to connect and\n // set #connectStart to undefined.\n // - If disconnect() is called with a defined #connectStart then we record\n // DID_NOT_CONNECT_VALUE and set #connectStart to undefined.\n //\n // TODO: this should be folded into the ConnectionManager.\n readonly timeToConnectMs = this.#register(\n new Gauge(MetricName.TimeToConnectMs),\n );\n\n // lastConnectError records the last error that occurred when connecting,\n // if any. It is cleared when connecting successfully or when reported, so this\n // state only gets reported if there was a failure during the reporting period and\n // we are still not connected.\n readonly lastConnectError = this.#register(\n new State(\n MetricName.LastConnectError,\n true, // clearOnFlush\n ),\n );\n\n // notConnected records the reason why the client is not currently connected.\n // It is cleared when the client successfully connects.\n readonly #notConnected = this.#register(new State(MetricName.NotConnected));\n\n // The time from the call to connect() to receiving the 'connected' ws message\n // for the current connection. Cleared when the client is not connected.\n // TODO: Not actually currently cleared on disconnect untill there is a\n // connect error, or client reports disconnected and waiting for visible.\n // Should have a value iff _notConnected has no value.\n readonly #timeToConnectMsV2 = this.#register(\n new Gauge(MetricName.TimeToConnectMsV2),\n );\n\n // lastConnectErrorV2 records the last error that occurred when connecting,\n // if any. It is cleared when the client successfully connects or\n // stops trying to connect due to being hidden.\n // Should have a value iff notConnected state is NotConnectedReason.Error.\n readonly #lastConnectErrorV2 = this.#register(\n new State(MetricName.LastConnectErrorV2),\n );\n\n // The total time it took to connect across retries for the current\n // connection. Cleared when the client is not connected.\n // TODO: Not actually currently cleared on disconnect until there is a\n // connect error, or client reports disconnected and waiting for visible.\n // See Zero.#totalToConnectStart for details of how this total is computed.\n // Should have a value iff _notConnected has no value.\n readonly #totalTimeToConnectMs = this.#register(\n new Gauge(MetricName.TotalTimeToConnectMs),\n );\n\n #setNotConnectedReason(reason: NotConnectedReason) {\n this.#notConnected.set(reason);\n }\n\n setConnected(timeToConnectMs: number, totalTimeToConnectMs: number) {\n this.#notConnected.clear();\n this.#lastConnectErrorV2.clear();\n this.#timeToConnectMsV2.set(timeToConnectMs);\n this.#totalTimeToConnectMs.set(totalTimeToConnectMs);\n }\n\n setDisconnectedWaitingForVisible() {\n this.#timeToConnectMsV2.clear();\n this.#totalTimeToConnectMs.clear();\n this.#lastConnectErrorV2.clear();\n let notConnectedReason: NotConnectedReason;\n switch (this.#notConnected.get()) {\n case 'init':\n notConnectedReason = 'hidden_was_init';\n break;\n case 'error':\n notConnectedReason = 'hidden_was_error';\n break;\n default:\n notConnectedReason = 'hidden';\n break;\n }\n this.#setNotConnectedReason(notConnectedReason);\n }\n\n setConnectError(reason: ZeroError) {\n this.#timeToConnectMsV2.clear();\n this.#totalTimeToConnectMs.clear();\n this.#setNotConnectedReason('error');\n this.#lastConnectErrorV2.set(getLastConnectErrorValue(reason));\n }\n\n /**\n * Tags to include in all metrics.\n */\n readonly tags: string[] = [];\n\n // Flushes all metrics to an array of time series (plural), one Series\n // per metric.\n async flush() {\n const lc = this.#lc;\n if (this.#timerID === null) {\n lc.error?.('MetricManager.flush() called but already stopped');\n return;\n }\n const allSeries: Series[] = [];\n for (const metric of this.#metrics) {\n const series = metric.flush();\n if (series !== undefined) {\n allSeries.push({\n ...series,\n host: this.#host,\n tags: this.tags,\n });\n }\n }\n if (allSeries.length === 0) {\n lc?.debug?.('No metrics to report');\n return;\n }\n try {\n await this.#reporter(allSeries);\n } catch (e) {\n lc?.error?.('Error reporting metrics', e);\n }\n }\n\n stop() {\n if (this.#timerID === null) {\n this.#lc.error?.('MetricManager.stop() called but already stopped');\n return;\n }\n clearInterval(this.#timerID);\n this.#timerID = null;\n }\n\n #register<M extends Flushable>(metric: M) {\n this.#metrics.push(metric);\n return metric;\n }\n}\n\n// These two types are influenced by Datadog's API's needs. We could change what\n// we use internally if necessary, but we'd just have to convert to/from before\n// sending to DD. So for convenience we go with their format.\n\n/** Series is a time series of points for a single metric. */\nexport type Series = {\n host: string;\n metric: string; // We call this 'name' bc 'metric' is overloaded in code.\n points: Point[];\n tags?: string[];\n};\n/**\n * A point is a second-resolution timestamp and a set of values for that\n * timestamp. A point represents exactly one second in time and the values\n * are those recorded for that second. The first element of this array\n * is the timestamp and the second element is an array of values.\n */\nexport type Point = [number, number[]];\n\nfunction makePoint(ts: number, value: number): Point {\n return [ts, [value]];\n}\n\ntype Flushable = {\n flush(): Pick<Series, 'metric' | 'points'> | undefined;\n};\n\n/**\n * Gauge is a metric type that represents a single value that can go up and\n * down. It's typically used to track discrete values or counts eg the number\n * of active users, number of connections, cpu load, etc. A gauge retains\n * its value when flushed.\n *\n * We use a Gauge to sample at the client. If we are interested in tracking\n * a metric value *per client*, the client can note the latest value in\n * a Gauge metric. The metric is periodically reported via Reporter. On the\n * server, we graph the value of the metric rolled up over the periodic\n * reporting period, that is, counted over a span of time equal to the\n * reporting period. The result is ~one point per client per reporting\n * period.\n */\nexport class Gauge implements Flushable {\n readonly #name: string;\n #value: number | undefined = undefined;\n\n constructor(name: string) {\n this.#name = name;\n }\n\n set(value: number) {\n this.#value = value;\n }\n\n get() {\n return this.#value;\n }\n\n clear() {\n this.#value = undefined;\n }\n\n flush() {\n if (this.#value === undefined) {\n return undefined;\n }\n // Gauge reports the timestamp at flush time, not at the point the value was\n // recorded.\n const points = [makePoint(t(), this.#value)];\n return {metric: this.#name, points};\n }\n}\n\nfunction t() {\n return Math.round(Date.now() / 1000);\n}\n\n/**\n * State is a metric type that represents a specific state that the system is\n * in, for example the state of a connection which may be 'open' or 'closed'.\n * The state is given a name/prefix at construction time (eg 'connection') and\n * then can be set to a specific state (eg 'open'). The prefix is prepended to\n * the set state (eg, 'connection_open') and a value of 1 is reported.\n * Unset/cleared states are not reported.\n *\n * Example:\n * const s = new State('connection');\n * s.set('open');\n * s.flush(); // returns {metric: 'connection_open', points: [[now(), [1]]]}\n */\nexport class State implements Flushable {\n readonly #prefix: string;\n readonly #clearOnFlush: boolean;\n #current: string | undefined = undefined;\n\n constructor(prefix: string, clearOnFlush = false) {\n this.#prefix = prefix;\n this.#clearOnFlush = clearOnFlush;\n }\n\n set(state: string) {\n this.#current = state;\n }\n\n get() {\n return this.#current;\n }\n\n clear() {\n this.#current = undefined;\n }\n\n flush() {\n if (this.#current === undefined) {\n return undefined;\n }\n const gauge = new Gauge([this.#prefix, this.#current].join('_'));\n gauge.set(1);\n const series = gauge.flush();\n if (this.#clearOnFlush) {\n this.clear();\n }\n return series;\n }\n}\n"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ function validateServerParam(paramName, server) {
|
|
|
13
13
|
const urlString = url.toString();
|
|
14
14
|
const pathComponents = url.pathname.split("/");
|
|
15
15
|
if (pathComponents[0] === "") pathComponents.shift();
|
|
16
|
-
if (pathComponents
|
|
16
|
+
if (pathComponents.at(-1) === "") pathComponents.pop();
|
|
17
17
|
if (pathComponents.length > 1) throw new Error(`ZeroOptions.${paramName} may have at most one path component.${forExample("zero")}`);
|
|
18
18
|
for (const [property, invalidEndsWith] of [["search", "?"], ["hash", "#"]]) if (url[property] || urlString.endsWith(invalidEndsWith)) throw new Error(`ZeroOptions.${paramName} must not contain a ${property} component.${forExample()}`);
|
|
19
19
|
return urlString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-option.js","names":[],"sources":["../../../../../zero-client/src/client/server-option.ts"],"sourcesContent":["import {getBrowserGlobal} from '../../../shared/src/browser-env.ts';\nimport type {HTTPString} from './http-string.ts';\n\nfunction validateServerParam(paramName: string, server: string): HTTPString {\n const expectedProtocol = 'http';\n const forExample = (path: string = '') =>\n ` For example: \"${expectedProtocol}s://myapp-myteam.zero.ms/${path}\".`;\n\n if (\n !server.startsWith(`${expectedProtocol}://`) &&\n !server.startsWith(`${expectedProtocol}s://`)\n ) {\n throw new Error(\n `ZeroOptions.${paramName} must use the \"${expectedProtocol}\" or \"${expectedProtocol}s\" scheme.`,\n );\n }\n let url;\n try {\n url = new URL(server);\n } catch {\n throw new Error(\n `ZeroOptions.${paramName} must be a valid URL.${forExample()}`,\n );\n }\n\n const urlString = url.toString();\n\n const pathComponents = url.pathname.split('/');\n if (pathComponents[0] === '') {\n pathComponents.shift();\n }\n if (pathComponents
|
|
1
|
+
{"version":3,"file":"server-option.js","names":[],"sources":["../../../../../zero-client/src/client/server-option.ts"],"sourcesContent":["import {getBrowserGlobal} from '../../../shared/src/browser-env.ts';\nimport type {HTTPString} from './http-string.ts';\n\nfunction validateServerParam(paramName: string, server: string): HTTPString {\n const expectedProtocol = 'http';\n const forExample = (path: string = '') =>\n ` For example: \"${expectedProtocol}s://myapp-myteam.zero.ms/${path}\".`;\n\n if (\n !server.startsWith(`${expectedProtocol}://`) &&\n !server.startsWith(`${expectedProtocol}s://`)\n ) {\n throw new Error(\n `ZeroOptions.${paramName} must use the \"${expectedProtocol}\" or \"${expectedProtocol}s\" scheme.`,\n );\n }\n let url;\n try {\n url = new URL(server);\n } catch {\n throw new Error(\n `ZeroOptions.${paramName} must be a valid URL.${forExample()}`,\n );\n }\n\n const urlString = url.toString();\n\n const pathComponents = url.pathname.split('/');\n if (pathComponents[0] === '') {\n pathComponents.shift();\n }\n if (pathComponents.at(-1) === '') {\n pathComponents.pop();\n }\n if (pathComponents.length > 1) {\n throw new Error(\n `ZeroOptions.${paramName} may have at most one path component.${forExample(\n 'zero',\n )}`,\n );\n }\n\n for (const [property, invalidEndsWith] of [\n ['search', '?'],\n ['hash', '#'],\n ] as const) {\n if (url[property] || urlString.endsWith(invalidEndsWith)) {\n throw new Error(\n `ZeroOptions.${paramName} must not contain a ${property} component.${forExample()}`,\n );\n }\n }\n\n return urlString as HTTPString;\n}\n\nexport function getServer(\n server: string | undefined | null,\n): HTTPString | null {\n const WS = getBrowserGlobal('WebSocket');\n if (!WS) {\n // oxlint-disable-next-line no-console\n console.warn(\n 'Zero started in an unsupported environment, no data will be synced.',\n );\n return null;\n }\n if (server === undefined || server === null) {\n // oxlint-disable-next-line no-console\n console.warn(\n 'Zero starting up with no server URL. No data will be synced.',\n );\n return null;\n }\n return validateServerParam('server', server);\n}\n"],"mappings":";;AAGA,SAAS,oBAAoB,WAAmB,QAA4B;CAC1E,MAAM,mBAAmB;CACzB,MAAM,cAAc,OAAe,OACjC,kBAAkB,iBAAiB,2BAA2B,KAAK;AAErE,KACE,CAAC,OAAO,WAAW,GAAG,iBAAiB,KAAK,IAC5C,CAAC,OAAO,WAAW,GAAG,iBAAiB,MAAM,CAE7C,OAAM,IAAI,MACR,eAAe,UAAU,iBAAiB,iBAAiB,QAAQ,iBAAiB,YACrF;CAEH,IAAI;AACJ,KAAI;AACF,QAAM,IAAI,IAAI,OAAO;SACf;AACN,QAAM,IAAI,MACR,eAAe,UAAU,uBAAuB,YAAY,GAC7D;;CAGH,MAAM,YAAY,IAAI,UAAU;CAEhC,MAAM,iBAAiB,IAAI,SAAS,MAAM,IAAI;AAC9C,KAAI,eAAe,OAAO,GACxB,gBAAe,OAAO;AAExB,KAAI,eAAe,GAAG,GAAG,KAAK,GAC5B,gBAAe,KAAK;AAEtB,KAAI,eAAe,SAAS,EAC1B,OAAM,IAAI,MACR,eAAe,UAAU,uCAAuC,WAC9D,OACD,GACF;AAGH,MAAK,MAAM,CAAC,UAAU,oBAAoB,CACxC,CAAC,UAAU,IAAI,EACf,CAAC,QAAQ,IAAI,CACd,CACC,KAAI,IAAI,aAAa,UAAU,SAAS,gBAAgB,CACtD,OAAM,IAAI,MACR,eAAe,UAAU,sBAAsB,SAAS,aAAa,YAAY,GAClF;AAIL,QAAO;;AAGT,SAAgB,UACd,QACmB;AAEnB,KAAI,CADO,iBAAiB,YAAY,EAC/B;AAEP,UAAQ,KACN,sEACD;AACD,SAAO;;AAET,KAAI,WAAW,KAAA,KAAa,WAAW,MAAM;AAE3C,UAAQ,KACN,+DACD;AACD,SAAO;;AAET,QAAO,oBAAoB,UAAU,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zero-poke-handler.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/zero-poke-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EACV,sBAAsB,EACtB,YAAY,EACb,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,qCAAqC,CAAC;AAElE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,+CAA+C,CAAC;AACjF,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACd,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAO9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,WAAW;;gBAiBpB,cAAc,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,EACrD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACrC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,UAAU,EACd,eAAe,EAAE,eAAe;IAWlC,eAAe,CAAC,SAAS,EAAE,aAAa;IAiBxC,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS;IAa1D,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAoBzC,gBAAgB,IAAI,IAAI;CAmFzB;AAED,wBAAgB,UAAU,CACxB,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,UAAU,GAExB,CAAC,YAAY,GAAG;IAAC,eAAe,CAAC,EAAE,aAAa,EAAE,GAAG,SAAS,CAAA;CAAC,CAAC,GAChE,SAAS,
|
|
1
|
+
{"version":3,"file":"zero-poke-handler.d.ts","sourceRoot":"","sources":["../../../../../zero-client/src/client/zero-poke-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EACV,sBAAsB,EACtB,YAAY,EACb,MAAM,iCAAiC,CAAC;AAEzC,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,qCAAqC,CAAC;AAElE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,+CAA+C,CAAC;AACjF,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,aAAa,EACd,MAAM,oCAAoC,CAAC;AAG5C,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAO9D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAE3D,KAAK,eAAe,GAAG;IACrB,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;CAC/B,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,WAAW;;gBAiBpB,cAAc,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,EACrD,WAAW,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACrC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,UAAU,EACd,eAAe,EAAE,eAAe;IAWlC,eAAe,CAAC,SAAS,EAAE,aAAa;IAiBxC,cAAc,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS;IAa1D,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAoBzC,gBAAgB,IAAI,IAAI;CAmFzB;AAED,wBAAgB,UAAU,CACxB,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,UAAU,GAExB,CAAC,YAAY,GAAG;IAAC,eAAe,CAAC,EAAE,aAAa,EAAE,GAAG,SAAS,CAAA;CAAC,CAAC,GAChE,SAAS,CAyFZ;AAyBD,wBAAgB,kCAAkC,CAChD,EAAE,EAAE,aAAa,GAChB,sBAAsB,CAcxB"}
|
|
@@ -130,7 +130,7 @@ var PokeHandler = class {
|
|
|
130
130
|
function mergePokes(pokeBuffer, schema, serverToClient) {
|
|
131
131
|
if (pokeBuffer.length === 0) return;
|
|
132
132
|
const { baseCookie } = pokeBuffer[0].pokeStart;
|
|
133
|
-
const { cookie } = pokeBuffer
|
|
133
|
+
const { cookie } = pokeBuffer.at(-1).pokeEnd;
|
|
134
134
|
const mergedPatch = [];
|
|
135
135
|
const mergedLastMutationIDChanges = {};
|
|
136
136
|
const mutationResults = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zero-poke-handler.js","names":["#replicachePoke","#onPokeError","#clientID","#lc","#pokeBuffer","#pokeLock","#schema","#serverToClient","#mutationTracker","#receivingPoke","#handlePokeError","#pokePlaybackLoopRunning","#startPlaybackLoop","#clear","#scheduledCallback","#lastScheduledTimestamp","#processPokesForFrame"],"sources":["../../../../../zero-client/src/client/zero-poke-handler.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {\n PatchOperationInternal,\n PokeInternal,\n} from '../../../replicache/src/impl.ts';\nimport type {PatchOperation} from '../../../replicache/src/patch-operation.ts';\nimport type {ClientID} from '../../../replicache/src/sync/ids.ts';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport type {MutationPatch} from '../../../zero-protocol/src/mutations-patch.ts';\nimport type {\n PokeEndBody,\n PokePartBody,\n PokeStartBody,\n} from '../../../zero-protocol/src/poke.ts';\nimport type {QueriesPatchOp} from '../../../zero-protocol/src/queries-patch.ts';\nimport type {RowPatchOp} from '../../../zero-protocol/src/row-patch.ts';\nimport {\n serverToClient,\n type NameMapper,\n} from '../../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {\n toDesiredQueriesKey,\n toGotQueriesKey,\n toMutationResponseKey,\n toPrimaryKeyString,\n} from './keys.ts';\nimport type {MutationTracker} from './mutation-tracker.ts';\n\ntype PokeAccumulator = {\n readonly pokeStart: PokeStartBody;\n readonly parts: PokePartBody[];\n readonly pokeEnd: PokeEndBody;\n};\n\n/**\n * Handles the multi-part format of zero pokes.\n * As an optimization it also debounces pokes, only poking Replicache with a\n * merged poke at most once per scheduled callback (using setTimeout).\n * This debouncing avoids wastefully computing separate diffs and IVM updates\n * for intermediate states. setTimeout is used instead of requestAnimationFrame\n * to ensure pokes are delivered even when the tab is in the background,\n * enabling notifications (sounds, favicon badges) to work correctly.\n */\nexport class PokeHandler {\n readonly #replicachePoke: (poke: PokeInternal) => Promise<void>;\n readonly #onPokeError: (error: unknown) => void;\n readonly #clientID: ClientID;\n readonly #lc: LogContext;\n #receivingPoke: Omit<PokeAccumulator, 'pokeEnd'> | undefined = undefined;\n readonly #pokeBuffer: PokeAccumulator[] = [];\n #pokePlaybackLoopRunning = false;\n #lastScheduledTimestamp = 0;\n // Serializes calls to this.#replicachePoke otherwise we can cause out of\n // order poke errors.\n readonly #pokeLock = new Lock();\n readonly #schema: Schema;\n readonly #serverToClient: NameMapper;\n readonly #mutationTracker: MutationTracker;\n\n constructor(\n replicachePoke: (poke: PokeInternal) => Promise<void>,\n onPokeError: (error: unknown) => void,\n clientID: ClientID,\n schema: Schema,\n lc: LogContext,\n mutationTracker: MutationTracker,\n ) {\n this.#replicachePoke = replicachePoke;\n this.#onPokeError = onPokeError;\n this.#clientID = clientID;\n this.#schema = schema;\n this.#serverToClient = serverToClient(schema.tables);\n this.#lc = lc.withContext('PokeHandler');\n this.#mutationTracker = mutationTracker;\n }\n\n handlePokeStart(pokeStart: PokeStartBody) {\n if (this.#receivingPoke) {\n this.#handlePokeError(\n `pokeStart ${JSON.stringify(\n pokeStart,\n )} while still receiving ${JSON.stringify(\n this.#receivingPoke.pokeStart,\n )} `,\n );\n return;\n }\n this.#receivingPoke = {\n pokeStart,\n parts: [],\n };\n }\n\n handlePokePart(pokePart: PokePartBody): number | undefined {\n if (pokePart.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {\n this.#handlePokeError(\n `pokePart for ${pokePart.pokeID}, when receiving ${\n this.#receivingPoke?.pokeStart.pokeID\n }`,\n );\n return;\n }\n this.#receivingPoke.parts.push(pokePart);\n return pokePart.lastMutationIDChanges?.[this.#clientID];\n }\n\n handlePokeEnd(pokeEnd: PokeEndBody): void {\n if (pokeEnd.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {\n this.#handlePokeError(\n `pokeEnd for ${pokeEnd.pokeID}, when receiving ${\n this.#receivingPoke?.pokeStart.pokeID\n }`,\n );\n return;\n }\n if (pokeEnd.cancel) {\n this.#receivingPoke = undefined;\n return;\n }\n this.#pokeBuffer.push({...this.#receivingPoke, pokeEnd});\n this.#receivingPoke = undefined;\n if (!this.#pokePlaybackLoopRunning) {\n this.#startPlaybackLoop();\n }\n }\n\n handleDisconnect(): void {\n this.#lc.debug?.('clearing due to disconnect');\n this.#clear();\n }\n\n #startPlaybackLoop() {\n this.#lc.debug?.('starting playback loop');\n this.#pokePlaybackLoopRunning = true;\n setTimeout(this.#scheduledCallback, 0);\n }\n\n #scheduledCallback = async () => {\n const lc = this.#lc.withContext(\n 'scheduledAt',\n Math.floor(performance.now()),\n );\n if (this.#pokeBuffer.length === 0) {\n lc.debug?.('stopping playback loop');\n this.#pokePlaybackLoopRunning = false;\n return;\n }\n setTimeout(this.#scheduledCallback, 0);\n const start = performance.now();\n lc.debug?.(\n 'scheduled callback fired, processing pokes. Since last callback',\n start - this.#lastScheduledTimestamp,\n );\n this.#lastScheduledTimestamp = start;\n await this.#processPokesForFrame(lc);\n lc.debug?.('processing pokes took', performance.now() - start);\n };\n\n #processPokesForFrame(lc: LogContext): Promise<void> {\n return this.#pokeLock.withLock(async () => {\n const now = Date.now();\n lc.debug?.('got poke lock at', now);\n lc.debug?.('merging', this.#pokeBuffer.length);\n try {\n const merged = mergePokes(\n this.#pokeBuffer,\n this.#schema,\n this.#serverToClient,\n );\n this.#pokeBuffer.length = 0;\n if (merged === undefined) {\n lc.debug?.('frame is empty');\n return;\n }\n const start = performance.now();\n lc.debug?.('poking replicache');\n await this.#replicachePoke(merged);\n lc.debug?.('poking replicache took', performance.now() - start);\n\n if (!('error' in merged.pullResponse)) {\n const lmid =\n merged.pullResponse.lastMutationIDChanges[this.#clientID];\n if (lmid !== undefined) {\n this.#mutationTracker.lmidAdvanced(lmid);\n }\n }\n } catch (e) {\n this.#handlePokeError(e);\n }\n });\n }\n\n #handlePokeError(e: unknown) {\n if (String(e).includes('unexpected base cookie for poke')) {\n // This can happen if cookie changes due to refresh from idb due\n // to an update arriving to different tabs in the same\n // client group at very different times. Unusual but possible.\n this.#lc.debug?.('clearing due to', e);\n } else {\n this.#lc.error?.('clearing due to unexpected poke error', e);\n }\n this.#clear();\n this.#onPokeError(e);\n }\n\n #clear() {\n this.#receivingPoke = undefined;\n this.#pokeBuffer.length = 0;\n }\n}\n\nexport function mergePokes(\n pokeBuffer: PokeAccumulator[],\n schema: Schema,\n serverToClient: NameMapper,\n):\n | (PokeInternal & {mutationResults?: MutationPatch[] | undefined})\n | undefined {\n if (pokeBuffer.length === 0) {\n return undefined;\n }\n const {baseCookie} = pokeBuffer[0].pokeStart;\n const lastPoke = pokeBuffer[pokeBuffer.length - 1];\n const {cookie} = lastPoke.pokeEnd;\n const mergedPatch: PatchOperationInternal[] = [];\n const mergedLastMutationIDChanges: Record<string, number> = {};\n const mutationResults: MutationPatch[] = [];\n\n let prevPokeEnd = undefined;\n for (const pokeAccumulator of pokeBuffer) {\n if (\n prevPokeEnd &&\n pokeAccumulator.pokeStart.baseCookie &&\n pokeAccumulator.pokeStart.baseCookie > prevPokeEnd.cookie\n ) {\n throw Error(\n `unexpected cookie gap ${JSON.stringify(prevPokeEnd)} ${JSON.stringify(\n pokeAccumulator.pokeStart,\n )}`,\n );\n }\n prevPokeEnd = pokeAccumulator.pokeEnd;\n for (const pokePart of pokeAccumulator.parts) {\n if (pokePart.lastMutationIDChanges) {\n for (const [clientID, lastMutationID] of Object.entries(\n pokePart.lastMutationIDChanges,\n )) {\n mergedLastMutationIDChanges[clientID] = lastMutationID;\n }\n }\n if (pokePart.desiredQueriesPatches) {\n for (const [clientID, queriesPatch] of Object.entries(\n pokePart.desiredQueriesPatches,\n )) {\n for (const op of queriesPatch) {\n mergedPatch.push(\n queryPatchOpToReplicachePatchOp(op, hash =>\n toDesiredQueriesKey(clientID, hash),\n ),\n );\n }\n }\n }\n if (pokePart.gotQueriesPatch) {\n for (const op of pokePart.gotQueriesPatch) {\n mergedPatch.push(\n queryPatchOpToReplicachePatchOp(op, toGotQueriesKey),\n );\n }\n }\n if (pokePart.rowsPatch) {\n for (const p of pokePart.rowsPatch) {\n const patchOp = rowsPatchOpToReplicachePatchOp(\n p,\n schema,\n serverToClient,\n );\n if (patchOp) {\n mergedPatch.push(patchOp);\n }\n }\n }\n if (pokePart.mutationsPatch) {\n for (const op of pokePart.mutationsPatch) {\n mergedPatch.push(mutationPatchOpToReplicachePatchOp(op));\n }\n }\n }\n }\n const ret: PokeInternal & {mutationResults?: MutationPatch[] | undefined} = {\n baseCookie,\n pullResponse: {\n lastMutationIDChanges: mergedLastMutationIDChanges,\n patch: mergedPatch,\n cookie,\n },\n };\n\n // For backwards compatibility. Because we're strict on our validation,\n // zero-client must be able to parse pokes with this field before we introduce it.\n // So users can update their clients and then start using custom mutators that write responses to the db.\n if (mutationResults.length > 0) {\n ret.mutationResults = mutationResults;\n }\n return ret;\n}\n\nfunction queryPatchOpToReplicachePatchOp(\n op: QueriesPatchOp,\n toKey: (hash: string) => string,\n): PatchOperation {\n switch (op.op) {\n case 'clear':\n return op;\n case 'del':\n return {\n op: 'del',\n key: toKey(op.hash),\n };\n case 'put':\n return {\n op: 'put',\n key: toKey(op.hash),\n value: null,\n };\n default:\n unreachable(op);\n }\n}\n\nexport function mutationPatchOpToReplicachePatchOp(\n op: MutationPatch,\n): PatchOperationInternal {\n switch (op.op) {\n case 'put':\n return {\n op: 'put',\n key: toMutationResponseKey(op.mutation.id),\n value: op.mutation.result,\n };\n case 'del':\n return {\n op: 'del',\n key: toMutationResponseKey(op.id),\n };\n }\n}\n\nfunction rowsPatchOpToReplicachePatchOp(\n op: RowPatchOp,\n schema: Schema,\n serverToClient: NameMapper,\n): PatchOperationInternal | undefined {\n if (op.op === 'clear') {\n return op;\n }\n // Skip rows for tables not in the client schema. This can happen when\n // the server-side query AST references tables (e.g. issueNotifications)\n // that are not yet part of the client schema definition.\n const tableName = serverToClient.tableNameIfKnown(op.tableName);\n if (!tableName) {\n return undefined;\n }\n switch (op.op) {\n case 'del':\n return {\n op: 'del',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.id),\n ),\n };\n case 'put':\n return {\n op: 'put',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.value),\n ),\n value: serverToClient.row(op.tableName, op.value),\n };\n case 'update':\n return {\n op: 'update',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.id),\n ),\n merge: op.merge\n ? serverToClient.row(op.tableName, op.merge)\n : undefined,\n constrain: serverToClient.columns(op.tableName, op.constrain),\n };\n default:\n unreachable(op);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"zero-poke-handler.js","names":["#replicachePoke","#onPokeError","#clientID","#lc","#pokeBuffer","#pokeLock","#schema","#serverToClient","#mutationTracker","#receivingPoke","#handlePokeError","#pokePlaybackLoopRunning","#startPlaybackLoop","#clear","#scheduledCallback","#lastScheduledTimestamp","#processPokesForFrame"],"sources":["../../../../../zero-client/src/client/zero-poke-handler.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport type {LogContext} from '@rocicorp/logger';\nimport type {\n PatchOperationInternal,\n PokeInternal,\n} from '../../../replicache/src/impl.ts';\nimport type {PatchOperation} from '../../../replicache/src/patch-operation.ts';\nimport type {ClientID} from '../../../replicache/src/sync/ids.ts';\nimport {unreachable} from '../../../shared/src/asserts.ts';\nimport type {MutationPatch} from '../../../zero-protocol/src/mutations-patch.ts';\nimport type {\n PokeEndBody,\n PokePartBody,\n PokeStartBody,\n} from '../../../zero-protocol/src/poke.ts';\nimport type {QueriesPatchOp} from '../../../zero-protocol/src/queries-patch.ts';\nimport type {RowPatchOp} from '../../../zero-protocol/src/row-patch.ts';\nimport {\n serverToClient,\n type NameMapper,\n} from '../../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../../zero-types/src/schema.ts';\nimport {\n toDesiredQueriesKey,\n toGotQueriesKey,\n toMutationResponseKey,\n toPrimaryKeyString,\n} from './keys.ts';\nimport type {MutationTracker} from './mutation-tracker.ts';\n\ntype PokeAccumulator = {\n readonly pokeStart: PokeStartBody;\n readonly parts: PokePartBody[];\n readonly pokeEnd: PokeEndBody;\n};\n\n/**\n * Handles the multi-part format of zero pokes.\n * As an optimization it also debounces pokes, only poking Replicache with a\n * merged poke at most once per scheduled callback (using setTimeout).\n * This debouncing avoids wastefully computing separate diffs and IVM updates\n * for intermediate states. setTimeout is used instead of requestAnimationFrame\n * to ensure pokes are delivered even when the tab is in the background,\n * enabling notifications (sounds, favicon badges) to work correctly.\n */\nexport class PokeHandler {\n readonly #replicachePoke: (poke: PokeInternal) => Promise<void>;\n readonly #onPokeError: (error: unknown) => void;\n readonly #clientID: ClientID;\n readonly #lc: LogContext;\n #receivingPoke: Omit<PokeAccumulator, 'pokeEnd'> | undefined = undefined;\n readonly #pokeBuffer: PokeAccumulator[] = [];\n #pokePlaybackLoopRunning = false;\n #lastScheduledTimestamp = 0;\n // Serializes calls to this.#replicachePoke otherwise we can cause out of\n // order poke errors.\n readonly #pokeLock = new Lock();\n readonly #schema: Schema;\n readonly #serverToClient: NameMapper;\n readonly #mutationTracker: MutationTracker;\n\n constructor(\n replicachePoke: (poke: PokeInternal) => Promise<void>,\n onPokeError: (error: unknown) => void,\n clientID: ClientID,\n schema: Schema,\n lc: LogContext,\n mutationTracker: MutationTracker,\n ) {\n this.#replicachePoke = replicachePoke;\n this.#onPokeError = onPokeError;\n this.#clientID = clientID;\n this.#schema = schema;\n this.#serverToClient = serverToClient(schema.tables);\n this.#lc = lc.withContext('PokeHandler');\n this.#mutationTracker = mutationTracker;\n }\n\n handlePokeStart(pokeStart: PokeStartBody) {\n if (this.#receivingPoke) {\n this.#handlePokeError(\n `pokeStart ${JSON.stringify(\n pokeStart,\n )} while still receiving ${JSON.stringify(\n this.#receivingPoke.pokeStart,\n )} `,\n );\n return;\n }\n this.#receivingPoke = {\n pokeStart,\n parts: [],\n };\n }\n\n handlePokePart(pokePart: PokePartBody): number | undefined {\n if (pokePart.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {\n this.#handlePokeError(\n `pokePart for ${pokePart.pokeID}, when receiving ${\n this.#receivingPoke?.pokeStart.pokeID\n }`,\n );\n return;\n }\n this.#receivingPoke.parts.push(pokePart);\n return pokePart.lastMutationIDChanges?.[this.#clientID];\n }\n\n handlePokeEnd(pokeEnd: PokeEndBody): void {\n if (pokeEnd.pokeID !== this.#receivingPoke?.pokeStart.pokeID) {\n this.#handlePokeError(\n `pokeEnd for ${pokeEnd.pokeID}, when receiving ${\n this.#receivingPoke?.pokeStart.pokeID\n }`,\n );\n return;\n }\n if (pokeEnd.cancel) {\n this.#receivingPoke = undefined;\n return;\n }\n this.#pokeBuffer.push({...this.#receivingPoke, pokeEnd});\n this.#receivingPoke = undefined;\n if (!this.#pokePlaybackLoopRunning) {\n this.#startPlaybackLoop();\n }\n }\n\n handleDisconnect(): void {\n this.#lc.debug?.('clearing due to disconnect');\n this.#clear();\n }\n\n #startPlaybackLoop() {\n this.#lc.debug?.('starting playback loop');\n this.#pokePlaybackLoopRunning = true;\n setTimeout(this.#scheduledCallback, 0);\n }\n\n #scheduledCallback = async () => {\n const lc = this.#lc.withContext(\n 'scheduledAt',\n Math.floor(performance.now()),\n );\n if (this.#pokeBuffer.length === 0) {\n lc.debug?.('stopping playback loop');\n this.#pokePlaybackLoopRunning = false;\n return;\n }\n setTimeout(this.#scheduledCallback, 0);\n const start = performance.now();\n lc.debug?.(\n 'scheduled callback fired, processing pokes. Since last callback',\n start - this.#lastScheduledTimestamp,\n );\n this.#lastScheduledTimestamp = start;\n await this.#processPokesForFrame(lc);\n lc.debug?.('processing pokes took', performance.now() - start);\n };\n\n #processPokesForFrame(lc: LogContext): Promise<void> {\n return this.#pokeLock.withLock(async () => {\n const now = Date.now();\n lc.debug?.('got poke lock at', now);\n lc.debug?.('merging', this.#pokeBuffer.length);\n try {\n const merged = mergePokes(\n this.#pokeBuffer,\n this.#schema,\n this.#serverToClient,\n );\n this.#pokeBuffer.length = 0;\n if (merged === undefined) {\n lc.debug?.('frame is empty');\n return;\n }\n const start = performance.now();\n lc.debug?.('poking replicache');\n await this.#replicachePoke(merged);\n lc.debug?.('poking replicache took', performance.now() - start);\n\n if (!('error' in merged.pullResponse)) {\n const lmid =\n merged.pullResponse.lastMutationIDChanges[this.#clientID];\n if (lmid !== undefined) {\n this.#mutationTracker.lmidAdvanced(lmid);\n }\n }\n } catch (e) {\n this.#handlePokeError(e);\n }\n });\n }\n\n #handlePokeError(e: unknown) {\n if (String(e).includes('unexpected base cookie for poke')) {\n // This can happen if cookie changes due to refresh from idb due\n // to an update arriving to different tabs in the same\n // client group at very different times. Unusual but possible.\n this.#lc.debug?.('clearing due to', e);\n } else {\n this.#lc.error?.('clearing due to unexpected poke error', e);\n }\n this.#clear();\n this.#onPokeError(e);\n }\n\n #clear() {\n this.#receivingPoke = undefined;\n this.#pokeBuffer.length = 0;\n }\n}\n\nexport function mergePokes(\n pokeBuffer: PokeAccumulator[],\n schema: Schema,\n serverToClient: NameMapper,\n):\n | (PokeInternal & {mutationResults?: MutationPatch[] | undefined})\n | undefined {\n if (pokeBuffer.length === 0) {\n return undefined;\n }\n const {baseCookie} = pokeBuffer[0].pokeStart;\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const lastPoke = pokeBuffer.at(-1)!;\n const {cookie} = lastPoke.pokeEnd;\n const mergedPatch: PatchOperationInternal[] = [];\n const mergedLastMutationIDChanges: Record<string, number> = {};\n const mutationResults: MutationPatch[] = [];\n\n let prevPokeEnd = undefined;\n for (const pokeAccumulator of pokeBuffer) {\n if (\n prevPokeEnd &&\n pokeAccumulator.pokeStart.baseCookie &&\n pokeAccumulator.pokeStart.baseCookie > prevPokeEnd.cookie\n ) {\n throw Error(\n `unexpected cookie gap ${JSON.stringify(prevPokeEnd)} ${JSON.stringify(\n pokeAccumulator.pokeStart,\n )}`,\n );\n }\n prevPokeEnd = pokeAccumulator.pokeEnd;\n for (const pokePart of pokeAccumulator.parts) {\n if (pokePart.lastMutationIDChanges) {\n for (const [clientID, lastMutationID] of Object.entries(\n pokePart.lastMutationIDChanges,\n )) {\n mergedLastMutationIDChanges[clientID] = lastMutationID;\n }\n }\n if (pokePart.desiredQueriesPatches) {\n for (const [clientID, queriesPatch] of Object.entries(\n pokePart.desiredQueriesPatches,\n )) {\n for (const op of queriesPatch) {\n mergedPatch.push(\n queryPatchOpToReplicachePatchOp(op, hash =>\n toDesiredQueriesKey(clientID, hash),\n ),\n );\n }\n }\n }\n if (pokePart.gotQueriesPatch) {\n for (const op of pokePart.gotQueriesPatch) {\n mergedPatch.push(\n queryPatchOpToReplicachePatchOp(op, toGotQueriesKey),\n );\n }\n }\n if (pokePart.rowsPatch) {\n for (const p of pokePart.rowsPatch) {\n const patchOp = rowsPatchOpToReplicachePatchOp(\n p,\n schema,\n serverToClient,\n );\n if (patchOp) {\n mergedPatch.push(patchOp);\n }\n }\n }\n if (pokePart.mutationsPatch) {\n for (const op of pokePart.mutationsPatch) {\n mergedPatch.push(mutationPatchOpToReplicachePatchOp(op));\n }\n }\n }\n }\n const ret: PokeInternal & {mutationResults?: MutationPatch[] | undefined} = {\n baseCookie,\n pullResponse: {\n lastMutationIDChanges: mergedLastMutationIDChanges,\n patch: mergedPatch,\n cookie,\n },\n };\n\n // For backwards compatibility. Because we're strict on our validation,\n // zero-client must be able to parse pokes with this field before we introduce it.\n // So users can update their clients and then start using custom mutators that write responses to the db.\n if (mutationResults.length > 0) {\n ret.mutationResults = mutationResults;\n }\n return ret;\n}\n\nfunction queryPatchOpToReplicachePatchOp(\n op: QueriesPatchOp,\n toKey: (hash: string) => string,\n): PatchOperation {\n switch (op.op) {\n case 'clear':\n return op;\n case 'del':\n return {\n op: 'del',\n key: toKey(op.hash),\n };\n case 'put':\n return {\n op: 'put',\n key: toKey(op.hash),\n value: null,\n };\n default:\n unreachable(op);\n }\n}\n\nexport function mutationPatchOpToReplicachePatchOp(\n op: MutationPatch,\n): PatchOperationInternal {\n switch (op.op) {\n case 'put':\n return {\n op: 'put',\n key: toMutationResponseKey(op.mutation.id),\n value: op.mutation.result,\n };\n case 'del':\n return {\n op: 'del',\n key: toMutationResponseKey(op.id),\n };\n }\n}\n\nfunction rowsPatchOpToReplicachePatchOp(\n op: RowPatchOp,\n schema: Schema,\n serverToClient: NameMapper,\n): PatchOperationInternal | undefined {\n if (op.op === 'clear') {\n return op;\n }\n // Skip rows for tables not in the client schema. This can happen when\n // the server-side query AST references tables (e.g. issueNotifications)\n // that are not yet part of the client schema definition.\n const tableName = serverToClient.tableNameIfKnown(op.tableName);\n if (!tableName) {\n return undefined;\n }\n switch (op.op) {\n case 'del':\n return {\n op: 'del',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.id),\n ),\n };\n case 'put':\n return {\n op: 'put',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.value),\n ),\n value: serverToClient.row(op.tableName, op.value),\n };\n case 'update':\n return {\n op: 'update',\n key: toPrimaryKeyString(\n tableName,\n schema.tables[tableName].primaryKey,\n serverToClient.row(op.tableName, op.id),\n ),\n merge: op.merge\n ? serverToClient.row(op.tableName, op.merge)\n : undefined,\n constrain: serverToClient.columns(op.tableName, op.constrain),\n };\n default:\n unreachable(op);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA6CA,IAAa,cAAb,MAAyB;CACvB;CACA;CACA;CACA;CACA,iBAA+D,KAAA;CAC/D,cAA0C,EAAE;CAC5C,2BAA2B;CAC3B,0BAA0B;CAG1B,YAAqB,IAAI,MAAM;CAC/B;CACA;CACA;CAEA,YACE,gBACA,aACA,UACA,QACA,IACA,iBACA;AACA,QAAA,iBAAuB;AACvB,QAAA,cAAoB;AACpB,QAAA,WAAiB;AACjB,QAAA,SAAe;AACf,QAAA,iBAAuB,eAAe,OAAO,OAAO;AACpD,QAAA,KAAW,GAAG,YAAY,cAAc;AACxC,QAAA,kBAAwB;;CAG1B,gBAAgB,WAA0B;AACxC,MAAI,MAAA,eAAqB;AACvB,SAAA,gBACE,aAAa,KAAK,UAChB,UACD,CAAC,0BAA0B,KAAK,UAC/B,MAAA,cAAoB,UACrB,CAAC,GACH;AACD;;AAEF,QAAA,gBAAsB;GACpB;GACA,OAAO,EAAE;GACV;;CAGH,eAAe,UAA4C;AACzD,MAAI,SAAS,WAAW,MAAA,eAAqB,UAAU,QAAQ;AAC7D,SAAA,gBACE,gBAAgB,SAAS,OAAO,mBAC9B,MAAA,eAAqB,UAAU,SAElC;AACD;;AAEF,QAAA,cAAoB,MAAM,KAAK,SAAS;AACxC,SAAO,SAAS,wBAAwB,MAAA;;CAG1C,cAAc,SAA4B;AACxC,MAAI,QAAQ,WAAW,MAAA,eAAqB,UAAU,QAAQ;AAC5D,SAAA,gBACE,eAAe,QAAQ,OAAO,mBAC5B,MAAA,eAAqB,UAAU,SAElC;AACD;;AAEF,MAAI,QAAQ,QAAQ;AAClB,SAAA,gBAAsB,KAAA;AACtB;;AAEF,QAAA,WAAiB,KAAK;GAAC,GAAG,MAAA;GAAqB;GAAQ,CAAC;AACxD,QAAA,gBAAsB,KAAA;AACtB,MAAI,CAAC,MAAA,wBACH,OAAA,mBAAyB;;CAI7B,mBAAyB;AACvB,QAAA,GAAS,QAAQ,6BAA6B;AAC9C,QAAA,OAAa;;CAGf,qBAAqB;AACnB,QAAA,GAAS,QAAQ,yBAAyB;AAC1C,QAAA,0BAAgC;AAChC,aAAW,MAAA,mBAAyB,EAAE;;CAGxC,qBAAqB,YAAY;EAC/B,MAAM,KAAK,MAAA,GAAS,YAClB,eACA,KAAK,MAAM,YAAY,KAAK,CAAC,CAC9B;AACD,MAAI,MAAA,WAAiB,WAAW,GAAG;AACjC,MAAG,QAAQ,yBAAyB;AACpC,SAAA,0BAAgC;AAChC;;AAEF,aAAW,MAAA,mBAAyB,EAAE;EACtC,MAAM,QAAQ,YAAY,KAAK;AAC/B,KAAG,QACD,mEACA,QAAQ,MAAA,uBACT;AACD,QAAA,yBAA+B;AAC/B,QAAM,MAAA,qBAA2B,GAAG;AACpC,KAAG,QAAQ,yBAAyB,YAAY,KAAK,GAAG,MAAM;;CAGhE,sBAAsB,IAA+B;AACnD,SAAO,MAAA,SAAe,SAAS,YAAY;GACzC,MAAM,MAAM,KAAK,KAAK;AACtB,MAAG,QAAQ,oBAAoB,IAAI;AACnC,MAAG,QAAQ,WAAW,MAAA,WAAiB,OAAO;AAC9C,OAAI;IACF,MAAM,SAAS,WACb,MAAA,YACA,MAAA,QACA,MAAA,eACD;AACD,UAAA,WAAiB,SAAS;AAC1B,QAAI,WAAW,KAAA,GAAW;AACxB,QAAG,QAAQ,iBAAiB;AAC5B;;IAEF,MAAM,QAAQ,YAAY,KAAK;AAC/B,OAAG,QAAQ,oBAAoB;AAC/B,UAAM,MAAA,eAAqB,OAAO;AAClC,OAAG,QAAQ,0BAA0B,YAAY,KAAK,GAAG,MAAM;AAE/D,QAAI,EAAE,WAAW,OAAO,eAAe;KACrC,MAAM,OACJ,OAAO,aAAa,sBAAsB,MAAA;AAC5C,SAAI,SAAS,KAAA,EACX,OAAA,gBAAsB,aAAa,KAAK;;YAGrC,GAAG;AACV,UAAA,gBAAsB,EAAE;;IAE1B;;CAGJ,iBAAiB,GAAY;AAC3B,MAAI,OAAO,EAAE,CAAC,SAAS,kCAAkC,CAIvD,OAAA,GAAS,QAAQ,mBAAmB,EAAE;MAEtC,OAAA,GAAS,QAAQ,yCAAyC,EAAE;AAE9D,QAAA,OAAa;AACb,QAAA,YAAkB,EAAE;;CAGtB,SAAS;AACP,QAAA,gBAAsB,KAAA;AACtB,QAAA,WAAiB,SAAS;;;AAI9B,SAAgB,WACd,YACA,QACA,gBAGY;AACZ,KAAI,WAAW,WAAW,EACxB;CAEF,MAAM,EAAC,eAAc,WAAW,GAAG;CAGnC,MAAM,EAAC,WADU,WAAW,GAAG,GAAG,CACR;CAC1B,MAAM,cAAwC,EAAE;CAChD,MAAM,8BAAsD,EAAE;CAC9D,MAAM,kBAAmC,EAAE;CAE3C,IAAI,cAAc,KAAA;AAClB,MAAK,MAAM,mBAAmB,YAAY;AACxC,MACE,eACA,gBAAgB,UAAU,cAC1B,gBAAgB,UAAU,aAAa,YAAY,OAEnD,OAAM,MACJ,yBAAyB,KAAK,UAAU,YAAY,CAAC,GAAG,KAAK,UAC3D,gBAAgB,UACjB,GACF;AAEH,gBAAc,gBAAgB;AAC9B,OAAK,MAAM,YAAY,gBAAgB,OAAO;AAC5C,OAAI,SAAS,sBACX,MAAK,MAAM,CAAC,UAAU,mBAAmB,OAAO,QAC9C,SAAS,sBACV,CACC,6BAA4B,YAAY;AAG5C,OAAI,SAAS,sBACX,MAAK,MAAM,CAAC,UAAU,iBAAiB,OAAO,QAC5C,SAAS,sBACV,CACC,MAAK,MAAM,MAAM,aACf,aAAY,KACV,gCAAgC,KAAI,SAClC,oBAAoB,UAAU,KAAK,CACpC,CACF;AAIP,OAAI,SAAS,gBACX,MAAK,MAAM,MAAM,SAAS,gBACxB,aAAY,KACV,gCAAgC,IAAI,gBAAgB,CACrD;AAGL,OAAI,SAAS,UACX,MAAK,MAAM,KAAK,SAAS,WAAW;IAClC,MAAM,UAAU,+BACd,GACA,QACA,eACD;AACD,QAAI,QACF,aAAY,KAAK,QAAQ;;AAI/B,OAAI,SAAS,eACX,MAAK,MAAM,MAAM,SAAS,eACxB,aAAY,KAAK,mCAAmC,GAAG,CAAC;;;CAKhE,MAAM,MAAsE;EAC1E;EACA,cAAc;GACZ,uBAAuB;GACvB,OAAO;GACP;GACD;EACF;AAKD,KAAI,gBAAgB,SAAS,EAC3B,KAAI,kBAAkB;AAExB,QAAO;;AAGT,SAAS,gCACP,IACA,OACgB;AAChB,SAAQ,GAAG,IAAX;EACE,KAAK,QACH,QAAO;EACT,KAAK,MACH,QAAO;GACL,IAAI;GACJ,KAAK,MAAM,GAAG,KAAK;GACpB;EACH,KAAK,MACH,QAAO;GACL,IAAI;GACJ,KAAK,MAAM,GAAG,KAAK;GACnB,OAAO;GACR;EACH,QACE,aAAY,GAAG;;;AAIrB,SAAgB,mCACd,IACwB;AACxB,SAAQ,GAAG,IAAX;EACE,KAAK,MACH,QAAO;GACL,IAAI;GACJ,KAAK,sBAAsB,GAAG,SAAS,GAAG;GAC1C,OAAO,GAAG,SAAS;GACpB;EACH,KAAK,MACH,QAAO;GACL,IAAI;GACJ,KAAK,sBAAsB,GAAG,GAAG;GAClC;;;AAIP,SAAS,+BACP,IACA,QACA,gBACoC;AACpC,KAAI,GAAG,OAAO,QACZ,QAAO;CAKT,MAAM,YAAY,eAAe,iBAAiB,GAAG,UAAU;AAC/D,KAAI,CAAC,UACH;AAEF,SAAQ,GAAG,IAAX;EACE,KAAK,MACH,QAAO;GACL,IAAI;GACJ,KAAK,mBACH,WACA,OAAO,OAAO,WAAW,YACzB,eAAe,IAAI,GAAG,WAAW,GAAG,GAAG,CACxC;GACF;EACH,KAAK,MACH,QAAO;GACL,IAAI;GACJ,KAAK,mBACH,WACA,OAAO,OAAO,WAAW,YACzB,eAAe,IAAI,GAAG,WAAW,GAAG,MAAM,CAC3C;GACD,OAAO,eAAe,IAAI,GAAG,WAAW,GAAG,MAAM;GAClD;EACH,KAAK,SACH,QAAO;GACL,IAAI;GACJ,KAAK,mBACH,WACA,OAAO,OAAO,WAAW,YACzB,eAAe,IAAI,GAAG,WAAW,GAAG,GAAG,CACxC;GACD,OAAO,GAAG,QACN,eAAe,IAAI,GAAG,WAAW,GAAG,MAAM,GAC1C,KAAA;GACJ,WAAW,eAAe,QAAQ,GAAG,WAAW,GAAG,UAAU;GAC9D;EACH,QACE,aAAY,GAAG"}
|
package/out/zero-pg/src/mod.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "../../zero-protocol/src/application-error.js";
|
|
2
2
|
import "../../zero-server/src/custom.js";
|
|
3
3
|
import "../../zero-server/src/pg-query-executor.js";
|
|
4
|
-
import "../../zero-server/src/process-mutations.js";
|
|
5
4
|
import "../../zero-server/src/push-processor.js";
|
|
5
|
+
import "../../zero-server/src/process-mutations.js";
|
|
6
6
|
import "../../zero-server/src/queries/process-queries.js";
|
|
7
7
|
import "../../zero-server/src/zql-database.js";
|
|
8
8
|
import "../../zero-server/src/mod.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../../../zero-protocol/src/ast.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,CAAC,MAAM,4BAA4B,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAY,KAAK,GAAG,EAAC,MAAM,WAAW,CAAC;AAE9C,eAAO,MAAM,WAAW,WAAW,CAAC;AAEpC,eAAO,MAAM,cAAc,gBAAa,CAAC;AACzC,eAAO,MAAM,aAAa,eAAW,CAAC;AACtC,eAAO,MAAM,YAAY,eAAmB,CAAC;AAM7C,eAAO,MAAM,cAAc,wDAAyC,CAAC;AACrE,MAAM,MAAM,MAAM,GAAG,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvD,eAAO,MAAM,eAAe,8EAK3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,sCAA4C,CAAC;AAE3E,eAAO,MAAM,cAAc,iCAAuC,CAAC;AAEnE,eAAO,MAAM,aAAa,qDAKzB,CAAC;AAEF,eAAO,MAAM,WAAW,yBAAiC,CAAC;AAE1D,eAAO,MAAM,oBAAoB,oKAKhC,CAAC;AAiBF;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,wBAAwB;;;;cAS5B,CAAC;AAQH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEjE,eAAO,MAAM,qBAAqB,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe,CAKxD,CAAC;AAIH,eAAO,MAAM,yCAAyC,EAAE,CAAC,CAAC,IAAI,CAAC,mCAAmC,CAC1D,CAAC;AAEzC,eAAO,MAAM,iCAAiC,EAAE,CAAC,CAAC,IAAI,CAAC,2BAA2B,CAO9E,CAAC;AAEL,eAAO,MAAM,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAK7C,CAAC;AAYF,MAAM,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;AAUzD,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../../../zero-protocol/src/ast.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast.js","names":[],"sources":["../../../../zero-protocol/src/ast.ts"],"sourcesContent":["/**\n * Wire-format representation of the zql AST interface.\n *\n * `v.Type<...>` types are explicitly declared to facilitate Typescript verification\n * that the schemas satisfy the zql type definitions. (Incidentally, explicit types\n * are also required for recursive schema definitions.)\n */\n\nimport {compareUTF8} from 'compare-utf8';\nimport {defined} from '../../shared/src/arrays.ts';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport type {NameMapper} from '../../zero-types/src/name-mapper.ts';\nimport {rowSchema, type Row} from './data.ts';\n\nexport const SUBQ_PREFIX = 'zsubq_';\n\nexport const selectorSchema = v.string();\nexport const toStaticParam = Symbol();\nexport const planIdSymbol = Symbol('planId');\n\nconst orderingElementSchema = v.readonly(\n v.tuple([selectorSchema, v.literalUnion('asc', 'desc')]),\n);\n\nexport const orderingSchema = v.readonlyArray(orderingElementSchema);\nexport type System = 'permissions' | 'client' | 'test';\n\nexport const primitiveSchema = v.union(\n v.string(),\n v.number(),\n v.boolean(),\n v.null(),\n);\n\nexport const equalityOpsSchema = v.literalUnion('=', '!=', 'IS', 'IS NOT');\n\nexport const orderOpsSchema = v.literalUnion('<', '>', '<=', '>=');\n\nexport const likeOpsSchema = v.literalUnion(\n 'LIKE',\n 'NOT LIKE',\n 'ILIKE',\n 'NOT ILIKE',\n);\n\nexport const inOpsSchema = v.literalUnion('IN', 'NOT IN');\n\nexport const simpleOperatorSchema = v.union(\n equalityOpsSchema,\n orderOpsSchema,\n likeOpsSchema,\n inOpsSchema,\n);\n\nconst literalReferenceSchema: v.Type<LiteralReference> = v.readonlyObject({\n type: v.literal('literal'),\n value: v.union(\n v.string(),\n v.number(),\n v.boolean(),\n v.null(),\n v.readonlyArray(v.union(v.string(), v.number(), v.boolean())),\n ),\n});\nconst columnReferenceSchema: v.Type<ColumnReference> = v.readonlyObject({\n type: v.literal('column'),\n name: v.string(),\n});\n\n/**\n * A parameter is a value that is not known at the time the query is written\n * and is resolved at runtime.\n *\n * Static parameters refer to something provided by the caller.\n * Static parameters are injected when the query pipeline is built from the AST\n * and do not change for the life of that pipeline.\n *\n * An example static parameter is the current authentication data.\n * When a user is authenticated, queries on the server have access\n * to the user's authentication data in order to evaluate authorization rules.\n * Authentication data doesn't change over the life of a query as a change\n * in auth data would represent a log-in / log-out of the user.\n *\n * AncestorParameters refer to rows encountered while running the query.\n * They are used by subqueries to refer to rows emitted by parent queries.\n */\nconst parameterReferenceSchema = v.readonlyObject({\n type: v.literal('static'),\n // The \"namespace\" of the injected parameter.\n // Write authorization will send the value of a row\n // prior to the mutation being run (preMutationRow).\n // Read and write authorization will both send the\n // current authentication data (authData).\n anchor: v.literalUnion('authData', 'preMutationRow'),\n field: v.union(v.string(), v.array(v.string())),\n});\n\nconst conditionValueSchema = v.union(\n literalReferenceSchema,\n columnReferenceSchema,\n parameterReferenceSchema,\n);\n\nexport type Parameter = v.Infer<typeof parameterReferenceSchema>;\n\nexport const simpleConditionSchema: v.Type<SimpleCondition> = v.readonlyObject({\n type: v.literal('simple'),\n op: simpleOperatorSchema,\n left: conditionValueSchema,\n right: v.union(parameterReferenceSchema, literalReferenceSchema),\n});\n\ntype ConditionValue = v.Infer<typeof conditionValueSchema>;\n\nexport const correlatedSubqueryConditionOperatorSchema: v.Type<CorrelatedSubqueryConditionOperator> =\n v.literalUnion('EXISTS', 'NOT EXISTS');\n\nexport const correlatedSubqueryConditionSchema: v.Type<CorrelatedSubqueryCondition> =\n v.readonlyObject({\n type: v.literal('correlatedSubquery'),\n related: v.lazy(() => correlatedSubquerySchema),\n op: correlatedSubqueryConditionOperatorSchema,\n flip: v.boolean().optional(),\n scalar: v.boolean().optional(),\n });\n\nexport const conditionSchema: v.Type<Condition> = v.union(\n simpleConditionSchema,\n v.lazy(() => conjunctionSchema),\n v.lazy(() => disjunctionSchema),\n correlatedSubqueryConditionSchema,\n);\n\nconst conjunctionSchema: v.Type<Conjunction> = v.readonlyObject({\n type: v.literal('and'),\n conditions: v.readonlyArray(conditionSchema),\n});\n\nconst disjunctionSchema: v.Type<Disjunction> = v.readonlyObject({\n type: v.literal('or'),\n conditions: v.readonlyArray(conditionSchema),\n});\n\nexport type CompoundKey = readonly [string, ...string[]];\n\nfunction mustCompoundKey(field: readonly string[]): CompoundKey {\n assert(\n Array.isArray(field) && field.length >= 1,\n 'Expected non-empty array for compound key',\n );\n return field as unknown as CompoundKey;\n}\n\nexport const compoundKeySchema: v.Type<CompoundKey> = v.readonly(\n v.tuple([v.string()]).concat(v.array(v.string())),\n);\n\nconst correlationSchema = v.readonlyObject({\n parentField: compoundKeySchema,\n childField: compoundKeySchema,\n});\n\n// Split out so that its inferred type can be checked against\n// Omit<CorrelatedSubquery, 'correlation'> in ast-type-test.ts.\n// The mutually-recursive reference of the 'other' field to astSchema\n// is the only thing added in v.lazy. The v.lazy is necessary due to the\n// mutually-recursive types, but v.lazy prevents inference of the resulting\n// type.\nexport const correlatedSubquerySchemaOmitSubquery = v.readonlyObject({\n correlation: correlationSchema,\n hidden: v.boolean().optional(),\n system: v.literalUnion('permissions', 'client', 'test').optional(),\n});\n\nexport const correlatedSubquerySchema: v.Type<CorrelatedSubquery> =\n correlatedSubquerySchemaOmitSubquery.extend({\n subquery: v.lazy(() => astSchema),\n });\n\nexport const astSchema: v.Type<AST> = v.readonlyObject({\n schema: v.string().optional(),\n table: v.string(),\n alias: v.string().optional(),\n where: conditionSchema.optional(),\n related: v.readonlyArray(correlatedSubquerySchema).optional(),\n limit: v.number().optional(),\n orderBy: orderingSchema.optional(),\n start: v\n .object({\n row: rowSchema,\n exclusive: v.boolean(),\n })\n .optional(),\n});\n\nexport type Bound = {\n row: Row;\n exclusive: boolean;\n};\n\n/**\n * As in SQL you can have multiple orderings. We don't currently\n * support ordering on anything other than the root query.\n */\nexport type OrderPart = readonly [field: string, direction: 'asc' | 'desc'];\nexport type Ordering = readonly OrderPart[];\n\nexport type SimpleOperator = EqualityOps | OrderOps | LikeOps | InOps;\nexport type EqualityOps = '=' | '!=' | 'IS' | 'IS NOT';\nexport type OrderOps = '<' | '>' | '<=' | '>=';\nexport type LikeOps = 'LIKE' | 'NOT LIKE' | 'ILIKE' | 'NOT ILIKE';\nexport type InOps = 'IN' | 'NOT IN';\n\nexport type AST = {\n readonly schema?: string | undefined;\n readonly table: string;\n\n // A query would be aliased if the AST is a subquery.\n // e.g., when two subqueries select from the same table\n // they need an alias to differentiate them.\n // `SELECT\n // [SELECT * FROM issue WHERE issue.id = outer.parentId] AS parent\n // [SELECT * FROM issue WHERE issue.parentId = outer.id] AS children\n // FROM issue as outer`\n readonly alias?: string | undefined;\n\n // `select` is missing given we return all columns for now.\n\n // The PipelineBuilder will pick what to use to correlate\n // a subquery with a parent query. It can choose something from the\n // where conditions or choose the _first_ `related` entry.\n // Choosing the first `related` entry is almost always the best choice if\n // one exists.\n readonly where?: Condition | undefined;\n\n readonly related?: readonly CorrelatedSubquery[] | undefined;\n readonly start?: Bound | undefined;\n readonly limit?: number | undefined;\n readonly orderBy?: Ordering | undefined;\n};\n\nexport type Correlation = {\n readonly parentField: CompoundKey;\n readonly childField: CompoundKey;\n};\n\nexport type CorrelatedSubquery = {\n /**\n * Only equality correlation are supported for now.\n * E.g., direct foreign key relationships.\n */\n readonly correlation: Correlation;\n readonly subquery: AST;\n readonly system?: System | undefined;\n // If a hop in the subquery chain should be hidden from the output view.\n // A common example is junction edges. The query API provides the illusion\n // that they don't exist: `issue.related('labels')` instead of `issue.related('issue_labels').related('labels')`.\n // To maintain this illusion, the junction edge should be hidden.\n // When `hidden` is set to true, this hop will not be included in the output view\n // but its children will be.\n readonly hidden?: boolean | undefined;\n};\n\nexport type ValuePosition = LiteralReference | Parameter | ColumnReference;\n\nexport type ColumnReference = {\n readonly type: 'column';\n /**\n * Not a path yet as we're currently not allowing\n * comparisons across tables. This will need to\n * be a path through the tree in the near future.\n */\n readonly name: string;\n};\n\nexport type LiteralReference = {\n readonly type: 'literal';\n readonly value: LiteralValue;\n};\n\nexport type LiteralValue =\n | string\n | number\n | boolean\n | null\n | ReadonlyArray<string | number | boolean>;\n\n/**\n * Starting only with SimpleCondition for now.\n * ivm1 supports Conjunctions and Disjunctions.\n * We'll support them in the future.\n */\nexport type Condition =\n | SimpleCondition\n | Conjunction\n | Disjunction\n | CorrelatedSubqueryCondition;\n\nexport type SimpleCondition = {\n readonly type: 'simple';\n readonly op: SimpleOperator;\n readonly left: ValuePosition;\n\n /**\n * `null` is absent since we do not have an `IS` or `IS NOT`\n * operator defined and `null != null` in SQL.\n */\n readonly right: Exclude<ValuePosition, ColumnReference>;\n};\n\nexport type Conjunction = {\n type: 'and';\n conditions: readonly Condition[];\n};\n\nexport type Disjunction = {\n type: 'or';\n conditions: readonly Condition[];\n};\n\nexport type CorrelatedSubqueryCondition = {\n type: 'correlatedSubquery';\n related: CorrelatedSubquery;\n op: CorrelatedSubqueryConditionOperator;\n flip?: boolean | undefined;\n scalar?: boolean | undefined;\n [planIdSymbol]?: number | undefined;\n};\n\nexport type CorrelatedSubqueryConditionOperator = 'EXISTS' | 'NOT EXISTS';\n\ninterface ASTTransform {\n tableName(orig: string): string;\n columnName(origTable: string, origColumn: string): string;\n related(subqueries: CorrelatedSubquery[]): readonly CorrelatedSubquery[];\n where(cond: Condition): Condition | undefined;\n // conjunction or disjunction, called when traversing the return value of where()\n conditions(conds: Condition[]): readonly Condition[];\n}\n\nfunction transformAST(ast: AST, transform: ASTTransform): Required<AST> {\n // Name mapping functions (e.g. to server names)\n const {tableName, columnName} = transform;\n const colName = (c: string) => columnName(ast.table, c);\n const key = (table: string, k: CompoundKey) => {\n const serverKey = k.map(col => columnName(table, col));\n return mustCompoundKey(serverKey);\n };\n\n const where = ast.where ? transform.where(ast.where) : undefined;\n const transformed = {\n schema: ast.schema,\n table: tableName(ast.table),\n alias: ast.alias,\n where: where ? transformWhere(where, ast.table, transform) : undefined,\n related: ast.related\n ? transform.related(\n ast.related.map(\n r =>\n ({\n correlation: {\n parentField: key(ast.table, r.correlation.parentField),\n childField: key(r.subquery.table, r.correlation.childField),\n },\n hidden: r.hidden,\n subquery: transformAST(r.subquery, transform),\n system: r.system,\n }) satisfies Required<CorrelatedSubquery>,\n ),\n )\n : undefined,\n start: ast.start\n ? {\n ...ast.start,\n row: Object.fromEntries(\n Object.entries(ast.start.row).map(([col, val]) => [\n colName(col),\n val,\n ]),\n ),\n }\n : undefined,\n limit: ast.limit,\n orderBy: ast.orderBy?.map(([col, dir]) => [colName(col), dir] as const),\n };\n\n return transformed;\n}\n\nfunction transformWhere(\n where: Condition,\n table: string,\n transform: ASTTransform,\n): Condition {\n // Name mapping functions (e.g. to server names)\n const {columnName} = transform;\n const condValue = (c: ConditionValue) =>\n c.type !== 'column' ? c : {...c, name: columnName(table, c.name)};\n const key = (table: string, k: CompoundKey) => {\n const serverKey = k.map(col => columnName(table, col));\n return mustCompoundKey(serverKey);\n };\n\n if (where.type === 'simple') {\n return {...where, left: condValue(where.left)};\n } else if (where.type === 'correlatedSubquery') {\n const {correlation, subquery} = where.related;\n return {\n ...where,\n related: {\n ...where.related,\n correlation: {\n parentField: key(table, correlation.parentField),\n childField: key(subquery.table, correlation.childField),\n },\n subquery: transformAST(subquery, transform),\n },\n };\n }\n\n return {\n type: where.type,\n conditions: transform.conditions(\n where.conditions.map(c => transformWhere(c, table, transform)),\n ),\n };\n}\n\nconst normalizeCache = new WeakMap<AST, Required<AST>>();\n\nconst NORMALIZE_TRANSFORM: ASTTransform = {\n tableName: t => t,\n columnName: (_, c) => c,\n related: sortedRelated,\n where: flattened,\n conditions: c => c.sort(cmpCondition),\n};\n\nexport function normalizeAST(ast: AST): Required<AST> {\n let normalized = normalizeCache.get(ast);\n if (!normalized) {\n normalized = transformAST(ast, NORMALIZE_TRANSFORM);\n normalizeCache.set(ast, normalized);\n }\n return normalized;\n}\n\nexport function mapAST(ast: AST, mapper: NameMapper) {\n return transformAST(ast, {\n tableName: table => mapper.tableName(table),\n columnName: (table, col) => mapper.columnName(table, col),\n related: r => r,\n where: w => w,\n conditions: c => c,\n });\n}\n\nexport function mapCondition(\n cond: Condition,\n table: string,\n mapper: NameMapper,\n) {\n return transformWhere(cond, table, {\n tableName: table => mapper.tableName(table),\n columnName: (table, col) => mapper.columnName(table, col),\n related: r => r,\n where: w => w,\n conditions: c => c,\n });\n}\n\nfunction sortedRelated(\n related: CorrelatedSubquery[],\n): readonly CorrelatedSubquery[] {\n return related.sort(cmpRelated);\n}\n\nfunction cmpCondition(a: Condition, b: Condition): number {\n if (a.type === 'simple') {\n if (b.type !== 'simple') {\n return -1; // Order SimpleConditions first\n }\n\n return (\n compareValuePosition(a.left, b.left) ||\n compareUTF8MaybeNull(a.op, b.op) ||\n compareValuePosition(a.right, b.right)\n );\n }\n\n if (b.type === 'simple') {\n return 1; // Order SimpleConditions first\n }\n\n if (a.type === 'correlatedSubquery') {\n if (b.type !== 'correlatedSubquery') {\n return -1; // Order subquery before conjuctions/disjuctions\n }\n return (\n cmpRelated(a.related, b.related) ||\n compareUTF8MaybeNull(a.op, b.op) ||\n cmpOptionalBool(a.flip, b.flip) ||\n cmpOptionalBool(a.scalar, b.scalar)\n );\n }\n if (b.type === 'correlatedSubquery') {\n return -1; // Order correlatedSubquery before conjuctions/disjuctions\n }\n\n const val = compareUTF8MaybeNull(a.type, b.type);\n if (val !== 0) {\n return val;\n }\n for (\n let l = 0, r = 0;\n l < a.conditions.length && r < b.conditions.length;\n l++, r++\n ) {\n const val = cmpCondition(a.conditions[l], b.conditions[r]);\n if (val !== 0) {\n return val;\n }\n }\n // prefixes first\n return a.conditions.length - b.conditions.length;\n}\n\nfunction compareValuePosition(a: ValuePosition, b: ValuePosition): number {\n if (a.type !== b.type) {\n return compareUTF8(a.type, b.type);\n }\n switch (a.type) {\n case 'literal':\n assert(b.type === 'literal', 'Expected literal type for comparison');\n return compareUTF8(String(a.value), String(b.value));\n case 'column':\n assert(b.type === 'column', 'Expected column type for comparison');\n return compareUTF8(a.name, b.name);\n case 'static':\n throw new Error(\n 'Static parameters should be resolved before normalization',\n );\n }\n}\n\nfunction cmpRelated(a: CorrelatedSubquery, b: CorrelatedSubquery): number {\n return compareUTF8(must(a.subquery.alias), must(b.subquery.alias));\n}\n\n/**\n * Returns a flattened version of the Conditions in which nested Conjunctions with\n * the same operation ('AND' or 'OR') are flattened to the same level. e.g.\n *\n * ```\n * ((a AND b) AND (c AND (d OR (e OR f)))) -> (a AND b AND c AND (d OR e OR f))\n * ```\n *\n * Also flattens singleton Conjunctions regardless of operator, and removes\n * empty Conjunctions.\n */\nfunction flattened(cond: Condition): Condition | undefined {\n if (cond.type === 'simple' || cond.type === 'correlatedSubquery') {\n return cond;\n }\n const conditions = defined(\n cond.conditions.flatMap(c =>\n c.type === cond.type ? c.conditions.map(c => flattened(c)) : flattened(c),\n ),\n );\n\n switch (conditions.length) {\n case 0:\n return undefined;\n case 1:\n return conditions[0];\n default:\n return {\n type: cond.type,\n conditions,\n };\n }\n}\n\nfunction compareUTF8MaybeNull(a: string | null, b: string | null): number {\n if (a !== null && b !== null) {\n return compareUTF8(a, b);\n }\n if (b !== null) {\n return -1;\n }\n if (a !== null) {\n return 1;\n }\n return 0;\n}\n\nfunction cmpOptionalBool(\n a: boolean | undefined,\n b: boolean | undefined,\n): number {\n // undefined < false < true\n const toNum = (v: boolean | undefined) => (v === undefined ? 0 : v ? 2 : 1);\n return toNum(a) - toNum(b);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ast.js","names":[],"sources":["../../../../zero-protocol/src/ast.ts"],"sourcesContent":["/**\n * Wire-format representation of the zql AST interface.\n *\n * `v.Type<...>` types are explicitly declared to facilitate Typescript verification\n * that the schemas satisfy the zql type definitions. (Incidentally, explicit types\n * are also required for recursive schema definitions.)\n */\n\nimport {compareUTF8} from 'compare-utf8';\nimport {defined} from '../../shared/src/arrays.ts';\nimport {assert} from '../../shared/src/asserts.ts';\nimport {must} from '../../shared/src/must.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport type {NameMapper} from '../../zero-types/src/name-mapper.ts';\nimport {rowSchema, type Row} from './data.ts';\n\nexport const SUBQ_PREFIX = 'zsubq_';\n\nexport const selectorSchema = v.string();\nexport const toStaticParam = Symbol();\nexport const planIdSymbol = Symbol('planId');\n\nconst orderingElementSchema = v.readonly(\n v.tuple([selectorSchema, v.literalUnion('asc', 'desc')]),\n);\n\nexport const orderingSchema = v.readonlyArray(orderingElementSchema);\nexport type System = 'permissions' | 'client' | 'test';\n\nexport const primitiveSchema = v.union(\n v.string(),\n v.number(),\n v.boolean(),\n v.null(),\n);\n\nexport const equalityOpsSchema = v.literalUnion('=', '!=', 'IS', 'IS NOT');\n\nexport const orderOpsSchema = v.literalUnion('<', '>', '<=', '>=');\n\nexport const likeOpsSchema = v.literalUnion(\n 'LIKE',\n 'NOT LIKE',\n 'ILIKE',\n 'NOT ILIKE',\n);\n\nexport const inOpsSchema = v.literalUnion('IN', 'NOT IN');\n\nexport const simpleOperatorSchema = v.union(\n equalityOpsSchema,\n orderOpsSchema,\n likeOpsSchema,\n inOpsSchema,\n);\n\nconst literalReferenceSchema: v.Type<LiteralReference> = v.readonlyObject({\n type: v.literal('literal'),\n value: v.union(\n v.string(),\n v.number(),\n v.boolean(),\n v.null(),\n v.readonlyArray(v.union(v.string(), v.number(), v.boolean())),\n ),\n});\nconst columnReferenceSchema: v.Type<ColumnReference> = v.readonlyObject({\n type: v.literal('column'),\n name: v.string(),\n});\n\n/**\n * A parameter is a value that is not known at the time the query is written\n * and is resolved at runtime.\n *\n * Static parameters refer to something provided by the caller.\n * Static parameters are injected when the query pipeline is built from the AST\n * and do not change for the life of that pipeline.\n *\n * An example static parameter is the current authentication data.\n * When a user is authenticated, queries on the server have access\n * to the user's authentication data in order to evaluate authorization rules.\n * Authentication data doesn't change over the life of a query as a change\n * in auth data would represent a log-in / log-out of the user.\n *\n * AncestorParameters refer to rows encountered while running the query.\n * They are used by subqueries to refer to rows emitted by parent queries.\n */\nconst parameterReferenceSchema = v.readonlyObject({\n type: v.literal('static'),\n // The \"namespace\" of the injected parameter.\n // Write authorization will send the value of a row\n // prior to the mutation being run (preMutationRow).\n // Read and write authorization will both send the\n // current authentication data (authData).\n anchor: v.literalUnion('authData', 'preMutationRow'),\n field: v.union(v.string(), v.array(v.string())),\n});\n\nconst conditionValueSchema = v.union(\n literalReferenceSchema,\n columnReferenceSchema,\n parameterReferenceSchema,\n);\n\nexport type Parameter = v.Infer<typeof parameterReferenceSchema>;\n\nexport const simpleConditionSchema: v.Type<SimpleCondition> = v.readonlyObject({\n type: v.literal('simple'),\n op: simpleOperatorSchema,\n left: conditionValueSchema,\n right: v.union(parameterReferenceSchema, literalReferenceSchema),\n});\n\ntype ConditionValue = v.Infer<typeof conditionValueSchema>;\n\nexport const correlatedSubqueryConditionOperatorSchema: v.Type<CorrelatedSubqueryConditionOperator> =\n v.literalUnion('EXISTS', 'NOT EXISTS');\n\nexport const correlatedSubqueryConditionSchema: v.Type<CorrelatedSubqueryCondition> =\n v.readonlyObject({\n type: v.literal('correlatedSubquery'),\n related: v.lazy(() => correlatedSubquerySchema),\n op: correlatedSubqueryConditionOperatorSchema,\n flip: v.boolean().optional(),\n scalar: v.boolean().optional(),\n });\n\nexport const conditionSchema: v.Type<Condition> = v.union(\n simpleConditionSchema,\n v.lazy(() => conjunctionSchema),\n v.lazy(() => disjunctionSchema),\n correlatedSubqueryConditionSchema,\n);\n\nconst conjunctionSchema: v.Type<Conjunction> = v.readonlyObject({\n type: v.literal('and'),\n conditions: v.readonlyArray(conditionSchema),\n});\n\nconst disjunctionSchema: v.Type<Disjunction> = v.readonlyObject({\n type: v.literal('or'),\n conditions: v.readonlyArray(conditionSchema),\n});\n\nexport type CompoundKey = readonly [string, ...string[]];\n\nfunction mustCompoundKey(field: readonly string[]): CompoundKey {\n assert(\n Array.isArray(field) && field.length >= 1,\n 'Expected non-empty array for compound key',\n );\n return field as unknown as CompoundKey;\n}\n\nexport const compoundKeySchema: v.Type<CompoundKey> = v.readonly(\n // oxlint-disable-next-line e18e/prefer-spread-syntax\n v.tuple([v.string()]).concat(v.array(v.string())),\n);\n\nconst correlationSchema = v.readonlyObject({\n parentField: compoundKeySchema,\n childField: compoundKeySchema,\n});\n\n// Split out so that its inferred type can be checked against\n// Omit<CorrelatedSubquery, 'correlation'> in ast-type-test.ts.\n// The mutually-recursive reference of the 'other' field to astSchema\n// is the only thing added in v.lazy. The v.lazy is necessary due to the\n// mutually-recursive types, but v.lazy prevents inference of the resulting\n// type.\nexport const correlatedSubquerySchemaOmitSubquery = v.readonlyObject({\n correlation: correlationSchema,\n hidden: v.boolean().optional(),\n system: v.literalUnion('permissions', 'client', 'test').optional(),\n});\n\nexport const correlatedSubquerySchema: v.Type<CorrelatedSubquery> =\n correlatedSubquerySchemaOmitSubquery.extend({\n subquery: v.lazy(() => astSchema),\n });\n\nexport const astSchema: v.Type<AST> = v.readonlyObject({\n schema: v.string().optional(),\n table: v.string(),\n alias: v.string().optional(),\n where: conditionSchema.optional(),\n related: v.readonlyArray(correlatedSubquerySchema).optional(),\n limit: v.number().optional(),\n orderBy: orderingSchema.optional(),\n start: v\n .object({\n row: rowSchema,\n exclusive: v.boolean(),\n })\n .optional(),\n});\n\nexport type Bound = {\n row: Row;\n exclusive: boolean;\n};\n\n/**\n * As in SQL you can have multiple orderings. We don't currently\n * support ordering on anything other than the root query.\n */\nexport type OrderPart = readonly [field: string, direction: 'asc' | 'desc'];\nexport type Ordering = readonly OrderPart[];\n\nexport type SimpleOperator = EqualityOps | OrderOps | LikeOps | InOps;\nexport type EqualityOps = '=' | '!=' | 'IS' | 'IS NOT';\nexport type OrderOps = '<' | '>' | '<=' | '>=';\nexport type LikeOps = 'LIKE' | 'NOT LIKE' | 'ILIKE' | 'NOT ILIKE';\nexport type InOps = 'IN' | 'NOT IN';\n\nexport type AST = {\n readonly schema?: string | undefined;\n readonly table: string;\n\n // A query would be aliased if the AST is a subquery.\n // e.g., when two subqueries select from the same table\n // they need an alias to differentiate them.\n // `SELECT\n // [SELECT * FROM issue WHERE issue.id = outer.parentId] AS parent\n // [SELECT * FROM issue WHERE issue.parentId = outer.id] AS children\n // FROM issue as outer`\n readonly alias?: string | undefined;\n\n // `select` is missing given we return all columns for now.\n\n // The PipelineBuilder will pick what to use to correlate\n // a subquery with a parent query. It can choose something from the\n // where conditions or choose the _first_ `related` entry.\n // Choosing the first `related` entry is almost always the best choice if\n // one exists.\n readonly where?: Condition | undefined;\n\n readonly related?: readonly CorrelatedSubquery[] | undefined;\n readonly start?: Bound | undefined;\n readonly limit?: number | undefined;\n readonly orderBy?: Ordering | undefined;\n};\n\nexport type Correlation = {\n readonly parentField: CompoundKey;\n readonly childField: CompoundKey;\n};\n\nexport type CorrelatedSubquery = {\n /**\n * Only equality correlation are supported for now.\n * E.g., direct foreign key relationships.\n */\n readonly correlation: Correlation;\n readonly subquery: AST;\n readonly system?: System | undefined;\n // If a hop in the subquery chain should be hidden from the output view.\n // A common example is junction edges. The query API provides the illusion\n // that they don't exist: `issue.related('labels')` instead of `issue.related('issue_labels').related('labels')`.\n // To maintain this illusion, the junction edge should be hidden.\n // When `hidden` is set to true, this hop will not be included in the output view\n // but its children will be.\n readonly hidden?: boolean | undefined;\n};\n\nexport type ValuePosition = LiteralReference | Parameter | ColumnReference;\n\nexport type ColumnReference = {\n readonly type: 'column';\n /**\n * Not a path yet as we're currently not allowing\n * comparisons across tables. This will need to\n * be a path through the tree in the near future.\n */\n readonly name: string;\n};\n\nexport type LiteralReference = {\n readonly type: 'literal';\n readonly value: LiteralValue;\n};\n\nexport type LiteralValue =\n | string\n | number\n | boolean\n | null\n | ReadonlyArray<string | number | boolean>;\n\n/**\n * Starting only with SimpleCondition for now.\n * ivm1 supports Conjunctions and Disjunctions.\n * We'll support them in the future.\n */\nexport type Condition =\n | SimpleCondition\n | Conjunction\n | Disjunction\n | CorrelatedSubqueryCondition;\n\nexport type SimpleCondition = {\n readonly type: 'simple';\n readonly op: SimpleOperator;\n readonly left: ValuePosition;\n\n /**\n * `null` is absent since we do not have an `IS` or `IS NOT`\n * operator defined and `null != null` in SQL.\n */\n readonly right: Exclude<ValuePosition, ColumnReference>;\n};\n\nexport type Conjunction = {\n type: 'and';\n conditions: readonly Condition[];\n};\n\nexport type Disjunction = {\n type: 'or';\n conditions: readonly Condition[];\n};\n\nexport type CorrelatedSubqueryCondition = {\n type: 'correlatedSubquery';\n related: CorrelatedSubquery;\n op: CorrelatedSubqueryConditionOperator;\n flip?: boolean | undefined;\n scalar?: boolean | undefined;\n [planIdSymbol]?: number | undefined;\n};\n\nexport type CorrelatedSubqueryConditionOperator = 'EXISTS' | 'NOT EXISTS';\n\ninterface ASTTransform {\n tableName(orig: string): string;\n columnName(origTable: string, origColumn: string): string;\n related(subqueries: CorrelatedSubquery[]): readonly CorrelatedSubquery[];\n where(cond: Condition): Condition | undefined;\n // conjunction or disjunction, called when traversing the return value of where()\n conditions(conds: Condition[]): readonly Condition[];\n}\n\nfunction transformAST(ast: AST, transform: ASTTransform): Required<AST> {\n // Name mapping functions (e.g. to server names)\n const {tableName, columnName} = transform;\n const colName = (c: string) => columnName(ast.table, c);\n const key = (table: string, k: CompoundKey) => {\n const serverKey = k.map(col => columnName(table, col));\n return mustCompoundKey(serverKey);\n };\n\n const where = ast.where ? transform.where(ast.where) : undefined;\n const transformed = {\n schema: ast.schema,\n table: tableName(ast.table),\n alias: ast.alias,\n where: where ? transformWhere(where, ast.table, transform) : undefined,\n related: ast.related\n ? transform.related(\n ast.related.map(\n r =>\n ({\n correlation: {\n parentField: key(ast.table, r.correlation.parentField),\n childField: key(r.subquery.table, r.correlation.childField),\n },\n hidden: r.hidden,\n subquery: transformAST(r.subquery, transform),\n system: r.system,\n }) satisfies Required<CorrelatedSubquery>,\n ),\n )\n : undefined,\n start: ast.start\n ? {\n ...ast.start,\n row: Object.fromEntries(\n Object.entries(ast.start.row).map(([col, val]) => [\n colName(col),\n val,\n ]),\n ),\n }\n : undefined,\n limit: ast.limit,\n orderBy: ast.orderBy?.map(([col, dir]) => [colName(col), dir] as const),\n };\n\n return transformed;\n}\n\nfunction transformWhere(\n where: Condition,\n table: string,\n transform: ASTTransform,\n): Condition {\n // Name mapping functions (e.g. to server names)\n const {columnName} = transform;\n const condValue = (c: ConditionValue) =>\n c.type !== 'column' ? c : {...c, name: columnName(table, c.name)};\n const key = (table: string, k: CompoundKey) => {\n const serverKey = k.map(col => columnName(table, col));\n return mustCompoundKey(serverKey);\n };\n\n if (where.type === 'simple') {\n return {...where, left: condValue(where.left)};\n } else if (where.type === 'correlatedSubquery') {\n const {correlation, subquery} = where.related;\n return {\n ...where,\n related: {\n ...where.related,\n correlation: {\n parentField: key(table, correlation.parentField),\n childField: key(subquery.table, correlation.childField),\n },\n subquery: transformAST(subquery, transform),\n },\n };\n }\n\n return {\n type: where.type,\n conditions: transform.conditions(\n where.conditions.map(c => transformWhere(c, table, transform)),\n ),\n };\n}\n\nconst normalizeCache = new WeakMap<AST, Required<AST>>();\n\nconst NORMALIZE_TRANSFORM: ASTTransform = {\n tableName: t => t,\n columnName: (_, c) => c,\n related: sortedRelated,\n where: flattened,\n conditions: c => c.sort(cmpCondition),\n};\n\nexport function normalizeAST(ast: AST): Required<AST> {\n let normalized = normalizeCache.get(ast);\n if (!normalized) {\n normalized = transformAST(ast, NORMALIZE_TRANSFORM);\n normalizeCache.set(ast, normalized);\n }\n return normalized;\n}\n\nexport function mapAST(ast: AST, mapper: NameMapper) {\n return transformAST(ast, {\n tableName: table => mapper.tableName(table),\n columnName: (table, col) => mapper.columnName(table, col),\n related: r => r,\n where: w => w,\n conditions: c => c,\n });\n}\n\nexport function mapCondition(\n cond: Condition,\n table: string,\n mapper: NameMapper,\n) {\n return transformWhere(cond, table, {\n tableName: table => mapper.tableName(table),\n columnName: (table, col) => mapper.columnName(table, col),\n related: r => r,\n where: w => w,\n conditions: c => c,\n });\n}\n\nfunction sortedRelated(\n related: CorrelatedSubquery[],\n): readonly CorrelatedSubquery[] {\n return related.sort(cmpRelated);\n}\n\nfunction cmpCondition(a: Condition, b: Condition): number {\n if (a.type === 'simple') {\n if (b.type !== 'simple') {\n return -1; // Order SimpleConditions first\n }\n\n return (\n compareValuePosition(a.left, b.left) ||\n compareUTF8MaybeNull(a.op, b.op) ||\n compareValuePosition(a.right, b.right)\n );\n }\n\n if (b.type === 'simple') {\n return 1; // Order SimpleConditions first\n }\n\n if (a.type === 'correlatedSubquery') {\n if (b.type !== 'correlatedSubquery') {\n return -1; // Order subquery before conjuctions/disjuctions\n }\n return (\n cmpRelated(a.related, b.related) ||\n compareUTF8MaybeNull(a.op, b.op) ||\n cmpOptionalBool(a.flip, b.flip) ||\n cmpOptionalBool(a.scalar, b.scalar)\n );\n }\n if (b.type === 'correlatedSubquery') {\n return -1; // Order correlatedSubquery before conjuctions/disjuctions\n }\n\n const val = compareUTF8MaybeNull(a.type, b.type);\n if (val !== 0) {\n return val;\n }\n for (\n let l = 0, r = 0;\n l < a.conditions.length && r < b.conditions.length;\n l++, r++\n ) {\n const val = cmpCondition(a.conditions[l], b.conditions[r]);\n if (val !== 0) {\n return val;\n }\n }\n // prefixes first\n return a.conditions.length - b.conditions.length;\n}\n\nfunction compareValuePosition(a: ValuePosition, b: ValuePosition): number {\n if (a.type !== b.type) {\n return compareUTF8(a.type, b.type);\n }\n switch (a.type) {\n case 'literal':\n assert(b.type === 'literal', 'Expected literal type for comparison');\n return compareUTF8(String(a.value), String(b.value));\n case 'column':\n assert(b.type === 'column', 'Expected column type for comparison');\n return compareUTF8(a.name, b.name);\n case 'static':\n throw new Error(\n 'Static parameters should be resolved before normalization',\n );\n }\n}\n\nfunction cmpRelated(a: CorrelatedSubquery, b: CorrelatedSubquery): number {\n return compareUTF8(must(a.subquery.alias), must(b.subquery.alias));\n}\n\n/**\n * Returns a flattened version of the Conditions in which nested Conjunctions with\n * the same operation ('AND' or 'OR') are flattened to the same level. e.g.\n *\n * ```\n * ((a AND b) AND (c AND (d OR (e OR f)))) -> (a AND b AND c AND (d OR e OR f))\n * ```\n *\n * Also flattens singleton Conjunctions regardless of operator, and removes\n * empty Conjunctions.\n */\nfunction flattened(cond: Condition): Condition | undefined {\n if (cond.type === 'simple' || cond.type === 'correlatedSubquery') {\n return cond;\n }\n const conditions = defined(\n cond.conditions.flatMap(c =>\n c.type === cond.type ? c.conditions.map(c => flattened(c)) : flattened(c),\n ),\n );\n\n switch (conditions.length) {\n case 0:\n return undefined;\n case 1:\n return conditions[0];\n default:\n return {\n type: cond.type,\n conditions,\n };\n }\n}\n\nfunction compareUTF8MaybeNull(a: string | null, b: string | null): number {\n if (a !== null && b !== null) {\n return compareUTF8(a, b);\n }\n if (b !== null) {\n return -1;\n }\n if (a !== null) {\n return 1;\n }\n return 0;\n}\n\nfunction cmpOptionalBool(\n a: boolean | undefined,\n b: boolean | undefined,\n): number {\n // undefined < false < true\n const toNum = (v: boolean | undefined) => (v === undefined ? 0 : v ? 2 : 1);\n return toNum(a) - toNum(b);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primary-key.d.ts","sourceRoot":"","sources":["../../../../zero-protocol/src/primary-key.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,4BAA4B,CAAC;AAEhD,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"primary-key.d.ts","sourceRoot":"","sources":["../../../../zero-protocol/src/primary-key.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,4BAA4B,CAAC;AAEhD,eAAO,MAAM,gBAAgB,wCAG5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,eAAO,MAAM,qBAAqB,gEAIjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,2BAA2B,6DAEvC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"primary-key.js","names":[],"sources":["../../../../zero-protocol/src/primary-key.ts"],"sourcesContent":["import * as v from '../../shared/src/valita.ts';\n\nexport const primaryKeySchema = v.readonly(\n v.tuple([v.string()]).concat(v.array(v.string())),\n);\n\nexport type PrimaryKey = v.Infer<typeof primaryKeySchema>;\n\nexport const primaryKeyValueSchema = v.union(\n v.string(),\n v.number(),\n v.boolean(),\n);\n\nexport type PrimaryKeyValue = v.Infer<typeof primaryKeyValueSchema>;\n\nexport const primaryKeyValueRecordSchema = v.readonlyRecord(\n primaryKeyValueSchema,\n);\n\nexport type PrimaryKeyValueRecord = v.Infer<typeof primaryKeyValueRecordSchema>;\n"],"mappings":";;AAEA,IAAa,mBAAmB,
|
|
1
|
+
{"version":3,"file":"primary-key.js","names":[],"sources":["../../../../zero-protocol/src/primary-key.ts"],"sourcesContent":["import * as v from '../../shared/src/valita.ts';\n\nexport const primaryKeySchema = v.readonly(\n // oxlint-disable-next-line e18e/prefer-spread-syntax\n v.tuple([v.string()]).concat(v.array(v.string())),\n);\n\nexport type PrimaryKey = v.Infer<typeof primaryKeySchema>;\n\nexport const primaryKeyValueSchema = v.union(\n v.string(),\n v.number(),\n v.boolean(),\n);\n\nexport type PrimaryKeyValue = v.Infer<typeof primaryKeyValueSchema>;\n\nexport const primaryKeyValueRecordSchema = v.readonlyRecord(\n primaryKeyValueSchema,\n);\n\nexport type PrimaryKeyValueRecord = v.Infer<typeof primaryKeyValueRecordSchema>;\n"],"mappings":";;AAEA,IAAa,mBAAmB,SAE9B,eAAE,MAAM,CAAC,eAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,eAAE,MAAM,eAAE,QAAQ,CAAC,CAAC,CAClD;AAID,IAAa,wBAAwB,eAAE,MACrC,eAAE,QAAQ,EACV,eAAE,QAAQ,EACV,eAAE,SAAS,CACZ;AAID,IAAa,8BAA8B,eACzC,sBACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../../zero-protocol/src/push.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,4BAA4B,CAAC;AAChD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,qCAAqC,CAAC;AAUpE,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAK/C,eAAO,MAAM,6BAA6B,yBAAyB,CAAC;AAEpE,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../../../zero-protocol/src/push.ts"],"names":[],"mappings":"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"}
|