@rocicorp/zero 1.2.0-canary.3 → 1.2.0-canary.4
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/shared/src/bigint-json.d.ts.map +1 -1
- package/out/shared/src/bigint-json.js.map +1 -1
- package/out/shared/src/btree-set.js.map +1 -1
- package/out/z2s/src/sql.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/auth/load-permissions.d.ts +2 -2
- package/out/zero-cache/src/auth/load-permissions.d.ts.map +1 -1
- package/out/zero-cache/src/auth/load-permissions.js.map +1 -1
- package/out/zero-cache/src/db/run-transaction.d.ts.map +1 -1
- package/out/zero-cache/src/db/run-transaction.js +2 -2
- package/out/zero-cache/src/db/run-transaction.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.map +1 -1
- package/out/zero-cache/src/server/logging.d.ts.map +1 -1
- package/out/zero-cache/src/server/logging.js +9 -1
- package/out/zero-cache/src/server/logging.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts +1 -0
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +2 -2
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.d.ts +2 -2
- package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/column-metadata.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-protocol/src/application-error.d.ts +1 -1
- package/out/zero-protocol/src/application-error.d.ts.map +1 -1
- package/out/zero-protocol/src/application-error.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +2 -2
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/planner/planner-debug.d.ts.map +1 -1
- package/out/zql/src/planner/planner-debug.js.map +1 -1
- package/out/zql/src/query/expression.d.ts +1 -1
- package/out/zql/src/query/expression.d.ts.map +1 -1
- package/out/zql/src/query/expression.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","names":["#ws","#wsID","#protocolVersion","#lc","#onClose","#messageHandler","#downstreamMsgTimer","#handleClose","#handleError","#proxyInbound","#maybeSendPong","#closeWithError","#closed","#viewSyncerOutboundStream","#pusherOutboundStream","#handleMessage","#handleMessageResult","#closeWithThrown","#proxyOutbound","#lastDownstreamMsgTime"],"sources":["../../../../../zero-cache/src/workers/connection.ts"],"sourcesContent":["import type {LogContext, LogLevel} from '@rocicorp/logger';\nimport {pipeline, Readable, Writable} from 'node:stream';\nimport type {CloseEvent, Data, ErrorEvent} from 'ws';\nimport WebSocket, {createWebSocketStream} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport type {ConnectedMessage} from '../../../zero-protocol/src/connect.ts';\nimport type {Downstream} from '../../../zero-protocol/src/down.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport {\n MIN_SERVER_SUPPORTED_SYNC_PROTOCOL,\n PROTOCOL_VERSION,\n} from '../../../zero-protocol/src/protocol-version.ts';\nimport {upstreamSchema, type Upstream} from '../../../zero-protocol/src/up.ts';\nimport {\n ProtocolErrorWithLevel,\n getLogLevel,\n wrapWithProtocolError,\n} from '../types/error-with-level.ts';\nimport type {Source} from '../types/streams.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {\n isProtocolError,\n type ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\n\nexport type HandlerResult =\n | {\n type: 'ok';\n }\n | {\n type: 'fatal';\n error: ErrorBody;\n }\n | {\n type: 'transient';\n errors: ErrorBody[];\n }\n | StreamResult;\n\nexport type StreamResult = {\n type: 'stream';\n source: 'viewSyncer' | 'pusher';\n stream: Source<Downstream>;\n};\n\nexport interface MessageHandler {\n handleMessage(msg: Upstream): Promise<HandlerResult[]>;\n}\n\n// Ensures that a downstream message is sent at least every interval, sending a\n// 'pong' if necessary. This is set to be slightly longer than the client-side\n// PING_INTERVAL of 5 seconds, so that in the common case, 'pong's are sent in\n// response to client-initiated 'ping's. However, if the inbound stream is\n// backed up because a command is taking a long time to process, the pings\n// will be stuck in the queue (i.e. back-pressured), in which case pongs will\n// be manually sent to notify the client of server liveness.\n//\n// This is equivalent to what is done for Postgres keepalives on the\n// replication stream (which can similarly be back-pressured):\n// https://github.com/rocicorp/mono/blob/f98cb369a2dbb15650328859c732db358f187ef0/packages/zero-cache/src/services/change-source/pg/logical-replication/stream.ts#L21\nconst DOWNSTREAM_MSG_INTERVAL_MS = 6_000;\n\n/**\n * Represents a connection between the client and server.\n *\n * Handles incoming messages on the connection and dispatches\n * them to the correct service.\n *\n * Listens to the ViewSyncer and sends messages to the client.\n */\nexport class Connection {\n readonly #ws: WebSocket;\n readonly #wsID: string;\n readonly #protocolVersion: number;\n readonly #lc: LogContext;\n readonly #onClose: () => void;\n readonly #messageHandler: MessageHandler;\n readonly #downstreamMsgTimer: NodeJS.Timeout | undefined;\n\n #viewSyncerOutboundStream: Source<Downstream> | undefined;\n #pusherOutboundStream: Source<Downstream> | undefined;\n #closed = false;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n ws: WebSocket,\n messageHandler: MessageHandler,\n onClose: () => void,\n ) {\n const {clientGroupID, clientID, wsID, protocolVersion} = connectParams;\n this.#messageHandler = messageHandler;\n\n this.#ws = ws;\n this.#wsID = wsID;\n this.#protocolVersion = protocolVersion;\n\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#lc.debug?.('new connection');\n this.#onClose = onClose;\n\n this.#ws.addEventListener('close', this.#handleClose);\n this.#ws.addEventListener('error', this.#handleError);\n\n this.#proxyInbound();\n this.#downstreamMsgTimer = setInterval(\n this.#maybeSendPong,\n DOWNSTREAM_MSG_INTERVAL_MS / 2,\n );\n }\n\n /**\n * Checks the protocol version and errors for unsupported protocols,\n * sending the initial `connected` response on success.\n *\n * This is early in the connection lifecycle because {@link #handleMessage}\n * will only parse messages with schema(s) of supported protocol versions.\n */\n init(): boolean {\n if (\n this.#protocolVersion > PROTOCOL_VERSION ||\n this.#protocolVersion < MIN_SERVER_SUPPORTED_SYNC_PROTOCOL\n ) {\n this.#closeWithError({\n kind: ErrorKind.VersionNotSupported,\n message: `server is at sync protocol v${PROTOCOL_VERSION} and does not support v${\n this.#protocolVersion\n }. The ${\n this.#protocolVersion > PROTOCOL_VERSION ? 'server' : 'client'\n } must be updated to a newer release.`,\n origin: ErrorOrigin.ZeroCache,\n });\n } else {\n const connectedMessage: ConnectedMessage = [\n 'connected',\n {wsid: this.#wsID, timestamp: Date.now()},\n ];\n this.send(connectedMessage, 'ignore-backpressure');\n return true;\n }\n return false;\n }\n\n close(reason: string, ...args: unknown[]) {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#lc.info?.(`closing connection: ${reason}`, ...args);\n this.#ws.removeEventListener('close', this.#handleClose);\n this.#ws.removeEventListener('error', this.#handleError);\n this.#viewSyncerOutboundStream?.cancel();\n this.#viewSyncerOutboundStream = undefined;\n this.#pusherOutboundStream?.cancel();\n this.#pusherOutboundStream = undefined;\n this.#onClose();\n if (this.#ws.readyState !== this.#ws.CLOSED) {\n this.#ws.close();\n }\n clearTimeout(this.#downstreamMsgTimer);\n\n // spin down services if we have\n // no more client connections for the client group?\n }\n\n handleInitConnection(initConnectionMsg: string) {\n return this.#handleMessage({data: initConnectionMsg});\n }\n\n #handleMessage = async (event: {data: Data}) => {\n const data = event.data.toString();\n if (this.#closed) {\n this.#lc.debug?.('Ignoring message received after closed', data);\n return;\n }\n\n let msg;\n try {\n const value = JSON.parse(data);\n msg = valita.parse(value, upstreamSchema);\n } catch (e) {\n this.#lc.warn?.(`failed to parse message \"${data}\": ${String(e)}`);\n this.#closeWithError(\n {\n kind: ErrorKind.InvalidMessage,\n message: String(e),\n origin: ErrorOrigin.ZeroCache,\n },\n e,\n );\n return;\n }\n\n try {\n const msgType = msg[0];\n if (msgType === 'ping') {\n this.send(['pong', {}], 'ignore-backpressure');\n return;\n }\n\n const result = await this.#messageHandler.handleMessage(msg);\n for (const r of result) {\n this.#handleMessageResult(r);\n }\n } catch (e) {\n this.#closeWithThrown(e);\n }\n };\n\n #handleMessageResult(result: HandlerResult): void {\n switch (result.type) {\n case 'fatal':\n this.#closeWithError(result.error);\n break;\n case 'ok':\n break;\n case 'stream': {\n switch (result.source) {\n case 'viewSyncer':\n assert(\n this.#viewSyncerOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#viewSyncerOutboundStream = result.stream;\n break;\n case 'pusher':\n assert(\n this.#pusherOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#pusherOutboundStream = result.stream;\n break;\n }\n this.#proxyOutbound(result.stream);\n break;\n }\n case 'transient': {\n for (const error of result.errors) {\n void this.sendError(error);\n }\n }\n }\n }\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.close('WebSocket close event', {code, reason, wasClean});\n };\n\n #handleError = (e: ErrorEvent) => {\n this.#lc.error?.('WebSocket error event', e.message, e.error);\n };\n\n #proxyInbound() {\n pipeline(\n createWebSocketStream(this.#ws),\n new Writable({\n write: (data, _encoding, callback) => {\n this.#handleMessage({data}).then(() => callback(), callback);\n },\n }),\n // The done callback is not used, as #handleClose and #handleError,\n // configured on the underlying WebSocket, provide more complete\n // information.\n () => {},\n );\n }\n\n #proxyOutbound(outboundStream: Source<Downstream>) {\n // Note: createWebSocketStream() is avoided here in order to control\n // exception handling with #closeWithThrown(). If the Writable\n // from createWebSocketStream() were instead used, exceptions\n // from the outboundStream result in the Writable closing the\n // the websocket before the error message can be sent.\n pipeline(\n Readable.from(outboundStream),\n new Writable({\n objectMode: true,\n write: (downstream: Downstream, _encoding, callback) =>\n this.send(downstream, callback),\n }),\n e =>\n e\n ? this.#closeWithThrown(e)\n : this.close(`downstream closed by ViewSyncer`),\n );\n }\n\n #closeWithThrown(e: unknown) {\n const errorBody =\n findProtocolError(e)?.errorBody ?? wrapWithProtocolError(e).errorBody;\n\n this.#closeWithError(errorBody, e);\n }\n\n #closeWithError(errorBody: ErrorBody, thrown?: unknown) {\n this.sendError(errorBody, thrown);\n this.close(\n `${errorBody.kind} (${errorBody.origin}): ${errorBody.message}`,\n errorBody,\n );\n }\n\n #lastDownstreamMsgTime = Date.now();\n\n #maybeSendPong = () => {\n if (Date.now() - this.#lastDownstreamMsgTime > DOWNSTREAM_MSG_INTERVAL_MS) {\n this.#lc.debug?.('manually sending pong');\n this.send(['pong', {}], 'ignore-backpressure');\n }\n };\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n ) {\n this.#lastDownstreamMsgTime = Date.now();\n return send(this.#lc, this.#ws, data, callback);\n }\n\n sendError(errorBody: ErrorBody, thrown?: unknown) {\n sendError(this.#lc, this.#ws, errorBody, thrown);\n }\n}\n\nexport type WebSocketLike = Pick<WebSocket, 'readyState'> & {\n send(data: string, cb?: (err?: Error) => void): void;\n};\n\n// Exported for testing purposes.\nexport function send(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(\n JSON.stringify(data),\n callback === 'ignore-backpressure' ? undefined : callback,\n );\n } else {\n lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, {\n dropped: data,\n });\n if (callback !== 'ignore-backpressure') {\n callback(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }\n }\n}\n\nexport function sendError(\n lc: LogContext,\n ws: WebSocket,\n errorBody: ErrorBody,\n thrown?: unknown,\n) {\n lc = lc.withContext('errorKind', errorBody.kind);\n\n let logLevel: LogLevel;\n\n // If the thrown error is a ProtocolErrorWithLevel, its explicit logLevel takes precedence\n if (thrown instanceof ProtocolErrorWithLevel) {\n logLevel = thrown.logLevel;\n }\n // Errors with errno or transient socket codes are low-level, transient I/O issues\n // (e.g., EPIPE, ECONNRESET) and should be warnings, not errors\n else if (\n hasErrno(thrown) ||\n hasTransientSocketCode(thrown) ||\n isTransientSocketMessage(errorBody.message)\n ) {\n logLevel = 'warn';\n }\n // Fallback: check errorBody.kind for errors that weren't thrown as ProtocolErrorWithLevel\n else if (\n errorBody.kind === ErrorKind.ClientNotFound ||\n errorBody.kind === ErrorKind.TransformFailed\n ) {\n logLevel = 'warn';\n } else {\n logLevel = thrown ? getLogLevel(thrown) : 'info';\n }\n\n lc[logLevel]?.('Sending error on WebSocket', errorBody, thrown ?? '');\n send(lc, ws, ['error', errorBody], 'ignore-backpressure');\n}\n\nexport function findProtocolError(error: unknown): ProtocolError | undefined {\n if (isProtocolError(error)) {\n return error;\n }\n if (error instanceof Error && error.cause) {\n return findProtocolError(error.cause);\n }\n return undefined;\n}\n\nfunction hasErrno(error: unknown): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'errno' in error &&\n typeof (error as {errno: unknown}).errno !== 'undefined',\n );\n}\n\n// System error codes that indicate transient socket conditions.\n// These are checked via the `code` property on errors.\nconst TRANSIENT_SOCKET_ERROR_CODES = new Set([\n 'EPIPE',\n 'ECONNRESET',\n 'ECANCELED',\n]);\n\n// Error messages that indicate transient socket conditions but don't have\n// standard error codes (e.g., WebSocket library errors).\nconst TRANSIENT_SOCKET_MESSAGE_PATTERNS = [\n 'socket was closed while data was being compressed',\n];\n\nfunction hasTransientSocketCode(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const maybeCode =\n 'code' in error ? String((error as {code?: unknown}).code) : undefined;\n return Boolean(\n maybeCode && TRANSIENT_SOCKET_ERROR_CODES.has(maybeCode.toUpperCase()),\n );\n}\n\nfunction isTransientSocketMessage(message: string | undefined): boolean {\n if (!message) {\n return false;\n }\n const lower = message.toLowerCase();\n return TRANSIENT_SOCKET_MESSAGE_PATTERNS.some(pattern =>\n lower.includes(pattern),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connection.js","names":["#ws","#wsID","#protocolVersion","#lc","#onClose","#messageHandler","#downstreamMsgTimer","#handleClose","#handleError","#proxyInbound","#maybeSendPong","#closeWithError","#closed","#viewSyncerOutboundStream","#pusherOutboundStream","#handleMessage","#handleMessageResult","#closeWithThrown","#proxyOutbound","#lastDownstreamMsgTime"],"sources":["../../../../../zero-cache/src/workers/connection.ts"],"sourcesContent":["import type {LogContext, LogLevel} from '@rocicorp/logger';\nimport {pipeline, Readable, Writable} from 'node:stream';\nimport type {CloseEvent, Data, ErrorEvent} from 'ws';\nimport WebSocket, {createWebSocketStream} from 'ws';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport * as valita from '../../../shared/src/valita.ts';\nimport type {ConnectedMessage} from '../../../zero-protocol/src/connect.ts';\nimport type {Downstream} from '../../../zero-protocol/src/down.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport type {ErrorBody} from '../../../zero-protocol/src/error.ts';\nimport {\n MIN_SERVER_SUPPORTED_SYNC_PROTOCOL,\n PROTOCOL_VERSION,\n} from '../../../zero-protocol/src/protocol-version.ts';\nimport {upstreamSchema, type Upstream} from '../../../zero-protocol/src/up.ts';\nimport {\n ProtocolErrorWithLevel,\n getLogLevel,\n wrapWithProtocolError,\n} from '../types/error-with-level.ts';\nimport type {Source} from '../types/streams.ts';\nimport type {ConnectParams} from './connect-params.ts';\nimport {\n isProtocolError,\n type ProtocolError,\n} from '../../../zero-protocol/src/error.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\n\nexport type HandlerResult =\n | {\n type: 'ok';\n }\n | {\n type: 'fatal';\n error: ErrorBody;\n }\n | {\n type: 'transient';\n errors: ErrorBody[];\n }\n | StreamResult;\n\nexport type StreamResult = {\n type: 'stream';\n source: 'viewSyncer' | 'pusher';\n stream: Source<Downstream>;\n};\n\nexport interface MessageHandler {\n handleMessage(msg: Upstream): Promise<HandlerResult[]>;\n}\n\n// Ensures that a downstream message is sent at least every interval, sending a\n// 'pong' if necessary. This is set to be slightly longer than the client-side\n// PING_INTERVAL of 5 seconds, so that in the common case, 'pong's are sent in\n// response to client-initiated 'ping's. However, if the inbound stream is\n// backed up because a command is taking a long time to process, the pings\n// will be stuck in the queue (i.e. back-pressured), in which case pongs will\n// be manually sent to notify the client of server liveness.\n//\n// This is equivalent to what is done for Postgres keepalives on the\n// replication stream (which can similarly be back-pressured):\n// https://github.com/rocicorp/mono/blob/f98cb369a2dbb15650328859c732db358f187ef0/packages/zero-cache/src/services/change-source/pg/logical-replication/stream.ts#L21\nconst DOWNSTREAM_MSG_INTERVAL_MS = 6_000;\n\n/**\n * Represents a connection between the client and server.\n *\n * Handles incoming messages on the connection and dispatches\n * them to the correct service.\n *\n * Listens to the ViewSyncer and sends messages to the client.\n */\nexport class Connection {\n readonly #ws: WebSocket;\n readonly #wsID: string;\n readonly #protocolVersion: number;\n readonly #lc: LogContext;\n readonly #onClose: () => void;\n readonly #messageHandler: MessageHandler;\n readonly #downstreamMsgTimer: NodeJS.Timeout | undefined;\n\n #viewSyncerOutboundStream: Source<Downstream> | undefined;\n #pusherOutboundStream: Source<Downstream> | undefined;\n #closed = false;\n\n constructor(\n lc: LogContext,\n connectParams: ConnectParams,\n ws: WebSocket,\n messageHandler: MessageHandler,\n onClose: () => void,\n ) {\n const {clientGroupID, clientID, wsID, protocolVersion} = connectParams;\n this.#messageHandler = messageHandler;\n\n this.#ws = ws;\n this.#wsID = wsID;\n this.#protocolVersion = protocolVersion;\n\n this.#lc = lc\n .withContext('connection')\n .withContext('clientID', clientID)\n .withContext('clientGroupID', clientGroupID)\n .withContext('wsID', wsID);\n this.#lc.debug?.('new connection');\n this.#onClose = onClose;\n\n this.#ws.addEventListener('close', this.#handleClose);\n this.#ws.addEventListener('error', this.#handleError);\n\n this.#proxyInbound();\n this.#downstreamMsgTimer = setInterval(\n this.#maybeSendPong,\n DOWNSTREAM_MSG_INTERVAL_MS / 2,\n );\n }\n\n /**\n * Checks the protocol version and errors for unsupported protocols,\n * sending the initial `connected` response on success.\n *\n * This is early in the connection lifecycle because {@link #handleMessage}\n * will only parse messages with schema(s) of supported protocol versions.\n */\n init(): boolean {\n if (\n this.#protocolVersion > PROTOCOL_VERSION ||\n this.#protocolVersion < MIN_SERVER_SUPPORTED_SYNC_PROTOCOL\n ) {\n this.#closeWithError({\n kind: ErrorKind.VersionNotSupported,\n message: `server is at sync protocol v${PROTOCOL_VERSION} and does not support v${\n this.#protocolVersion\n }. The ${\n this.#protocolVersion > PROTOCOL_VERSION ? 'server' : 'client'\n } must be updated to a newer release.`,\n origin: ErrorOrigin.ZeroCache,\n });\n } else {\n const connectedMessage: ConnectedMessage = [\n 'connected',\n {wsid: this.#wsID, timestamp: Date.now()},\n ];\n this.send(connectedMessage, 'ignore-backpressure');\n return true;\n }\n return false;\n }\n\n close(reason: string, ...args: unknown[]) {\n if (this.#closed) {\n return;\n }\n this.#closed = true;\n this.#lc.info?.(`closing connection: ${reason}`, ...args);\n this.#ws.removeEventListener('close', this.#handleClose);\n this.#ws.removeEventListener('error', this.#handleError);\n this.#viewSyncerOutboundStream?.cancel();\n this.#viewSyncerOutboundStream = undefined;\n this.#pusherOutboundStream?.cancel();\n this.#pusherOutboundStream = undefined;\n this.#onClose();\n if (this.#ws.readyState !== this.#ws.CLOSED) {\n this.#ws.close();\n }\n clearTimeout(this.#downstreamMsgTimer);\n\n // spin down services if we have\n // no more client connections for the client group?\n }\n\n handleInitConnection(initConnectionMsg: string) {\n return this.#handleMessage({data: initConnectionMsg});\n }\n\n #handleMessage = async (event: {data: Data}) => {\n const data = event.data.toString();\n if (this.#closed) {\n this.#lc.debug?.('Ignoring message received after closed', data);\n return;\n }\n\n let msg;\n try {\n const value = JSON.parse(data);\n msg = valita.parse(value, upstreamSchema);\n } catch (e) {\n this.#lc.warn?.(`failed to parse message \"${data}\": ${String(e)}`);\n this.#closeWithError(\n {\n kind: ErrorKind.InvalidMessage,\n message: String(e),\n origin: ErrorOrigin.ZeroCache,\n },\n e,\n );\n return;\n }\n\n try {\n const msgType = msg[0];\n if (msgType === 'ping') {\n this.send(['pong', {}], 'ignore-backpressure');\n return;\n }\n\n const result = await this.#messageHandler.handleMessage(msg);\n for (const r of result) {\n this.#handleMessageResult(r);\n }\n } catch (e) {\n this.#closeWithThrown(e);\n }\n };\n\n #handleMessageResult(result: HandlerResult): void {\n switch (result.type) {\n case 'fatal':\n this.#closeWithError(result.error);\n break;\n case 'ok':\n break;\n case 'stream': {\n switch (result.source) {\n case 'viewSyncer':\n assert(\n this.#viewSyncerOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#viewSyncerOutboundStream = result.stream;\n break;\n case 'pusher':\n assert(\n this.#pusherOutboundStream === undefined,\n 'Outbound stream already set for this connection!',\n );\n this.#pusherOutboundStream = result.stream;\n break;\n }\n this.#proxyOutbound(result.stream);\n break;\n }\n case 'transient': {\n for (const error of result.errors) {\n this.sendError(error);\n }\n }\n }\n }\n\n #handleClose = (e: CloseEvent) => {\n const {code, reason, wasClean} = e;\n this.close('WebSocket close event', {code, reason, wasClean});\n };\n\n #handleError = (e: ErrorEvent) => {\n this.#lc.error?.('WebSocket error event', e.message, e.error);\n };\n\n #proxyInbound() {\n pipeline(\n createWebSocketStream(this.#ws),\n new Writable({\n write: (data, _encoding, callback) => {\n this.#handleMessage({data}).then(() => callback(), callback);\n },\n }),\n // The done callback is not used, as #handleClose and #handleError,\n // configured on the underlying WebSocket, provide more complete\n // information.\n () => {},\n );\n }\n\n #proxyOutbound(outboundStream: Source<Downstream>) {\n // Note: createWebSocketStream() is avoided here in order to control\n // exception handling with #closeWithThrown(). If the Writable\n // from createWebSocketStream() were instead used, exceptions\n // from the outboundStream result in the Writable closing the\n // the websocket before the error message can be sent.\n pipeline(\n Readable.from(outboundStream),\n new Writable({\n objectMode: true,\n write: (downstream: Downstream, _encoding, callback) =>\n this.send(downstream, callback),\n }),\n e =>\n e\n ? this.#closeWithThrown(e)\n : this.close(`downstream closed by ViewSyncer`),\n );\n }\n\n #closeWithThrown(e: unknown) {\n const errorBody =\n findProtocolError(e)?.errorBody ?? wrapWithProtocolError(e).errorBody;\n\n this.#closeWithError(errorBody, e);\n }\n\n #closeWithError(errorBody: ErrorBody, thrown?: unknown) {\n this.sendError(errorBody, thrown);\n this.close(\n `${errorBody.kind} (${errorBody.origin}): ${errorBody.message}`,\n errorBody,\n );\n }\n\n #lastDownstreamMsgTime = Date.now();\n\n #maybeSendPong = () => {\n if (Date.now() - this.#lastDownstreamMsgTime > DOWNSTREAM_MSG_INTERVAL_MS) {\n this.#lc.debug?.('manually sending pong');\n this.send(['pong', {}], 'ignore-backpressure');\n }\n };\n\n send(\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n ) {\n this.#lastDownstreamMsgTime = Date.now();\n return send(this.#lc, this.#ws, data, callback);\n }\n\n sendError(errorBody: ErrorBody, thrown?: unknown) {\n sendError(this.#lc, this.#ws, errorBody, thrown);\n }\n}\n\nexport type WebSocketLike = Pick<WebSocket, 'readyState'> & {\n send(data: string, cb?: (err?: Error) => void): void;\n};\n\n// Exported for testing purposes.\nexport function send(\n lc: LogContext,\n ws: WebSocketLike,\n data: Downstream,\n callback: ((err?: Error | null) => void) | 'ignore-backpressure',\n) {\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(\n JSON.stringify(data),\n callback === 'ignore-backpressure' ? undefined : callback,\n );\n } else {\n lc.debug?.(`Dropping outbound message on ws (state: ${ws.readyState})`, {\n dropped: data,\n });\n if (callback !== 'ignore-backpressure') {\n callback(\n new ProtocolErrorWithLevel(\n {\n kind: ErrorKind.Internal,\n message: 'WebSocket closed',\n origin: ErrorOrigin.ZeroCache,\n },\n 'info',\n ),\n );\n }\n }\n}\n\nexport function sendError(\n lc: LogContext,\n ws: WebSocket,\n errorBody: ErrorBody,\n thrown?: unknown,\n) {\n lc = lc.withContext('errorKind', errorBody.kind);\n\n let logLevel: LogLevel;\n\n // If the thrown error is a ProtocolErrorWithLevel, its explicit logLevel takes precedence\n if (thrown instanceof ProtocolErrorWithLevel) {\n logLevel = thrown.logLevel;\n }\n // Errors with errno or transient socket codes are low-level, transient I/O issues\n // (e.g., EPIPE, ECONNRESET) and should be warnings, not errors\n else if (\n hasErrno(thrown) ||\n hasTransientSocketCode(thrown) ||\n isTransientSocketMessage(errorBody.message)\n ) {\n logLevel = 'warn';\n }\n // Fallback: check errorBody.kind for errors that weren't thrown as ProtocolErrorWithLevel\n else if (\n errorBody.kind === ErrorKind.ClientNotFound ||\n errorBody.kind === ErrorKind.TransformFailed\n ) {\n logLevel = 'warn';\n } else {\n logLevel = thrown ? getLogLevel(thrown) : 'info';\n }\n\n lc[logLevel]?.('Sending error on WebSocket', errorBody, thrown ?? '');\n send(lc, ws, ['error', errorBody], 'ignore-backpressure');\n}\n\nexport function findProtocolError(error: unknown): ProtocolError | undefined {\n if (isProtocolError(error)) {\n return error;\n }\n if (error instanceof Error && error.cause) {\n return findProtocolError(error.cause);\n }\n return undefined;\n}\n\nfunction hasErrno(error: unknown): boolean {\n return Boolean(\n error &&\n typeof error === 'object' &&\n 'errno' in error &&\n typeof (error as {errno: unknown}).errno !== 'undefined',\n );\n}\n\n// System error codes that indicate transient socket conditions.\n// These are checked via the `code` property on errors.\nconst TRANSIENT_SOCKET_ERROR_CODES = new Set([\n 'EPIPE',\n 'ECONNRESET',\n 'ECANCELED',\n]);\n\n// Error messages that indicate transient socket conditions but don't have\n// standard error codes (e.g., WebSocket library errors).\nconst TRANSIENT_SOCKET_MESSAGE_PATTERNS = [\n 'socket was closed while data was being compressed',\n];\n\nfunction hasTransientSocketCode(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const maybeCode =\n 'code' in error ? String((error as {code?: unknown}).code) : undefined;\n return Boolean(\n maybeCode && TRANSIENT_SOCKET_ERROR_CODES.has(maybeCode.toUpperCase()),\n );\n}\n\nfunction isTransientSocketMessage(message: string | undefined): boolean {\n if (!message) {\n return false;\n }\n const lower = message.toLowerCase();\n return TRANSIENT_SOCKET_MESSAGE_PATTERNS.some(pattern =>\n lower.includes(pattern),\n );\n}\n"],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ export interface ApplicationErrorOptions<T extends ReadonlyJSONValue | undefined
|
|
|
18
18
|
*/
|
|
19
19
|
export declare class ApplicationError<const T extends ReadonlyJSONValue | undefined = ReadonlyJSONValue | undefined> extends Error {
|
|
20
20
|
#private;
|
|
21
|
-
constructor(message: string, options?: ApplicationErrorOptions<T>
|
|
21
|
+
constructor(message: string, options?: ApplicationErrorOptions<T>);
|
|
22
22
|
get details(): T;
|
|
23
23
|
get kind(): 'Application';
|
|
24
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application-error.d.ts","sourceRoot":"","sources":["../../../../zero-protocol/src/application-error.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB,CACtC,CAAC,SAAS,iBAAiB,GAAG,SAAS;IAEvC,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;GAMG;AACH,qBAAa,gBAAgB,CAC3B,KAAK,CAAC,CAAC,SAAS,iBAAiB,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,CAC7E,SAAQ,KAAK;;
|
|
1
|
+
{"version":3,"file":"application-error.d.ts","sourceRoot":"","sources":["../../../../zero-protocol/src/application-error.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB,CACtC,CAAC,SAAS,iBAAiB,GAAG,SAAS;IAEvC,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;GAMG;AACH,qBAAa,gBAAgB,CAC3B,KAAK,CAAC,CAAC,SAAS,iBAAiB,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,CAC7E,SAAQ,KAAK;;gBAMD,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,CAAC,CAAC,CAAC;IAMjE,IAAI,OAAO,IAAI,CAAC,CAEf;IAED,IAAI,IAAI,IAAI,aAAa,CAExB;CACF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAED,wBAAgB,wBAAwB,CACtC,CAAC,SAAS,iBAAiB,GAAG,SAAS,GAAG,iBAAiB,GAAG,SAAS,EACvE,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAYrC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"application-error.js","names":["#details"],"sources":["../../../../zero-protocol/src/application-error.ts"],"sourcesContent":["import {getErrorDetails, getErrorMessage} from '../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../shared/src/json.ts';\n\n/**\n * Options accepted by {@link ApplicationError}.\n *\n * Use these when you need to attach additional context to an error that will\n * be sent back to the client.\n */\nexport interface ApplicationErrorOptions<\n T extends ReadonlyJSONValue | undefined,\n> {\n details?: T;\n cause?: unknown;\n}\n\n/**\n * Error type that application code can throw to surface structured metadata back to\n * the client.\n *\n * Use this when you want to return a descriptive message along with the\n * JSON-serializable `details`.\n */\nexport class ApplicationError<\n const T extends ReadonlyJSONValue | undefined = ReadonlyJSONValue | undefined,\n> extends Error {\n /**\n * This maps onto errors for transform and push app-level failures.\n */\n readonly #details: T;\n\n constructor(
|
|
1
|
+
{"version":3,"file":"application-error.js","names":["#details"],"sources":["../../../../zero-protocol/src/application-error.ts"],"sourcesContent":["import {getErrorDetails, getErrorMessage} from '../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../shared/src/json.ts';\n\n/**\n * Options accepted by {@link ApplicationError}.\n *\n * Use these when you need to attach additional context to an error that will\n * be sent back to the client.\n */\nexport interface ApplicationErrorOptions<\n T extends ReadonlyJSONValue | undefined,\n> {\n details?: T;\n cause?: unknown;\n}\n\n/**\n * Error type that application code can throw to surface structured metadata back to\n * the client.\n *\n * Use this when you want to return a descriptive message along with the\n * JSON-serializable `details`.\n */\nexport class ApplicationError<\n const T extends ReadonlyJSONValue | undefined = ReadonlyJSONValue | undefined,\n> extends Error {\n /**\n * This maps onto errors for transform and push app-level failures.\n */\n readonly #details: T;\n\n constructor(message: string, options?: ApplicationErrorOptions<T>) {\n super(message, {cause: options?.cause});\n this.name = 'ApplicationError';\n this.#details = options?.details ?? (undefined as T);\n }\n\n get details(): T {\n return this.#details;\n }\n\n get kind(): 'Application' {\n return 'Application';\n }\n}\n\nexport function isApplicationError(error: unknown): error is ApplicationError {\n return error instanceof ApplicationError;\n}\n\nexport function wrapWithApplicationError<\n T extends ReadonlyJSONValue | undefined = ReadonlyJSONValue | undefined,\n>(error: unknown): ApplicationError<T> {\n if (isApplicationError(error)) {\n return error as ApplicationError<T>;\n }\n\n const message = getErrorMessage(error);\n const details = getErrorDetails(error);\n\n return new ApplicationError<T>(message, {\n cause: error,\n details: details as T,\n });\n}\n"],"mappings":";;;;;;;;;AAuBA,IAAa,mBAAb,cAEU,MAAM;;;;CAId;CAEA,YAAY,SAAiB,SAAsC;AACjE,QAAM,SAAS,EAAC,OAAO,SAAS,OAAM,CAAC;AACvC,OAAK,OAAO;AACZ,QAAA,UAAgB,SAAS,WAAY,KAAA;;CAGvC,IAAI,UAAa;AACf,SAAO,MAAA;;CAGT,IAAI,OAAsB;AACxB,SAAO;;;AAIX,SAAgB,mBAAmB,OAA2C;AAC5E,QAAO,iBAAiB;;AAG1B,SAAgB,yBAEd,OAAqC;AACrC,KAAI,mBAAmB,MAAM,CAC3B,QAAO;AAMT,QAAO,IAAI,iBAHK,gBAAgB,MAAM,EAGE;EACtC,OAAO;EACE,SAJK,gBAAgB,MAAM;EAKrC,CAAC"}
|
|
@@ -84,7 +84,7 @@ export declare function generateWithOverlayInner(rowIterator: Iterable<Row>, ove
|
|
|
84
84
|
* No `startAt` or comparator needed. Injects remove/old-edit rows eagerly
|
|
85
85
|
* at the start, and suppresses add/new-edit rows inline by PK match.
|
|
86
86
|
*/
|
|
87
|
-
export declare function generateWithOverlayUnordered(rows: Iterable<Row>, constraint: Constraint | undefined, overlay: Overlay | undefined, lastPushedEpoch: number, primaryKey: PrimaryKey, filterPredicate?: (
|
|
87
|
+
export declare function generateWithOverlayUnordered(rows: Iterable<Row>, constraint: Constraint | undefined, overlay: Overlay | undefined, lastPushedEpoch: number, primaryKey: PrimaryKey, filterPredicate?: (row: Row) => boolean): Generator<{
|
|
88
88
|
row: Readonly<Record<string, import("../../../shared/src/json.ts").ReadonlyJSONValue | undefined>>;
|
|
89
89
|
relationships: {};
|
|
90
90
|
}, void, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/memory-source.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,QAAQ,EAAC,MAAM,kCAAkC,CAAC;AAI1D,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAET,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAC,GAAG,EAAQ,MAAM,oCAAoC,CAAC;AACnE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,2CAA2C,CAAC;AAC1E,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,IAAI,EACV,MAAM,WAAW,CAAC;AAEnB,OAAO,EAGL,KAAK,KAAK,EACV,KAAK,MAAM,EACX,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EAIZ,WAAW,EACZ,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,GAAG,GAAG,SAAS,CAAC;IACrB,MAAM,EAAE,GAAG,GAAG,SAAS,CAAC;CACzB,CAAC;AAQF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC5B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IACvC,WAAW,EAAE,UAAU,CAAC;IACxB,OAAO,EACH;QACE,SAAS,EAAE,mBAAmB,CAAC;QAC/B,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;KAClC,GACD,SAAS,CAAC;IACd,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC3C,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,YAAa,YAAW,MAAM;;gBAYvC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,gBAAgB,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC;IAclC,IAAI,WAAW;;;;MAMd;IAED,IAAI;IAUJ,IAAI,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC,CAExB;IAeD,OAAO,CACL,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,OAAO,CAAC,EAAE,SAAS,EACnB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,WAAW;IA4Fd,YAAY,IAAI,MAAM,EAAE;
|
|
1
|
+
{"version":3,"file":"memory-source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/memory-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -130,14 +130,14 @@ var MemorySource = class MemorySource {
|
|
|
130
130
|
*#fetch(req, conn) {
|
|
131
131
|
const requestedSort = must(conn.sort);
|
|
132
132
|
const { compareRows } = conn;
|
|
133
|
-
const connectionComparator = (r1, r2) => compareRows(
|
|
133
|
+
const connectionComparator = req.reverse ? (r1, r2) => compareRows(r2, r1) : compareRows;
|
|
134
134
|
const pkConstraint = primaryKeyConstraintFromFilters(conn.filters?.condition, this.#primaryKey);
|
|
135
135
|
const fetchOrPkConstraint = pkConstraint ?? req.constraint;
|
|
136
136
|
const indexSort = [];
|
|
137
137
|
if (fetchOrPkConstraint) for (const key of Object.keys(fetchOrPkConstraint)) indexSort.push([key, "asc"]);
|
|
138
138
|
if (this.#primaryKey.length > 1 || !fetchOrPkConstraint || !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)) indexSort.push(...requestedSort);
|
|
139
139
|
const { data, comparator: compare } = this.#getOrCreateIndex(indexSort, conn);
|
|
140
|
-
const indexComparator = (r1, r2) => compare(
|
|
140
|
+
const indexComparator = req.reverse ? (r1, r2) => compare(r2, r1) : compare;
|
|
141
141
|
const startAt = req.start?.row;
|
|
142
142
|
let scanStart;
|
|
143
143
|
if (fetchOrPkConstraint) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-source.js","names":["#tableName","#columns","#primaryKey","#primaryIndexSort","#indexes","#connections","#getPrimaryIndex","#fetch","#disconnect","#getSchema","#getOrCreateIndex","#overlay","#writeChange","#pushEpoch"],"sources":["../../../../../zql/src/ivm/memory-source.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {BTreeSet} from '../../../shared/src/btree-set.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {once} from '../../../shared/src/iterables.ts';\nimport type {\n Condition,\n Ordering,\n OrderPart,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition,\n} from '../builder/filter.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport type {Change} from './change.ts';\nimport {\n constraintMatchesPrimaryKey,\n constraintMatchesRow,\n primaryKeyConstraintFromFilters,\n type Constraint,\n} from './constraint.ts';\nimport {\n compareValues,\n makeComparator,\n valuesEqual,\n type Comparator,\n type Node,\n} from './data.ts';\nimport {filterPush} from './filter-push.ts';\nimport {\n skipYields,\n type FetchRequest,\n type Input,\n type Output,\n type Start,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {\n Source,\n SourceChange,\n SourceChangeAdd,\n SourceChangeEdit,\n SourceChangeRemove,\n SourceInput,\n} from './source.ts';\nimport type {Stream} from './stream.ts';\n\nexport type Overlay = {\n epoch: number;\n change: SourceChange;\n};\n\nexport type Overlays = {\n add: Row | undefined;\n remove: Row | undefined;\n};\n\ntype Index = {\n comparator: Comparator;\n data: BTreeSet<Row>;\n usedBy: Set<Connection>;\n};\n\nexport type Connection = {\n input: Input;\n output: Output | undefined;\n sort?: Ordering | undefined;\n splitEditKeys: Set<string> | undefined;\n compareRows: Comparator;\n filters:\n | {\n condition: NoSubqueryCondition;\n predicate: (row: Row) => boolean;\n }\n | undefined;\n readonly debug?: DebugDelegate | undefined;\n lastPushedEpoch: number;\n};\n\n/**\n * A `MemorySource` is a source that provides data to the pipeline from an\n * in-memory data source.\n *\n * This data is kept in sorted order as downstream pipelines will always expect\n * the data they receive from `pull` to be in sorted order.\n */\nexport class MemorySource implements Source {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #primaryKey: PrimaryKey;\n readonly #primaryIndexSort: Ordering;\n readonly #indexes: Map<string, Index> = new Map();\n readonly #connections: Connection[] = [];\n\n #overlay: Overlay | undefined;\n #pushEpoch = 0;\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n primaryIndexData?: BTreeSet<Row>,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#primaryKey = primaryKey;\n this.#primaryIndexSort = primaryKey.map(k => [k, 'asc']);\n const comparator = makeBoundComparator(this.#primaryIndexSort);\n this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {\n comparator,\n data: primaryIndexData ?? new BTreeSet<Row>(comparator),\n usedBy: new Set(),\n });\n }\n\n get tableSchema() {\n return {\n name: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n fork() {\n const primaryIndex = this.#getPrimaryIndex();\n return new MemorySource(\n this.#tableName,\n this.#columns,\n this.#primaryKey,\n primaryIndex.data.clone(),\n );\n }\n\n get data(): BTreeSet<Row> {\n return this.#getPrimaryIndex().data;\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n system: 'client',\n relationships: {},\n isHidden: false,\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n ): SourceInput {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n const internalSort = sort ?? this.#primaryIndexSort;\n\n const input: SourceInput = {\n getSchema: () => schema,\n fetch: req => this.#fetch(req, connection),\n setOutput: output => {\n connection.output = output;\n },\n destroy: () => {\n this.#disconnect(input);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n output: undefined,\n sort: internalSort,\n splitEditKeys,\n compareRows: makeComparator(internalSort),\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(internalSort, this.#primaryKey);\n }\n this.#connections.push(connection);\n return input;\n }\n\n #disconnect(input: Input): void {\n const idx = this.#connections.findIndex(c => c.input === input);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n\n // TODO: We used to delete unused indexes here. But in common cases like\n // navigating into issue detail pages it caused a ton of constantly\n // building and destroying indexes.\n //\n // Perhaps some intelligent LRU or something is needed here but for now,\n // the opposite extreme of keeping all indexes for the lifetime of the\n // page seems better.\n }\n\n #getPrimaryIndex(): Index {\n const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));\n assert(index, 'Primary index not found');\n return index;\n }\n\n #getOrCreateIndex(sort: Ordering, usedBy: Connection): Index {\n const key = JSON.stringify(sort);\n const index = this.#indexes.get(key);\n // Future optimization could use existing index if it's the same just sorted\n // in reverse of needed.\n if (index) {\n index.usedBy.add(usedBy);\n return index;\n }\n\n const comparator = makeBoundComparator(sort);\n\n // When creating these synchronously becomes a problem, a few options:\n // 1. Allow users to specify needed indexes up front\n // 2. Create indexes in a different thread asynchronously (this would require\n // modifying the BTree to be able to be passed over structured-clone, or using\n // a different library.)\n // 3. We could even theoretically do (2) on multiple threads and then merge the\n // results!\n const data = new BTreeSet<Row>(comparator);\n\n // I checked, there's no special path for adding data in bulk faster.\n // The constructor takes an array, but it just calls add/set over and over.\n for (const row of this.#getPrimaryIndex().data) {\n data.add(row);\n }\n\n const newIndex = {comparator, data, usedBy: new Set([usedBy])};\n this.#indexes.set(key, newIndex);\n return newIndex;\n }\n\n // For unit testing that we correctly clean up indexes.\n getIndexKeys(): string[] {\n return [...this.#indexes.keys()];\n }\n\n *#fetch(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n const requestedSort = must(conn.sort);\n const {compareRows} = conn;\n const connectionComparator = (r1: Row, r2: Row) =>\n compareRows(r1, r2) * (req.reverse ? -1 : 1);\n\n const pkConstraint = primaryKeyConstraintFromFilters(\n conn.filters?.condition,\n this.#primaryKey,\n );\n // The primary key constraint will be more limiting than the constraint\n // so swap out to that if it exists.\n const fetchOrPkConstraint = pkConstraint ?? req.constraint;\n\n // If there is a constraint, we need an index sorted by it first.\n const indexSort: OrderPart[] = [];\n if (fetchOrPkConstraint) {\n for (const key of Object.keys(fetchOrPkConstraint)) {\n indexSort.push([key, 'asc']);\n }\n }\n\n // For the special case of constraining by PK, we don't need to worry about\n // any requested sort since there can only be one result. Otherwise we also\n // need the index sorted by the requested sort.\n if (\n this.#primaryKey.length > 1 ||\n !fetchOrPkConstraint ||\n !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)\n ) {\n indexSort.push(...requestedSort);\n }\n\n const index = this.#getOrCreateIndex(indexSort, conn);\n const {data, comparator: compare} = index;\n const indexComparator = (r1: Row, r2: Row) =>\n compare(r1, r2) * (req.reverse ? -1 : 1);\n\n const startAt = req.start?.row;\n\n // If there is a constraint, we want to start our scan at the first row that\n // matches the constraint. But because the next OrderPart can be `desc`,\n // it's not true that {[constraintKey]: constraintValue} is the first\n // matching row. Because in that case, the other fields will all be\n // `undefined`, and in Zero `undefined` is always less than any other value.\n // So if the second OrderPart is descending then `undefined` values will\n // actually be the *last* row. We need a way to stay \"start at the first row\n // with this constraint value\". RowBound with the corresponding compareBound\n // comparator accomplishes this. The right thing is probably to teach the\n // btree library to support this concept.\n let scanStart: RowBound | undefined;\n\n if (fetchOrPkConstraint) {\n scanStart = {};\n for (const [key, dir] of indexSort) {\n if (hasOwn(fetchOrPkConstraint, key)) {\n scanStart[key] = fetchOrPkConstraint[key];\n } else {\n if (req.reverse) {\n scanStart[key] = dir === 'asc' ? maxValue : minValue;\n } else {\n scanStart[key] = dir === 'asc' ? minValue : maxValue;\n }\n }\n }\n } else {\n scanStart = startAt;\n }\n\n const rowsIterable = generateRows(data, scanStart, req.reverse);\n const withOverlay = generateWithOverlay(\n startAt,\n pkConstraint ? once(rowsIterable) : rowsIterable,\n // use `req.constraint` here and not `fetchOrPkConstraint` since `fetchOrPkConstraint` could be the\n // primary key constraint. The primary key constraint comes from filters and is acting as a filter\n // rather than as the fetch constraint.\n req.constraint,\n this.#overlay,\n conn.lastPushedEpoch,\n // Use indexComparator, generateWithOverlayInner has a subtle dependency\n // on this. Since generateWithConstraint is done after\n // generateWithOverlay, the generator consumed by generateWithOverlayInner\n // does not end when the constraint stops matching and so the final\n // check to yield an add overlay if not yet yielded is not reached.\n // However, using the indexComparator the add overlay will be less than\n // the first row that does not match the constraint, and so any\n // not yet yielded add overlay will be yielded when the first row\n // not matching the constraint is reached.\n indexComparator,\n conn.filters?.predicate,\n );\n\n const withConstraint = generateWithConstraint(\n skipYields(\n generateWithStart(withOverlay, req.start, connectionComparator),\n ),\n // we use `req.constraint` and not `fetchOrPkConstraint` here because we need to\n // AND the constraint with what could have been the primary key constraint\n req.constraint,\n );\n\n yield* conn.filters\n ? generateWithFilter(withConstraint, conn.filters.predicate)\n : withConstraint;\n }\n\n *push(change: SourceChange): Stream<'yield'> {\n for (const result of this.genPush(change)) {\n if (result === 'yield') {\n yield result;\n }\n }\n }\n\n *genPush(change: SourceChange) {\n const primaryIndex = this.#getPrimaryIndex();\n const {data} = primaryIndex;\n const exists = (row: Row) => data.has(row);\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n yield* genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n for (const {data} of this.#indexes.values()) {\n switch (change.type) {\n case 'add': {\n const added = data.add(change.row);\n // must succeed since we checked has() above.\n assert(\n added,\n 'MemorySource: add must succeed since row existence was already checked',\n );\n break;\n }\n case 'remove': {\n const removed = data.delete(change.row);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: remove must succeed since row existence was already checked',\n );\n break;\n }\n case 'edit': {\n // TODO: We could see if the PK (form the index tree's perspective)\n // changed and if not we could use set.\n // We cannot just do `set` with the new value since the `oldRow` might\n // not map to the same entry as the new `row` in the index btree.\n const removed = data.delete(change.oldRow);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: edit remove must succeed since row existence was already checked',\n );\n data.add(change.row);\n break;\n }\n default:\n unreachable(change);\n }\n }\n }\n}\n\nfunction* generateWithConstraint(\n it: Stream<Node>,\n constraint: Constraint | undefined,\n) {\n for (const node of it) {\n if (constraint && !constraintMatchesRow(constraint, node.row)) {\n break;\n }\n yield node;\n }\n}\n\nfunction* generateWithFilter(it: Stream<Node>, filter: (row: Row) => boolean) {\n for (const node of it) {\n if (filter(node.row)) {\n yield node;\n }\n }\n}\n\nexport function* genPushAndWriteWithSplitEdit(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n getNextEpoch: () => number,\n) {\n let shouldSplitEdit = false;\n if (change.type === 'edit') {\n for (const {splitEditKeys} of connections) {\n if (splitEditKeys) {\n for (const key of splitEditKeys) {\n if (!valuesEqual(change.row[key], change.oldRow[key])) {\n shouldSplitEdit = true;\n break;\n }\n }\n }\n }\n }\n\n if (change.type === 'edit' && shouldSplitEdit) {\n yield* genPushAndWrite(\n connections,\n {\n type: 'remove',\n row: change.oldRow,\n },\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n yield* genPushAndWrite(\n connections,\n {\n type: 'add',\n row: change.row,\n },\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n } else {\n yield* genPushAndWrite(\n connections,\n change,\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n }\n}\n\nfunction* genPushAndWrite(\n connections: readonly Connection[],\n change: SourceChangeAdd | SourceChangeRemove | SourceChangeEdit,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n pushEpoch: number,\n) {\n for (const x of genPush(connections, change, exists, setOverlay, pushEpoch)) {\n yield x;\n }\n writeChange(change);\n}\n\nfunction* genPush(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => void,\n pushEpoch: number,\n) {\n switch (change.type) {\n case 'add':\n assert(\n !exists(change.row),\n () => `Row already exists ${stringify(change)}`,\n );\n break;\n case 'remove':\n assert(exists(change.row), () => `Row not found ${stringify(change)}`);\n break;\n case 'edit':\n assert(exists(change.oldRow), () => `Row not found ${stringify(change)}`);\n break;\n default:\n unreachable(change);\n }\n\n for (const conn of connections) {\n const {output, filters, input} = conn;\n if (output) {\n conn.lastPushedEpoch = pushEpoch;\n setOverlay({epoch: pushEpoch, change});\n const outputChange: Change =\n change.type === 'edit'\n ? {\n type: change.type,\n oldNode: {\n row: change.oldRow,\n relationships: {},\n },\n node: {\n row: change.row,\n relationships: {},\n },\n }\n : {\n type: change.type,\n node: {\n row: change.row,\n relationships: {},\n },\n };\n yield* filterPush(outputChange, output, input, filters?.predicate);\n yield undefined;\n }\n }\n\n setOverlay(undefined);\n}\n\nexport function* generateWithStart(\n nodes: Iterable<Node | 'yield'>,\n start: Start | undefined,\n compare: (r1: Row, r2: Row) => number,\n): Stream<Node | 'yield'> {\n if (!start) {\n yield* nodes;\n return;\n }\n let started = false;\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!started) {\n if (start.basis === 'at') {\n if (compare(node.row, start.row) >= 0) {\n started = true;\n }\n } else if (start.basis === 'after') {\n if (compare(node.row, start.row) > 0) {\n started = true;\n }\n }\n }\n if (started) {\n yield node;\n }\n }\n}\n\n/**\n * Takes an iterator and overlay.\n * Splices the overlay into the iterator at the correct position.\n *\n * @param startAt - if there is a lower bound to the stream. If the lower bound of the stream\n * is above the overlay, the overlay will be skipped.\n * @param rows - the stream into which the overlay should be spliced\n * @param constraint - constraint that was applied to the rowIterator and should\n * also be applied to the overlay.\n * @param overlay - the overlay values to splice in\n * @param compare - the comparator to use to find the position for the overlay\n */\nexport function* generateWithOverlay(\n startAt: Row | undefined,\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n compare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n const overlays = computeOverlays(\n startAt,\n constraint,\n overlayToApply,\n compare,\n filterPredicate,\n );\n yield* generateWithOverlayInner(rows, overlays, compare);\n}\n\nfunction computeOverlays(\n startAt: Row | undefined,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n compare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n): Overlays {\n let overlays: Overlays = {\n add: undefined,\n remove: undefined,\n };\n switch (overlay?.change.type) {\n case 'add':\n overlays = {\n add: overlay.change.row,\n remove: undefined,\n };\n break;\n case 'remove':\n overlays = {\n add: undefined,\n remove: overlay.change.row,\n };\n break;\n case 'edit':\n overlays = {\n add: overlay.change.row,\n remove: overlay.change.oldRow,\n };\n break;\n }\n\n if (startAt) {\n overlays = overlaysForStartAt(overlays, startAt, compare);\n }\n\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n\n return overlays;\n}\n\nexport {overlaysForStartAt as overlaysForStartAtForTest};\n\nfunction overlaysForStartAt(\n {add, remove}: Overlays,\n startAt: Row,\n compare: Comparator,\n): Overlays {\n const undefinedIfBeforeStartAt = (row: Row | undefined) =>\n row === undefined || compare(row, startAt) < 0 ? undefined : row;\n return {\n add: undefinedIfBeforeStartAt(add),\n remove: undefinedIfBeforeStartAt(remove),\n };\n}\n\nexport {overlaysForConstraint as overlaysForConstraintForTest};\n\nfunction overlaysForConstraint(\n {add, remove}: Overlays,\n constraint: Constraint,\n): Overlays {\n const undefinedIfDoesntMatchConstraint = (row: Row | undefined) =>\n row === undefined || !constraintMatchesRow(constraint, row)\n ? undefined\n : row;\n\n return {\n add: undefinedIfDoesntMatchConstraint(add),\n remove: undefinedIfDoesntMatchConstraint(remove),\n };\n}\n\nfunction overlaysForFilterPredicate(\n {add, remove}: Overlays,\n filterPredicate: (row: Row) => boolean | undefined,\n): Overlays {\n const undefinedIfDoesntMatchFilter = (row: Row | undefined) =>\n row === undefined || !filterPredicate(row) ? undefined : row;\n\n return {\n add: undefinedIfDoesntMatchFilter(add),\n remove: undefinedIfDoesntMatchFilter(remove),\n };\n}\n\nexport function* generateWithOverlayInner(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n compare: (r1: Row, r2: Row) => number,\n) {\n let addOverlayYielded = false;\n let removeOverlaySkipped = false;\n for (const row of rowIterator) {\n if (!addOverlayYielded && overlays.add) {\n const cmp = compare(overlays.add, row);\n if (cmp < 0) {\n addOverlayYielded = true;\n yield {row: overlays.add, relationships: {}};\n }\n }\n\n if (!removeOverlaySkipped && overlays.remove) {\n const cmp = compare(overlays.remove, row);\n if (cmp === 0) {\n removeOverlaySkipped = true;\n continue;\n }\n }\n yield {row, relationships: {}};\n }\n\n if (!addOverlayYielded && overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n}\n\n/**\n * Like {@link generateWithOverlay} but for unordered streams.\n * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly\n * at the start, and suppresses add/new-edit rows inline by PK match.\n */\nexport function* generateWithOverlayUnordered(\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n primaryKey: PrimaryKey,\n filterPredicate?: ((row: Row) => boolean) | undefined,\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n let overlays: Overlays = {add: undefined, remove: undefined};\n switch (overlayToApply?.change.type) {\n case 'add':\n overlays = {add: overlayToApply.change.row, remove: undefined};\n break;\n case 'remove':\n overlays = {add: undefined, remove: overlayToApply.change.row};\n break;\n case 'edit':\n overlays = {\n add: overlayToApply.change.row,\n remove: overlayToApply.change.oldRow,\n };\n break;\n }\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n yield* generateWithOverlayInnerUnordered(rows, overlays, primaryKey);\n}\n\nexport function* generateWithOverlayInnerUnordered(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n primaryKey: PrimaryKey,\n) {\n // Eager inject: yield the add overlay at the start (row not yet in storage)\n if (overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n // Stream with inline suppress: skip the remove overlay (row still in storage)\n let removeSkipped = false;\n for (const row of rowIterator) {\n if (\n !removeSkipped &&\n overlays.remove &&\n rowMatchesPK(overlays.remove, row, primaryKey)\n ) {\n removeSkipped = true;\n continue;\n }\n yield {row, relationships: {}};\n }\n}\n\nfunction rowMatchesPK(a: Row, b: Row, primaryKey: PrimaryKey): boolean {\n for (const key of primaryKey) {\n if (!valuesEqual(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A location to begin scanning an index from. Can either be a specific value\n * or the min or max possible value for the type. This is used to start a scan\n * at the beginning of the rows matching a constraint.\n */\ntype Bound = Value | MinValue | MaxValue;\ntype RowBound = Record<string, Bound>;\nconst minValue = Symbol('min-value');\ntype MinValue = typeof minValue;\nconst maxValue = Symbol('max-value');\ntype MaxValue = typeof maxValue;\n\nfunction makeBoundComparator(sort: Ordering) {\n return (a: RowBound, b: RowBound) => {\n // Hot! Do not use destructuring\n for (const entry of sort) {\n const key = entry[0];\n const cmp = compareBounds(a[key], b[key]);\n if (cmp !== 0) {\n return entry[1] === 'asc' ? cmp : -cmp;\n }\n }\n return 0;\n };\n}\n\nfunction compareBounds(a: Bound, b: Bound): number {\n if (a === b) {\n return 0;\n }\n if (a === minValue) {\n return -1;\n }\n if (b === minValue) {\n return 1;\n }\n if (a === maxValue) {\n return 1;\n }\n if (b === maxValue) {\n return -1;\n }\n return compareValues(a, b);\n}\n\nfunction* generateRows(\n data: BTreeSet<Row>,\n scanStart: RowBound | undefined,\n reverse: boolean | undefined,\n) {\n yield* data[reverse ? 'valuesFromReversed' : 'valuesFrom'](\n scanStart as Row | undefined,\n );\n}\n\nexport function stringify(change: SourceChange) {\n return JSON.stringify(change, (_, v) =>\n typeof v === 'bigint' ? v.toString() : v,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"memory-source.js","names":["#tableName","#columns","#primaryKey","#primaryIndexSort","#indexes","#connections","#getPrimaryIndex","#fetch","#disconnect","#getSchema","#getOrCreateIndex","#overlay","#writeChange","#pushEpoch"],"sources":["../../../../../zql/src/ivm/memory-source.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {BTreeSet} from '../../../shared/src/btree-set.ts';\nimport {hasOwn} from '../../../shared/src/has-own.ts';\nimport {once} from '../../../shared/src/iterables.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport type {\n Condition,\n Ordering,\n OrderPart,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {PrimaryKey} from '../../../zero-protocol/src/primary-key.ts';\nimport type {SchemaValue} from '../../../zero-types/src/schema-value.ts';\nimport type {DebugDelegate} from '../builder/debug-delegate.ts';\nimport {\n createPredicate,\n transformFilters,\n type NoSubqueryCondition,\n} from '../builder/filter.ts';\nimport {assertOrderingIncludesPK} from '../query/complete-ordering.ts';\nimport type {Change} from './change.ts';\nimport {\n constraintMatchesPrimaryKey,\n constraintMatchesRow,\n primaryKeyConstraintFromFilters,\n type Constraint,\n} from './constraint.ts';\nimport {\n compareValues,\n makeComparator,\n valuesEqual,\n type Comparator,\n type Node,\n} from './data.ts';\nimport {filterPush} from './filter-push.ts';\nimport {\n skipYields,\n type FetchRequest,\n type Input,\n type Output,\n type Start,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {\n Source,\n SourceChange,\n SourceChangeAdd,\n SourceChangeEdit,\n SourceChangeRemove,\n SourceInput,\n} from './source.ts';\nimport type {Stream} from './stream.ts';\n\nexport type Overlay = {\n epoch: number;\n change: SourceChange;\n};\n\nexport type Overlays = {\n add: Row | undefined;\n remove: Row | undefined;\n};\n\ntype Index = {\n comparator: Comparator;\n data: BTreeSet<Row>;\n usedBy: Set<Connection>;\n};\n\nexport type Connection = {\n input: Input;\n output: Output | undefined;\n sort?: Ordering | undefined;\n splitEditKeys: Set<string> | undefined;\n compareRows: Comparator;\n filters:\n | {\n condition: NoSubqueryCondition;\n predicate: (row: Row) => boolean;\n }\n | undefined;\n readonly debug?: DebugDelegate | undefined;\n lastPushedEpoch: number;\n};\n\n/**\n * A `MemorySource` is a source that provides data to the pipeline from an\n * in-memory data source.\n *\n * This data is kept in sorted order as downstream pipelines will always expect\n * the data they receive from `pull` to be in sorted order.\n */\nexport class MemorySource implements Source {\n readonly #tableName: string;\n readonly #columns: Record<string, SchemaValue>;\n readonly #primaryKey: PrimaryKey;\n readonly #primaryIndexSort: Ordering;\n readonly #indexes: Map<string, Index> = new Map();\n readonly #connections: Connection[] = [];\n\n #overlay: Overlay | undefined;\n #pushEpoch = 0;\n\n constructor(\n tableName: string,\n columns: Record<string, SchemaValue>,\n primaryKey: PrimaryKey,\n primaryIndexData?: BTreeSet<Row>,\n ) {\n this.#tableName = tableName;\n this.#columns = columns;\n this.#primaryKey = primaryKey;\n this.#primaryIndexSort = primaryKey.map(k => [k, 'asc']);\n const comparator = makeBoundComparator(this.#primaryIndexSort);\n this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {\n comparator,\n data: primaryIndexData ?? new BTreeSet<Row>(comparator),\n usedBy: new Set(),\n });\n }\n\n get tableSchema() {\n return {\n name: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n };\n }\n\n fork() {\n const primaryIndex = this.#getPrimaryIndex();\n return new MemorySource(\n this.#tableName,\n this.#columns,\n this.#primaryKey,\n primaryIndex.data.clone(),\n );\n }\n\n get data(): BTreeSet<Row> {\n return this.#getPrimaryIndex().data;\n }\n\n #getSchema(connection: Connection, unordered: boolean): SourceSchema {\n return {\n tableName: this.#tableName,\n columns: this.#columns,\n primaryKey: this.#primaryKey,\n sort: unordered ? undefined : connection.sort,\n system: 'client',\n relationships: {},\n isHidden: false,\n compareRows: connection.compareRows,\n };\n }\n\n connect(\n sort: Ordering | undefined,\n filters?: Condition,\n splitEditKeys?: Set<string>,\n ): SourceInput {\n const transformedFilters = transformFilters(filters);\n const unordered = sort === undefined;\n const internalSort = sort ?? this.#primaryIndexSort;\n\n const input: SourceInput = {\n getSchema: () => schema,\n fetch: req => this.#fetch(req, connection),\n setOutput: output => {\n connection.output = output;\n },\n destroy: () => {\n this.#disconnect(input);\n },\n fullyAppliedFilters: !transformedFilters.conditionsRemoved,\n };\n\n const connection: Connection = {\n input,\n output: undefined,\n sort: internalSort,\n splitEditKeys,\n compareRows: makeComparator(internalSort),\n filters: transformedFilters.filters\n ? {\n condition: transformedFilters.filters,\n predicate: createPredicate(transformedFilters.filters),\n }\n : undefined,\n lastPushedEpoch: 0,\n };\n const schema = this.#getSchema(connection, unordered);\n if (!unordered) {\n assertOrderingIncludesPK(internalSort, this.#primaryKey);\n }\n this.#connections.push(connection);\n return input;\n }\n\n #disconnect(input: Input): void {\n const idx = this.#connections.findIndex(c => c.input === input);\n assert(idx !== -1, 'Connection not found');\n this.#connections.splice(idx, 1);\n\n // TODO: We used to delete unused indexes here. But in common cases like\n // navigating into issue detail pages it caused a ton of constantly\n // building and destroying indexes.\n //\n // Perhaps some intelligent LRU or something is needed here but for now,\n // the opposite extreme of keeping all indexes for the lifetime of the\n // page seems better.\n }\n\n #getPrimaryIndex(): Index {\n const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));\n assert(index, 'Primary index not found');\n return index;\n }\n\n #getOrCreateIndex(sort: Ordering, usedBy: Connection): Index {\n const key = JSON.stringify(sort);\n const index = this.#indexes.get(key);\n // Future optimization could use existing index if it's the same just sorted\n // in reverse of needed.\n if (index) {\n index.usedBy.add(usedBy);\n return index;\n }\n\n const comparator = makeBoundComparator(sort);\n\n // When creating these synchronously becomes a problem, a few options:\n // 1. Allow users to specify needed indexes up front\n // 2. Create indexes in a different thread asynchronously (this would require\n // modifying the BTree to be able to be passed over structured-clone, or using\n // a different library.)\n // 3. We could even theoretically do (2) on multiple threads and then merge the\n // results!\n const data = new BTreeSet<Row>(comparator);\n\n // I checked, there's no special path for adding data in bulk faster.\n // The constructor takes an array, but it just calls add/set over and over.\n for (const row of this.#getPrimaryIndex().data) {\n data.add(row);\n }\n\n const newIndex = {comparator, data, usedBy: new Set([usedBy])};\n this.#indexes.set(key, newIndex);\n return newIndex;\n }\n\n // For unit testing that we correctly clean up indexes.\n getIndexKeys(): string[] {\n return [...this.#indexes.keys()];\n }\n\n *#fetch(req: FetchRequest, conn: Connection): Stream<Node | 'yield'> {\n const requestedSort = must(conn.sort);\n const {compareRows} = conn;\n // Avoid allocating a new closure when not reversing (the common case).\n const connectionComparator: Comparator = req.reverse\n ? (r1, r2) => compareRows(r2, r1)\n : compareRows;\n\n const pkConstraint = primaryKeyConstraintFromFilters(\n conn.filters?.condition,\n this.#primaryKey,\n );\n // The primary key constraint will be more limiting than the constraint\n // so swap out to that if it exists.\n const fetchOrPkConstraint = pkConstraint ?? req.constraint;\n\n // If there is a constraint, we need an index sorted by it first.\n const indexSort: OrderPart[] = [];\n if (fetchOrPkConstraint) {\n for (const key of Object.keys(fetchOrPkConstraint)) {\n indexSort.push([key, 'asc']);\n }\n }\n\n // For the special case of constraining by PK, we don't need to worry about\n // any requested sort since there can only be one result. Otherwise we also\n // need the index sorted by the requested sort.\n if (\n this.#primaryKey.length > 1 ||\n !fetchOrPkConstraint ||\n !constraintMatchesPrimaryKey(fetchOrPkConstraint, this.#primaryKey)\n ) {\n indexSort.push(...requestedSort);\n }\n\n const index = this.#getOrCreateIndex(indexSort, conn);\n const {data, comparator: compare} = index;\n // Avoid allocating a new closure when not reversing (the common case).\n const indexComparator: Comparator = req.reverse\n ? (r1, r2) => compare(r2, r1)\n : compare;\n\n const startAt = req.start?.row;\n\n // If there is a constraint, we want to start our scan at the first row that\n // matches the constraint. But because the next OrderPart can be `desc`,\n // it's not true that {[constraintKey]: constraintValue} is the first\n // matching row. Because in that case, the other fields will all be\n // `undefined`, and in Zero `undefined` is always less than any other value.\n // So if the second OrderPart is descending then `undefined` values will\n // actually be the *last* row. We need a way to stay \"start at the first row\n // with this constraint value\". RowBound with the corresponding compareBound\n // comparator accomplishes this. The right thing is probably to teach the\n // btree library to support this concept.\n let scanStart: RowBound | undefined;\n\n if (fetchOrPkConstraint) {\n scanStart = {};\n for (const [key, dir] of indexSort) {\n if (hasOwn(fetchOrPkConstraint, key)) {\n scanStart[key] = fetchOrPkConstraint[key];\n } else {\n if (req.reverse) {\n scanStart[key] = dir === 'asc' ? maxValue : minValue;\n } else {\n scanStart[key] = dir === 'asc' ? minValue : maxValue;\n }\n }\n }\n } else {\n scanStart = startAt;\n }\n\n const rowsIterable = generateRows(data, scanStart, req.reverse);\n const withOverlay = generateWithOverlay(\n startAt,\n pkConstraint ? once(rowsIterable) : rowsIterable,\n // use `req.constraint` here and not `fetchOrPkConstraint` since `fetchOrPkConstraint` could be the\n // primary key constraint. The primary key constraint comes from filters and is acting as a filter\n // rather than as the fetch constraint.\n req.constraint,\n this.#overlay,\n conn.lastPushedEpoch,\n // Use indexComparator, generateWithOverlayInner has a subtle dependency\n // on this. Since generateWithConstraint is done after\n // generateWithOverlay, the generator consumed by generateWithOverlayInner\n // does not end when the constraint stops matching and so the final\n // check to yield an add overlay if not yet yielded is not reached.\n // However, using the indexComparator the add overlay will be less than\n // the first row that does not match the constraint, and so any\n // not yet yielded add overlay will be yielded when the first row\n // not matching the constraint is reached.\n indexComparator,\n conn.filters?.predicate,\n );\n\n const withConstraint = generateWithConstraint(\n skipYields(\n generateWithStart(withOverlay, req.start, connectionComparator),\n ),\n // we use `req.constraint` and not `fetchOrPkConstraint` here because we need to\n // AND the constraint with what could have been the primary key constraint\n req.constraint,\n );\n\n yield* conn.filters\n ? generateWithFilter(withConstraint, conn.filters.predicate)\n : withConstraint;\n }\n\n *push(change: SourceChange): Stream<'yield'> {\n for (const result of this.genPush(change)) {\n if (result === 'yield') {\n yield result;\n }\n }\n }\n\n *genPush(change: SourceChange) {\n const primaryIndex = this.#getPrimaryIndex();\n const {data} = primaryIndex;\n const exists = (row: Row) => data.has(row);\n const setOverlay = (o: Overlay | undefined) => (this.#overlay = o);\n const writeChange = (c: SourceChange) => this.#writeChange(c);\n yield* genPushAndWriteWithSplitEdit(\n this.#connections,\n change,\n exists,\n setOverlay,\n writeChange,\n () => ++this.#pushEpoch,\n );\n }\n\n #writeChange(change: SourceChange) {\n for (const {data} of this.#indexes.values()) {\n switch (change.type) {\n case 'add': {\n const added = data.add(change.row);\n // must succeed since we checked has() above.\n assert(\n added,\n 'MemorySource: add must succeed since row existence was already checked',\n );\n break;\n }\n case 'remove': {\n const removed = data.delete(change.row);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: remove must succeed since row existence was already checked',\n );\n break;\n }\n case 'edit': {\n // TODO: We could see if the PK (form the index tree's perspective)\n // changed and if not we could use set.\n // We cannot just do `set` with the new value since the `oldRow` might\n // not map to the same entry as the new `row` in the index btree.\n const removed = data.delete(change.oldRow);\n // must succeed since we checked has() above.\n assert(\n removed,\n 'MemorySource: edit remove must succeed since row existence was already checked',\n );\n data.add(change.row);\n break;\n }\n default:\n unreachable(change);\n }\n }\n }\n}\n\nfunction* generateWithConstraint(\n it: Stream<Node>,\n constraint: Constraint | undefined,\n) {\n for (const node of it) {\n if (constraint && !constraintMatchesRow(constraint, node.row)) {\n break;\n }\n yield node;\n }\n}\n\nfunction* generateWithFilter(it: Stream<Node>, filter: (row: Row) => boolean) {\n for (const node of it) {\n if (filter(node.row)) {\n yield node;\n }\n }\n}\n\nexport function* genPushAndWriteWithSplitEdit(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n getNextEpoch: () => number,\n) {\n let shouldSplitEdit = false;\n if (change.type === 'edit') {\n for (const {splitEditKeys} of connections) {\n if (splitEditKeys) {\n for (const key of splitEditKeys) {\n if (!valuesEqual(change.row[key], change.oldRow[key])) {\n shouldSplitEdit = true;\n break;\n }\n }\n }\n }\n }\n\n if (change.type === 'edit' && shouldSplitEdit) {\n yield* genPushAndWrite(\n connections,\n {\n type: 'remove',\n row: change.oldRow,\n },\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n yield* genPushAndWrite(\n connections,\n {\n type: 'add',\n row: change.row,\n },\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n } else {\n yield* genPushAndWrite(\n connections,\n change,\n exists,\n setOverlay,\n writeChange,\n getNextEpoch(),\n );\n }\n}\n\nfunction* genPushAndWrite(\n connections: readonly Connection[],\n change: SourceChangeAdd | SourceChangeRemove | SourceChangeEdit,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => Overlay | undefined,\n writeChange: (c: SourceChange) => void,\n pushEpoch: number,\n) {\n for (const x of genPush(connections, change, exists, setOverlay, pushEpoch)) {\n yield x;\n }\n writeChange(change);\n}\n\nfunction* genPush(\n connections: readonly Connection[],\n change: SourceChange,\n exists: (row: Row) => boolean,\n setOverlay: (o: Overlay | undefined) => void,\n pushEpoch: number,\n) {\n switch (change.type) {\n case 'add':\n assert(\n !exists(change.row),\n () => `Row already exists ${stringify(change)}`,\n );\n break;\n case 'remove':\n assert(exists(change.row), () => `Row not found ${stringify(change)}`);\n break;\n case 'edit':\n assert(exists(change.oldRow), () => `Row not found ${stringify(change)}`);\n break;\n default:\n unreachable(change);\n }\n\n for (const conn of connections) {\n const {output, filters, input} = conn;\n if (output) {\n conn.lastPushedEpoch = pushEpoch;\n setOverlay({epoch: pushEpoch, change});\n const outputChange: Change =\n change.type === 'edit'\n ? {\n type: change.type,\n oldNode: {\n row: change.oldRow,\n relationships: {},\n },\n node: {\n row: change.row,\n relationships: {},\n },\n }\n : {\n type: change.type,\n node: {\n row: change.row,\n relationships: {},\n },\n };\n yield* filterPush(outputChange, output, input, filters?.predicate);\n yield undefined;\n }\n }\n\n setOverlay(undefined);\n}\n\nexport function* generateWithStart(\n nodes: Iterable<Node | 'yield'>,\n start: Start | undefined,\n compare: (r1: Row, r2: Row) => number,\n): Stream<Node | 'yield'> {\n if (!start) {\n yield* nodes;\n return;\n }\n let started = false;\n for (const node of nodes) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!started) {\n if (start.basis === 'at') {\n if (compare(node.row, start.row) >= 0) {\n started = true;\n }\n } else if (start.basis === 'after') {\n if (compare(node.row, start.row) > 0) {\n started = true;\n }\n }\n }\n if (started) {\n yield node;\n }\n }\n}\n\n/**\n * Takes an iterator and overlay.\n * Splices the overlay into the iterator at the correct position.\n *\n * @param startAt - if there is a lower bound to the stream. If the lower bound of the stream\n * is above the overlay, the overlay will be skipped.\n * @param rows - the stream into which the overlay should be spliced\n * @param constraint - constraint that was applied to the rowIterator and should\n * also be applied to the overlay.\n * @param overlay - the overlay values to splice in\n * @param compare - the comparator to use to find the position for the overlay\n */\nexport function* generateWithOverlay(\n startAt: Row | undefined,\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n compare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n const overlays = computeOverlays(\n startAt,\n constraint,\n overlayToApply,\n compare,\n filterPredicate,\n );\n yield* generateWithOverlayInner(rows, overlays, compare);\n}\n\nfunction computeOverlays(\n startAt: Row | undefined,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n compare: Comparator,\n filterPredicate?: (row: Row) => boolean | undefined,\n): Overlays {\n let overlays: Overlays = {\n add: undefined,\n remove: undefined,\n };\n switch (overlay?.change.type) {\n case 'add':\n overlays = {\n add: overlay.change.row,\n remove: undefined,\n };\n break;\n case 'remove':\n overlays = {\n add: undefined,\n remove: overlay.change.row,\n };\n break;\n case 'edit':\n overlays = {\n add: overlay.change.row,\n remove: overlay.change.oldRow,\n };\n break;\n }\n\n if (startAt) {\n overlays = overlaysForStartAt(overlays, startAt, compare);\n }\n\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n\n return overlays;\n}\n\nexport {overlaysForStartAt as overlaysForStartAtForTest};\n\nfunction overlaysForStartAt(\n {add, remove}: Overlays,\n startAt: Row,\n compare: Comparator,\n): Overlays {\n const undefinedIfBeforeStartAt = (row: Row | undefined) =>\n row === undefined || compare(row, startAt) < 0 ? undefined : row;\n return {\n add: undefinedIfBeforeStartAt(add),\n remove: undefinedIfBeforeStartAt(remove),\n };\n}\n\nexport {overlaysForConstraint as overlaysForConstraintForTest};\n\nfunction overlaysForConstraint(\n {add, remove}: Overlays,\n constraint: Constraint,\n): Overlays {\n const undefinedIfDoesntMatchConstraint = (row: Row | undefined) =>\n row === undefined || !constraintMatchesRow(constraint, row)\n ? undefined\n : row;\n\n return {\n add: undefinedIfDoesntMatchConstraint(add),\n remove: undefinedIfDoesntMatchConstraint(remove),\n };\n}\n\nfunction overlaysForFilterPredicate(\n {add, remove}: Overlays,\n filterPredicate: (row: Row) => boolean | undefined,\n): Overlays {\n const undefinedIfDoesntMatchFilter = (row: Row | undefined) =>\n row === undefined || !filterPredicate(row) ? undefined : row;\n\n return {\n add: undefinedIfDoesntMatchFilter(add),\n remove: undefinedIfDoesntMatchFilter(remove),\n };\n}\n\nexport function* generateWithOverlayInner(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n compare: (r1: Row, r2: Row) => number,\n) {\n let addOverlayYielded = false;\n let removeOverlaySkipped = false;\n for (const row of rowIterator) {\n if (!addOverlayYielded && overlays.add) {\n const cmp = compare(overlays.add, row);\n if (cmp < 0) {\n addOverlayYielded = true;\n yield {row: overlays.add, relationships: {}};\n }\n }\n\n if (!removeOverlaySkipped && overlays.remove) {\n const cmp = compare(overlays.remove, row);\n if (cmp === 0) {\n removeOverlaySkipped = true;\n continue;\n }\n }\n yield {row, relationships: {}};\n }\n\n if (!addOverlayYielded && overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n}\n\n/**\n * Like {@link generateWithOverlay} but for unordered streams.\n * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly\n * at the start, and suppresses add/new-edit rows inline by PK match.\n */\nexport function* generateWithOverlayUnordered(\n rows: Iterable<Row>,\n constraint: Constraint | undefined,\n overlay: Overlay | undefined,\n lastPushedEpoch: number,\n primaryKey: PrimaryKey,\n filterPredicate?: (row: Row) => boolean,\n) {\n let overlayToApply: Overlay | undefined = undefined;\n if (overlay && lastPushedEpoch >= overlay.epoch) {\n overlayToApply = overlay;\n }\n let overlays: Overlays = {add: undefined, remove: undefined};\n switch (overlayToApply?.change.type) {\n case 'add':\n overlays = {add: overlayToApply.change.row, remove: undefined};\n break;\n case 'remove':\n overlays = {add: undefined, remove: overlayToApply.change.row};\n break;\n case 'edit':\n overlays = {\n add: overlayToApply.change.row,\n remove: overlayToApply.change.oldRow,\n };\n break;\n }\n if (constraint) {\n overlays = overlaysForConstraint(overlays, constraint);\n }\n if (filterPredicate) {\n overlays = overlaysForFilterPredicate(overlays, filterPredicate);\n }\n yield* generateWithOverlayInnerUnordered(rows, overlays, primaryKey);\n}\n\nexport function* generateWithOverlayInnerUnordered(\n rowIterator: Iterable<Row>,\n overlays: Overlays,\n primaryKey: PrimaryKey,\n) {\n // Eager inject: yield the add overlay at the start (row not yet in storage)\n if (overlays.add) {\n yield {row: overlays.add, relationships: {}};\n }\n // Stream with inline suppress: skip the remove overlay (row still in storage)\n let removeSkipped = false;\n for (const row of rowIterator) {\n if (\n !removeSkipped &&\n overlays.remove &&\n rowMatchesPK(overlays.remove, row, primaryKey)\n ) {\n removeSkipped = true;\n continue;\n }\n yield {row, relationships: {}};\n }\n}\n\nfunction rowMatchesPK(a: Row, b: Row, primaryKey: PrimaryKey): boolean {\n for (const key of primaryKey) {\n if (!valuesEqual(a[key], b[key])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * A location to begin scanning an index from. Can either be a specific value\n * or the min or max possible value for the type. This is used to start a scan\n * at the beginning of the rows matching a constraint.\n */\ntype Bound = Value | MinValue | MaxValue;\ntype RowBound = Record<string, Bound>;\nconst minValue = Symbol('min-value');\ntype MinValue = typeof minValue;\nconst maxValue = Symbol('max-value');\ntype MaxValue = typeof maxValue;\n\nfunction makeBoundComparator(sort: Ordering) {\n return (a: RowBound, b: RowBound) => {\n // Hot! Do not use destructuring\n for (const entry of sort) {\n const key = entry[0];\n const cmp = compareBounds(a[key], b[key]);\n if (cmp !== 0) {\n return entry[1] === 'asc' ? cmp : -cmp;\n }\n }\n return 0;\n };\n}\n\nfunction compareBounds(a: Bound, b: Bound): number {\n if (a === b) {\n return 0;\n }\n if (a === minValue) {\n return -1;\n }\n if (b === minValue) {\n return 1;\n }\n if (a === maxValue) {\n return 1;\n }\n if (b === maxValue) {\n return -1;\n }\n return compareValues(a, b);\n}\n\nfunction* generateRows(\n data: BTreeSet<Row>,\n scanStart: RowBound | undefined,\n reverse: boolean | undefined,\n) {\n yield* data[reverse ? 'valuesFromReversed' : 'valuesFrom'](\n scanStart as Row | undefined,\n );\n}\n\nexport function stringify(change: SourceChange) {\n return JSON.stringify(change, (_, v) =>\n typeof v === 'bigint' ? v.toString() : v,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-debug.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-debug.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACxD,OAAO,KAAK,EACV,SAAS,EACT,QAAQ,EAET,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EACV,2BAA2B,EAC3B,+BAA+B,EAC/B,iCAAiC,EACjC,6BAA6B,EAC7B,kBAAkB,EAClB,yBAAyB,EAC1B,MAAM,oDAAoD,CAAC;AAC5D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAElD;;;;GAIG;AAEH;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAE5E;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,kBAAkB,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,EAAE,OAAO,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC;QAC3D,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;KAC/D,CAAC,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,iCAAiC,CACzC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,KAAK,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC,CAAC;QAC3D,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;KAC/D,CAAC,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,eAAe,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,QAAQ,CAAC;KAChB,CAAC,CAAC;IAEH,YAAY,EAAE,SAAS,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CACzC,OAAO,+BAA+B,CACvC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,WAAW,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,0BAA0B,EAAE,MAAM,CAAC;IACnC,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAC3C,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACjC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEhF;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,iBAAiB,GACjB,oBAAoB,GACpB,uBAAuB,GACvB,0BAA0B,GAC1B,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,aAAa,GACb,mBAAmB,CAAC;AAExB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;CAClC;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACtD,QAAQ,CAAC,MAAM,EAAE,cAAc,EAAE,CAAM;IACvC,OAAO,CAAC,cAAc,CAAK;IAE3B,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAehC;;OAEG;IACH,SAAS,CAAC,CAAC,SAAS,cAAc,CAAC,MAAM,CAAC,EACxC,IAAI,EAAE,CAAC,GACN,OAAO,CAAC,cAAc,EAAE;QAAC,IAAI,EAAE,CAAC,CAAA;KAAC,CAAC,EAAE;IAOvC;;OAEG;IACH,MAAM,IAAI,MAAM;CAGjB;
|
|
1
|
+
{"version":3,"file":"planner-debug.d.ts","sourceRoot":"","sources":["../../../../../zql/src/planner/planner-debug.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import type * as v from '../../../shared/src/valita.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\nimport type {PlanState} from './planner-graph.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType: 'connection' | 'join' | 'fan-out' | 'fan-in' | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: (\n | NodeConstraintEvent\n | Extract<PlanDebugEventJSON, {type: 'node-constraint'}>\n )[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n let attemptEvents = eventsByAttempt.get(attempt);\n if (!attemptEvents) {\n attemptEvents = [];\n eventsByAttempt.set(attempt, attemptEvents);\n }\n attemptEvents.push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"planner-debug.js","names":[],"sources":["../../../../../zql/src/planner/planner-debug.ts"],"sourcesContent":["import type * as v from '../../../shared/src/valita.ts';\nimport type {\n Condition,\n Ordering,\n ValuePosition,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {\n attemptStartEventJSONSchema,\n bestPlanSelectedEventJSONSchema,\n connectionSelectedEventJSONSchema,\n nodeConstraintEventJSONSchema,\n PlanDebugEventJSON,\n planFailedEventJSONSchema,\n} from '../../../zero-protocol/src/analyze-query-result.ts';\nimport type {PlannerConstraint} from './planner-constraint.ts';\nimport type {CostEstimate, JoinType} from './planner-node.ts';\nimport type {PlanState} from './planner-graph.ts';\n\n/**\n * Structured debug events emitted during query planning.\n * These events can be accumulated, printed, or analyzed to understand\n * the planner's decision-making process.\n */\n\n/**\n * Starting a new planning attempt with a different root connection.\n */\nexport type AttemptStartEvent = v.Infer<typeof attemptStartEventJSONSchema>;\n\n/**\n * Snapshot of connection costs before selecting the next connection.\n */\nexport type ConnectionCostsEvent = {\n type: 'connection-costs';\n attemptNumber: number;\n costs: Array<{\n connection: string;\n cost: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n pinned: boolean;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A connection was chosen and pinned.\n */\nexport type ConnectionSelectedEvent = v.Infer<\n typeof connectionSelectedEventJSONSchema\n>;\n\n/**\n * Constraints have been propagated through the graph.\n */\nexport type ConstraintsPropagatedEvent = {\n type: 'constraints-propagated';\n attemptNumber: number;\n connectionConstraints: Array<{\n connection: string;\n constraints: Record<string, PlannerConstraint | undefined>;\n constraintCosts: Record<string, Omit<CostEstimate, 'fanout'>>;\n }>;\n};\n\n/**\n * A complete plan was found for this attempt.\n */\nexport type PlanCompleteEvent = {\n type: 'plan-complete';\n attemptNumber: number;\n totalCost: number;\n flipPattern: number; // Bitmask indicating which joins are flipped\n joinStates: Array<{\n join: string;\n type: JoinType;\n }>;\n // Planning snapshot that can be restored and applied to AST\n planSnapshot: PlanState;\n};\n\n/**\n * Planning attempt failed (e.g., unflippable join).\n */\nexport type PlanFailedEvent = v.Infer<typeof planFailedEventJSONSchema>;\n\n/**\n * The best plan across all attempts was selected.\n */\nexport type BestPlanSelectedEvent = v.Infer<\n typeof bestPlanSelectedEventJSONSchema\n>;\n\n/**\n * A node computed its cost estimate during planning.\n * Emitted by nodes during estimateCost() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeCostEvent = {\n type: 'node-cost';\n attemptNumber?: number;\n nodeType: 'connection' | 'join' | 'fan-out' | 'fan-in' | 'terminus';\n node: string;\n branchPattern: number[];\n downstreamChildSelectivity: number;\n costEstimate: Omit<CostEstimate, 'fanout'>;\n filters?: Condition | undefined; // Only for connections\n ordering?: Ordering | undefined; // Only for connections\n joinType?: JoinType | undefined; // Only for joins\n};\n\n/**\n * A node received constraints during constraint propagation.\n * Emitted by nodes during propagateConstraints() traversal.\n * attemptNumber is added by the debugger.\n */\nexport type NodeConstraintEvent = v.Infer<typeof nodeConstraintEventJSONSchema>;\n\n/**\n * Union of all debug event types.\n */\nexport type PlanDebugEvent =\n | AttemptStartEvent\n | ConnectionCostsEvent\n | ConnectionSelectedEvent\n | ConstraintsPropagatedEvent\n | PlanCompleteEvent\n | PlanFailedEvent\n | BestPlanSelectedEvent\n | NodeCostEvent\n | NodeConstraintEvent;\n\n/**\n * Interface for objects that receive debug events during planning.\n */\nexport interface PlanDebugger {\n log(event: PlanDebugEvent): void;\n}\n\n/**\n * Simple accumulator debugger that stores all events.\n * Useful for tests and debugging.\n */\nexport class AccumulatorDebugger implements PlanDebugger {\n readonly events: PlanDebugEvent[] = [];\n private currentAttempt = 0;\n\n log(event: PlanDebugEvent): void {\n // Track current attempt number\n if (event.type === 'attempt-start') {\n this.currentAttempt = event.attemptNumber;\n }\n\n // Add attempt number to node events\n if (event.type === 'node-cost' || event.type === 'node-constraint') {\n (event as NodeCostEvent | NodeConstraintEvent).attemptNumber =\n this.currentAttempt;\n }\n\n this.events.push(event);\n }\n\n /**\n * Get all events of a specific type.\n */\n getEvents<T extends PlanDebugEvent['type']>(\n type: T,\n ): Extract<PlanDebugEvent, {type: T}>[] {\n return this.events.filter(e => e.type === type) as Extract<\n PlanDebugEvent,\n {type: T}\n >[];\n }\n\n /**\n * Format events as a human-readable string.\n */\n format(): string {\n return formatPlannerEvents(this.events);\n }\n}\n\n/**\n * Format a constraint object as a human-readable string.\n */\nfunction formatConstraint(\n constraint: PlannerConstraint | Record<string, unknown> | null | undefined,\n): string {\n if (!constraint) return '{}';\n const keys = Object.keys(constraint);\n if (keys.length === 0) return '{}';\n return '{' + keys.join(', ') + '}';\n}\n\n/**\n * Format a ValuePosition (column, literal, or static parameter) as a human-readable string.\n */\nfunction formatValuePosition(value: ValuePosition): string {\n switch (value.type) {\n case 'column':\n return value.name;\n case 'literal':\n // Format literal values with SQL-style quoting for strings\n if (typeof value.value === 'string') {\n return `'${value.value}'`;\n }\n return JSON.stringify(value.value);\n case 'static':\n return `@${value.anchor}.${Array.isArray(value.field) ? value.field.join('.') : value.field}`;\n }\n}\n\n/**\n * Format a Condition (filter) as a human-readable string.\n */\nfunction formatFilter(filter: Condition | undefined): string {\n if (!filter) return 'none';\n\n switch (filter.type) {\n case 'simple':\n return `${formatValuePosition(filter.left)} ${filter.op} ${formatValuePosition(filter.right)}`;\n case 'and':\n return `(${filter.conditions.map(formatFilter).join(' AND ')})`;\n case 'or':\n return `(${filter.conditions.map(formatFilter).join(' OR ')})`;\n case 'correlatedSubquery':\n return `EXISTS(${filter.related.subquery.table})`;\n default:\n return JSON.stringify(filter);\n }\n}\n\n/**\n * Format an Ordering as a human-readable string.\n */\nfunction formatOrdering(ordering: Ordering | undefined): string {\n if (!ordering || ordering.length === 0) return 'none';\n return ordering\n .map(([field, direction]) => `${field} ${direction}`)\n .join(', ');\n}\n\n/**\n * Format a compact summary for a single planning attempt.\n */\nfunction formatAttemptSummary(\n attemptNum: number,\n events: (PlanDebugEvent | PlanDebugEventJSON)[],\n): string[] {\n const lines: string[] = [];\n\n // Find the attempt-start event to get total attempts\n const startEvent = events.find(e => e.type === 'attempt-start') as\n | AttemptStartEvent\n | undefined;\n const totalAttempts = startEvent?.totalAttempts ?? '?';\n\n // Calculate number of bits needed for pattern\n const numBits =\n typeof totalAttempts === 'number'\n ? Math.ceil(Math.log2(totalAttempts)) || 1\n : 1;\n const bitPattern = attemptNum.toString(2).padStart(numBits, '0');\n\n lines.push(\n `[Attempt ${attemptNum + 1}/${totalAttempts}] Pattern ${attemptNum} (${bitPattern})`,\n );\n\n // Collect connection costs (use array to preserve all connections, including duplicates)\n const connectionCostEvents: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n const connectionConstraintEvents: NodeConstraintEvent[] = [];\n\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'connection') {\n connectionCostEvents.push(event);\n }\n if (event.type === 'node-constraint' && event.nodeType === 'connection') {\n connectionConstraintEvents.push(event);\n }\n }\n\n // Show connection summary\n if (connectionCostEvents.length > 0) {\n lines.push(' Connections:');\n for (const cost of connectionCostEvents) {\n // Find matching constraint event (same node name and branch pattern)\n const constraint = connectionConstraintEvents.find(\n c =>\n c.node === cost.node &&\n c.branchPattern.join(',') === cost.branchPattern.join(','),\n )?.constraint;\n\n const constraintStr = formatConstraint(constraint);\n const filterStr = formatFilter(cost.filters);\n const orderingStr = formatOrdering(cost.ordering);\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n lines.push(` constraints=${constraintStr}`);\n lines.push(` filters=${filterStr}`);\n lines.push(` ordering=${orderingStr}`);\n }\n }\n\n // Collect join costs from node-cost events\n const joinCosts: (\n | NodeCostEvent\n | Extract<PlanDebugEventJSON, {type: 'node-cost'}>\n )[] = [];\n for (const event of events) {\n if (event.type === 'node-cost' && event.nodeType === 'join') {\n joinCosts.push(event);\n }\n }\n\n if (joinCosts.length > 0) {\n lines.push(' Joins:');\n for (const cost of joinCosts) {\n const typeStr = cost.joinType ? ` (${cost.joinType})` : '';\n const limitStr =\n cost.costEstimate.limit !== undefined\n ? cost.costEstimate.limit.toString()\n : 'none';\n\n lines.push(` ${cost.node}${typeStr}:`);\n lines.push(\n ` cost=${cost.costEstimate.cost.toFixed(2)}, startup=${cost.costEstimate.startupCost.toFixed(2)}, scan=${cost.costEstimate.scanEst.toFixed(2)}`,\n );\n lines.push(\n ` rows=${cost.costEstimate.returnedRows.toFixed(2)}, selectivity=${cost.costEstimate.selectivity.toFixed(8)}, limit=${limitStr}`,\n );\n lines.push(\n ` downstreamChildSelectivity=${cost.downstreamChildSelectivity.toFixed(8)}`,\n );\n }\n }\n\n // Find completion/failure events\n const completeEvent = events.find(e => e.type === 'plan-complete') as\n | PlanCompleteEvent\n | undefined;\n const failedEvent = events.find(e => e.type === 'plan-failed') as\n | PlanFailedEvent\n | undefined;\n\n // Show final status\n\n if (completeEvent) {\n lines.push(\n ` ✓ Plan complete: total cost = ${completeEvent.totalCost.toFixed(2)}`,\n );\n } else if (failedEvent) {\n lines.push(` ✗ Plan failed: ${failedEvent.reason}`);\n }\n\n return lines;\n}\n\n/**\n * Convert undefined values to null in a constraint object for JSON serialization.\n * PlannerConstraint uses Record<string, undefined> which loses keys during JSON.stringify.\n */\nfunction convertConstraintUndefinedToNull(\n constraint: PlannerConstraint | Record<string, unknown> | undefined | null,\n): Record<string, unknown> | undefined | null {\n if (constraint === undefined) {\n return undefined;\n }\n if (constraint === null) {\n return null;\n }\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(constraint)) {\n result[key] = val === undefined ? null : val;\n }\n return result;\n}\n\n/**\n * Serialize a single debug event to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n * Undefined values in constraints are converted to null for JSON serialization.\n */\nfunction serializeEvent(event: PlanDebugEvent): PlanDebugEventJSON {\n // Remove planSnapshot from plan-complete events\n if (event.type === 'plan-complete') {\n const {planSnapshot: _, ...rest} = event;\n return rest as PlanDebugEventJSON;\n }\n\n // Convert constraint undefined values to null for specific event types\n if (event.type === 'node-constraint') {\n return {\n ...event,\n constraint: convertConstraintUndefinedToNull(event.constraint),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'connection-costs') {\n return {\n ...event,\n costs: event.costs.map(cost => ({\n ...cost,\n constraints: Object.fromEntries(\n Object.entries(cost.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n if (event.type === 'constraints-propagated') {\n return {\n ...event,\n connectionConstraints: event.connectionConstraints.map(cc => ({\n ...cc,\n constraints: Object.fromEntries(\n Object.entries(cc.constraints).map(([key, val]) => [\n key,\n convertConstraintUndefinedToNull(val),\n ]),\n ),\n })),\n } as PlanDebugEventJSON;\n }\n\n return event as PlanDebugEventJSON;\n}\n\n/**\n * Serialize an array of debug events to JSON-compatible format.\n * The fanout function is already omitted when events are created.\n * The planSnapshot is excluded as it's internal state not needed for debugging.\n */\nexport function serializePlanDebugEvents(\n events: PlanDebugEvent[],\n): PlanDebugEventJSON[] {\n return events.map(serializeEvent);\n}\n\n/**\n * Format planner debug events as a human-readable string.\n * Works with JSON-serialized events (from inspector API) or native events (from AccumulatorDebugger).\n *\n * @param events - Array of planner debug events (either JSON or native format)\n * @returns Formatted string showing planning attempts, costs, and final plan selection\n *\n * @example\n * ```typescript\n * const result = await inspector.analyzeQuery(query, { joinPlans: true });\n * if (result.joinPlans) {\n * console.log(formatPlannerEvents(result.joinPlans));\n * }\n * ```\n */\nexport function formatPlannerEvents(\n events: PlanDebugEventJSON[] | PlanDebugEvent[],\n): string {\n const lines: string[] = [];\n\n // Group events by attempt\n const eventsByAttempt = new Map<\n number,\n (PlanDebugEventJSON | PlanDebugEvent)[]\n >();\n let bestPlanEvent:\n | {\n type: 'best-plan-selected';\n bestAttemptNumber: number;\n totalCost: number;\n flipPattern: number;\n joinStates: Array<{join: string; type: string}>;\n }\n | undefined;\n\n for (const event of events) {\n if ('attemptNumber' in event) {\n const attempt = event.attemptNumber;\n if (attempt !== undefined) {\n let attemptEvents = eventsByAttempt.get(attempt);\n if (!attemptEvents) {\n attemptEvents = [];\n eventsByAttempt.set(attempt, attemptEvents);\n }\n attemptEvents.push(event);\n }\n } else if (event.type === 'best-plan-selected') {\n // Save for displaying at the end\n bestPlanEvent = event;\n }\n }\n\n // Format each attempt as a compact summary\n for (const [attemptNum, events] of eventsByAttempt.entries()) {\n lines.push(...formatAttemptSummary(attemptNum, events));\n lines.push(''); // Blank line between attempts\n }\n\n // Show the final plan selection\n if (bestPlanEvent) {\n lines.push('─'.repeat(60));\n lines.push(\n `✓ Best plan: Attempt ${bestPlanEvent.bestAttemptNumber + 1} (cost=${bestPlanEvent.totalCost.toFixed(2)})`,\n );\n if (bestPlanEvent.joinStates.length > 0) {\n lines.push(' Join types:');\n for (const j of bestPlanEvent.joinStates) {\n lines.push(` ${j.join}: ${j.type}`);\n }\n }\n lines.push('─'.repeat(60));\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -38,7 +38,7 @@ export declare class ExpressionBuilder<TTable extends keyof TSchema['tables'] &
|
|
|
38
38
|
export declare function and(...conditions: (Condition | undefined)[]): Condition;
|
|
39
39
|
export declare function or(...conditions: (Condition | undefined)[]): Condition;
|
|
40
40
|
export declare function not(expression: Condition): Condition;
|
|
41
|
-
export declare function cmp(field: string, opOrValue: SimpleOperator | ParameterReference | LiteralValue | undefined, value?: ParameterReference | LiteralValue
|
|
41
|
+
export declare function cmp(field: string, opOrValue: SimpleOperator | ParameterReference | LiteralValue | undefined, value?: ParameterReference | LiteralValue): Condition;
|
|
42
42
|
export declare const TRUE: Condition;
|
|
43
43
|
export declare function simplifyCondition(c: Condition): Condition;
|
|
44
44
|
export declare function flatten(type: 'and' | 'or', conditions: readonly Condition[]): Condition[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expression.d.ts","sourceRoot":"","sources":["../../../../../zql/src/query/expression.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"expression.d.ts","sourceRoot":"","sources":["../../../../../zql/src/query/expression.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expression.js","names":["#exists"],"sources":["../../../../../zql/src/query/expression.ts"],"sourcesContent":["/* oxlint-disable @typescript-eslint/no-explicit-any */\nimport {must} from '../../../shared/src/must.ts';\nimport {\n toStaticParam,\n type Condition,\n type LiteralValue,\n type Parameter,\n type SimpleOperator,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Schema as ZeroSchema} from '../../../zero-types/src/schema.ts';\nimport type {\n AvailableRelationships,\n DestTableName,\n ExistsOptions,\n GetFilterType,\n NoCompoundTypeSelector,\n PullTableSchema,\n Query,\n} from './query.ts';\n\nexport type ParameterReference = {\n [toStaticParam](): Parameter;\n};\n\n/**\n * A factory function that creates a condition. This is used to create\n * complex conditions that can be passed to the `where` method of a query.\n *\n * @example\n *\n * ```ts\n * const condition: ExpressionFactory<User> = ({and, cmp, or}) =>\n * and(\n * cmp('name', '=', 'Alice'),\n * or(cmp('age', '>', 18), cmp('isStudent', '=', true)),\n * );\n *\n * const query = z.query.user.where(condition);\n * ```\n */\nexport interface ExpressionFactory<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends ZeroSchema,\n> {\n (eb: ExpressionBuilder<TTable, TSchema>): Condition;\n}\n\nexport class ExpressionBuilder<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends ZeroSchema,\n> {\n readonly #exists: (\n relationship: string,\n cb?: (query: Query<TTable, TSchema>) => Query<TTable, TSchema, any>,\n options?: ExistsOptions,\n ) => Condition;\n\n constructor(\n exists: (\n relationship: string,\n cb?: (query: Query<TTable, TSchema>) => Query<TTable, TSchema, any>,\n options?: ExistsOptions,\n ) => Condition,\n ) {\n this.#exists = exists;\n this.exists = this.exists.bind(this);\n }\n\n get eb() {\n return this;\n }\n\n cmp<\n TSelector extends NoCompoundTypeSelector<PullTableSchema<TTable, TSchema>>,\n TOperator extends SimpleOperator,\n >(\n field: TSelector,\n op: TOperator,\n value:\n | GetFilterType<PullTableSchema<TTable, TSchema>, TSelector, TOperator>\n | ParameterReference\n | undefined,\n ): Condition;\n cmp<\n TSelector extends NoCompoundTypeSelector<PullTableSchema<TTable, TSchema>>,\n >(\n field: TSelector,\n value:\n | GetFilterType<PullTableSchema<TTable, TSchema>, TSelector, '='>\n | ParameterReference\n | undefined,\n ): Condition;\n cmp(\n field: string,\n opOrValue: SimpleOperator | ParameterReference | LiteralValue | undefined,\n value?: ParameterReference | LiteralValue | undefined,\n ): Condition {\n if (arguments.length === 2) {\n return cmp(field, opOrValue);\n }\n return cmp(field, opOrValue, value);\n }\n\n cmpLit(\n left: ParameterReference | LiteralValue | undefined,\n op: SimpleOperator,\n right: ParameterReference | LiteralValue | undefined,\n ): Condition {\n return {\n type: 'simple',\n left: isParameterReference(left)\n ? left[toStaticParam]()\n : {type: 'literal', value: left ?? null},\n right: isParameterReference(right)\n ? right[toStaticParam]()\n : {type: 'literal', value: right ?? null},\n op,\n };\n }\n\n and = and;\n or = or;\n not = not;\n\n exists = <TRelationship extends AvailableRelationships<TTable, TSchema>>(\n relationship: TRelationship,\n cb?: (\n query: Query<DestTableName<TTable, TSchema, TRelationship>, TSchema>,\n ) => Query<any, TSchema>,\n options?: ExistsOptions,\n ): Condition => this.#exists(relationship, cb, options);\n}\n\nexport function and(...conditions: (Condition | undefined)[]): Condition {\n const expressions = filterTrue(filterUndefined(conditions));\n\n if (expressions.length === 1) {\n return expressions[0];\n }\n\n if (expressions.some(isAlwaysFalse)) {\n return FALSE;\n }\n\n return {type: 'and', conditions: expressions};\n}\n\nexport function or(...conditions: (Condition | undefined)[]): Condition {\n const expressions = filterFalse(filterUndefined(conditions));\n\n if (expressions.length === 1) {\n return expressions[0];\n }\n\n if (expressions.some(isAlwaysTrue)) {\n return TRUE;\n }\n\n return {type: 'or', conditions: expressions};\n}\n\nexport function not(expression: Condition): Condition {\n switch (expression.type) {\n case 'and':\n return {\n type: 'or',\n conditions: expression.conditions.map(not),\n };\n case 'or':\n return {\n type: 'and',\n conditions: expression.conditions.map(not),\n };\n case 'correlatedSubquery':\n return {\n type: 'correlatedSubquery',\n related: expression.related,\n op: negateOperator(expression.op),\n ...(expression.flip !== undefined ? {flip: expression.flip} : {}),\n ...(expression.scalar !== undefined ? {scalar: expression.scalar} : {}),\n };\n case 'simple':\n return {\n type: 'simple',\n op: negateOperator(expression.op),\n left: expression.left,\n right: expression.right,\n };\n }\n}\n\nexport function cmp(\n field: string,\n opOrValue: SimpleOperator | ParameterReference | LiteralValue | undefined,\n value?: ParameterReference | LiteralValue | undefined,\n): Condition {\n let op: SimpleOperator;\n let actualValue: ParameterReference | LiteralValue | undefined;\n\n if (arguments.length === 2) {\n // 2-arg form: cmp(field, value) - defaults to '=' operator\n actualValue = opOrValue as ParameterReference | LiteralValue | undefined;\n op = '=';\n } else {\n // 3-arg form: cmp(field, op, value)\n op = opOrValue as SimpleOperator;\n actualValue = value;\n }\n\n return {\n type: 'simple',\n left: {type: 'column', name: field},\n right: isParameterReference(actualValue)\n ? actualValue[toStaticParam]()\n : {type: 'literal', value: actualValue ?? null},\n op,\n };\n}\n\nfunction isParameterReference(\n value: ParameterReference | LiteralValue | null | undefined,\n): value is ParameterReference {\n return (\n value !== null &&\n value !== undefined &&\n typeof value === 'object' &&\n (value as any)[toStaticParam]\n );\n}\n\nexport const TRUE: Condition = {\n type: 'and',\n conditions: [],\n};\n\nconst FALSE: Condition = {\n type: 'or',\n conditions: [],\n};\n\nfunction isAlwaysTrue(condition: Condition): boolean {\n return condition.type === 'and' && condition.conditions.length === 0;\n}\n\nfunction isAlwaysFalse(condition: Condition): boolean {\n return condition.type === 'or' && condition.conditions.length === 0;\n}\n\nexport function simplifyCondition(c: Condition): Condition {\n if (c.type === 'simple' || c.type === 'correlatedSubquery') {\n return c;\n }\n if (c.conditions.length === 1) {\n return simplifyCondition(c.conditions[0]);\n }\n const conditions = flatten(c.type, c.conditions.map(simplifyCondition));\n if (c.type === 'and' && conditions.some(isAlwaysFalse)) {\n return FALSE;\n }\n if (c.type === 'or' && conditions.some(isAlwaysTrue)) {\n return TRUE;\n }\n return {\n type: c.type,\n conditions,\n };\n}\n\nexport function flatten(\n type: 'and' | 'or',\n conditions: readonly Condition[],\n): Condition[] {\n const flattened: Condition[] = [];\n for (const c of conditions) {\n if (c.type === type) {\n flattened.push(...c.conditions);\n } else {\n flattened.push(c);\n }\n }\n\n return flattened;\n}\n\nconst negateSimpleOperatorMap = {\n ['=']: '!=',\n ['!=']: '=',\n ['<']: '>=',\n ['>']: '<=',\n ['>=']: '<',\n ['<=']: '>',\n ['IN']: 'NOT IN',\n ['NOT IN']: 'IN',\n ['LIKE']: 'NOT LIKE',\n ['NOT LIKE']: 'LIKE',\n ['ILIKE']: 'NOT ILIKE',\n ['NOT ILIKE']: 'ILIKE',\n ['IS']: 'IS NOT',\n ['IS NOT']: 'IS',\n} as const;\n\nconst negateOperatorMap = {\n ...negateSimpleOperatorMap,\n ['EXISTS']: 'NOT EXISTS',\n ['NOT EXISTS']: 'EXISTS',\n} as const;\n\nexport function negateOperator<OP extends keyof typeof negateOperatorMap>(\n op: OP,\n): (typeof negateOperatorMap)[OP] {\n return must(negateOperatorMap[op]);\n}\n\nfunction filterUndefined<T>(array: (T | undefined)[]): T[] {\n return array.filter(e => e !== undefined);\n}\n\nfunction filterTrue(conditions: Condition[]): Condition[] {\n return conditions.filter(c => !isAlwaysTrue(c));\n}\n\nfunction filterFalse(conditions: Condition[]): Condition[] {\n return conditions.filter(c => !isAlwaysFalse(c));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"expression.js","names":["#exists"],"sources":["../../../../../zql/src/query/expression.ts"],"sourcesContent":["/* oxlint-disable @typescript-eslint/no-explicit-any */\nimport {must} from '../../../shared/src/must.ts';\nimport {\n toStaticParam,\n type Condition,\n type LiteralValue,\n type Parameter,\n type SimpleOperator,\n} from '../../../zero-protocol/src/ast.ts';\nimport type {Schema as ZeroSchema} from '../../../zero-types/src/schema.ts';\nimport type {\n AvailableRelationships,\n DestTableName,\n ExistsOptions,\n GetFilterType,\n NoCompoundTypeSelector,\n PullTableSchema,\n Query,\n} from './query.ts';\n\nexport type ParameterReference = {\n [toStaticParam](): Parameter;\n};\n\n/**\n * A factory function that creates a condition. This is used to create\n * complex conditions that can be passed to the `where` method of a query.\n *\n * @example\n *\n * ```ts\n * const condition: ExpressionFactory<User> = ({and, cmp, or}) =>\n * and(\n * cmp('name', '=', 'Alice'),\n * or(cmp('age', '>', 18), cmp('isStudent', '=', true)),\n * );\n *\n * const query = z.query.user.where(condition);\n * ```\n */\nexport interface ExpressionFactory<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends ZeroSchema,\n> {\n (eb: ExpressionBuilder<TTable, TSchema>): Condition;\n}\n\nexport class ExpressionBuilder<\n TTable extends keyof TSchema['tables'] & string,\n TSchema extends ZeroSchema,\n> {\n readonly #exists: (\n relationship: string,\n cb?: (query: Query<TTable, TSchema>) => Query<TTable, TSchema, any>,\n options?: ExistsOptions,\n ) => Condition;\n\n constructor(\n exists: (\n relationship: string,\n cb?: (query: Query<TTable, TSchema>) => Query<TTable, TSchema, any>,\n options?: ExistsOptions,\n ) => Condition,\n ) {\n this.#exists = exists;\n this.exists = this.exists.bind(this);\n }\n\n get eb() {\n return this;\n }\n\n cmp<\n TSelector extends NoCompoundTypeSelector<PullTableSchema<TTable, TSchema>>,\n TOperator extends SimpleOperator,\n >(\n field: TSelector,\n op: TOperator,\n value:\n | GetFilterType<PullTableSchema<TTable, TSchema>, TSelector, TOperator>\n | ParameterReference\n | undefined,\n ): Condition;\n cmp<\n TSelector extends NoCompoundTypeSelector<PullTableSchema<TTable, TSchema>>,\n >(\n field: TSelector,\n value:\n | GetFilterType<PullTableSchema<TTable, TSchema>, TSelector, '='>\n | ParameterReference\n | undefined,\n ): Condition;\n cmp(\n field: string,\n opOrValue: SimpleOperator | ParameterReference | LiteralValue | undefined,\n value?: ParameterReference | LiteralValue,\n ): Condition {\n if (arguments.length === 2) {\n return cmp(field, opOrValue);\n }\n return cmp(field, opOrValue, value);\n }\n\n cmpLit(\n left: ParameterReference | LiteralValue | undefined,\n op: SimpleOperator,\n right: ParameterReference | LiteralValue | undefined,\n ): Condition {\n return {\n type: 'simple',\n left: isParameterReference(left)\n ? left[toStaticParam]()\n : {type: 'literal', value: left ?? null},\n right: isParameterReference(right)\n ? right[toStaticParam]()\n : {type: 'literal', value: right ?? null},\n op,\n };\n }\n\n and = and;\n or = or;\n not = not;\n\n exists = <TRelationship extends AvailableRelationships<TTable, TSchema>>(\n relationship: TRelationship,\n cb?: (\n query: Query<DestTableName<TTable, TSchema, TRelationship>, TSchema>,\n ) => Query<any, TSchema>,\n options?: ExistsOptions,\n ): Condition => this.#exists(relationship, cb, options);\n}\n\nexport function and(...conditions: (Condition | undefined)[]): Condition {\n const expressions = filterTrue(filterUndefined(conditions));\n\n if (expressions.length === 1) {\n return expressions[0];\n }\n\n if (expressions.some(isAlwaysFalse)) {\n return FALSE;\n }\n\n return {type: 'and', conditions: expressions};\n}\n\nexport function or(...conditions: (Condition | undefined)[]): Condition {\n const expressions = filterFalse(filterUndefined(conditions));\n\n if (expressions.length === 1) {\n return expressions[0];\n }\n\n if (expressions.some(isAlwaysTrue)) {\n return TRUE;\n }\n\n return {type: 'or', conditions: expressions};\n}\n\nexport function not(expression: Condition): Condition {\n switch (expression.type) {\n case 'and':\n return {\n type: 'or',\n conditions: expression.conditions.map(not),\n };\n case 'or':\n return {\n type: 'and',\n conditions: expression.conditions.map(not),\n };\n case 'correlatedSubquery':\n return {\n type: 'correlatedSubquery',\n related: expression.related,\n op: negateOperator(expression.op),\n ...(expression.flip !== undefined ? {flip: expression.flip} : {}),\n ...(expression.scalar !== undefined ? {scalar: expression.scalar} : {}),\n };\n case 'simple':\n return {\n type: 'simple',\n op: negateOperator(expression.op),\n left: expression.left,\n right: expression.right,\n };\n }\n}\n\nexport function cmp(\n field: string,\n opOrValue: SimpleOperator | ParameterReference | LiteralValue | undefined,\n value?: ParameterReference | LiteralValue,\n): Condition {\n let op: SimpleOperator;\n let actualValue: ParameterReference | LiteralValue | undefined;\n\n if (arguments.length === 2) {\n // 2-arg form: cmp(field, value) - defaults to '=' operator\n actualValue = opOrValue as ParameterReference | LiteralValue | undefined;\n op = '=';\n } else {\n // 3-arg form: cmp(field, op, value)\n op = opOrValue as SimpleOperator;\n actualValue = value;\n }\n\n return {\n type: 'simple',\n left: {type: 'column', name: field},\n right: isParameterReference(actualValue)\n ? actualValue[toStaticParam]()\n : {type: 'literal', value: actualValue ?? null},\n op,\n };\n}\n\nfunction isParameterReference(\n value: ParameterReference | LiteralValue | null | undefined,\n): value is ParameterReference {\n return (\n value !== null &&\n value !== undefined &&\n typeof value === 'object' &&\n (value as any)[toStaticParam]\n );\n}\n\nexport const TRUE: Condition = {\n type: 'and',\n conditions: [],\n};\n\nconst FALSE: Condition = {\n type: 'or',\n conditions: [],\n};\n\nfunction isAlwaysTrue(condition: Condition): boolean {\n return condition.type === 'and' && condition.conditions.length === 0;\n}\n\nfunction isAlwaysFalse(condition: Condition): boolean {\n return condition.type === 'or' && condition.conditions.length === 0;\n}\n\nexport function simplifyCondition(c: Condition): Condition {\n if (c.type === 'simple' || c.type === 'correlatedSubquery') {\n return c;\n }\n if (c.conditions.length === 1) {\n return simplifyCondition(c.conditions[0]);\n }\n const conditions = flatten(c.type, c.conditions.map(simplifyCondition));\n if (c.type === 'and' && conditions.some(isAlwaysFalse)) {\n return FALSE;\n }\n if (c.type === 'or' && conditions.some(isAlwaysTrue)) {\n return TRUE;\n }\n return {\n type: c.type,\n conditions,\n };\n}\n\nexport function flatten(\n type: 'and' | 'or',\n conditions: readonly Condition[],\n): Condition[] {\n const flattened: Condition[] = [];\n for (const c of conditions) {\n if (c.type === type) {\n flattened.push(...c.conditions);\n } else {\n flattened.push(c);\n }\n }\n\n return flattened;\n}\n\nconst negateSimpleOperatorMap = {\n ['=']: '!=',\n ['!=']: '=',\n ['<']: '>=',\n ['>']: '<=',\n ['>=']: '<',\n ['<=']: '>',\n ['IN']: 'NOT IN',\n ['NOT IN']: 'IN',\n ['LIKE']: 'NOT LIKE',\n ['NOT LIKE']: 'LIKE',\n ['ILIKE']: 'NOT ILIKE',\n ['NOT ILIKE']: 'ILIKE',\n ['IS']: 'IS NOT',\n ['IS NOT']: 'IS',\n} as const;\n\nconst negateOperatorMap = {\n ...negateSimpleOperatorMap,\n ['EXISTS']: 'NOT EXISTS',\n ['NOT EXISTS']: 'EXISTS',\n} as const;\n\nexport function negateOperator<OP extends keyof typeof negateOperatorMap>(\n op: OP,\n): (typeof negateOperatorMap)[OP] {\n return must(negateOperatorMap[op]);\n}\n\nfunction filterUndefined<T>(array: (T | undefined)[]): T[] {\n return array.filter(e => e !== undefined);\n}\n\nfunction filterTrue(conditions: Condition[]): Condition[] {\n return conditions.filter(c => !isAlwaysTrue(c));\n}\n\nfunction filterFalse(conditions: Condition[]): Condition[] {\n return conditions.filter(c => !isAlwaysFalse(c));\n}\n"],"mappings":"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"}
|