@rocicorp/zero 1.0.1-canary.0 → 1.1.1

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.
Files changed (98) hide show
  1. package/out/_virtual/{_@oxc-project_runtime@0.115.0 → _@oxc-project_runtime@0.122.0}/helpers/usingCtx.js +1 -1
  2. package/out/replicache/src/mutation-recovery.js +0 -3
  3. package/out/zero/package.js +7 -6
  4. package/out/zero/package.js.map +1 -1
  5. package/out/zero-cache/src/config/zero-config.d.ts +1 -1
  6. package/out/zero-cache/src/config/zero-config.js +2 -2
  7. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  8. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  9. package/out/zero-cache/src/server/change-streamer.js +2 -2
  10. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  11. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  12. package/out/zero-cache/src/server/replicator.js +3 -3
  13. package/out/zero-cache/src/server/replicator.js.map +1 -1
  14. package/out/zero-cache/src/services/analyze.js +1 -1
  15. package/out/zero-cache/src/services/change-source/change-source.d.ts +1 -1
  16. package/out/zero-cache/src/services/change-source/change-source.d.ts.map +1 -1
  17. package/out/zero-cache/src/services/change-source/custom/change-source.js +3 -3
  18. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  19. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  20. package/out/zero-cache/src/services/change-source/pg/change-source.js +55 -22
  21. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  22. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  23. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +1 -1
  24. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  25. package/out/zero-cache/src/services/change-source/pg/lsn.js +1 -1
  26. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +3 -3
  27. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  28. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  29. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +3 -3
  30. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  31. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  32. package/out/zero-cache/src/services/life-cycle.js +6 -2
  33. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  34. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts +2 -2
  35. package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
  36. package/out/zero-cache/src/services/replicator/incremental-sync.js +10 -14
  37. package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
  38. package/out/zero-cache/src/services/replicator/replication-status.d.ts +4 -2
  39. package/out/zero-cache/src/services/replicator/replication-status.d.ts.map +1 -1
  40. package/out/zero-cache/src/services/replicator/replication-status.js +20 -7
  41. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  42. package/out/zero-cache/src/services/replicator/replicator.d.ts +2 -2
  43. package/out/zero-cache/src/services/replicator/replicator.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/replicator/replicator.js +2 -2
  45. package/out/zero-cache/src/services/replicator/replicator.js.map +1 -1
  46. package/out/zero-cache/src/services/running-state.d.ts +3 -1
  47. package/out/zero-cache/src/services/running-state.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/running-state.js +4 -0
  49. package/out/zero-cache/src/services/running-state.js.map +1 -1
  50. package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
  51. package/out/zero-cache/src/types/pg.d.ts +1 -0
  52. package/out/zero-cache/src/types/pg.d.ts.map +1 -1
  53. package/out/zero-cache/src/types/pg.js +4 -1
  54. package/out/zero-cache/src/types/pg.js.map +1 -1
  55. package/out/zero-cache/src/workers/connection.js +2 -2
  56. package/out/zero-cache/src/workers/connection.js.map +1 -1
  57. package/out/zero-cache/src/workers/replicator.d.ts.map +1 -1
  58. package/out/zero-cache/src/workers/replicator.js +18 -3
  59. package/out/zero-cache/src/workers/replicator.js.map +1 -1
  60. package/out/zero-client/src/client/version.js +1 -1
  61. package/out/zql/src/ivm/join-utils.d.ts +2 -0
  62. package/out/zql/src/ivm/join-utils.d.ts.map +1 -1
  63. package/out/zql/src/ivm/join-utils.js +35 -1
  64. package/out/zql/src/ivm/join-utils.js.map +1 -1
  65. package/out/zql/src/ivm/join.d.ts.map +1 -1
  66. package/out/zql/src/ivm/join.js +6 -2
  67. package/out/zql/src/ivm/join.js.map +1 -1
  68. package/out/zql/src/ivm/memory-source.d.ts +15 -2
  69. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  70. package/out/zql/src/ivm/memory-source.js +69 -8
  71. package/out/zql/src/ivm/memory-source.js.map +1 -1
  72. package/out/zql/src/ivm/schema.d.ts +1 -1
  73. package/out/zql/src/ivm/schema.d.ts.map +1 -1
  74. package/out/zql/src/ivm/skip.d.ts.map +1 -1
  75. package/out/zql/src/ivm/skip.js +3 -0
  76. package/out/zql/src/ivm/skip.js.map +1 -1
  77. package/out/zql/src/ivm/source.d.ts +1 -1
  78. package/out/zql/src/ivm/source.d.ts.map +1 -1
  79. package/out/zql/src/ivm/take.d.ts +4 -1
  80. package/out/zql/src/ivm/take.d.ts.map +1 -1
  81. package/out/zql/src/ivm/take.js +3 -1
  82. package/out/zql/src/ivm/take.js.map +1 -1
  83. package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
  84. package/out/zql/src/ivm/union-fan-in.js +1 -0
  85. package/out/zql/src/ivm/union-fan-in.js.map +1 -1
  86. package/out/zqlite/src/db.d.ts.map +1 -1
  87. package/out/zqlite/src/db.js +1 -1
  88. package/out/zqlite/src/db.js.map +1 -1
  89. package/out/zqlite/src/query-builder.d.ts +1 -1
  90. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  91. package/out/zqlite/src/query-builder.js +7 -2
  92. package/out/zqlite/src/query-builder.js.map +1 -1
  93. package/out/zqlite/src/table-source.d.ts +1 -1
  94. package/out/zqlite/src/table-source.d.ts.map +1 -1
  95. package/out/zqlite/src/table-source.js +15 -10
  96. package/out/zqlite/src/table-source.js.map +1 -1
  97. package/package.json +7 -6
  98. package/out/replicache/src/mutation-recovery.js.map +0 -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 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 +1 @@
1
- {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/replicator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAC;AACnD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAEV,oBAAoB,EACpB,UAAU,EACX,MAAM,sCAAsC,CAAC;AAK9C,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAElD,eAAO,MAAM,qBAAqB,+CAIjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,UAEzE;AAkED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,YAAY,CAMnE;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,eAAe,EACrB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,QAAQ,CAAC,CA8BnB;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,QAGf;AAID,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,oBAAoB,QA8B/B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAM5E;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,QAE1D"}
1
+ {"version":3,"file":"replicator.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/workers/replicator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,EAAC,QAAQ,EAAC,MAAM,2BAA2B,CAAC;AACnD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAC,QAAQ,EAAC,MAAM,oCAAoC,CAAC;AAC5D,OAAO,KAAK,EAEV,oBAAoB,EACpB,UAAU,EACX,MAAM,sCAAsC,CAAC;AAK9C,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,+CAA+C,CAAC;AACvD,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,uBAAuB,CAAC;AAElD,eAAO,MAAM,qBAAqB,+CAIjC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,UAEzE;AAyFD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,YAAY,CAMnE;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,eAAe,EACrB,cAAc,EAAE,cAAc,GAC7B,OAAO,CAAC,QAAQ,CAAC,CA8BnB;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,MAAM,QAGf;AAID,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,oBAAoB,QA8B/B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,CAM5E;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,QAE1D"}
@@ -1,3 +1,4 @@
1
+ import { sleep } from "../../../shared/src/sleep.js";
1
2
  import { literalUnion } from "../../../shared/src/valita.js";
2
3
  import { Database } from "../../../zqlite/src/db.js";
3
4
  import { getAscendingEvents, recordEvent } from "../services/replicator/schema/replication-state.js";
@@ -15,7 +16,7 @@ var MB = 1024 * 1024;
15
16
  async function connect(lc, { file, vacuumIntervalHours }, walMode, mode) {
16
17
  const replica = new Database(lc, file);
17
18
  await upgradeReplica(lc, `${mode}-replica`, file);
18
- replica.pragma("journal_mode = delete");
19
+ await setJournalMode(lc, replica, "delete");
19
20
  const [{ page_size: pageSize }] = replica.pragma("page_size");
20
21
  const [{ page_count: pageCount }] = replica.pragma("page_count");
21
22
  const [{ freelist_count: freelistCount }] = replica.pragma("freelist_count");
@@ -37,13 +38,27 @@ async function connect(lc, { file, vacuumIntervalHours }, walMode, mode) {
37
38
  lc.info?.(`VACUUM completed (${t1 - t0} ms)`);
38
39
  }
39
40
  }
40
- lc.info?.(`setting ${file} to ${walMode} mode`);
41
- replica.pragma(`journal_mode = ${walMode}`);
41
+ await setJournalMode(lc, replica, walMode);
42
42
  applyPragmas(replica, getPragmaConfig(mode));
43
43
  replica.pragma("optimize = 0x10002");
44
44
  lc.info?.(`optimized ${file}`);
45
45
  return replica;
46
46
  }
47
+ async function setJournalMode(lc, replica, mode) {
48
+ lc.info?.(`setting ${replica.name} to ${mode} mode`);
49
+ let err;
50
+ for (let i = 0; i < 5; i++) {
51
+ try {
52
+ replica.pragma(`journal_mode = ${mode}`);
53
+ return;
54
+ } catch (e) {
55
+ lc.warn?.(`error setting journal_mode to ${mode} (attempt ${i + 1})`, e);
56
+ err = e;
57
+ }
58
+ await sleep(500);
59
+ }
60
+ throw err;
61
+ }
47
62
  /**
48
63
  * Returns the PragmaConfig for a given replica file mode.
49
64
  * This is used by both the main thread (setupReplica) and
@@ -1 +1 @@
1
- {"version":3,"file":"replicator.js","names":[],"sources":["../../../../../zero-cache/src/workers/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {ReplicaOptions} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {Notifier} from '../services/replicator/notifier.ts';\nimport type {\n ReplicaState,\n ReplicaStateNotifier,\n Replicator,\n} from '../services/replicator/replicator.ts';\nimport {\n getAscendingEvents,\n recordEvent,\n} from '../services/replicator/schema/replication-state.ts';\nimport {\n applyPragmas,\n type PragmaConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport type {Worker} from '../types/processes.ts';\n\nexport const replicaFileModeSchema = v.literalUnion(\n 'serving',\n 'serving-copy',\n 'backup',\n);\n\nexport type ReplicaFileMode = v.Infer<typeof replicaFileModeSchema>;\n\nexport function replicaFileName(replicaFile: string, mode: ReplicaFileMode) {\n return mode === 'serving-copy' ? `${replicaFile}-serving-copy` : replicaFile;\n}\n\nconst MILLIS_PER_HOUR = 1000 * 60 * 60;\nconst MB = 1024 * 1024;\n\nasync function connect(\n lc: LogContext,\n {file, vacuumIntervalHours}: ReplicaOptions,\n walMode: 'wal' | 'wal2',\n mode: ReplicaFileMode,\n): Promise<Database> {\n const replica = new Database(lc, file);\n\n // Perform any upgrades to the replica in case the backup is an\n // earlier version.\n await upgradeReplica(lc, `${mode}-replica`, file);\n\n // Start by folding any (e.g. restored) WAL(2) files into the main db.\n replica.pragma('journal_mode = delete');\n\n const [{page_size: pageSize}] = replica.pragma<{page_size: number}>(\n 'page_size',\n );\n const [{page_count: pageCount}] = replica.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{freelist_count: freelistCount}] = replica.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const dbSize = ((pageCount * pageSize) / MB).toFixed(2);\n const freelistSize = ((freelistCount * pageSize) / MB).toFixed(2);\n\n // TODO: Consider adding a freelist size or ratio based vacuum trigger.\n lc.info?.(`Size of db ${file}: ${dbSize} MB (${freelistSize} MB freeable)`);\n\n // Check for the VACUUM threshold.\n const events = getAscendingEvents(replica);\n lc.debug?.(`Runtime events for db ${file}`, {events});\n if (vacuumIntervalHours !== undefined) {\n const millisSinceLastEvent =\n Date.now() - (events.at(-1)?.timestamp.getTime() ?? 0);\n if (millisSinceLastEvent / MILLIS_PER_HOUR > vacuumIntervalHours) {\n lc.info?.(`Performing maintenance cleanup on ${file}`);\n const t0 = performance.now();\n replica.unsafeMode(true);\n replica.pragma('journal_mode = OFF');\n replica.exec('VACUUM');\n recordEvent(replica, 'vacuum');\n replica.unsafeMode(false);\n const t1 = performance.now();\n lc.info?.(`VACUUM completed (${t1 - t0} ms)`);\n }\n }\n\n lc.info?.(`setting ${file} to ${walMode} mode`);\n replica.pragma(`journal_mode = ${walMode}`);\n\n const pragmas = getPragmaConfig(mode);\n applyPragmas(replica, pragmas);\n\n replica.pragma('optimize = 0x10002');\n lc.info?.(`optimized ${file}`);\n return replica;\n}\n\n/**\n * Returns the PragmaConfig for a given replica file mode.\n * This is used by both the main thread (setupReplica) and\n * the write worker thread to apply the same pragma settings.\n */\nexport function getPragmaConfig(mode: ReplicaFileMode): PragmaConfig {\n return {\n busyTimeout: 30000,\n analysisLimit: 1000,\n walAutocheckpoint: mode === 'backup' ? 0 : undefined,\n };\n}\n\nexport async function setupReplica(\n lc: LogContext,\n mode: ReplicaFileMode,\n replicaOptions: ReplicaOptions,\n): Promise<Database> {\n lc.info?.(`setting up ${mode} replica`);\n\n switch (mode) {\n case 'backup':\n return await connect(lc, replicaOptions, 'wal', mode);\n\n case 'serving-copy': {\n // In 'serving-copy' mode, the original file is being used for 'backup'\n // mode, so we make a copy for servicing sync requests.\n const {file} = replicaOptions;\n const copyLocation = replicaFileName(file, mode);\n deleteLiteDB(copyLocation);\n\n const start = Date.now();\n lc.info?.(`copying ${file} to ${copyLocation}`);\n const replica = new Database(lc, file);\n replica.prepare(`VACUUM INTO ?`).run(copyLocation);\n replica.close();\n lc.info?.(`finished copy (${Date.now() - start} ms)`);\n\n return connect(lc, {...replicaOptions, file: copyLocation}, 'wal2', mode);\n }\n\n case 'serving':\n return connect(lc, replicaOptions, 'wal2', mode);\n\n default:\n throw new Error(`Invalid ReplicaMode ${mode}`);\n }\n}\n\nexport function setUpMessageHandlers(\n lc: LogContext,\n replicator: Replicator,\n parent: Worker,\n) {\n handleSubscriptionsFrom(lc, parent, replicator);\n}\n\ntype Notification = ['notify', ReplicaState];\n\nexport function handleSubscriptionsFrom(\n lc: LogContext,\n subscriber: Worker,\n notifier: ReplicaStateNotifier,\n) {\n subscriber.onMessageType('subscribe', async () => {\n const subscription = notifier.subscribe();\n\n subscriber.on('close', () => {\n lc.debug?.(`closing replication subscription from ${subscriber.pid}`);\n subscription.cancel();\n });\n\n for await (const msg of subscription) {\n try {\n subscriber.send<Notification>(['notify', msg]);\n } catch (e) {\n const log =\n e instanceof Error &&\n 'code' in e &&\n // This can happen in a race condition if the subscribing process\n // is closed before the 'close' message is processed.\n e.code === 'ERR_IPC_CHANNEL_CLOSED'\n ? 'warn'\n : 'error';\n\n lc[log]?.(\n `error sending replicator notification to ${subscriber.pid}: ${String(e)}`,\n e,\n );\n }\n }\n });\n}\n\n/**\n * Creates a Notifier to relay notifications the notifier of another Worker.\n * This does not send the initial subscription message. Use {@link subscribeTo}\n * to initiate the subscription.\n */\nexport function createNotifierFrom(_lc: LogContext, source: Worker): Notifier {\n const notifier = new Notifier();\n source.onMessageType<Notification>('notify', msg =>\n notifier.notifySubscribers(msg),\n );\n return notifier;\n}\n\nexport function subscribeTo(_lc: LogContext, source: Worker) {\n source.send(['subscribe', {}]);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"replicator.js","names":[],"sources":["../../../../../zero-cache/src/workers/replicator.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\nimport type {ReplicaOptions} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {Notifier} from '../services/replicator/notifier.ts';\nimport type {\n ReplicaState,\n ReplicaStateNotifier,\n Replicator,\n} from '../services/replicator/replicator.ts';\nimport {\n getAscendingEvents,\n recordEvent,\n} from '../services/replicator/schema/replication-state.ts';\nimport {\n applyPragmas,\n type PragmaConfig,\n} from '../services/replicator/write-worker-client.ts';\nimport type {Worker} from '../types/processes.ts';\n\nexport const replicaFileModeSchema = v.literalUnion(\n 'serving',\n 'serving-copy',\n 'backup',\n);\n\nexport type ReplicaFileMode = v.Infer<typeof replicaFileModeSchema>;\n\nexport function replicaFileName(replicaFile: string, mode: ReplicaFileMode) {\n return mode === 'serving-copy' ? `${replicaFile}-serving-copy` : replicaFile;\n}\n\nconst MILLIS_PER_HOUR = 1000 * 60 * 60;\nconst MB = 1024 * 1024;\n\nasync function connect(\n lc: LogContext,\n {file, vacuumIntervalHours}: ReplicaOptions,\n walMode: 'wal' | 'wal2',\n mode: ReplicaFileMode,\n): Promise<Database> {\n const replica = new Database(lc, file);\n\n // Perform any upgrades to the replica in case the backup is an\n // earlier version.\n await upgradeReplica(lc, `${mode}-replica`, file);\n\n // Start by folding any (e.g. restored) WAL(2) files into the main db.\n await setJournalMode(lc, replica, 'delete');\n\n const [{page_size: pageSize}] = replica.pragma<{page_size: number}>(\n 'page_size',\n );\n const [{page_count: pageCount}] = replica.pragma<{page_count: number}>(\n 'page_count',\n );\n const [{freelist_count: freelistCount}] = replica.pragma<{\n freelist_count: number;\n }>('freelist_count');\n\n const dbSize = ((pageCount * pageSize) / MB).toFixed(2);\n const freelistSize = ((freelistCount * pageSize) / MB).toFixed(2);\n\n // TODO: Consider adding a freelist size or ratio based vacuum trigger.\n lc.info?.(`Size of db ${file}: ${dbSize} MB (${freelistSize} MB freeable)`);\n\n // Check for the VACUUM threshold.\n const events = getAscendingEvents(replica);\n lc.debug?.(`Runtime events for db ${file}`, {events});\n if (vacuumIntervalHours !== undefined) {\n const millisSinceLastEvent =\n Date.now() - (events.at(-1)?.timestamp.getTime() ?? 0);\n if (millisSinceLastEvent / MILLIS_PER_HOUR > vacuumIntervalHours) {\n lc.info?.(`Performing maintenance cleanup on ${file}`);\n const t0 = performance.now();\n replica.unsafeMode(true);\n replica.pragma('journal_mode = OFF');\n replica.exec('VACUUM');\n recordEvent(replica, 'vacuum');\n replica.unsafeMode(false);\n const t1 = performance.now();\n lc.info?.(`VACUUM completed (${t1 - t0} ms)`);\n }\n }\n\n await setJournalMode(lc, replica, walMode);\n\n const pragmas = getPragmaConfig(mode);\n applyPragmas(replica, pragmas);\n\n replica.pragma('optimize = 0x10002');\n lc.info?.(`optimized ${file}`);\n return replica;\n}\n\n// Setting the journal_mode requires an exclusive lock on the replica.\n// Add resilience against random replica reads (for stats, etc.) by\n// retrying if the database is locked. Note that the busy_timeout doesn't\n// work here.\nasync function setJournalMode(\n lc: LogContext,\n replica: Database,\n mode: 'delete' | 'wal' | 'wal2',\n) {\n lc.info?.(`setting ${replica.name} to ${mode} mode`);\n let err: unknown;\n for (let i = 0; i < 5; i++) {\n try {\n replica.pragma(`journal_mode = ${mode}`);\n return;\n } catch (e) {\n lc.warn?.(`error setting journal_mode to ${mode} (attempt ${i + 1})`, e);\n err = e;\n }\n await sleep(500);\n }\n throw err;\n}\n\n/**\n * Returns the PragmaConfig for a given replica file mode.\n * This is used by both the main thread (setupReplica) and\n * the write worker thread to apply the same pragma settings.\n */\nexport function getPragmaConfig(mode: ReplicaFileMode): PragmaConfig {\n return {\n busyTimeout: 30000,\n analysisLimit: 1000,\n walAutocheckpoint: mode === 'backup' ? 0 : undefined,\n };\n}\n\nexport async function setupReplica(\n lc: LogContext,\n mode: ReplicaFileMode,\n replicaOptions: ReplicaOptions,\n): Promise<Database> {\n lc.info?.(`setting up ${mode} replica`);\n\n switch (mode) {\n case 'backup':\n return await connect(lc, replicaOptions, 'wal', mode);\n\n case 'serving-copy': {\n // In 'serving-copy' mode, the original file is being used for 'backup'\n // mode, so we make a copy for servicing sync requests.\n const {file} = replicaOptions;\n const copyLocation = replicaFileName(file, mode);\n deleteLiteDB(copyLocation);\n\n const start = Date.now();\n lc.info?.(`copying ${file} to ${copyLocation}`);\n const replica = new Database(lc, file);\n replica.prepare(`VACUUM INTO ?`).run(copyLocation);\n replica.close();\n lc.info?.(`finished copy (${Date.now() - start} ms)`);\n\n return connect(lc, {...replicaOptions, file: copyLocation}, 'wal2', mode);\n }\n\n case 'serving':\n return connect(lc, replicaOptions, 'wal2', mode);\n\n default:\n throw new Error(`Invalid ReplicaMode ${mode}`);\n }\n}\n\nexport function setUpMessageHandlers(\n lc: LogContext,\n replicator: Replicator,\n parent: Worker,\n) {\n handleSubscriptionsFrom(lc, parent, replicator);\n}\n\ntype Notification = ['notify', ReplicaState];\n\nexport function handleSubscriptionsFrom(\n lc: LogContext,\n subscriber: Worker,\n notifier: ReplicaStateNotifier,\n) {\n subscriber.onMessageType('subscribe', async () => {\n const subscription = notifier.subscribe();\n\n subscriber.on('close', () => {\n lc.debug?.(`closing replication subscription from ${subscriber.pid}`);\n subscription.cancel();\n });\n\n for await (const msg of subscription) {\n try {\n subscriber.send<Notification>(['notify', msg]);\n } catch (e) {\n const log =\n e instanceof Error &&\n 'code' in e &&\n // This can happen in a race condition if the subscribing process\n // is closed before the 'close' message is processed.\n e.code === 'ERR_IPC_CHANNEL_CLOSED'\n ? 'warn'\n : 'error';\n\n lc[log]?.(\n `error sending replicator notification to ${subscriber.pid}: ${String(e)}`,\n e,\n );\n }\n }\n });\n}\n\n/**\n * Creates a Notifier to relay notifications the notifier of another Worker.\n * This does not send the initial subscription message. Use {@link subscribeTo}\n * to initiate the subscription.\n */\nexport function createNotifierFrom(_lc: LogContext, source: Worker): Notifier {\n const notifier = new Notifier();\n source.onMessageType<Notification>('notify', msg =>\n notifier.notifySubscribers(msg),\n );\n return notifier;\n}\n\nexport function subscribeTo(_lc: LogContext, source: Worker) {\n source.send(['subscribe', {}]);\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * The current version of Zero.
4
4
  */
5
- var version = "1.0.1-canary.0";
5
+ var version = "1.1.1";
6
6
  //#endregion
7
7
  export { version };
8
8
 
@@ -10,6 +10,8 @@ export type JoinChangeOverlay = {
10
10
  };
11
11
  export declare function generateWithOverlayNoYield(stream: Stream<Node>, overlay: Change, schema: SourceSchema): Stream<Node>;
12
12
  export declare function generateWithOverlay(stream: Stream<Node | 'yield'>, overlay: Change, schema: SourceSchema): Stream<Node | 'yield'>;
13
+ export declare function generateWithOverlayNoYieldUnordered(stream: Stream<Node>, overlay: Change, schema: SourceSchema): Stream<Node>;
14
+ export declare function generateWithOverlayUnordered(stream: Stream<Node | 'yield'>, overlay: Change, schema: SourceSchema): Stream<Node | 'yield'>;
13
15
  export declare function rowEqualsForCompoundKey(a: Row, b: Row, key: CompoundKey): boolean;
14
16
  export declare function isJoinMatch(parent: Row, parentKey: CompoundKey, child: Row, childKey: CompoundKey): boolean;
15
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"join-utils.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,GAAG,EAAE,KAAK,EAAC,MAAM,oCAAoC,CAAC;AACnE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,EAA6B,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAEhE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mCAAmC,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,GAAG,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,CAAC,CAEd;AAED,wBAAiB,mBAAmB,CAClC,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CA6FxB;AAED,wBAAgB,uBAAuB,CACrC,CAAC,EAAE,GAAG,EACN,CAAC,EAAE,GAAG,EACN,GAAG,EAAE,WAAW,GACf,OAAO,CAOT;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,WAAW,WAQtB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,GAAG,EACd,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,WAAW,GACrB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS,CAUnC"}
1
+ {"version":3,"file":"join-utils.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,GAAG,EAAE,KAAK,EAAC,MAAM,oCAAoC,CAAC;AACnE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,EAA6B,KAAK,IAAI,EAAC,MAAM,WAAW,CAAC;AAEhE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,mCAAmC,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,GAAG,GAAG,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,CAAC,CAEd;AAED,wBAAiB,mBAAmB,CAClC,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CA6FxB;AAED,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,CAAC,CAEd;AAED,wBAAiB,4BAA4B,CAC3C,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,YAAY,GACnB,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAmDxB;AAED,wBAAgB,uBAAuB,CACrC,CAAC,EAAE,GAAG,EACN,CAAC,EAAE,GAAG,EACN,GAAG,EAAE,WAAW,GACf,OAAO,CAOT;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,GAAG,EACV,QAAQ,EAAE,WAAW,WAQtB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,GAAG,EACd,SAAS,EAAE,WAAW,EACtB,SAAS,EAAE,WAAW,GACrB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS,CAUnC"}
@@ -68,6 +68,40 @@ function* generateWithOverlay(stream, overlay, schema) {
68
68
  }
69
69
  assert(applied, "overlayGenerator: overlay was never applied to any fetched node");
70
70
  }
71
+ function* generateWithOverlayUnordered(stream, overlay, schema) {
72
+ if (overlay.type === "remove") yield overlay.node;
73
+ else if (overlay.type === "edit") yield overlay.oldNode;
74
+ let suppressed = false;
75
+ for (const node of stream) {
76
+ if (node === "yield") {
77
+ yield node;
78
+ continue;
79
+ }
80
+ if (!suppressed) {
81
+ if (overlay.type === "add" || overlay.type === "edit") {
82
+ if (rowEqualsForCompoundKey(overlay.node.row, node.row, schema.primaryKey)) {
83
+ suppressed = true;
84
+ continue;
85
+ }
86
+ }
87
+ if (overlay.type === "child") {
88
+ if (rowEqualsForCompoundKey(overlay.node.row, node.row, schema.primaryKey)) {
89
+ suppressed = true;
90
+ yield {
91
+ row: node.row,
92
+ relationships: {
93
+ ...node.relationships,
94
+ [overlay.child.relationshipName]: () => generateWithOverlay(node.relationships[overlay.child.relationshipName](), overlay.child.change, schema.relationships[overlay.child.relationshipName])
95
+ }
96
+ };
97
+ continue;
98
+ }
99
+ }
100
+ }
101
+ yield node;
102
+ }
103
+ assert(suppressed || overlay.type === "remove", "overlayGenerator: overlay was never applied to any fetched node");
104
+ }
71
105
  function rowEqualsForCompoundKey(a, b, key) {
72
106
  for (let i = 0; i < key.length; i++) if (compareValues(a[key[i]], b[key[i]]) !== 0) return false;
73
107
  return true;
@@ -91,6 +125,6 @@ function buildJoinConstraint(sourceRow, sourceKey, targetKey) {
91
125
  return constraint;
92
126
  }
93
127
  //#endregion
94
- export { buildJoinConstraint, generateWithOverlay, generateWithOverlayNoYield, isJoinMatch, rowEqualsForCompoundKey };
128
+ export { buildJoinConstraint, generateWithOverlay, generateWithOverlayNoYield, generateWithOverlayUnordered, isJoinMatch, rowEqualsForCompoundKey };
95
129
 
96
130
  //# sourceMappingURL=join-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"join-utils.js","names":[],"sources":["../../../../../zql/src/ivm/join-utils.ts"],"sourcesContent":["import type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {Change} from './change.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\nimport {compareValues, valuesEqual, type Node} from './data.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey} from '../../../zero-protocol/src/ast.ts';\n\nexport type JoinChangeOverlay = {\n change: Change;\n position: Row | undefined;\n};\n\nexport function generateWithOverlayNoYield(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlay(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlay(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n let applied = false;\n let editOldApplied = false;\n let editNewApplied = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n let yieldNode = true;\n if (!applied) {\n switch (overlay.type) {\n case 'add': {\n if (schema.compareRows(overlay.node.row, node.row) === 0) {\n applied = true;\n yieldNode = false;\n }\n break;\n }\n case 'remove': {\n if (schema.compareRows(overlay.node.row, node.row) < 0) {\n applied = true;\n yield overlay.node;\n }\n break;\n }\n case 'edit': {\n if (\n !editOldApplied &&\n schema.compareRows(overlay.oldNode.row, node.row) < 0\n ) {\n editOldApplied = true;\n if (editNewApplied) {\n applied = true;\n }\n yield overlay.oldNode;\n }\n if (\n !editNewApplied &&\n schema.compareRows(overlay.node.row, node.row) === 0\n ) {\n editNewApplied = true;\n if (editOldApplied) {\n applied = true;\n }\n yieldNode = false;\n }\n break;\n }\n case 'child': {\n if (schema.compareRows(overlay.node.row, node.row) === 0) {\n applied = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay.child.relationshipName]: () =>\n generateWithOverlay(\n node.relationships[overlay.child.relationshipName](),\n overlay.child.change,\n schema.relationships[overlay.child.relationshipName],\n ),\n },\n };\n yieldNode = false;\n }\n break;\n }\n }\n }\n if (yieldNode) {\n yield node;\n }\n }\n if (!applied) {\n if (overlay.type === 'remove') {\n applied = true;\n yield overlay.node;\n } else if (overlay.type === 'edit') {\n assert(\n editNewApplied,\n 'edit overlay: new node must be applied before old node',\n );\n editOldApplied = true;\n applied = true;\n yield overlay.oldNode;\n }\n }\n\n assert(\n applied,\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function rowEqualsForCompoundKey(\n a: Row,\n b: Row,\n key: CompoundKey,\n): boolean {\n for (let i = 0; i < key.length; i++) {\n if (compareValues(a[key[i]], b[key[i]]) !== 0) {\n return false;\n }\n }\n return true;\n}\n\nexport function isJoinMatch(\n parent: Row,\n parentKey: CompoundKey,\n child: Row,\n childKey: CompoundKey,\n) {\n for (let i = 0; i < parentKey.length; i++) {\n if (!valuesEqual(parent[parentKey[i]], child[childKey[i]])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Builds a constraint object by mapping values from `sourceRow` using `sourceKey`\n * to keys specified by `targetKey`. Returns `undefined` if any source value is `null`,\n * since null foreign keys cannot match any rows.\n */\nexport function buildJoinConstraint(\n sourceRow: Row,\n sourceKey: CompoundKey,\n targetKey: CompoundKey,\n): Record<string, Value> | undefined {\n const constraint: Record<string, Value> = {};\n for (let i = 0; i < targetKey.length; i++) {\n const value = sourceRow[sourceKey[i]];\n if (value === null) {\n return undefined;\n }\n constraint[targetKey[i]] = value;\n }\n return constraint;\n}\n"],"mappings":";;;AAaA,SAAgB,2BACd,QACA,SACA,QACc;AACd,QAAO,oBAAoB,QAAQ,SAAS,OAAO;;AAGrD,UAAiB,oBACf,QACA,SACA,QACwB;CACxB,IAAI,UAAU;CACd,IAAI,iBAAiB;CACrB,IAAI,iBAAiB;AACrB,MAAK,MAAM,QAAQ,QAAQ;AACzB,MAAI,SAAS,SAAS;AACpB,SAAM;AACN;;EAEF,IAAI,YAAY;AAChB,MAAI,CAAC,QACH,SAAQ,QAAQ,MAAhB;GACE,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GAAG;AACxD,eAAU;AACV,iBAAY;;AAEd;GAEF,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,GAAG,GAAG;AACtD,eAAU;AACV,WAAM,QAAQ;;AAEhB;GAEF,KAAK;AACH,QACE,CAAC,kBACD,OAAO,YAAY,QAAQ,QAAQ,KAAK,KAAK,IAAI,GAAG,GACpD;AACA,sBAAiB;AACjB,SAAI,eACF,WAAU;AAEZ,WAAM,QAAQ;;AAEhB,QACE,CAAC,kBACD,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GACnD;AACA,sBAAiB;AACjB,SAAI,eACF,WAAU;AAEZ,iBAAY;;AAEd;GAEF,KAAK;AACH,QAAI,OAAO,YAAY,QAAQ,KAAK,KAAK,KAAK,IAAI,KAAK,GAAG;AACxD,eAAU;AACV,WAAM;MACJ,KAAK,KAAK;MACV,eAAe;OACb,GAAG,KAAK;QACP,QAAQ,MAAM,yBACb,oBACE,KAAK,cAAc,QAAQ,MAAM,mBAAmB,EACpD,QAAQ,MAAM,QACd,OAAO,cAAc,QAAQ,MAAM,kBACpC;OACJ;MACF;AACD,iBAAY;;AAEd;;AAIN,MAAI,UACF,OAAM;;AAGV,KAAI,CAAC;MACC,QAAQ,SAAS,UAAU;AAC7B,aAAU;AACV,SAAM,QAAQ;aACL,QAAQ,SAAS,QAAQ;AAClC,UACE,gBACA,yDACD;AACD,oBAAiB;AACjB,aAAU;AACV,SAAM,QAAQ;;;AAIlB,QACE,SACA,kEACD;;AAGH,SAAgB,wBACd,GACA,GACA,KACS;AACT,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,IAC9B,KAAI,cAAc,EAAE,IAAI,KAAK,EAAE,IAAI,IAAI,KAAK,EAC1C,QAAO;AAGX,QAAO;;AAGT,SAAgB,YACd,QACA,WACA,OACA,UACA;AACA,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,IACpC,KAAI,CAAC,YAAY,OAAO,UAAU,KAAK,MAAM,SAAS,IAAI,CACxD,QAAO;AAGX,QAAO;;;;;;;AAQT,SAAgB,oBACd,WACA,WACA,WACmC;CACnC,MAAM,aAAoC,EAAE;AAC5C,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;EACzC,MAAM,QAAQ,UAAU,UAAU;AAClC,MAAI,UAAU,KACZ;AAEF,aAAW,UAAU,MAAM;;AAE7B,QAAO"}
1
+ {"version":3,"file":"join-utils.js","names":[],"sources":["../../../../../zql/src/ivm/join-utils.ts"],"sourcesContent":["import type {Row, Value} from '../../../zero-protocol/src/data.ts';\nimport type {Change} from './change.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\nimport {compareValues, valuesEqual, type Node} from './data.ts';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey} from '../../../zero-protocol/src/ast.ts';\n\nexport type JoinChangeOverlay = {\n change: Change;\n position: Row | undefined;\n};\n\nexport function generateWithOverlayNoYield(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlay(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlay(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n let applied = false;\n let editOldApplied = false;\n let editNewApplied = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n let yieldNode = true;\n if (!applied) {\n switch (overlay.type) {\n case 'add': {\n if (schema.compareRows(overlay.node.row, node.row) === 0) {\n applied = true;\n yieldNode = false;\n }\n break;\n }\n case 'remove': {\n if (schema.compareRows(overlay.node.row, node.row) < 0) {\n applied = true;\n yield overlay.node;\n }\n break;\n }\n case 'edit': {\n if (\n !editOldApplied &&\n schema.compareRows(overlay.oldNode.row, node.row) < 0\n ) {\n editOldApplied = true;\n if (editNewApplied) {\n applied = true;\n }\n yield overlay.oldNode;\n }\n if (\n !editNewApplied &&\n schema.compareRows(overlay.node.row, node.row) === 0\n ) {\n editNewApplied = true;\n if (editOldApplied) {\n applied = true;\n }\n yieldNode = false;\n }\n break;\n }\n case 'child': {\n if (schema.compareRows(overlay.node.row, node.row) === 0) {\n applied = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay.child.relationshipName]: () =>\n generateWithOverlay(\n node.relationships[overlay.child.relationshipName](),\n overlay.child.change,\n schema.relationships[overlay.child.relationshipName],\n ),\n },\n };\n yieldNode = false;\n }\n break;\n }\n }\n }\n if (yieldNode) {\n yield node;\n }\n }\n if (!applied) {\n if (overlay.type === 'remove') {\n applied = true;\n yield overlay.node;\n } else if (overlay.type === 'edit') {\n assert(\n editNewApplied,\n 'edit overlay: new node must be applied before old node',\n );\n editOldApplied = true;\n applied = true;\n yield overlay.oldNode;\n }\n }\n\n assert(\n applied,\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function generateWithOverlayNoYieldUnordered(\n stream: Stream<Node>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node> {\n return generateWithOverlayUnordered(stream, overlay, schema) as Stream<Node>;\n}\n\nexport function* generateWithOverlayUnordered(\n stream: Stream<Node | 'yield'>,\n overlay: Change,\n schema: SourceSchema,\n): Stream<Node | 'yield'> {\n // Eager inject\n if (overlay.type === 'remove') {\n yield overlay.node;\n } else if (overlay.type === 'edit') {\n yield overlay.oldNode;\n }\n\n // Stream with inline suppress\n let suppressed = false;\n for (const node of stream) {\n if (node === 'yield') {\n yield node;\n continue;\n }\n if (!suppressed) {\n if (overlay.type === 'add' || overlay.type === 'edit') {\n if (\n rowEqualsForCompoundKey(overlay.node.row, node.row, schema.primaryKey)\n ) {\n suppressed = true;\n continue;\n }\n }\n if (overlay.type === 'child') {\n if (\n rowEqualsForCompoundKey(overlay.node.row, node.row, schema.primaryKey)\n ) {\n suppressed = true;\n yield {\n row: node.row,\n relationships: {\n ...node.relationships,\n [overlay.child.relationshipName]: () =>\n generateWithOverlay(\n node.relationships[overlay.child.relationshipName](),\n overlay.child.change,\n schema.relationships[overlay.child.relationshipName],\n ),\n },\n };\n continue;\n }\n }\n }\n yield node;\n }\n assert(\n suppressed || overlay.type === 'remove',\n 'overlayGenerator: overlay was never applied to any fetched node',\n );\n}\n\nexport function rowEqualsForCompoundKey(\n a: Row,\n b: Row,\n key: CompoundKey,\n): boolean {\n for (let i = 0; i < key.length; i++) {\n if (compareValues(a[key[i]], b[key[i]]) !== 0) {\n return false;\n }\n }\n return true;\n}\n\nexport function isJoinMatch(\n parent: Row,\n parentKey: CompoundKey,\n child: Row,\n childKey: CompoundKey,\n) {\n for (let i = 0; i < parentKey.length; i++) {\n if (!valuesEqual(parent[parentKey[i]], child[childKey[i]])) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Builds a constraint object by mapping values from `sourceRow` using `sourceKey`\n * to keys specified by `targetKey`. Returns `undefined` if any source value is `null`,\n * since null foreign keys cannot match any rows.\n */\nexport function buildJoinConstraint(\n sourceRow: Row,\n sourceKey: CompoundKey,\n targetKey: CompoundKey,\n): Record<string, Value> | undefined {\n const constraint: Record<string, Value> = {};\n for (let i = 0; i < targetKey.length; i++) {\n const value = sourceRow[sourceKey[i]];\n if (value === null) {\n return undefined;\n }\n constraint[targetKey[i]] = value;\n }\n return constraint;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAG3E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAQpC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,KAAK,IAAI,GAAG;IACV,MAAM,EAAE,KAAK,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IAEb,SAAS,EAAE,WAAW,CAAC;IACvB,QAAQ,EAAE,WAAW,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;GASG;AACH,qBAAa,IAAK,YAAW,KAAK;;gBAYpB,EACV,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,MAAM,GACP,EAAE,IAAI;IAkCP,OAAO,IAAI,IAAI;IAKf,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,SAAS,IAAI,YAAY;IAIxB,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;CA0LlD"}
1
+ {"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/join.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,mCAAmC,CAAC;AAG3E,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AASpC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,KAAK,IAAI,GAAG;IACV,MAAM,EAAE,KAAK,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IAEb,SAAS,EAAE,WAAW,CAAC;IACvB,QAAQ,EAAE,WAAW,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;GASG;AACH,qBAAa,IAAK,YAAW,KAAK;;gBAYpB,EACV,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,MAAM,EACN,MAAM,GACP,EAAE,IAAI;IAkCP,OAAO,IAAI,IAAI;IAKf,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,SAAS,IAAI,YAAY;IAIxB,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;CAkMlD"}
@@ -1,6 +1,6 @@
1
1
  import { assert, unreachable } from "../../../shared/src/asserts.js";
2
2
  import { throwOutput } from "./operator.js";
3
- import { buildJoinConstraint, generateWithOverlay, isJoinMatch, rowEqualsForCompoundKey } from "./join-utils.js";
3
+ import { buildJoinConstraint, generateWithOverlay, generateWithOverlayUnordered, isJoinMatch, rowEqualsForCompoundKey } from "./join-utils.js";
4
4
  //#region ../zql/src/ivm/join.ts
5
5
  /**
6
6
  * The Join operator joins the output from two upstream inputs. Zero's join
@@ -147,7 +147,11 @@ var Join = class {
147
147
  const childStream = () => {
148
148
  const constraint = buildJoinConstraint(parentNodeRow, this.#parentKey, this.#childKey);
149
149
  const stream = constraint ? this.#child.fetch({ constraint }) : [];
150
- if (this.#inprogressChildChange && isJoinMatch(parentNodeRow, this.#parentKey, this.#inprogressChildChange.change.node.row, this.#childKey) && this.#inprogressChildChange.position && this.#schema.compareRows(parentNodeRow, this.#inprogressChildChange.position) > 0) return generateWithOverlay(stream, this.#inprogressChildChange.change, this.#child.getSchema());
150
+ if (this.#inprogressChildChange && isJoinMatch(parentNodeRow, this.#parentKey, this.#inprogressChildChange.change.node.row, this.#childKey) && this.#inprogressChildChange.position && this.#schema.compareRows(parentNodeRow, this.#inprogressChildChange.position) > 0) {
151
+ const childSchema = this.#child.getSchema();
152
+ if (childSchema.sort === void 0) return generateWithOverlayUnordered(stream, this.#inprogressChildChange.change, childSchema);
153
+ return generateWithOverlay(stream, this.#inprogressChildChange.change, childSchema);
154
+ }
151
155
  return stream;
152
156
  };
153
157
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"join.js","names":["#parent","#child","#parentKey","#childKey","#relationshipName","#schema","#pushParent","#pushChild","#output","#processParentNode","#pushChildChange","#inprogressChildChange"],"sources":["../../../../../zql/src/ivm/join.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey, System} from '../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {Change, ChildChange} from './change.ts';\nimport type {Node} from './data.ts';\nimport {\n buildJoinConstraint,\n generateWithOverlay,\n isJoinMatch,\n rowEqualsForCompoundKey,\n type JoinChangeOverlay,\n} from './join-utils.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type Output,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\n\ntype Args = {\n parent: Input;\n child: Input;\n // The nth key in parentKey corresponds to the nth key in childKey.\n parentKey: CompoundKey;\n childKey: CompoundKey;\n relationshipName: string;\n hidden: boolean;\n system: System;\n};\n\n/**\n * The Join operator joins the output from two upstream inputs. Zero's join\n * is a little different from SQL's join in that we output hierarchical data,\n * not a flat table. This makes it a lot more useful for UI programming and\n * avoids duplicating tons of data like left join would.\n *\n * The Nodes output from Join have a new relationship added to them, which has\n * the name #relationshipName. The value of the relationship is a stream of\n * child nodes which are the corresponding values from the child source.\n */\nexport class Join implements Input {\n readonly #parent: Input;\n readonly #child: Input;\n readonly #parentKey: CompoundKey;\n readonly #childKey: CompoundKey;\n readonly #relationshipName: string;\n readonly #schema: SourceSchema;\n\n #output: Output = throwOutput;\n\n #inprogressChildChange: JoinChangeOverlay | undefined;\n\n constructor({\n parent,\n child,\n parentKey,\n childKey,\n relationshipName,\n hidden,\n system,\n }: Args) {\n assert(parent !== child, 'Parent and child must be different operators');\n assert(\n parentKey.length === childKey.length,\n 'The parentKey and childKey keys must have same length',\n );\n this.#parent = parent;\n this.#child = child;\n this.#parentKey = parentKey;\n this.#childKey = childKey;\n this.#relationshipName = relationshipName;\n\n const parentSchema = parent.getSchema();\n const childSchema = child.getSchema();\n this.#schema = {\n ...parentSchema,\n relationships: {\n ...parentSchema.relationships,\n [relationshipName]: {\n ...childSchema,\n isHidden: hidden,\n system,\n },\n },\n };\n\n parent.setOutput({\n push: (change: Change) => this.#pushParent(change),\n });\n child.setOutput({\n push: (change: Change) => this.#pushChild(change),\n });\n }\n\n destroy(): void {\n this.#parent.destroy();\n this.#child.destroy();\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#schema;\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n for (const parentNode of this.#parent.fetch(req)) {\n if (parentNode === 'yield') {\n yield parentNode;\n continue;\n }\n yield this.#processParentNode(parentNode.row, parentNode.relationships);\n }\n }\n\n *#pushParent(change: Change): Stream<'yield'> {\n switch (change.type) {\n case 'add':\n yield* this.#output.push(\n {\n type: 'add',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n case 'remove':\n yield* this.#output.push(\n {\n type: 'remove',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n case 'child':\n yield* this.#output.push(\n {\n type: 'child',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n child: change.child,\n },\n this,\n );\n break;\n case 'edit': {\n // Assert the edit could not change the relationship.\n assert(\n rowEqualsForCompoundKey(\n change.oldNode.row,\n change.node.row,\n this.#parentKey,\n ),\n `Parent edit must not change relationship.`,\n );\n yield* this.#output.push(\n {\n type: 'edit',\n oldNode: this.#processParentNode(\n change.oldNode.row,\n change.oldNode.relationships,\n ),\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n }\n default:\n unreachable(change);\n }\n }\n\n *#pushChild(change: Change): Stream<'yield'> {\n switch (change.type) {\n case 'add':\n case 'remove':\n yield* this.#pushChildChange(change.node.row, change);\n break;\n case 'child':\n yield* this.#pushChildChange(change.node.row, change);\n break;\n case 'edit': {\n const childRow = change.node.row;\n const oldChildRow = change.oldNode.row;\n // Assert the edit could not change the relationship.\n assert(\n rowEqualsForCompoundKey(oldChildRow, childRow, this.#childKey),\n 'Child edit must not change relationship.',\n );\n yield* this.#pushChildChange(childRow, change);\n break;\n }\n\n default:\n unreachable(change);\n }\n }\n\n *#pushChildChange(childRow: Row, change: Change): Stream<'yield'> {\n this.#inprogressChildChange = {\n change,\n position: undefined,\n };\n try {\n const constraint = buildJoinConstraint(\n childRow,\n this.#childKey,\n this.#parentKey,\n );\n const parentNodes = constraint ? this.#parent.fetch({constraint}) : [];\n\n for (const parentNode of parentNodes) {\n if (parentNode === 'yield') {\n yield parentNode;\n continue;\n }\n this.#inprogressChildChange.position = parentNode.row;\n const childChange: ChildChange = {\n type: 'child',\n node: this.#processParentNode(\n parentNode.row,\n parentNode.relationships,\n ),\n child: {\n relationshipName: this.#relationshipName,\n change,\n },\n };\n yield* this.#output.push(childChange, this);\n }\n } finally {\n this.#inprogressChildChange = undefined;\n }\n }\n\n #processParentNode(\n parentNodeRow: Row,\n parentNodeRelations: Record<string, () => Stream<Node | 'yield'>>,\n ): Node {\n const childStream = () => {\n const constraint = buildJoinConstraint(\n parentNodeRow,\n this.#parentKey,\n this.#childKey,\n );\n const stream = constraint ? this.#child.fetch({constraint}) : [];\n\n if (\n this.#inprogressChildChange &&\n isJoinMatch(\n parentNodeRow,\n this.#parentKey,\n this.#inprogressChildChange.change.node.row,\n this.#childKey,\n ) &&\n this.#inprogressChildChange.position &&\n this.#schema.compareRows(\n parentNodeRow,\n this.#inprogressChildChange.position,\n ) > 0\n ) {\n return generateWithOverlay(\n stream,\n this.#inprogressChildChange.change,\n this.#child.getSchema(),\n );\n }\n return stream;\n };\n\n return {\n row: parentNodeRow,\n relationships: {\n ...parentNodeRelations,\n [this.#relationshipName]: childStream,\n },\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"join.js","names":["#parent","#child","#parentKey","#childKey","#relationshipName","#schema","#pushParent","#pushChild","#output","#processParentNode","#pushChildChange","#inprogressChildChange"],"sources":["../../../../../zql/src/ivm/join.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport type {CompoundKey, System} from '../../../zero-protocol/src/ast.ts';\nimport type {Row} from '../../../zero-protocol/src/data.ts';\nimport type {Change, ChildChange} from './change.ts';\nimport type {Node} from './data.ts';\nimport {\n buildJoinConstraint,\n generateWithOverlay,\n generateWithOverlayUnordered,\n isJoinMatch,\n rowEqualsForCompoundKey,\n type JoinChangeOverlay,\n} from './join-utils.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type Output,\n} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {type Stream} from './stream.ts';\n\ntype Args = {\n parent: Input;\n child: Input;\n // The nth key in parentKey corresponds to the nth key in childKey.\n parentKey: CompoundKey;\n childKey: CompoundKey;\n relationshipName: string;\n hidden: boolean;\n system: System;\n};\n\n/**\n * The Join operator joins the output from two upstream inputs. Zero's join\n * is a little different from SQL's join in that we output hierarchical data,\n * not a flat table. This makes it a lot more useful for UI programming and\n * avoids duplicating tons of data like left join would.\n *\n * The Nodes output from Join have a new relationship added to them, which has\n * the name #relationshipName. The value of the relationship is a stream of\n * child nodes which are the corresponding values from the child source.\n */\nexport class Join implements Input {\n readonly #parent: Input;\n readonly #child: Input;\n readonly #parentKey: CompoundKey;\n readonly #childKey: CompoundKey;\n readonly #relationshipName: string;\n readonly #schema: SourceSchema;\n\n #output: Output = throwOutput;\n\n #inprogressChildChange: JoinChangeOverlay | undefined;\n\n constructor({\n parent,\n child,\n parentKey,\n childKey,\n relationshipName,\n hidden,\n system,\n }: Args) {\n assert(parent !== child, 'Parent and child must be different operators');\n assert(\n parentKey.length === childKey.length,\n 'The parentKey and childKey keys must have same length',\n );\n this.#parent = parent;\n this.#child = child;\n this.#parentKey = parentKey;\n this.#childKey = childKey;\n this.#relationshipName = relationshipName;\n\n const parentSchema = parent.getSchema();\n const childSchema = child.getSchema();\n this.#schema = {\n ...parentSchema,\n relationships: {\n ...parentSchema.relationships,\n [relationshipName]: {\n ...childSchema,\n isHidden: hidden,\n system,\n },\n },\n };\n\n parent.setOutput({\n push: (change: Change) => this.#pushParent(change),\n });\n child.setOutput({\n push: (change: Change) => this.#pushChild(change),\n });\n }\n\n destroy(): void {\n this.#parent.destroy();\n this.#child.destroy();\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n\n getSchema(): SourceSchema {\n return this.#schema;\n }\n\n *fetch(req: FetchRequest): Stream<Node | 'yield'> {\n for (const parentNode of this.#parent.fetch(req)) {\n if (parentNode === 'yield') {\n yield parentNode;\n continue;\n }\n yield this.#processParentNode(parentNode.row, parentNode.relationships);\n }\n }\n\n *#pushParent(change: Change): Stream<'yield'> {\n switch (change.type) {\n case 'add':\n yield* this.#output.push(\n {\n type: 'add',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n case 'remove':\n yield* this.#output.push(\n {\n type: 'remove',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n case 'child':\n yield* this.#output.push(\n {\n type: 'child',\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n child: change.child,\n },\n this,\n );\n break;\n case 'edit': {\n // Assert the edit could not change the relationship.\n assert(\n rowEqualsForCompoundKey(\n change.oldNode.row,\n change.node.row,\n this.#parentKey,\n ),\n `Parent edit must not change relationship.`,\n );\n yield* this.#output.push(\n {\n type: 'edit',\n oldNode: this.#processParentNode(\n change.oldNode.row,\n change.oldNode.relationships,\n ),\n node: this.#processParentNode(\n change.node.row,\n change.node.relationships,\n ),\n },\n this,\n );\n break;\n }\n default:\n unreachable(change);\n }\n }\n\n *#pushChild(change: Change): Stream<'yield'> {\n switch (change.type) {\n case 'add':\n case 'remove':\n yield* this.#pushChildChange(change.node.row, change);\n break;\n case 'child':\n yield* this.#pushChildChange(change.node.row, change);\n break;\n case 'edit': {\n const childRow = change.node.row;\n const oldChildRow = change.oldNode.row;\n // Assert the edit could not change the relationship.\n assert(\n rowEqualsForCompoundKey(oldChildRow, childRow, this.#childKey),\n 'Child edit must not change relationship.',\n );\n yield* this.#pushChildChange(childRow, change);\n break;\n }\n\n default:\n unreachable(change);\n }\n }\n\n *#pushChildChange(childRow: Row, change: Change): Stream<'yield'> {\n this.#inprogressChildChange = {\n change,\n position: undefined,\n };\n try {\n const constraint = buildJoinConstraint(\n childRow,\n this.#childKey,\n this.#parentKey,\n );\n const parentNodes = constraint ? this.#parent.fetch({constraint}) : [];\n\n for (const parentNode of parentNodes) {\n if (parentNode === 'yield') {\n yield parentNode;\n continue;\n }\n this.#inprogressChildChange.position = parentNode.row;\n const childChange: ChildChange = {\n type: 'child',\n node: this.#processParentNode(\n parentNode.row,\n parentNode.relationships,\n ),\n child: {\n relationshipName: this.#relationshipName,\n change,\n },\n };\n yield* this.#output.push(childChange, this);\n }\n } finally {\n this.#inprogressChildChange = undefined;\n }\n }\n\n #processParentNode(\n parentNodeRow: Row,\n parentNodeRelations: Record<string, () => Stream<Node | 'yield'>>,\n ): Node {\n const childStream = () => {\n const constraint = buildJoinConstraint(\n parentNodeRow,\n this.#parentKey,\n this.#childKey,\n );\n const stream = constraint ? this.#child.fetch({constraint}) : [];\n\n if (\n this.#inprogressChildChange &&\n isJoinMatch(\n parentNodeRow,\n this.#parentKey,\n this.#inprogressChildChange.change.node.row,\n this.#childKey,\n ) &&\n this.#inprogressChildChange.position &&\n this.#schema.compareRows(\n parentNodeRow,\n this.#inprogressChildChange.position,\n ) > 0\n ) {\n const childSchema = this.#child.getSchema();\n if (childSchema.sort === undefined) {\n return generateWithOverlayUnordered(\n stream,\n this.#inprogressChildChange.change,\n childSchema,\n );\n }\n return generateWithOverlay(\n stream,\n this.#inprogressChildChange.change,\n childSchema,\n );\n }\n return stream;\n };\n\n return {\n row: parentNodeRow,\n relationships: {\n ...parentNodeRelations,\n [this.#relationshipName]: childStream,\n },\n };\n }\n}\n"],"mappings":"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"}
@@ -21,7 +21,7 @@ export type Overlays = {
21
21
  export type Connection = {
22
22
  input: Input;
23
23
  output: Output | undefined;
24
- sort: Ordering;
24
+ sort?: Ordering | undefined;
25
25
  splitEditKeys: Set<string> | undefined;
26
26
  compareRows: Comparator;
27
27
  filters: {
@@ -48,7 +48,7 @@ export declare class MemorySource implements Source {
48
48
  };
49
49
  fork(): MemorySource;
50
50
  get data(): BTreeSet<Row>;
51
- connect(sort: Ordering, filters?: Condition, splitEditKeys?: Set<string>): SourceInput;
51
+ connect(sort: Ordering | undefined, filters?: Condition, splitEditKeys?: Set<string>): SourceInput;
52
52
  getIndexKeys(): string[];
53
53
  push(change: SourceChange): Stream<'yield'>;
54
54
  genPush(change: SourceChange): Generator<"yield" | undefined, void, unknown>;
@@ -79,5 +79,18 @@ export declare function generateWithOverlayInner(rowIterator: Iterable<Row>, ove
79
79
  row: Readonly<Record<string, import("../../../shared/src/json.ts").ReadonlyJSONValue | undefined>>;
80
80
  relationships: {};
81
81
  }, void, unknown>;
82
+ /**
83
+ * Like {@link generateWithOverlay} but for unordered streams.
84
+ * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly
85
+ * at the start, and suppresses add/new-edit rows inline by PK match.
86
+ */
87
+ export declare function generateWithOverlayUnordered(rows: Iterable<Row>, constraint: Constraint | undefined, overlay: Overlay | undefined, lastPushedEpoch: number, primaryKey: PrimaryKey, filterPredicate?: ((row: Row) => boolean) | undefined): Generator<{
88
+ row: Readonly<Record<string, import("../../../shared/src/json.ts").ReadonlyJSONValue | undefined>>;
89
+ relationships: {};
90
+ }, void, unknown>;
91
+ export declare function generateWithOverlayInnerUnordered(rowIterator: Iterable<Row>, overlays: Overlays, primaryKey: PrimaryKey): Generator<{
92
+ row: Readonly<Record<string, import("../../../shared/src/json.ts").ReadonlyJSONValue | undefined>>;
93
+ relationships: {};
94
+ }, void, unknown>;
82
95
  export declare function stringify(change: SourceChange): string;
83
96
  //# sourceMappingURL=memory-source.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"memory-source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/memory-source.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"memory-source.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/memory-source.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { assert, unreachable } from "../../../shared/src/asserts.js";
2
2
  import { hasOwn } from "../../../shared/src/has-own.js";
3
3
  import { once } from "../../../shared/src/iterables.js";
4
+ import { must } from "../../../shared/src/must.js";
4
5
  import { skipYields } from "./operator.js";
5
6
  import { compareValues, makeComparator, valuesEqual } from "./data.js";
6
7
  import { filterPush } from "./filter-push.js";
@@ -51,12 +52,12 @@ var MemorySource = class MemorySource {
51
52
  get data() {
52
53
  return this.#getPrimaryIndex().data;
53
54
  }
54
- #getSchema(connection) {
55
+ #getSchema(connection, unordered) {
55
56
  return {
56
57
  tableName: this.#tableName,
57
58
  columns: this.#columns,
58
59
  primaryKey: this.#primaryKey,
59
- sort: connection.sort,
60
+ sort: unordered ? void 0 : connection.sort,
60
61
  system: "client",
61
62
  relationships: {},
62
63
  isHidden: false,
@@ -65,6 +66,8 @@ var MemorySource = class MemorySource {
65
66
  }
66
67
  connect(sort, filters, splitEditKeys) {
67
68
  const transformedFilters = transformFilters(filters);
69
+ const unordered = sort === void 0;
70
+ const internalSort = sort ?? this.#primaryIndexSort;
68
71
  const input = {
69
72
  getSchema: () => schema,
70
73
  fetch: (req) => this.#fetch(req, connection),
@@ -79,17 +82,17 @@ var MemorySource = class MemorySource {
79
82
  const connection = {
80
83
  input,
81
84
  output: void 0,
82
- sort,
85
+ sort: internalSort,
83
86
  splitEditKeys,
84
- compareRows: makeComparator(sort),
87
+ compareRows: makeComparator(internalSort),
85
88
  filters: transformedFilters.filters ? {
86
89
  condition: transformedFilters.filters,
87
90
  predicate: createPredicate(transformedFilters.filters)
88
91
  } : void 0,
89
92
  lastPushedEpoch: 0
90
93
  };
91
- const schema = this.#getSchema(connection);
92
- assertOrderingIncludesPK(sort, this.#primaryKey);
94
+ const schema = this.#getSchema(connection, unordered);
95
+ if (!unordered) assertOrderingIncludesPK(internalSort, this.#primaryKey);
93
96
  this.#connections.push(connection);
94
97
  return input;
95
98
  }
@@ -125,7 +128,8 @@ var MemorySource = class MemorySource {
125
128
  return [...this.#indexes.keys()];
126
129
  }
127
130
  *#fetch(req, conn) {
128
- const { sort: requestedSort, compareRows } = conn;
131
+ const requestedSort = must(conn.sort);
132
+ const { compareRows } = conn;
129
133
  const connectionComparator = (r1, r2) => compareRows(r1, r2) * (req.reverse ? -1 : 1);
130
134
  const pkConstraint = primaryKeyConstraintFromFilters(conn.filters?.condition, this.#primaryKey);
131
135
  const fetchOrPkConstraint = pkConstraint ?? req.constraint;
@@ -367,6 +371,63 @@ function* generateWithOverlayInner(rowIterator, overlays, compare) {
367
371
  relationships: {}
368
372
  };
369
373
  }
374
+ /**
375
+ * Like {@link generateWithOverlay} but for unordered streams.
376
+ * No `startAt` or comparator needed. Injects remove/old-edit rows eagerly
377
+ * at the start, and suppresses add/new-edit rows inline by PK match.
378
+ */
379
+ function* generateWithOverlayUnordered(rows, constraint, overlay, lastPushedEpoch, primaryKey, filterPredicate) {
380
+ let overlayToApply = void 0;
381
+ if (overlay && lastPushedEpoch >= overlay.epoch) overlayToApply = overlay;
382
+ let overlays = {
383
+ add: void 0,
384
+ remove: void 0
385
+ };
386
+ switch (overlayToApply?.change.type) {
387
+ case "add":
388
+ overlays = {
389
+ add: overlayToApply.change.row,
390
+ remove: void 0
391
+ };
392
+ break;
393
+ case "remove":
394
+ overlays = {
395
+ add: void 0,
396
+ remove: overlayToApply.change.row
397
+ };
398
+ break;
399
+ case "edit":
400
+ overlays = {
401
+ add: overlayToApply.change.row,
402
+ remove: overlayToApply.change.oldRow
403
+ };
404
+ break;
405
+ }
406
+ if (constraint) overlays = overlaysForConstraint(overlays, constraint);
407
+ if (filterPredicate) overlays = overlaysForFilterPredicate(overlays, filterPredicate);
408
+ yield* generateWithOverlayInnerUnordered(rows, overlays, primaryKey);
409
+ }
410
+ function* generateWithOverlayInnerUnordered(rowIterator, overlays, primaryKey) {
411
+ if (overlays.add) yield {
412
+ row: overlays.add,
413
+ relationships: {}
414
+ };
415
+ let removeSkipped = false;
416
+ for (const row of rowIterator) {
417
+ if (!removeSkipped && overlays.remove && rowMatchesPK(overlays.remove, row, primaryKey)) {
418
+ removeSkipped = true;
419
+ continue;
420
+ }
421
+ yield {
422
+ row,
423
+ relationships: {}
424
+ };
425
+ }
426
+ }
427
+ function rowMatchesPK(a, b, primaryKey) {
428
+ for (const key of primaryKey) if (!valuesEqual(a[key], b[key])) return false;
429
+ return true;
430
+ }
370
431
  var minValue = Symbol("min-value");
371
432
  var maxValue = Symbol("max-value");
372
433
  function makeBoundComparator(sort) {
@@ -394,6 +455,6 @@ function stringify(change) {
394
455
  return JSON.stringify(change, (_, v) => typeof v === "bigint" ? v.toString() : v);
395
456
  }
396
457
  //#endregion
397
- export { MemorySource, genPushAndWriteWithSplitEdit, generateWithOverlay, generateWithStart };
458
+ export { MemorySource, genPushAndWriteWithSplitEdit, generateWithOverlay, generateWithOverlayUnordered, generateWithStart };
398
459
 
399
460
  //# sourceMappingURL=memory-source.js.map