@rocicorp/zero 1.5.0-canary.3 → 1.6.0-canary.0

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 (172) hide show
  1. package/out/analyze-query/src/analyze-cli.js +2 -2
  2. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  3. package/out/replicache/src/btree/node.d.ts +3 -0
  4. package/out/replicache/src/btree/node.d.ts.map +1 -1
  5. package/out/replicache/src/btree/node.js +114 -1
  6. package/out/replicache/src/btree/node.js.map +1 -1
  7. package/out/replicache/src/btree/write.d.ts +7 -0
  8. package/out/replicache/src/btree/write.d.ts.map +1 -1
  9. package/out/replicache/src/btree/write.js +50 -0
  10. package/out/replicache/src/btree/write.js.map +1 -1
  11. package/out/replicache/src/db/write.d.ts +8 -0
  12. package/out/replicache/src/db/write.d.ts.map +1 -1
  13. package/out/replicache/src/db/write.js +15 -0
  14. package/out/replicache/src/db/write.js.map +1 -1
  15. package/out/replicache/src/kv/sqlite-store.d.ts +2 -5
  16. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  17. package/out/replicache/src/kv/sqlite-store.js +21 -24
  18. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  19. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  20. package/out/replicache/src/replicache-impl.js.map +1 -1
  21. package/out/replicache/src/sync/patch.d.ts +15 -0
  22. package/out/replicache/src/sync/patch.d.ts.map +1 -1
  23. package/out/replicache/src/sync/patch.js +85 -26
  24. package/out/replicache/src/sync/patch.js.map +1 -1
  25. package/out/shared/src/testing.d.ts +3 -0
  26. package/out/shared/src/testing.d.ts.map +1 -0
  27. package/out/zero/package.js +5 -6
  28. package/out/zero/package.js.map +1 -1
  29. package/out/zero-cache/src/auth/write-authorizer.js +1 -1
  30. package/out/zero-cache/src/config/zero-config.d.ts +4 -0
  31. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  32. package/out/zero-cache/src/config/zero-config.js +8 -0
  33. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  34. package/out/zero-cache/src/server/inspector-delegate.d.ts +3 -2
  35. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  36. package/out/zero-cache/src/server/inspector-delegate.js +19 -9
  37. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  38. package/out/zero-cache/src/server/runner/run-worker.js +1 -1
  39. package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
  40. package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
  41. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +7 -6
  42. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  43. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/change-source/pg/change-source.js +49 -66
  45. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  46. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +0 -8
  47. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +22 -52
  49. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  50. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts +57 -0
  51. package/out/zero-cache/src/services/change-source/pg/replication-slots.d.ts.map +1 -0
  52. package/out/zero-cache/src/services/change-source/pg/replication-slots.js +162 -0
  53. package/out/zero-cache/src/services/change-source/pg/replication-slots.js.map +1 -0
  54. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/change-source/pg/schema/init.js +18 -0
  56. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  57. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts +17 -3
  58. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  59. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +43 -16
  60. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  61. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts +2 -3
  62. package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
  63. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +5 -5
  64. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  65. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +10 -1
  66. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  67. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +13 -3
  68. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  69. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +6 -11
  70. package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
  71. package/out/zero-cache/src/services/change-streamer/change-streamer.js +0 -1
  72. package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
  73. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  74. package/out/zero-cache/src/services/change-streamer/forwarder.js +2 -2
  75. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  76. package/out/zero-cache/src/services/change-streamer/storer.d.ts +12 -5
  77. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  78. package/out/zero-cache/src/services/change-streamer/storer.js +43 -21
  79. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  80. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +4 -5
  81. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  82. package/out/zero-cache/src/services/change-streamer/subscriber.js +18 -16
  83. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  84. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  85. package/out/zero-cache/src/services/litestream/commands.js +3 -2
  86. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  87. package/out/zero-cache/src/services/litestream/config.yml +1 -0
  88. package/out/zero-cache/src/services/mutagen/pusher.d.ts +2 -2
  89. package/out/zero-cache/src/services/view-syncer/cvr-store.js +2 -2
  90. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  91. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +1 -1
  92. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  93. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  94. package/out/zero-cache/src/services/view-syncer/view-syncer.js +5 -6
  95. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  96. package/out/zero-cache/src/types/streams.d.ts +4 -0
  97. package/out/zero-cache/src/types/streams.d.ts.map +1 -1
  98. package/out/zero-cache/src/types/streams.js +13 -10
  99. package/out/zero-cache/src/types/streams.js.map +1 -1
  100. package/out/zero-cache/src/workers/connection.js +5 -5
  101. package/out/zero-cache/src/workers/connection.js.map +1 -1
  102. package/out/zero-client/src/client/inspector/inspector.d.ts.map +1 -1
  103. package/out/zero-client/src/client/inspector/inspector.js +15 -2
  104. package/out/zero-client/src/client/inspector/inspector.js.map +1 -1
  105. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts +9 -3
  106. package/out/zero-client/src/client/inspector/lazy-inspector.d.ts.map +1 -1
  107. package/out/zero-client/src/client/inspector/lazy-inspector.js +27 -6
  108. package/out/zero-client/src/client/inspector/lazy-inspector.js.map +1 -1
  109. package/out/zero-client/src/client/inspector/query.d.ts.map +1 -1
  110. package/out/zero-client/src/client/inspector/query.js +3 -3
  111. package/out/zero-client/src/client/inspector/query.js.map +1 -1
  112. package/out/zero-client/src/client/ivm-branch.d.ts.map +1 -1
  113. package/out/zero-client/src/client/ivm-branch.js +16 -2
  114. package/out/zero-client/src/client/ivm-branch.js.map +1 -1
  115. package/out/zero-client/src/client/options.d.ts +12 -4
  116. package/out/zero-client/src/client/options.d.ts.map +1 -1
  117. package/out/zero-client/src/client/options.js.map +1 -1
  118. package/out/zero-client/src/client/query-manager.d.ts +8 -1
  119. package/out/zero-client/src/client/query-manager.d.ts.map +1 -1
  120. package/out/zero-client/src/client/query-manager.js +28 -3
  121. package/out/zero-client/src/client/query-manager.js.map +1 -1
  122. package/out/zero-client/src/client/version.js +1 -1
  123. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  124. package/out/zero-client/src/client/zero.js +12 -11
  125. package/out/zero-client/src/client/zero.js.map +1 -1
  126. package/out/zero-protocol/src/down.d.ts +1 -1
  127. package/out/zero-protocol/src/inspect-down.d.ts +15 -4
  128. package/out/zero-protocol/src/inspect-down.d.ts.map +1 -1
  129. package/out/zero-protocol/src/inspect-down.js +11 -1
  130. package/out/zero-protocol/src/inspect-down.js.map +1 -1
  131. package/out/zero-protocol/src/protocol-version.d.ts +1 -1
  132. package/out/zero-protocol/src/protocol-version.d.ts.map +1 -1
  133. package/out/zero-protocol/src/protocol-version.js.map +1 -1
  134. package/out/zero-react/src/use-query.d.ts.map +1 -1
  135. package/out/zero-react/src/use-query.js.map +1 -1
  136. package/out/zero-react/src/zero-provider.d.ts +6 -0
  137. package/out/zero-react/src/zero-provider.d.ts.map +1 -1
  138. package/out/zero-react/src/zero-provider.js +21 -1
  139. package/out/zero-react/src/zero-provider.js.map +1 -1
  140. package/out/zero-solid/src/use-zero.d.ts +6 -0
  141. package/out/zero-solid/src/use-zero.d.ts.map +1 -1
  142. package/out/zero-solid/src/use-zero.js +24 -4
  143. package/out/zero-solid/src/use-zero.js.map +1 -1
  144. package/out/zql/src/builder/builder.d.ts.map +1 -1
  145. package/out/zql/src/builder/builder.js +18 -8
  146. package/out/zql/src/builder/builder.js.map +1 -1
  147. package/out/zql/src/ivm/cap.d.ts +32 -0
  148. package/out/zql/src/ivm/cap.d.ts.map +1 -0
  149. package/out/zql/src/ivm/cap.js +205 -0
  150. package/out/zql/src/ivm/cap.js.map +1 -0
  151. package/out/zql/src/ivm/constraint.d.ts.map +1 -1
  152. package/out/zql/src/ivm/constraint.js.map +1 -1
  153. package/out/zql/src/ivm/flipped-join.d.ts +9 -0
  154. package/out/zql/src/ivm/flipped-join.d.ts.map +1 -1
  155. package/out/zql/src/ivm/flipped-join.js +56 -69
  156. package/out/zql/src/ivm/flipped-join.js.map +1 -1
  157. package/out/zql/src/ivm/memory-source.d.ts +24 -3
  158. package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
  159. package/out/zql/src/ivm/memory-source.js +162 -7
  160. package/out/zql/src/ivm/memory-source.js.map +1 -1
  161. package/out/zql/src/ivm/operator.d.ts +26 -0
  162. package/out/zql/src/ivm/operator.d.ts.map +1 -1
  163. package/out/zql/src/ivm/operator.js.map +1 -1
  164. package/out/zql/src/ivm/take.js +2 -2
  165. package/out/zqlite/src/query-builder.d.ts +14 -2
  166. package/out/zqlite/src/query-builder.d.ts.map +1 -1
  167. package/out/zqlite/src/query-builder.js +32 -1
  168. package/out/zqlite/src/query-builder.js.map +1 -1
  169. package/out/zqlite/src/table-source.d.ts.map +1 -1
  170. package/out/zqlite/src/table-source.js +4 -4
  171. package/out/zqlite/src/table-source.js.map +1 -1
  172. package/package.json +5 -6
@@ -8,7 +8,7 @@ import { parseOptions } from "../../shared/src/options.js";
8
8
  import { ZERO_ENV_VAR_PREFIX } from "../../zero-cache/src/config/zero-config.js";
9
9
  import { Console } from "node:console";
10
10
  import { styleText } from "node:util";
11
- import { WebSocket } from "ws";
11
+ import { WebSocket as WebSocket$1 } from "ws";
12
12
  //#region ../analyze-query/src/analyze-cli.ts
13
13
  var options = {
14
14
  zeroCacheURL: {
@@ -217,7 +217,7 @@ function resolveHandshakeHeaders(config) {
217
217
  return headers;
218
218
  }
219
219
  function installWebSocketHeaderShim(headers) {
220
- class HeaderInjectingWebSocket extends WebSocket {
220
+ class HeaderInjectingWebSocket extends WebSocket$1 {
221
221
  constructor(url, protocols) {
222
222
  super(url, protocols, { headers });
223
223
  }
@@ -1 +1 @@
1
- {"version":3,"file":"analyze-cli.js","names":[],"sources":["../../../../analyze-query/src/analyze-cli.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport {Console} from 'node:console';\nimport {styleText} from 'node:util';\nimport type {LogSink} from '@rocicorp/logger';\nimport {WebSocket as NodeWebSocket} from 'ws';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole} from '../../shared/src/logging.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../../zero-cache/src/config/zero-config.ts';\nimport {Zero} from '../../zero-client/src/client/zero.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {createBuilder} from '../../zql/src/query/create-builder.ts';\nimport type {AnyQuery} from '../../zql/src/query/query.ts';\nimport type {SchemaQuery} from '../../zql/src/query/schema-query.ts';\n\nexport type AnalyzeCLIOptions = {\n schema: Schema;\n /** Defaults to `process.argv.slice(2)`. */\n argv?: readonly string[] | undefined;\n};\n\nconst options = {\n zeroCacheURL: {\n type: v.string().optional(),\n desc: [\n 'URL of the remote zero-cache to analyze against.',\n 'Accepts http(s):// or ws(s):// (ws(s) is the transport actually used).',\n ],\n },\n adminPassword: {\n type: v.string().optional(),\n desc: [\n 'Admin password for zero-cache.',\n 'Required when the server is configured with one; ignored in dev mode.',\n ],\n },\n authToken: {\n type: v.string().optional(),\n desc: [\n 'Raw JWT forwarded to zero-cache.',\n 'Used server-side to fill permission variables for the query.',\n ],\n },\n cookie: {\n type: v.string().optional(),\n desc: [\n 'Cookie header value sent on the WebSocket upgrade request,',\n 'e.g. `session=abc; foo=bar`. Use this when zero-cache is behind',\n 'a proxy that resolves auth via cookies. Merged with --headers-json',\n '(--cookie wins on conflict).',\n ],\n },\n headersJson: {\n type: v.string().optional(),\n desc: [\n 'JSON object of arbitrary headers to send on the WebSocket upgrade',\n 'request, e.g. `{\"x-api-key\":\"...\"}`. Escape hatch for exotic auth',\n 'schemes; prefer --auth-token or --cookie when possible.',\n ],\n },\n userId: {\n type: v.string().optional(),\n desc: [\n 'Optional userID to report to zero-cache.',\n 'Has no functional effect on analysis; defaults to \"analyze-cli\".',\n ],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded AST. Exactly one of --ast / --query / --query-name is required.',\n 'The AST is sent to the server verbatim — provide it in server (post-mapping) form.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'ZQL query in chain form, e.g. `issue.related(\"comments\").limit(10)`.',\n 'Evaluated against the schema you pass to runAnalyzeCLI.',\n ],\n },\n queryName: {\n type: v.string().optional(),\n desc: [\n 'Name of a server-registered custom (named) query.',\n 'The server resolves the name + args via its registered query handler.',\n ],\n },\n queryArgs: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded array of arguments for --query-name. Defaults to `[]`.',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Include the rows read from the replica to execute the query.',\n 'Each row appears once per read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: ['Include the rows that would be synced to the client.'],\n },\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\ntype QueryPlan =\n | {kind: 'ast'; ast: AST}\n | {kind: 'zql'; text: string}\n | {kind: 'named'; name: string; args: ReadonlyArray<unknown>};\n\n// Route all Zero client log output to stderr so stdout contains only the\n// analyze result. Shell redirection (`2>/dev/null`) can then cleanly silence\n// logs without affecting output.\nconst stderrConsole = new Console({\n stdout: process.stderr,\n stderr: process.stderr,\n});\nconst stderrLogSink: LogSink = {\n log(level, context, ...args) {\n const ctx = context\n ? Object.entries(context).map(([k, v]) =>\n v === undefined ? k : `${k}=${v}`,\n )\n : [];\n stderrConsole[level](...ctx, ...args);\n },\n};\n\n/**\n * Entry point for a user's `cli.ts`. Parses argv, connects to a remote\n * zero-cache by standing up an in-process Zero client (in-memory storage,\n * no subscriptions), calls the inspector's `analyze-query` RPC, and\n * renders the result. Intended to be called as:\n *\n * ```ts\n * import {schema} from './schema.ts';\n * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';\n * await runAnalyzeCLI({schema});\n * ```\n *\n * Exits the process with code 1 on error.\n */\nexport async function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void> {\n const argv = (opts.argv ?? process.argv.slice(2)).map(s =>\n s.replaceAll('\\n', ' '),\n );\n\n const config = parseOptions(options, {\n argv,\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query (remote)',\n content: `Analyze a ZQL query against a remote zero-cache.\n\n Connects to zero-cache's inspector protocol and reports the server-observed\n row scans, SQLite query plans, and timings.`,\n },\n {\n header: 'Examples',\n content: ` tsx cli.ts --zero-cache-url=https://zero.example.com \\\\\n --admin-password=\"$ZERO_ADMIN_PASSWORD\" \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --ast='\\\\{\"table\": \"issue\", \"limit\": 5\\\\}'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --query-name=issueList --query-args='[]'`,\n },\n ],\n });\n\n if (!config.zeroCacheURL) {\n colorConsole.error('--zero-cache-url is required. See --help for usage.');\n process.exit(1);\n }\n\n const plan = buildQueryPlan(config);\n\n const handshakeHeaders = resolveHandshakeHeaders(config);\n if (Object.keys(handshakeHeaders).length > 0) {\n installWebSocketHeaderShim(handshakeHeaders);\n }\n\n // zero-client and replicache reference a build-time `TESTING` global that\n // bundlers replace with a boolean literal; under tsx there's no replacement,\n // so provide a runtime default.\n (globalThis as {TESTING?: boolean}).TESTING ??= false;\n\n const z = new Zero({\n schema: opts.schema,\n server: config.zeroCacheURL,\n auth: config.authToken,\n userID: config.userId ?? 'analyze-cli',\n kvStore: 'mem',\n logLevel: config.log.level,\n logSink: stderrLogSink,\n });\n\n let result: AnalyzeQueryResult;\n try {\n const authOk = await z.inspector.authenticate(config.adminPassword ?? '');\n if (!authOk) {\n throw new Error(\n 'admin password rejected (or --admin-password is required)',\n );\n }\n\n const rpcOptions = {\n vendedRows: config.outputVendedRows,\n syncedRows: config.outputSyncedRows,\n };\n\n if (plan.kind === 'ast') {\n result = await z.inspector.analyzeServerAST(plan.ast, rpcOptions);\n } else if (plan.kind === 'named') {\n result = await z.inspector.analyzeNamedQuery(\n plan.name,\n plan.args as ReadonlyArray<never>,\n rpcOptions,\n );\n } else {\n const built = buildZqlQuery(plan.text, createBuilder(opts.schema));\n result = await z.inspector.analyzeQuery(built, rpcOptions);\n }\n } catch (e) {\n colorConsole.error(e instanceof Error ? e.message : String(e));\n await z.close().catch(() => {});\n process.exit(1);\n }\n\n renderResult(result, {\n outputSyncedRows: config.outputSyncedRows,\n outputVendedRows: config.outputVendedRows,\n });\n\n await z.close();\n}\n\nfunction buildQueryPlan(config: {\n ast?: string | undefined;\n query?: string | undefined;\n queryName?: string | undefined;\n queryArgs?: string | undefined;\n}): QueryPlan {\n const selectors = [\n config.ast !== undefined && 'ast',\n config.query !== undefined && 'query',\n config.queryName !== undefined && 'queryName',\n ].filter(Boolean) as string[];\n\n if (selectors.length === 0) {\n colorConsole.error(\n 'Exactly one of --ast / --query / --query-name is required.',\n );\n process.exit(1);\n }\n if (selectors.length > 1) {\n colorConsole.error(\n `Only one of --ast / --query / --query-name may be provided; got: ${selectors.join(', ')}`,\n );\n process.exit(1);\n }\n\n if (config.ast !== undefined) {\n return {kind: 'ast', ast: JSON.parse(config.ast) as AST};\n }\n if (config.query !== undefined) {\n return {kind: 'zql', text: config.query};\n }\n const args = config.queryArgs\n ? (JSON.parse(config.queryArgs) as ReadonlyArray<unknown>)\n : [];\n return {kind: 'named', name: config.queryName as string, args};\n}\n\nfunction buildZqlQuery(\n queryString: string,\n builder: SchemaQuery<Schema>,\n): AnyQuery {\n const f = new Function('builder', `return builder.${queryString};`);\n return f(builder) as AnyQuery;\n}\n\nfunction resolveHandshakeHeaders(config: {\n cookie?: string | undefined;\n headersJson?: string | undefined;\n}): Record<string, string> {\n const headers: Record<string, string> = {};\n if (config.headersJson !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(config.headersJson);\n } catch (e) {\n colorConsole.error(\n `--headers-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,\n );\n process.exit(1);\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n colorConsole.error('--headers-json must be a JSON object.');\n process.exit(1);\n }\n for (const [k, val] of Object.entries(parsed)) {\n if (typeof val !== 'string') {\n colorConsole.error(\n `--headers-json values must be strings; got ${typeof val} for \"${k}\".`,\n );\n process.exit(1);\n }\n headers[k] = val;\n }\n }\n if (config.cookie !== undefined) {\n headers.cookie = config.cookie;\n }\n return headers;\n}\n\nfunction installWebSocketHeaderShim(headers: Record<string, string>): void {\n class HeaderInjectingWebSocket extends NodeWebSocket {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols, {headers});\n }\n }\n (globalThis as {WebSocket?: unknown}).WebSocket = HeaderInjectingWebSocket;\n}\n\nfunction renderResult(\n result: AnalyzeQueryResult,\n opts: {outputSyncedRows: boolean; outputVendedRows: boolean},\n) {\n if (opts.outputSyncedRows) {\n colorConsole.log(styleText(['blue', 'bold'], '=== Synced Rows: ===\\n'));\n for (const [table, rows] of Object.entries(result.syncedRows ?? {})) {\n colorConsole.log(styleText('bold', table + ':'), rows);\n }\n }\n\n colorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\n colorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n );\n\n const readRowCountsByQuery = result.readRowCountsByQuery ?? {};\n let totalRowsRead = 0;\n for (const table of Object.keys(readRowCountsByQuery).sort()) {\n const counts = readRowCountsByQuery[table];\n for (const n of Object.values(counts)) {\n totalRowsRead += n;\n }\n colorConsole.log(styleText('bold', `${table} vended:`), counts);\n }\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsRead),\n );\n const duration = result.elapsed ?? result.end - result.start;\n colorConsole.log(styleText('bold', 'time:'), colorTime(duration), 'ms');\n\n if (opts.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const [table, rows] of Object.entries(result.readRows ?? {})) {\n colorConsole.log(styleText('bold', `${table}:`), rows);\n }\n }\n\n colorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n );\n const dbScansByQuery = result.dbScansByQuery ?? {};\n let totalNVisit = 0;\n for (const [table, queries] of Object.entries(dbScansByQuery)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n }\n colorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n );\n\n colorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\n const plans = result.sqlitePlans ?? {};\n for (const [query, plan] of Object.entries(plans)) {\n colorConsole.log(styleText('bold', 'query'), query);\n colorConsole.log(plan.map((row, i) => colorPlanRow(row, i)).join('\\n'));\n colorConsole.log('\\n');\n }\n\n if (result.warnings.length > 0) {\n colorConsole.log(styleText(['yellow', 'bold'], '=== Warnings: ===\\n'));\n for (const w of result.warnings) {\n colorConsole.log(styleText('yellow', w));\n }\n }\n}\n\nfunction colorTime(duration: number) {\n if (duration < 100) {\n return styleText('green', duration.toFixed(2) + 'ms');\n } else if (duration < 1000) {\n return styleText('yellow', duration.toFixed(2) + 'ms');\n }\n return styleText('red', duration.toFixed(2) + 'ms');\n}\n\nfunction colorRowsConsidered(n: number) {\n if (n < 1000) {\n return styleText('green', n.toString());\n } else if (n < 10000) {\n return styleText('yellow', n.toString());\n }\n return styleText('red', n.toString());\n}\n\nfunction colorPlanRow(row: string, i: number) {\n if (row.includes('SCAN')) {\n if (i === 0) {\n return styleText('yellow', row);\n }\n return styleText('red', row);\n }\n return styleText('green', row);\n}\n"],"mappings":";;;;;;;;;;;;AAyBA,IAAM,UAAU;CACd,cAAc;EACZ,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM,CACJ,oDACA,yEACD;EACF;CACD,eAAe;EACb,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM,CACJ,kCACA,wEACD;EACF;CACD,WAAW;EACT,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM,CACJ,oCACA,+DACD;EACF;CACD,QAAQ;EACN,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM;GACJ;GACA;GACA;GACA;GACD;EACF;CACD,aAAa;EACX,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM;GACJ;GACA;GACA;GACD;EACF;CACD,QAAQ;EACN,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM,CACJ,4CACA,qEACD;EACF;CACD,KAAK;EACH,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM,CACJ,gFACA,qFACD;EACF;CACD,OAAO;EACL,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM,CACJ,0EACA,0DACD;EACF;CACD,WAAW;EACT,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM,CACJ,qDACA,wEACD;EACF;CACD,WAAW;EACT,MAAM,eAAE,QAAQ,CAAC,UAAU;EAC3B,MAAM,CACJ,sEACD;EACF;CACD,kBAAkB;EAChB,MAAM,eAAE,SAAS,CAAC,QAAQ,MAAM;EAChC,MAAM,CACJ,gEACA,kCACD;EACF;CACD,kBAAkB;EAChB,MAAM,eAAE,SAAS,CAAC,QAAQ,MAAM;EAChC,MAAM,CAAC,uDAAuD;EAC/D;CACD,KAAK;EACH,GAAG;EACH,OAAO,SAAS,QAAQ,QAAQ;EACjC;CACF;AAUD,IAAM,gBAAgB,IAAI,QAAQ;CAChC,QAAQ,QAAQ;CAChB,QAAQ,QAAQ;CACjB,CAAC;AACF,IAAM,gBAAyB,EAC7B,IAAI,OAAO,SAAS,GAAG,MAAM;CAC3B,MAAM,MAAM,UACR,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,GAAG,OAC/B,MAAM,KAAA,IAAY,IAAI,GAAG,EAAE,GAAG,IAC/B,GACD,EAAE;AACN,eAAc,OAAO,GAAG,KAAK,GAAG,KAAK;GAExC;;;;;;;;;;;;;;;AAgBD,eAAsB,cAAc,MAAwC;CAK1E,MAAM,SAAS,aAAa,SAAS;EACnC,OALY,KAAK,QAAQ,QAAQ,KAAK,MAAM,EAAE,EAAE,KAAI,MACpD,EAAE,WAAW,MAAM,IAAI,CACxB;EAIC,eAAe;EACf,aAAa,CACX;GACE,QAAQ;GACR,SAAS;;;;GAIV,EACD;GACE,QAAQ;GACR,SAAS;;;;;;;;;GASV,CACF;EACF,CAAC;AAEF,KAAI,CAAC,OAAO,cAAc;AACxB,eAAa,MAAM,sDAAsD;AACzE,UAAQ,KAAK,EAAE;;CAGjB,MAAM,OAAO,eAAe,OAAO;CAEnC,MAAM,mBAAmB,wBAAwB,OAAO;AACxD,KAAI,OAAO,KAAK,iBAAiB,CAAC,SAAS,EACzC,4BAA2B,iBAAiB;AAM7C,YAAmC,YAAY;CAEhD,MAAM,IAAI,IAAI,KAAK;EACjB,QAAQ,KAAK;EACb,QAAQ,OAAO;EACf,MAAM,OAAO;EACb,QAAQ,OAAO,UAAU;EACzB,SAAS;EACT,UAAU,OAAO,IAAI;EACrB,SAAS;EACV,CAAC;CAEF,IAAI;AACJ,KAAI;AAEF,MAAI,CADW,MAAM,EAAE,UAAU,aAAa,OAAO,iBAAiB,GAAG,CAEvE,OAAM,IAAI,MACR,4DACD;EAGH,MAAM,aAAa;GACjB,YAAY,OAAO;GACnB,YAAY,OAAO;GACpB;AAED,MAAI,KAAK,SAAS,MAChB,UAAS,MAAM,EAAE,UAAU,iBAAiB,KAAK,KAAK,WAAW;WACxD,KAAK,SAAS,QACvB,UAAS,MAAM,EAAE,UAAU,kBACzB,KAAK,MACL,KAAK,MACL,WACD;OACI;GACL,MAAM,QAAQ,cAAc,KAAK,MAAM,cAAc,KAAK,OAAO,CAAC;AAClE,YAAS,MAAM,EAAE,UAAU,aAAa,OAAO,WAAW;;UAErD,GAAG;AACV,eAAa,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC;AAC9D,QAAM,EAAE,OAAO,CAAC,YAAY,GAAG;AAC/B,UAAQ,KAAK,EAAE;;AAGjB,cAAa,QAAQ;EACnB,kBAAkB,OAAO;EACzB,kBAAkB,OAAO;EAC1B,CAAC;AAEF,OAAM,EAAE,OAAO;;AAGjB,SAAS,eAAe,QAKV;CACZ,MAAM,YAAY;EAChB,OAAO,QAAQ,KAAA,KAAa;EAC5B,OAAO,UAAU,KAAA,KAAa;EAC9B,OAAO,cAAc,KAAA,KAAa;EACnC,CAAC,OAAO,QAAQ;AAEjB,KAAI,UAAU,WAAW,GAAG;AAC1B,eAAa,MACX,6DACD;AACD,UAAQ,KAAK,EAAE;;AAEjB,KAAI,UAAU,SAAS,GAAG;AACxB,eAAa,MACX,oEAAoE,UAAU,KAAK,KAAK,GACzF;AACD,UAAQ,KAAK,EAAE;;AAGjB,KAAI,OAAO,QAAQ,KAAA,EACjB,QAAO;EAAC,MAAM;EAAO,KAAK,KAAK,MAAM,OAAO,IAAI;EAAQ;AAE1D,KAAI,OAAO,UAAU,KAAA,EACnB,QAAO;EAAC,MAAM;EAAO,MAAM,OAAO;EAAM;CAE1C,MAAM,OAAO,OAAO,YACf,KAAK,MAAM,OAAO,UAAU,GAC7B,EAAE;AACN,QAAO;EAAC,MAAM;EAAS,MAAM,OAAO;EAAqB;EAAK;;AAGhE,SAAS,cACP,aACA,SACU;AAEV,QADU,IAAI,SAAS,WAAW,kBAAkB,YAAY,GAAG,CAC1D,QAAQ;;AAGnB,SAAS,wBAAwB,QAGN;CACzB,MAAM,UAAkC,EAAE;AAC1C,KAAI,OAAO,gBAAgB,KAAA,GAAW;EACpC,IAAI;AACJ,MAAI;AACF,YAAS,KAAK,MAAM,OAAO,YAAY;WAChC,GAAG;AACV,gBAAa,MACX,qCAAqC,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,GAChF;AACD,WAAQ,KAAK,EAAE;;AAEjB,MACE,WAAW,QACX,OAAO,WAAW,YAClB,MAAM,QAAQ,OAAO,EACrB;AACA,gBAAa,MAAM,wCAAwC;AAC3D,WAAQ,KAAK,EAAE;;AAEjB,OAAK,MAAM,CAAC,GAAG,QAAQ,OAAO,QAAQ,OAAO,EAAE;AAC7C,OAAI,OAAO,QAAQ,UAAU;AAC3B,iBAAa,MACX,8CAA8C,OAAO,IAAI,QAAQ,EAAE,IACpE;AACD,YAAQ,KAAK,EAAE;;AAEjB,WAAQ,KAAK;;;AAGjB,KAAI,OAAO,WAAW,KAAA,EACpB,SAAQ,SAAS,OAAO;AAE1B,QAAO;;AAGT,SAAS,2BAA2B,SAAuC;CACzE,MAAM,iCAAiC,UAAc;EACnD,YAAY,KAAmB,WAA+B;AAC5D,SAAM,KAAK,WAAW,EAAC,SAAQ,CAAC;;;AAGnC,YAAqC,YAAY;;AAGpD,SAAS,aACP,QACA,MACA;AACA,KAAI,KAAK,kBAAkB;AACzB,eAAa,IAAI,UAAU,CAAC,QAAQ,OAAO,EAAE,yBAAyB,CAAC;AACvE,OAAK,MAAM,CAAC,OAAO,SAAS,OAAO,QAAQ,OAAO,cAAc,EAAE,CAAC,CACjE,cAAa,IAAI,UAAU,QAAQ,QAAQ,IAAI,EAAE,KAAK;;AAI1D,cAAa,IAAI,UAAU,CAAC,QAAQ,OAAO,EAAE,yBAAyB,CAAC;AACvE,cAAa,IACX,UAAU,QAAQ,qBAAqB,EACvC,OAAO,eACR;CAED,MAAM,uBAAuB,OAAO,wBAAwB,EAAE;CAC9D,IAAI,gBAAgB;AACpB,MAAK,MAAM,SAAS,OAAO,KAAK,qBAAqB,CAAC,MAAM,EAAE;EAC5D,MAAM,SAAS,qBAAqB;AACpC,OAAK,MAAM,KAAK,OAAO,OAAO,OAAO,CACnC,kBAAiB;AAEnB,eAAa,IAAI,UAAU,QAAQ,GAAG,MAAM,UAAU,EAAE,OAAO;;AAEjE,cAAa,IACX,UAAU,QAAQ,uBAAuB,EACzC,oBAAoB,cAAc,CACnC;CACD,MAAM,WAAW,OAAO,WAAW,OAAO,MAAM,OAAO;AACvD,cAAa,IAAI,UAAU,QAAQ,QAAQ,EAAE,UAAU,SAAS,EAAE,KAAK;AAEvE,KAAI,KAAK,kBAAkB;AACzB,eAAa,IACX,UAAU,CAAC,QAAQ,OAAO,EAAE,gCAAgC,CAC7D;AACD,OAAK,MAAM,CAAC,OAAO,SAAS,OAAO,QAAQ,OAAO,YAAY,EAAE,CAAC,CAC/D,cAAa,IAAI,UAAU,QAAQ,GAAG,MAAM,GAAG,EAAE,KAAK;;AAI1D,cAAa,IACX,UAAU,CAAC,QAAQ,OAAO,EAAE,wCAAwC,CACrE;CACD,MAAM,iBAAiB,OAAO,kBAAkB,EAAE;CAClD,IAAI,cAAc;AAClB,MAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,eAAe,EAAE;AAC7D,eAAa,IAAI,UAAU,QAAQ,GAAG,MAAM,GAAG,EAAE,QAAQ;AACzD,OAAK,MAAM,SAAS,OAAO,OAAO,QAAQ,CACxC,gBAAe;;AAGnB,cAAa,IACX,UAAU,QAAQ,sBAAsB,EACxC,oBAAoB,YAAY,CACjC;AAED,cAAa,IAAI,UAAU,CAAC,QAAQ,OAAO,EAAE,6BAA6B,CAAC;CAC3E,MAAM,QAAQ,OAAO,eAAe,EAAE;AACtC,MAAK,MAAM,CAAC,OAAO,SAAS,OAAO,QAAQ,MAAM,EAAE;AACjD,eAAa,IAAI,UAAU,QAAQ,QAAQ,EAAE,MAAM;AACnD,eAAa,IAAI,KAAK,KAAK,KAAK,MAAM,aAAa,KAAK,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC;AACvE,eAAa,IAAI,KAAK;;AAGxB,KAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,eAAa,IAAI,UAAU,CAAC,UAAU,OAAO,EAAE,sBAAsB,CAAC;AACtE,OAAK,MAAM,KAAK,OAAO,SACrB,cAAa,IAAI,UAAU,UAAU,EAAE,CAAC;;;AAK9C,SAAS,UAAU,UAAkB;AACnC,KAAI,WAAW,IACb,QAAO,UAAU,SAAS,SAAS,QAAQ,EAAE,GAAG,KAAK;UAC5C,WAAW,IACpB,QAAO,UAAU,UAAU,SAAS,QAAQ,EAAE,GAAG,KAAK;AAExD,QAAO,UAAU,OAAO,SAAS,QAAQ,EAAE,GAAG,KAAK;;AAGrD,SAAS,oBAAoB,GAAW;AACtC,KAAI,IAAI,IACN,QAAO,UAAU,SAAS,EAAE,UAAU,CAAC;UAC9B,IAAI,IACb,QAAO,UAAU,UAAU,EAAE,UAAU,CAAC;AAE1C,QAAO,UAAU,OAAO,EAAE,UAAU,CAAC;;AAGvC,SAAS,aAAa,KAAa,GAAW;AAC5C,KAAI,IAAI,SAAS,OAAO,EAAE;AACxB,MAAI,MAAM,EACR,QAAO,UAAU,UAAU,IAAI;AAEjC,SAAO,UAAU,OAAO,IAAI;;AAE9B,QAAO,UAAU,SAAS,IAAI"}
1
+ {"version":3,"file":"analyze-cli.js","names":[],"sources":["../../../../analyze-query/src/analyze-cli.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport {Console} from 'node:console';\nimport {styleText} from 'node:util';\nimport type {LogSink} from '@rocicorp/logger';\nimport {WebSocket as NodeWebSocket} from 'ws';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole} from '../../shared/src/logging.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../../zero-cache/src/config/zero-config.ts';\nimport {Zero} from '../../zero-client/src/client/zero.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport type {AST} from '../../zero-protocol/src/ast.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {createBuilder} from '../../zql/src/query/create-builder.ts';\nimport type {AnyQuery} from '../../zql/src/query/query.ts';\nimport type {SchemaQuery} from '../../zql/src/query/schema-query.ts';\n\nexport type AnalyzeCLIOptions = {\n schema: Schema;\n /** Defaults to `process.argv.slice(2)`. */\n argv?: readonly string[] | undefined;\n};\n\nconst options = {\n zeroCacheURL: {\n type: v.string().optional(),\n desc: [\n 'URL of the remote zero-cache to analyze against.',\n 'Accepts http(s):// or ws(s):// (ws(s) is the transport actually used).',\n ],\n },\n adminPassword: {\n type: v.string().optional(),\n desc: [\n 'Admin password for zero-cache.',\n 'Required when the server is configured with one; ignored in dev mode.',\n ],\n },\n authToken: {\n type: v.string().optional(),\n desc: [\n 'Raw JWT forwarded to zero-cache.',\n 'Used server-side to fill permission variables for the query.',\n ],\n },\n cookie: {\n type: v.string().optional(),\n desc: [\n 'Cookie header value sent on the WebSocket upgrade request,',\n 'e.g. `session=abc; foo=bar`. Use this when zero-cache is behind',\n 'a proxy that resolves auth via cookies. Merged with --headers-json',\n '(--cookie wins on conflict).',\n ],\n },\n headersJson: {\n type: v.string().optional(),\n desc: [\n 'JSON object of arbitrary headers to send on the WebSocket upgrade',\n 'request, e.g. `{\"x-api-key\":\"...\"}`. Escape hatch for exotic auth',\n 'schemes; prefer --auth-token or --cookie when possible.',\n ],\n },\n userId: {\n type: v.string().optional(),\n desc: [\n 'Optional userID to report to zero-cache.',\n 'Has no functional effect on analysis; defaults to \"analyze-cli\".',\n ],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded AST. Exactly one of --ast / --query / --query-name is required.',\n 'The AST is sent to the server verbatim — provide it in server (post-mapping) form.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'ZQL query in chain form, e.g. `issue.related(\"comments\").limit(10)`.',\n 'Evaluated against the schema you pass to runAnalyzeCLI.',\n ],\n },\n queryName: {\n type: v.string().optional(),\n desc: [\n 'Name of a server-registered custom (named) query.',\n 'The server resolves the name + args via its registered query handler.',\n ],\n },\n queryArgs: {\n type: v.string().optional(),\n desc: [\n 'JSON-encoded array of arguments for --query-name. Defaults to `[]`.',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Include the rows read from the replica to execute the query.',\n 'Each row appears once per read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: ['Include the rows that would be synced to the client.'],\n },\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\ntype QueryPlan =\n | {kind: 'ast'; ast: AST}\n | {kind: 'zql'; text: string}\n | {kind: 'named'; name: string; args: ReadonlyArray<unknown>};\n\n// Route all Zero client log output to stderr so stdout contains only the\n// analyze result. Shell redirection (`2>/dev/null`) can then cleanly silence\n// logs without affecting output.\nconst stderrConsole = new Console({\n stdout: process.stderr,\n stderr: process.stderr,\n});\nconst stderrLogSink: LogSink = {\n log(level, context, ...args) {\n const ctx = context\n ? Object.entries(context).map(([k, v]) =>\n v === undefined ? k : `${k}=${v}`,\n )\n : [];\n stderrConsole[level](...ctx, ...args);\n },\n};\n\n/**\n * Entry point for a user's `cli.ts`. Parses argv, connects to a remote\n * zero-cache by standing up an in-process Zero client (in-memory storage,\n * no subscriptions), calls the inspector's `analyze-query` RPC, and\n * renders the result. Intended to be called as:\n *\n * ```ts\n * import {schema} from './schema.ts';\n * import {runAnalyzeCLI} from '@rocicorp/zero/analyze';\n * await runAnalyzeCLI({schema});\n * ```\n *\n * Exits the process with code 1 on error.\n */\nexport async function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void> {\n const argv = (opts.argv ?? process.argv.slice(2)).map(s =>\n s.replaceAll('\\n', ' '),\n );\n\n const config = parseOptions(options, {\n argv,\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query (remote)',\n content: `Analyze a ZQL query against a remote zero-cache.\n\n Connects to zero-cache's inspector protocol and reports the server-observed\n row scans, SQLite query plans, and timings.`,\n },\n {\n header: 'Examples',\n content: ` tsx cli.ts --zero-cache-url=https://zero.example.com \\\\\n --admin-password=\"$ZERO_ADMIN_PASSWORD\" \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --ast='\\\\{\"table\": \"issue\", \"limit\": 5\\\\}'\n\n tsx cli.ts --zero-cache-url=http://localhost:4848 \\\\\n --query-name=issueList --query-args='[]'`,\n },\n ],\n });\n\n if (!config.zeroCacheURL) {\n colorConsole.error('--zero-cache-url is required. See --help for usage.');\n process.exit(1);\n }\n\n const plan = buildQueryPlan(config);\n\n const handshakeHeaders = resolveHandshakeHeaders(config);\n if (Object.keys(handshakeHeaders).length > 0) {\n installWebSocketHeaderShim(handshakeHeaders);\n }\n\n // zero-client and replicache reference a build-time `TESTING` global that\n // bundlers replace with a boolean literal; under tsx there's no replacement,\n // so provide a runtime default.\n (globalThis as {TESTING?: boolean}).TESTING ??= false;\n\n const z = new Zero({\n schema: opts.schema,\n server: config.zeroCacheURL,\n auth: config.authToken,\n userID: config.userId ?? 'analyze-cli',\n kvStore: 'mem',\n logLevel: config.log.level,\n logSink: stderrLogSink,\n });\n\n let result: AnalyzeQueryResult;\n try {\n const authOk = await z.inspector.authenticate(config.adminPassword ?? '');\n if (!authOk) {\n throw new Error(\n 'admin password rejected (or --admin-password is required)',\n );\n }\n\n const rpcOptions = {\n vendedRows: config.outputVendedRows,\n syncedRows: config.outputSyncedRows,\n };\n\n if (plan.kind === 'ast') {\n result = await z.inspector.analyzeServerAST(plan.ast, rpcOptions);\n } else if (plan.kind === 'named') {\n result = await z.inspector.analyzeNamedQuery(\n plan.name,\n plan.args as ReadonlyArray<never>,\n rpcOptions,\n );\n } else {\n const built = buildZqlQuery(plan.text, createBuilder(opts.schema));\n result = await z.inspector.analyzeQuery(built, rpcOptions);\n }\n } catch (e) {\n colorConsole.error(e instanceof Error ? e.message : String(e));\n await z.close().catch(() => {});\n process.exit(1);\n }\n\n renderResult(result, {\n outputSyncedRows: config.outputSyncedRows,\n outputVendedRows: config.outputVendedRows,\n });\n\n await z.close();\n}\n\nfunction buildQueryPlan(config: {\n ast?: string | undefined;\n query?: string | undefined;\n queryName?: string | undefined;\n queryArgs?: string | undefined;\n}): QueryPlan {\n const selectors = [\n config.ast !== undefined && 'ast',\n config.query !== undefined && 'query',\n config.queryName !== undefined && 'queryName',\n ].filter(Boolean) as string[];\n\n if (selectors.length === 0) {\n colorConsole.error(\n 'Exactly one of --ast / --query / --query-name is required.',\n );\n process.exit(1);\n }\n if (selectors.length > 1) {\n colorConsole.error(\n `Only one of --ast / --query / --query-name may be provided; got: ${selectors.join(', ')}`,\n );\n process.exit(1);\n }\n\n if (config.ast !== undefined) {\n return {kind: 'ast', ast: JSON.parse(config.ast) as AST};\n }\n if (config.query !== undefined) {\n return {kind: 'zql', text: config.query};\n }\n const args = config.queryArgs\n ? (JSON.parse(config.queryArgs) as ReadonlyArray<unknown>)\n : [];\n return {kind: 'named', name: config.queryName as string, args};\n}\n\nfunction buildZqlQuery(\n queryString: string,\n builder: SchemaQuery<Schema>,\n): AnyQuery {\n const f = new Function('builder', `return builder.${queryString};`);\n return f(builder) as AnyQuery;\n}\n\nfunction resolveHandshakeHeaders(config: {\n cookie?: string | undefined;\n headersJson?: string | undefined;\n}): Record<string, string> {\n const headers: Record<string, string> = {};\n if (config.headersJson !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(config.headersJson);\n } catch (e) {\n colorConsole.error(\n `--headers-json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,\n );\n process.exit(1);\n }\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n colorConsole.error('--headers-json must be a JSON object.');\n process.exit(1);\n }\n for (const [k, val] of Object.entries(parsed)) {\n if (typeof val !== 'string') {\n colorConsole.error(\n `--headers-json values must be strings; got ${typeof val} for \"${k}\".`,\n );\n process.exit(1);\n }\n headers[k] = val;\n }\n }\n if (config.cookie !== undefined) {\n headers.cookie = config.cookie;\n }\n return headers;\n}\n\nfunction installWebSocketHeaderShim(headers: Record<string, string>): void {\n class HeaderInjectingWebSocket extends NodeWebSocket {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols, {headers});\n }\n }\n (globalThis as {WebSocket?: unknown}).WebSocket = HeaderInjectingWebSocket;\n}\n\nfunction renderResult(\n result: AnalyzeQueryResult,\n opts: {outputSyncedRows: boolean; outputVendedRows: boolean},\n) {\n if (opts.outputSyncedRows) {\n colorConsole.log(styleText(['blue', 'bold'], '=== Synced Rows: ===\\n'));\n for (const [table, rows] of Object.entries(result.syncedRows ?? {})) {\n colorConsole.log(styleText('bold', table + ':'), rows);\n }\n }\n\n colorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\n colorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n );\n\n const readRowCountsByQuery = result.readRowCountsByQuery ?? {};\n let totalRowsRead = 0;\n for (const table of Object.keys(readRowCountsByQuery).sort()) {\n const counts = readRowCountsByQuery[table];\n for (const n of Object.values(counts)) {\n totalRowsRead += n;\n }\n colorConsole.log(styleText('bold', `${table} vended:`), counts);\n }\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsRead),\n );\n const duration = result.elapsed ?? result.end - result.start;\n colorConsole.log(styleText('bold', 'time:'), colorTime(duration), 'ms');\n\n if (opts.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const [table, rows] of Object.entries(result.readRows ?? {})) {\n colorConsole.log(styleText('bold', `${table}:`), rows);\n }\n }\n\n colorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n );\n const dbScansByQuery = result.dbScansByQuery ?? {};\n let totalNVisit = 0;\n for (const [table, queries] of Object.entries(dbScansByQuery)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n }\n colorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n );\n\n colorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\n const plans = result.sqlitePlans ?? {};\n for (const [query, plan] of Object.entries(plans)) {\n colorConsole.log(styleText('bold', 'query'), query);\n colorConsole.log(plan.map((row, i) => colorPlanRow(row, i)).join('\\n'));\n colorConsole.log('\\n');\n }\n\n if (result.warnings.length > 0) {\n colorConsole.log(styleText(['yellow', 'bold'], '=== Warnings: ===\\n'));\n for (const w of result.warnings) {\n colorConsole.log(styleText('yellow', w));\n }\n }\n}\n\nfunction colorTime(duration: number) {\n if (duration < 100) {\n return styleText('green', duration.toFixed(2) + 'ms');\n } else if (duration < 1000) {\n return styleText('yellow', duration.toFixed(2) + 'ms');\n }\n return styleText('red', duration.toFixed(2) + 'ms');\n}\n\nfunction colorRowsConsidered(n: number) {\n if (n < 1000) {\n return styleText('green', n.toString());\n } else if (n < 10000) {\n return styleText('yellow', n.toString());\n }\n return styleText('red', n.toString());\n}\n\nfunction colorPlanRow(row: string, i: number) {\n if (row.includes('SCAN')) {\n if (i === 0) {\n return styleText('yellow', row);\n }\n return styleText('red', row);\n }\n return styleText('green', row);\n}\n"],"mappings":"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"}
@@ -90,6 +90,7 @@ declare abstract class NodeImpl<Value> {
90
90
  readonly isMutable: boolean;
91
91
  constructor(entries: Array<Entry<Value>>, hash: Hash, isMutable: boolean);
92
92
  abstract set(key: string, value: FrozenJSONValue, entrySize: number, tree: BTreeWrite): Promise<NodeImpl<Value>>;
93
+ abstract putMany(entries: ReadonlyArray<Entry<FrozenJSONValue>>, tree: BTreeWrite): Promise<NodeImpl<Value>>;
93
94
  abstract del(key: string, tree: BTreeWrite): Promise<NodeImpl<Value> | DataNodeImpl>;
94
95
  maxKey(): string;
95
96
  getChildNodeSize(tree: BTreeRead): number;
@@ -100,6 +101,7 @@ export declare class DataNodeImpl extends NodeImpl<FrozenJSONValue> {
100
101
  #private;
101
102
  readonly level = 0;
102
103
  set(key: string, value: FrozenJSONValue, entrySize: number, tree: BTreeWrite): Promise<DataNodeImpl>;
104
+ putMany(entries: ReadonlyArray<Entry<FrozenJSONValue>>, tree: BTreeWrite): Promise<DataNodeImpl>;
103
105
  del(key: string, tree: BTreeWrite): Promise<DataNodeImpl>;
104
106
  keys(_tree: BTreeRead): AsyncGenerator<string, void>;
105
107
  entriesIter(_tree: BTreeRead): AsyncGenerator<Entry<FrozenJSONValue>, void>;
@@ -109,6 +111,7 @@ export declare class InternalNodeImpl extends NodeImpl<Hash> {
109
111
  readonly level: number;
110
112
  constructor(entries: Array<Entry<Hash>>, hash: Hash, level: number, isMutable: boolean);
111
113
  set(key: string, value: FrozenJSONValue, entrySize: number, tree: BTreeWrite): Promise<InternalNodeImpl>;
114
+ putMany(entries: ReadonlyArray<Entry<FrozenJSONValue>>, tree: BTreeWrite): Promise<InternalNodeImpl>;
112
115
  del(key: string, tree: BTreeWrite): Promise<InternalNodeImpl | DataNodeImpl>;
113
116
  keys(tree: BTreeRead): AsyncGenerator<string, void>;
114
117
  entriesIter(tree: BTreeRead): AsyncGenerator<Entry<FrozenJSONValue>, void>;
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/btree/node.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/btree/node.ts"],"names":[],"mappings":"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"}
@@ -34,7 +34,13 @@ async function findLeaf(key, hash, source, expectedRootHash) {
34
34
  * be inserted at
35
35
  */
36
36
  function binarySearch(key, entries) {
37
- return binarySearch$1(entries.length, (i) => compareUTF8(key, entries[i][0]));
37
+ return binarySearchFrom(key, entries, 0);
38
+ }
39
+ /**
40
+ * Binary search starting from a given index.
41
+ */
42
+ function binarySearchFrom(key, entries, start) {
43
+ return start + binarySearch$1(entries.length - start, (i) => compareUTF8(key, entries[start + i][0]));
38
44
  }
39
45
  function binarySearchFound(i, entries, key) {
40
46
  return i !== entries.length && entries[i][0] === key;
@@ -117,6 +123,42 @@ var DataNodeImpl = class extends NodeImpl {
117
123
  entrySize
118
124
  ]));
119
125
  }
126
+ putMany(entries, tree) {
127
+ if (entries.length === 0) return Promise.resolve(this);
128
+ const merged = [];
129
+ let i = 0;
130
+ let j = 0;
131
+ while (i < this.entries.length && j < entries.length) {
132
+ const existingEntry = this.entries[i];
133
+ const newEntry = entries[j];
134
+ const cmp = compareUTF8(existingEntry[0], newEntry[0]);
135
+ if (cmp < 0) {
136
+ merged.push(existingEntry);
137
+ i++;
138
+ } else if (cmp > 0) {
139
+ merged.push(newEntry);
140
+ j++;
141
+ } else {
142
+ merged.push(newEntry);
143
+ i++;
144
+ j++;
145
+ }
146
+ }
147
+ while (i < this.entries.length) {
148
+ merged.push(this.entries[i]);
149
+ i++;
150
+ }
151
+ while (j < entries.length) {
152
+ merged.push(entries[j]);
153
+ j++;
154
+ }
155
+ if (this.isMutable) {
156
+ this.entries = merged;
157
+ this._updateNode(tree);
158
+ return Promise.resolve(this);
159
+ }
160
+ return Promise.resolve(tree.newDataNodeImpl(merged));
161
+ }
120
162
  #splice(tree, start, deleteCount, ...items) {
121
163
  if (this.isMutable) {
122
164
  this.entries.splice(start, deleteCount, ...items);
@@ -160,6 +202,77 @@ var InternalNodeImpl = class InternalNodeImpl extends NodeImpl {
160
202
  const newEntry = createNewInternalEntryForNode(childNode, tree.getEntrySize);
161
203
  return this.#replaceChild(tree, i, newEntry);
162
204
  }
205
+ async putMany(entries, tree) {
206
+ if (entries.length === 0) return this;
207
+ const childGroups = /* @__PURE__ */ new Map();
208
+ let searchStart = 0;
209
+ for (const entry of entries) {
210
+ const key = entry[0];
211
+ let i = binarySearchFrom(key, this.entries, searchStart);
212
+ if (i === this.entries.length) i--;
213
+ searchStart = i;
214
+ let group = childGroups.get(i);
215
+ if (!group) {
216
+ group = [];
217
+ childGroups.set(i, group);
218
+ }
219
+ group.push(entry);
220
+ }
221
+ const newEntries = [...this.entries];
222
+ const childrenToRebalance = [];
223
+ for (const [childIndex, childEntries] of childGroups) {
224
+ const childHash = this.entries[childIndex][1];
225
+ const childNode = await (await tree.getNode(childHash)).putMany(childEntries, tree);
226
+ const childNodeSize = childNode.getChildNodeSize(tree);
227
+ if (childNodeSize > tree.maxSize || childNodeSize < tree.minSize) childrenToRebalance.push({
228
+ index: childIndex,
229
+ node: childNode
230
+ });
231
+ else newEntries[childIndex] = createNewInternalEntryForNode(childNode, tree.getEntrySize);
232
+ }
233
+ if (childrenToRebalance.length > 0) {
234
+ childrenToRebalance.sort((a, b) => a.index - b.index);
235
+ const groups = [];
236
+ for (const { index, node } of childrenToRebalance) {
237
+ const lastGroup = groups.at(-1);
238
+ if (lastGroup && index <= lastGroup.maxIndex + 1) {
239
+ lastGroup.nodes.set(index, node);
240
+ lastGroup.maxIndex = index;
241
+ } else groups.push({
242
+ nodes: new Map([[index, node]]),
243
+ minIndex: index,
244
+ maxIndex: index
245
+ });
246
+ }
247
+ for (let g = groups.length - 1; g >= 0; g--) {
248
+ const group = groups[g];
249
+ const startIndex = Math.max(0, group.minIndex - 1);
250
+ const endIndex = Math.min(newEntries.length - 1, group.maxIndex + 1);
251
+ const removeCount = endIndex - startIndex + 1;
252
+ const allValues = [];
253
+ for (let idx = startIndex; idx <= endIndex; idx++) {
254
+ const rebalancedNode = group.nodes.get(idx);
255
+ if (rebalancedNode) allValues.push(rebalancedNode.entries);
256
+ else {
257
+ const hash = newEntries[idx][1];
258
+ const existingNode = await tree.getNode(hash);
259
+ allValues.push(existingNode.entries);
260
+ }
261
+ }
262
+ const level = this.level - 1;
263
+ const rebalanced = partition(joinIterables(...allValues), (e) => e[2], tree.minSize - tree.chunkHeaderSize, tree.maxSize - tree.chunkHeaderSize, (entries) => {
264
+ return createNewInternalEntryForNode(tree.newNodeImpl(entries, level), tree.getEntrySize);
265
+ });
266
+ newEntries.splice(startIndex, removeCount, ...rebalanced);
267
+ }
268
+ }
269
+ if (this.isMutable) {
270
+ this.entries = newEntries;
271
+ this._updateNode(tree);
272
+ return this;
273
+ }
274
+ return tree.newInternalNodeImpl(newEntries, this.level);
275
+ }
163
276
  /**
164
277
  * This merges the child node entries with previous or next sibling and then
165
278
  * partitions the merged entries.
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","names":["#childNodeSize","#splice","#mergeAndPartition","#replaceChild"],"sources":["../../../../../replicache/src/btree/node.ts"],"sourcesContent":["import {compareUTF8} from 'compare-utf8';\nimport {\n assert,\n assertArray,\n assertNumber,\n assertString,\n} from '../../../shared/src/asserts.ts';\nimport {binarySearch as binarySearchWithFunc} from '../../../shared/src/binary-search.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {joinIterables} from '../../../shared/src/iterables.ts';\nimport {\n type JSONValue,\n type ReadonlyJSONValue,\n assertJSONValue,\n} from '../../../shared/src/json.ts';\nimport {skipBTreeNodeAsserts} from '../config.ts';\nimport type {IndexKey} from '../db/index.ts';\nimport * as FormatVersion from '../format-version-enum.ts';\nimport {\n type FrozenJSONValue,\n type FrozenTag,\n assertDeepFrozen,\n deepFreeze,\n} from '../frozen-json.ts';\nimport {type Hash, emptyHash, newRandomHash} from '../hash.ts';\nimport type {BTreeRead} from './read.ts';\nimport type {BTreeWrite} from './write.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nexport type Entry<V> = readonly [key: string, value: V, sizeOfEntry: number];\n\nexport const NODE_LEVEL = 0;\nexport const NODE_ENTRIES = 1;\n\n/**\n * The type of B+Tree node chunk data\n */\ntype BaseNode<V> = FrozenTag<\n readonly [level: number, entries: ReadonlyArray<Entry<V>>]\n>;\nexport type InternalNode = BaseNode<Hash>;\n\nexport type DataNode = BaseNode<FrozenJSONValue>;\n\nexport function makeNodeChunkData<V>(\n level: number,\n entries: ReadonlyArray<Entry<V>>,\n formatVersion: FormatVersion,\n): BaseNode<V> {\n return deepFreeze([\n level,\n (formatVersion >= FormatVersion.V7\n ? entries\n : entries.map(e => e.slice(0, 2))) as readonly ReadonlyJSONValue[],\n ]) as BaseNode<V>;\n}\n\nexport type Node = DataNode | InternalNode;\n\n/**\n * Describes the changes that happened to Replicache after a\n * {@link WriteTransaction} was committed.\n *\n * @experimental This type is experimental and may change in the future.\n */\nexport type Diff = IndexDiff | NoIndexDiff;\n\n/**\n * @experimental This type is experimental and may change in the future.\n */\nexport type IndexDiff = readonly DiffOperation<IndexKey>[];\n\n/**\n * @experimental This type is experimental and may change in the future.\n */\nexport type NoIndexDiff = readonly DiffOperation<string>[];\n\n/**\n * InternalDiff uses string keys even for the secondary index maps.\n */\nexport type InternalDiff = readonly InternalDiffOperation[];\n\nexport type DiffOperationAdd<Key, Value = ReadonlyJSONValue> = {\n readonly op: 'add';\n readonly key: Key;\n readonly newValue: Value;\n};\n\nexport type DiffOperationDel<Key, Value = ReadonlyJSONValue> = {\n readonly op: 'del';\n readonly key: Key;\n readonly oldValue: Value;\n};\n\nexport type DiffOperationChange<Key, Value = ReadonlyJSONValue> = {\n readonly op: 'change';\n readonly key: Key;\n readonly oldValue: Value;\n readonly newValue: Value;\n};\n\n/**\n * The individual parts describing the changes that happened to the Replicache\n * data. There are three different kinds of operations:\n * - `add`: A new entry was added.\n * - `del`: An entry was deleted.\n * - `change`: An entry was changed.\n *\n * @experimental This type is experimental and may change in the future.\n */\nexport type DiffOperation<Key> =\n | DiffOperationAdd<Key>\n | DiffOperationDel<Key>\n | DiffOperationChange<Key>;\n\n// Duplicated with DiffOperation to make the docs less confusing.\nexport type InternalDiffOperation<Key = string, Value = FrozenJSONValue> =\n | DiffOperationAdd<Key, Value>\n | DiffOperationDel<Key, Value>\n | DiffOperationChange<Key, Value>;\n\n/**\n * Finds the leaf where a key is (if present) or where it should go if not\n * present.\n */\nexport async function findLeaf(\n key: string,\n hash: Hash,\n source: BTreeRead,\n expectedRootHash: Hash,\n): Promise<DataNodeImpl> {\n const node = await source.getNode(hash);\n // The root changed. Try again\n if (expectedRootHash !== source.rootHash) {\n return findLeaf(key, source.rootHash, source, source.rootHash);\n }\n if (isDataNodeImpl(node)) {\n return node;\n }\n const {entries} = node;\n let i = binarySearch(key, entries);\n if (i === entries.length) {\n i--;\n }\n const entry = entries[i];\n return findLeaf(key, entry[1], source, expectedRootHash);\n}\n\ntype BinarySearchEntries = readonly Entry<unknown>[];\n\n/**\n * Does a binary search over entries\n *\n * If the key found then the return value is the index it was found at.\n *\n * If the key was *not* found then the return value is the index where it should\n * be inserted at\n */\nexport function binarySearch(\n key: string,\n entries: BinarySearchEntries,\n): number {\n return binarySearchWithFunc(entries.length, i =>\n compareUTF8(key, entries[i][0]),\n );\n}\n\nexport function binarySearchFound(\n i: number,\n entries: BinarySearchEntries,\n key: string,\n): boolean {\n return i !== entries.length && entries[i][0] === key;\n}\n\nexport function parseBTreeNode(\n v: unknown,\n formatVersion: FormatVersion,\n getSizeOfEntry: <K, V>(key: K, value: V) => number,\n): InternalNode | DataNode {\n if (skipBTreeNodeAsserts && formatVersion >= FormatVersion.V7) {\n return v as InternalNode | DataNode;\n }\n\n assertArray(v);\n assertDeepFrozen(v);\n // Be relaxed about what we accept.\n assert(v.length >= 2, 'Expected node array to have at least 2 elements');\n const [level, entries] = v;\n assertNumber(level);\n assertArray(entries);\n\n const f = level > 0 ? assertString : assertJSONValue;\n\n // For V7 we do not need to change the entries. Just assert that they are correct.\n if (formatVersion >= FormatVersion.V7) {\n for (const e of entries) {\n assertEntry(e, f);\n }\n return v as unknown as InternalNode | DataNode;\n }\n\n const newEntries = entries.map(e => convertNonV7Entry(e, f, getSizeOfEntry));\n return [level, newEntries] as unknown as InternalNode | DataNode;\n}\n\nfunction assertEntry(\n entry: unknown,\n f:\n | ((v: unknown) => asserts v is Hash)\n | ((v: unknown) => asserts v is JSONValue),\n): asserts entry is Entry<Hash | JSONValue> {\n assertArray(entry);\n // Be relaxed about what we accept.\n assert(entry.length >= 3, 'Expected entry array to have at least 3 elements');\n assertString(entry[0]);\n f(entry[1]);\n assertNumber(entry[2]);\n}\n\n/**\n * Converts an entry that was from a format version before V7 to the format\n * wanted by V7.\n */\nfunction convertNonV7Entry(\n entry: unknown,\n f:\n | ((v: unknown) => asserts v is Hash)\n | ((v: unknown) => asserts v is JSONValue),\n getSizeOfEntry: <K, V>(key: K, value: V) => number,\n): Entry<Hash | JSONValue> {\n assertArray(entry);\n assert(entry.length >= 2, 'Expected entry array to have at least 2 elements');\n assertString(entry[0]);\n f(entry[1]);\n const entrySize = getSizeOfEntry(entry[0], entry[1]);\n return [entry[0], entry[1], entrySize] as Entry<Hash | JSONValue>;\n}\n\nexport function isInternalNode(node: Node): node is InternalNode {\n return node[NODE_LEVEL] > 0;\n}\n\nabstract class NodeImpl<Value> {\n entries: Array<Entry<Value>>;\n hash: Hash;\n abstract readonly level: number;\n readonly isMutable: boolean;\n\n #childNodeSize = -1;\n\n constructor(entries: Array<Entry<Value>>, hash: Hash, isMutable: boolean) {\n this.entries = entries;\n this.hash = hash;\n this.isMutable = isMutable;\n }\n\n abstract set(\n key: string,\n value: FrozenJSONValue,\n entrySize: number,\n tree: BTreeWrite,\n ): Promise<NodeImpl<Value>>;\n\n abstract del(\n key: string,\n tree: BTreeWrite,\n ): Promise<NodeImpl<Value> | DataNodeImpl>;\n\n maxKey(): string {\n // oxlint-disable-next-line typescript/no-non-null-assertion\n return this.entries.at(-1)![0];\n }\n\n getChildNodeSize(tree: BTreeRead): number {\n if (this.#childNodeSize !== -1) {\n return this.#childNodeSize;\n }\n\n let sum = tree.chunkHeaderSize;\n for (const entry of this.entries) {\n sum += entry[2];\n }\n return (this.#childNodeSize = sum);\n }\n\n protected _updateNode(tree: BTreeWrite) {\n this.#childNodeSize = -1;\n tree.updateNode(\n this as NodeImpl<unknown> as DataNodeImpl | InternalNodeImpl,\n );\n }\n}\n\nexport function toChunkData<V>(\n node: NodeImpl<V>,\n formatVersion: FormatVersion,\n): BaseNode<V> {\n return makeNodeChunkData(node.level, node.entries, formatVersion);\n}\n\nexport class DataNodeImpl extends NodeImpl<FrozenJSONValue> {\n readonly level = 0;\n\n set(\n key: string,\n value: FrozenJSONValue,\n entrySize: number,\n tree: BTreeWrite,\n ): Promise<DataNodeImpl> {\n let deleteCount: number;\n const i = binarySearch(key, this.entries);\n if (!binarySearchFound(i, this.entries, key)) {\n // Not found, insert.\n deleteCount = 0;\n } else {\n deleteCount = 1;\n }\n\n return Promise.resolve(\n this.#splice(tree, i, deleteCount, [key, value, entrySize]),\n );\n }\n\n #splice(\n tree: BTreeWrite,\n start: number,\n deleteCount: number,\n ...items: Entry<FrozenJSONValue>[]\n ): DataNodeImpl {\n if (this.isMutable) {\n this.entries.splice(start, deleteCount, ...items);\n this._updateNode(tree);\n return this;\n }\n\n const entries = readonlySplice(this.entries, start, deleteCount, ...items);\n return tree.newDataNodeImpl(entries);\n }\n\n del(key: string, tree: BTreeWrite): Promise<DataNodeImpl> {\n const i = binarySearch(key, this.entries);\n if (!binarySearchFound(i, this.entries, key)) {\n // Not found. Return this without changes.\n return Promise.resolve(this);\n }\n\n // Found. Create new node or mutate existing one.\n return Promise.resolve(this.#splice(tree, i, 1));\n }\n\n async *keys(_tree: BTreeRead): AsyncGenerator<string, void> {\n for (const entry of this.entries) {\n yield entry[0];\n }\n }\n\n async *entriesIter(\n _tree: BTreeRead,\n ): AsyncGenerator<Entry<FrozenJSONValue>, void> {\n for (const entry of this.entries) {\n yield entry;\n }\n }\n}\n\nfunction readonlySplice<T>(\n array: ReadonlyArray<T>,\n start: number,\n deleteCount: number,\n ...items: T[]\n): T[] {\n const arr = array.slice(0, start);\n for (let i = 0; i < items.length; i++) {\n arr.push(items[i]);\n }\n for (let i = start + deleteCount; i < array.length; i++) {\n arr.push(array[i]);\n }\n return arr;\n}\n\nexport class InternalNodeImpl extends NodeImpl<Hash> {\n readonly level: number;\n\n constructor(\n entries: Array<Entry<Hash>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n ) {\n super(entries, hash, isMutable);\n this.level = level;\n }\n\n async set(\n key: string,\n value: FrozenJSONValue,\n entrySize: number,\n tree: BTreeWrite,\n ): Promise<InternalNodeImpl> {\n let i = binarySearch(key, this.entries);\n if (i === this.entries.length) {\n // We are going to insert into last (right most) leaf.\n i--;\n }\n\n const childHash = this.entries[i][1];\n const oldChildNode = await tree.getNode(childHash);\n\n const childNode = await oldChildNode.set(key, value, entrySize, tree);\n\n const childNodeSize = childNode.getChildNodeSize(tree);\n if (childNodeSize > tree.maxSize || childNodeSize < tree.minSize) {\n return this.#mergeAndPartition(tree, i, childNode);\n }\n\n const newEntry = createNewInternalEntryForNode(\n childNode,\n tree.getEntrySize,\n );\n return this.#replaceChild(tree, i, newEntry);\n }\n\n /**\n * This merges the child node entries with previous or next sibling and then\n * partitions the merged entries.\n */\n async #mergeAndPartition(\n tree: BTreeWrite,\n i: number,\n childNode: DataNodeImpl | InternalNodeImpl,\n ): Promise<InternalNodeImpl> {\n const level = this.level - 1;\n const thisEntries = this.entries;\n\n type IterableHashEntries = Iterable<Entry<Hash>>;\n\n let values: IterableHashEntries;\n let startIndex: number;\n let removeCount: number;\n if (i > 0) {\n const hash = thisEntries[i - 1][1];\n const previousSibling = await tree.getNode(hash);\n values = joinIterables(\n previousSibling.entries as IterableHashEntries,\n childNode.entries as IterableHashEntries,\n );\n startIndex = i - 1;\n removeCount = 2;\n } else if (i < thisEntries.length - 1) {\n const hash = thisEntries[i + 1][1];\n const nextSibling = await tree.getNode(hash);\n values = joinIterables(\n childNode.entries as IterableHashEntries,\n nextSibling.entries as IterableHashEntries,\n );\n startIndex = i;\n removeCount = 2;\n } else {\n values = childNode.entries as IterableHashEntries;\n startIndex = i;\n removeCount = 1;\n }\n\n const newEntries = partition(\n values,\n value => value[2],\n tree.minSize - tree.chunkHeaderSize,\n tree.maxSize - tree.chunkHeaderSize,\n entries => {\n const node = tree.newNodeImpl(entries, level);\n return createNewInternalEntryForNode(node, tree.getEntrySize);\n },\n );\n\n if (this.isMutable) {\n this.entries.splice(startIndex, removeCount, ...newEntries);\n this._updateNode(tree);\n return this;\n }\n\n const entries = readonlySplice(\n thisEntries,\n startIndex,\n removeCount,\n ...newEntries,\n );\n\n return tree.newInternalNodeImpl(entries, this.level);\n }\n\n #replaceChild(\n tree: BTreeWrite,\n index: number,\n newEntry: Entry<Hash>,\n ): InternalNodeImpl {\n if (this.isMutable) {\n this.entries.splice(index, 1, newEntry);\n this._updateNode(tree);\n return this;\n }\n const entries = readonlySplice(this.entries, index, 1, newEntry);\n return tree.newInternalNodeImpl(entries, this.level);\n }\n\n async del(\n key: string,\n tree: BTreeWrite,\n ): Promise<InternalNodeImpl | DataNodeImpl> {\n const i = binarySearch(key, this.entries);\n if (i === this.entries.length) {\n // Key is larger than maxKey of rightmost entry so it is not present.\n return this;\n }\n\n const childHash = this.entries[i][1];\n const oldChildNode = await tree.getNode(childHash);\n const oldHash = oldChildNode.hash;\n\n const childNode = await oldChildNode.del(key, tree);\n if (childNode.hash === oldHash) {\n // Not changed so not found.\n return this;\n }\n\n if (childNode.entries.length === 0) {\n // Subtree is now empty. Remove internal node.\n const entries = readonlySplice(this.entries, i, 1);\n return tree.newInternalNodeImpl(entries, this.level);\n }\n\n if (i === 0 && this.entries.length === 1) {\n // There was only one node at this level and it was removed. We can return\n // the modified subtree.\n return childNode;\n }\n\n // The child node is still a good size.\n if (childNode.getChildNodeSize(tree) > tree.minSize) {\n // No merging needed.\n const entry = createNewInternalEntryForNode(childNode, tree.getEntrySize);\n return this.#replaceChild(tree, i, entry);\n }\n\n // Child node size is too small.\n return this.#mergeAndPartition(tree, i, childNode);\n }\n\n async *keys(tree: BTreeRead): AsyncGenerator<string, void> {\n for (const entry of this.entries) {\n const childNode = await tree.getNode(entry[1]);\n yield* childNode.keys(tree);\n }\n }\n\n async *entriesIter(\n tree: BTreeRead,\n ): AsyncGenerator<Entry<FrozenJSONValue>, void> {\n for (const entry of this.entries) {\n const childNode = await tree.getNode(entry[1]);\n yield* childNode.entriesIter(tree);\n }\n }\n\n getChildren(\n start: number,\n length: number,\n tree: BTreeRead,\n ): Promise<Array<InternalNodeImpl | DataNodeImpl>> {\n const ps: Promise<DataNodeImpl | InternalNodeImpl>[] = [];\n for (let i = start; i < length && i < this.entries.length; i++) {\n ps.push(tree.getNode(this.entries[i][1]));\n }\n return Promise.all(ps);\n }\n\n async getCompositeChildren(\n start: number,\n length: number,\n tree: BTreeRead,\n ): Promise<InternalNodeImpl | DataNodeImpl> {\n const {level} = this;\n\n if (length === 0) {\n return new InternalNodeImpl([], newRandomHash(), level - 1, true);\n }\n\n const output = await this.getChildren(start, start + length, tree);\n\n if (level > 1) {\n const entries: Entry<Hash>[] = [];\n for (const child of output as InternalNodeImpl[]) {\n entries.push(...child.entries);\n }\n return new InternalNodeImpl(entries, newRandomHash(), level - 1, true);\n }\n\n assert(level === 1, 'Expected level to be 1');\n const entries: Entry<FrozenJSONValue>[] = [];\n for (const child of output as DataNodeImpl[]) {\n entries.push(...child.entries);\n }\n return new DataNodeImpl(entries, newRandomHash(), true);\n }\n}\n\nexport function newNodeImpl(\n entries: Array<Entry<FrozenJSONValue>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n): DataNodeImpl;\nexport function newNodeImpl(\n entries: Array<Entry<Hash>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n): InternalNodeImpl;\nexport function newNodeImpl(\n entries: Array<Entry<FrozenJSONValue>> | Array<Entry<Hash>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n): DataNodeImpl | InternalNodeImpl;\nexport function newNodeImpl(\n entries: Array<Entry<FrozenJSONValue>> | Array<Entry<Hash>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n): DataNodeImpl | InternalNodeImpl {\n if (level === 0) {\n return new DataNodeImpl(\n entries as Entry<FrozenJSONValue>[],\n hash,\n isMutable,\n );\n }\n return new InternalNodeImpl(entries as Entry<Hash>[], hash, level, isMutable);\n}\n\nexport function isDataNodeImpl(\n node: DataNodeImpl | InternalNodeImpl,\n): node is DataNodeImpl {\n return node.level === 0;\n}\n\nexport function partition<T, R>(\n values: Iterable<T>,\n // This is the size of each Entry\n getSizeOfEntry: (v: T) => number,\n min: number,\n max: number,\n create: (entries: T[]) => R,\n): R[] {\n const results: R[] = [];\n let sum = 0;\n let accum: T[] = [];\n // The most recently finalized partition, held back until we know it won't be\n // merged with trailing entries.\n let lastPartition: T[] | null = null;\n let lastSize = 0;\n\n function commitLast() {\n if (lastPartition !== null) {\n results.push(create(lastPartition));\n lastPartition = null;\n }\n }\n\n for (const value of values) {\n const size = getSizeOfEntry(value);\n if (size >= max) {\n if (accum.length > 0) {\n commitLast();\n lastPartition = accum;\n lastSize = sum;\n accum = [];\n sum = 0;\n }\n commitLast();\n lastPartition = [value];\n lastSize = size;\n } else if (sum + size >= min) {\n accum.push(value);\n commitLast();\n lastPartition = accum;\n lastSize = sum + size;\n accum = [];\n sum = 0;\n } else {\n sum += size;\n accum.push(value);\n }\n }\n\n if (sum > 0) {\n if (lastPartition !== null && sum + lastSize <= max) {\n lastPartition.push(...accum);\n commitLast();\n } else {\n commitLast();\n results.push(create(accum));\n }\n } else {\n commitLast();\n }\n\n return results;\n}\n\nexport const emptyDataNode = makeNodeChunkData<ReadonlyJSONValue>(\n 0,\n [],\n FormatVersion.Latest,\n);\nexport const emptyDataNodeImpl = new DataNodeImpl([], emptyHash, false);\n\nexport function createNewInternalEntryForNode(\n node: NodeImpl<unknown>,\n getSizeOfEntry: <K, V>(k: K, v: V) => number,\n): [string, Hash, number] {\n const key = node.maxKey();\n const value = node.hash;\n const size = getSizeOfEntry(key, value);\n return [key, value, size];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"node.js","names":["#childNodeSize","#splice","#mergeAndPartition","#replaceChild"],"sources":["../../../../../replicache/src/btree/node.ts"],"sourcesContent":["import {compareUTF8} from 'compare-utf8';\nimport {\n assert,\n assertArray,\n assertNumber,\n assertString,\n} from '../../../shared/src/asserts.ts';\nimport {binarySearch as binarySearchWithFunc} from '../../../shared/src/binary-search.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport {joinIterables} from '../../../shared/src/iterables.ts';\nimport {\n type JSONValue,\n type ReadonlyJSONValue,\n assertJSONValue,\n} from '../../../shared/src/json.ts';\nimport {skipBTreeNodeAsserts} from '../config.ts';\nimport type {IndexKey} from '../db/index.ts';\nimport * as FormatVersion from '../format-version-enum.ts';\nimport {\n type FrozenJSONValue,\n type FrozenTag,\n assertDeepFrozen,\n deepFreeze,\n} from '../frozen-json.ts';\nimport {type Hash, emptyHash, newRandomHash} from '../hash.ts';\nimport type {BTreeRead} from './read.ts';\nimport type {BTreeWrite} from './write.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nexport type Entry<V> = readonly [key: string, value: V, sizeOfEntry: number];\n\nexport const NODE_LEVEL = 0;\nexport const NODE_ENTRIES = 1;\n\n/**\n * The type of B+Tree node chunk data\n */\ntype BaseNode<V> = FrozenTag<\n readonly [level: number, entries: ReadonlyArray<Entry<V>>]\n>;\nexport type InternalNode = BaseNode<Hash>;\n\nexport type DataNode = BaseNode<FrozenJSONValue>;\n\nexport function makeNodeChunkData<V>(\n level: number,\n entries: ReadonlyArray<Entry<V>>,\n formatVersion: FormatVersion,\n): BaseNode<V> {\n return deepFreeze([\n level,\n (formatVersion >= FormatVersion.V7\n ? entries\n : entries.map(e => e.slice(0, 2))) as readonly ReadonlyJSONValue[],\n ]) as BaseNode<V>;\n}\n\nexport type Node = DataNode | InternalNode;\n\n/**\n * Describes the changes that happened to Replicache after a\n * {@link WriteTransaction} was committed.\n *\n * @experimental This type is experimental and may change in the future.\n */\nexport type Diff = IndexDiff | NoIndexDiff;\n\n/**\n * @experimental This type is experimental and may change in the future.\n */\nexport type IndexDiff = readonly DiffOperation<IndexKey>[];\n\n/**\n * @experimental This type is experimental and may change in the future.\n */\nexport type NoIndexDiff = readonly DiffOperation<string>[];\n\n/**\n * InternalDiff uses string keys even for the secondary index maps.\n */\nexport type InternalDiff = readonly InternalDiffOperation[];\n\nexport type DiffOperationAdd<Key, Value = ReadonlyJSONValue> = {\n readonly op: 'add';\n readonly key: Key;\n readonly newValue: Value;\n};\n\nexport type DiffOperationDel<Key, Value = ReadonlyJSONValue> = {\n readonly op: 'del';\n readonly key: Key;\n readonly oldValue: Value;\n};\n\nexport type DiffOperationChange<Key, Value = ReadonlyJSONValue> = {\n readonly op: 'change';\n readonly key: Key;\n readonly oldValue: Value;\n readonly newValue: Value;\n};\n\n/**\n * The individual parts describing the changes that happened to the Replicache\n * data. There are three different kinds of operations:\n * - `add`: A new entry was added.\n * - `del`: An entry was deleted.\n * - `change`: An entry was changed.\n *\n * @experimental This type is experimental and may change in the future.\n */\nexport type DiffOperation<Key> =\n | DiffOperationAdd<Key>\n | DiffOperationDel<Key>\n | DiffOperationChange<Key>;\n\n// Duplicated with DiffOperation to make the docs less confusing.\nexport type InternalDiffOperation<Key = string, Value = FrozenJSONValue> =\n | DiffOperationAdd<Key, Value>\n | DiffOperationDel<Key, Value>\n | DiffOperationChange<Key, Value>;\n\n/**\n * Finds the leaf where a key is (if present) or where it should go if not\n * present.\n */\nexport async function findLeaf(\n key: string,\n hash: Hash,\n source: BTreeRead,\n expectedRootHash: Hash,\n): Promise<DataNodeImpl> {\n const node = await source.getNode(hash);\n // The root changed. Try again\n if (expectedRootHash !== source.rootHash) {\n return findLeaf(key, source.rootHash, source, source.rootHash);\n }\n if (isDataNodeImpl(node)) {\n return node;\n }\n const {entries} = node;\n let i = binarySearch(key, entries);\n if (i === entries.length) {\n i--;\n }\n const entry = entries[i];\n return findLeaf(key, entry[1], source, expectedRootHash);\n}\n\ntype BinarySearchEntries = readonly Entry<unknown>[];\n\n/**\n * Does a binary search over entries\n *\n * If the key found then the return value is the index it was found at.\n *\n * If the key was *not* found then the return value is the index where it should\n * be inserted at\n */\nexport function binarySearch(\n key: string,\n entries: BinarySearchEntries,\n): number {\n return binarySearchFrom(key, entries, 0);\n}\n\n/**\n * Binary search starting from a given index.\n */\nfunction binarySearchFrom(\n key: string,\n entries: BinarySearchEntries,\n start: number,\n): number {\n const result = binarySearchWithFunc(entries.length - start, i =>\n compareUTF8(key, entries[start + i][0]),\n );\n return start + result;\n}\n\nexport function binarySearchFound(\n i: number,\n entries: BinarySearchEntries,\n key: string,\n): boolean {\n return i !== entries.length && entries[i][0] === key;\n}\n\nexport function parseBTreeNode(\n v: unknown,\n formatVersion: FormatVersion,\n getSizeOfEntry: <K, V>(key: K, value: V) => number,\n): InternalNode | DataNode {\n if (skipBTreeNodeAsserts && formatVersion >= FormatVersion.V7) {\n return v as InternalNode | DataNode;\n }\n\n assertArray(v);\n assertDeepFrozen(v);\n // Be relaxed about what we accept.\n assert(v.length >= 2, 'Expected node array to have at least 2 elements');\n const [level, entries] = v;\n assertNumber(level);\n assertArray(entries);\n\n const f = level > 0 ? assertString : assertJSONValue;\n\n // For V7 we do not need to change the entries. Just assert that they are correct.\n if (formatVersion >= FormatVersion.V7) {\n for (const e of entries) {\n assertEntry(e, f);\n }\n return v as unknown as InternalNode | DataNode;\n }\n\n const newEntries = entries.map(e => convertNonV7Entry(e, f, getSizeOfEntry));\n return [level, newEntries] as unknown as InternalNode | DataNode;\n}\n\nfunction assertEntry(\n entry: unknown,\n f:\n | ((v: unknown) => asserts v is Hash)\n | ((v: unknown) => asserts v is JSONValue),\n): asserts entry is Entry<Hash | JSONValue> {\n assertArray(entry);\n // Be relaxed about what we accept.\n assert(entry.length >= 3, 'Expected entry array to have at least 3 elements');\n assertString(entry[0]);\n f(entry[1]);\n assertNumber(entry[2]);\n}\n\n/**\n * Converts an entry that was from a format version before V7 to the format\n * wanted by V7.\n */\nfunction convertNonV7Entry(\n entry: unknown,\n f:\n | ((v: unknown) => asserts v is Hash)\n | ((v: unknown) => asserts v is JSONValue),\n getSizeOfEntry: <K, V>(key: K, value: V) => number,\n): Entry<Hash | JSONValue> {\n assertArray(entry);\n assert(entry.length >= 2, 'Expected entry array to have at least 2 elements');\n assertString(entry[0]);\n f(entry[1]);\n const entrySize = getSizeOfEntry(entry[0], entry[1]);\n return [entry[0], entry[1], entrySize] as Entry<Hash | JSONValue>;\n}\n\nexport function isInternalNode(node: Node): node is InternalNode {\n return node[NODE_LEVEL] > 0;\n}\n\nabstract class NodeImpl<Value> {\n entries: Array<Entry<Value>>;\n hash: Hash;\n abstract readonly level: number;\n readonly isMutable: boolean;\n\n #childNodeSize = -1;\n\n constructor(entries: Array<Entry<Value>>, hash: Hash, isMutable: boolean) {\n this.entries = entries;\n this.hash = hash;\n this.isMutable = isMutable;\n }\n\n abstract set(\n key: string,\n value: FrozenJSONValue,\n entrySize: number,\n tree: BTreeWrite,\n ): Promise<NodeImpl<Value>>;\n\n abstract putMany(\n entries: ReadonlyArray<Entry<FrozenJSONValue>>,\n tree: BTreeWrite,\n ): Promise<NodeImpl<Value>>;\n\n abstract del(\n key: string,\n tree: BTreeWrite,\n ): Promise<NodeImpl<Value> | DataNodeImpl>;\n\n maxKey(): string {\n // oxlint-disable-next-line typescript/no-non-null-assertion\n return this.entries.at(-1)![0];\n }\n\n getChildNodeSize(tree: BTreeRead): number {\n if (this.#childNodeSize !== -1) {\n return this.#childNodeSize;\n }\n\n let sum = tree.chunkHeaderSize;\n for (const entry of this.entries) {\n sum += entry[2];\n }\n return (this.#childNodeSize = sum);\n }\n\n protected _updateNode(tree: BTreeWrite) {\n this.#childNodeSize = -1;\n tree.updateNode(\n this as NodeImpl<unknown> as DataNodeImpl | InternalNodeImpl,\n );\n }\n}\n\nexport function toChunkData<V>(\n node: NodeImpl<V>,\n formatVersion: FormatVersion,\n): BaseNode<V> {\n return makeNodeChunkData(node.level, node.entries, formatVersion);\n}\n\nexport class DataNodeImpl extends NodeImpl<FrozenJSONValue> {\n readonly level = 0;\n\n set(\n key: string,\n value: FrozenJSONValue,\n entrySize: number,\n tree: BTreeWrite,\n ): Promise<DataNodeImpl> {\n let deleteCount: number;\n const i = binarySearch(key, this.entries);\n if (!binarySearchFound(i, this.entries, key)) {\n // Not found, insert.\n deleteCount = 0;\n } else {\n deleteCount = 1;\n }\n\n return Promise.resolve(\n this.#splice(tree, i, deleteCount, [key, value, entrySize]),\n );\n }\n\n putMany(\n entries: ReadonlyArray<Entry<FrozenJSONValue>>,\n tree: BTreeWrite,\n ): Promise<DataNodeImpl> {\n if (entries.length === 0) {\n return Promise.resolve(this);\n }\n\n // Merge sorted entries with existing entries\n const merged: Entry<FrozenJSONValue>[] = [];\n let i = 0; // index in this.entries\n let j = 0; // index in entries\n\n while (i < this.entries.length && j < entries.length) {\n const existingEntry = this.entries[i];\n const newEntry = entries[j];\n const cmp = compareUTF8(existingEntry[0], newEntry[0]);\n\n if (cmp < 0) {\n merged.push(existingEntry);\n i++;\n } else if (cmp > 0) {\n merged.push(newEntry);\n j++;\n } else {\n // Same key, new entry wins (update)\n merged.push(newEntry);\n i++;\n j++;\n }\n }\n\n // Add remaining entries\n while (i < this.entries.length) {\n merged.push(this.entries[i]);\n i++;\n }\n while (j < entries.length) {\n merged.push(entries[j]);\n j++;\n }\n\n if (this.isMutable) {\n this.entries = merged;\n this._updateNode(tree);\n return Promise.resolve(this);\n }\n\n return Promise.resolve(tree.newDataNodeImpl(merged));\n }\n\n #splice(\n tree: BTreeWrite,\n start: number,\n deleteCount: number,\n ...items: Entry<FrozenJSONValue>[]\n ): DataNodeImpl {\n if (this.isMutable) {\n this.entries.splice(start, deleteCount, ...items);\n this._updateNode(tree);\n return this;\n }\n\n const entries = readonlySplice(this.entries, start, deleteCount, ...items);\n return tree.newDataNodeImpl(entries);\n }\n\n del(key: string, tree: BTreeWrite): Promise<DataNodeImpl> {\n const i = binarySearch(key, this.entries);\n if (!binarySearchFound(i, this.entries, key)) {\n // Not found. Return this without changes.\n return Promise.resolve(this);\n }\n\n // Found. Create new node or mutate existing one.\n return Promise.resolve(this.#splice(tree, i, 1));\n }\n\n async *keys(_tree: BTreeRead): AsyncGenerator<string, void> {\n for (const entry of this.entries) {\n yield entry[0];\n }\n }\n\n async *entriesIter(\n _tree: BTreeRead,\n ): AsyncGenerator<Entry<FrozenJSONValue>, void> {\n for (const entry of this.entries) {\n yield entry;\n }\n }\n}\n\nfunction readonlySplice<T>(\n array: ReadonlyArray<T>,\n start: number,\n deleteCount: number,\n ...items: T[]\n): T[] {\n const arr = array.slice(0, start);\n for (let i = 0; i < items.length; i++) {\n arr.push(items[i]);\n }\n for (let i = start + deleteCount; i < array.length; i++) {\n arr.push(array[i]);\n }\n return arr;\n}\n\nexport class InternalNodeImpl extends NodeImpl<Hash> {\n readonly level: number;\n\n constructor(\n entries: Array<Entry<Hash>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n ) {\n super(entries, hash, isMutable);\n this.level = level;\n }\n\n async set(\n key: string,\n value: FrozenJSONValue,\n entrySize: number,\n tree: BTreeWrite,\n ): Promise<InternalNodeImpl> {\n let i = binarySearch(key, this.entries);\n if (i === this.entries.length) {\n // We are going to insert into last (right most) leaf.\n i--;\n }\n\n const childHash = this.entries[i][1];\n const oldChildNode = await tree.getNode(childHash);\n\n const childNode = await oldChildNode.set(key, value, entrySize, tree);\n\n const childNodeSize = childNode.getChildNodeSize(tree);\n if (childNodeSize > tree.maxSize || childNodeSize < tree.minSize) {\n return this.#mergeAndPartition(tree, i, childNode);\n }\n\n const newEntry = createNewInternalEntryForNode(\n childNode,\n tree.getEntrySize,\n );\n return this.#replaceChild(tree, i, newEntry);\n }\n\n async putMany(\n entries: ReadonlyArray<Entry<FrozenJSONValue>>,\n tree: BTreeWrite,\n ): Promise<InternalNodeImpl> {\n if (entries.length === 0) {\n return this;\n }\n\n // Group entries by child index\n // Since entries are sorted, we can restrict the binary search range\n const childGroups: Map<number, Entry<FrozenJSONValue>[]> = new Map();\n\n let searchStart = 0;\n for (const entry of entries) {\n const key = entry[0];\n // Binary search from searchStart to end (entries are sorted)\n let i = binarySearchFrom(key, this.entries, searchStart);\n if (i === this.entries.length) {\n // Insert into last (right most) leaf.\n i--;\n }\n searchStart = i;\n\n let group = childGroups.get(i);\n if (!group) {\n group = [];\n childGroups.set(i, group);\n }\n group.push(entry);\n }\n\n // Process each affected child\n const newEntries = [...this.entries];\n const childrenToRebalance: Array<{\n index: number;\n node: DataNodeImpl | InternalNodeImpl;\n }> = [];\n\n for (const [childIndex, childEntries] of childGroups) {\n const childHash = this.entries[childIndex][1];\n const oldChildNode = await tree.getNode(childHash);\n const childNode = await oldChildNode.putMany(childEntries, tree);\n\n const childNodeSize = childNode.getChildNodeSize(tree);\n if (childNodeSize > tree.maxSize || childNodeSize < tree.minSize) {\n childrenToRebalance.push({index: childIndex, node: childNode});\n } else {\n const newEntry = createNewInternalEntryForNode(\n childNode,\n tree.getEntrySize,\n );\n newEntries[childIndex] = newEntry;\n }\n }\n\n // Handle rebalancing - merge adjacent children that need rebalancing\n // into contiguous groups and process each group as one unit.\n //\n // BUG FIX: Previously, each child was rebalanced independently R-to-L.\n // When adjacent children (e.g., indices 0 and 1) both needed rebalancing,\n // child[1] would merge with sibling[0] (the ORIGINAL), then child[0]\n // would merge with the rebalanced result at position 1, duplicating\n // child[0]'s data. The fix groups adjacent indices together.\n if (childrenToRebalance.length > 0) {\n childrenToRebalance.sort((a, b) => a.index - b.index);\n\n // Group adjacent indices into contiguous runs, each extended by one\n // sibling on either side for merge context.\n const groups: Array<{\n nodes: Map<number, DataNodeImpl | InternalNodeImpl>;\n minIndex: number;\n maxIndex: number;\n }> = [];\n\n for (const {index, node} of childrenToRebalance) {\n const lastGroup = groups.at(-1);\n // Adjacent to previous group? (within 1 index of the last group's max)\n if (lastGroup && index <= lastGroup.maxIndex + 1) {\n lastGroup.nodes.set(index, node);\n lastGroup.maxIndex = index;\n } else {\n groups.push({\n nodes: new Map([[index, node]]),\n minIndex: index,\n maxIndex: index,\n });\n }\n }\n\n // Process groups from right to left to maintain indices during splices.\n for (let g = groups.length - 1; g >= 0; g--) {\n const group = groups[g];\n\n // Extend range by one sibling on each side for merge context.\n const startIndex = Math.max(0, group.minIndex - 1);\n const endIndex = Math.min(newEntries.length - 1, group.maxIndex + 1);\n const removeCount = endIndex - startIndex + 1;\n\n // Collect all entries from the range: use the putMany result for\n // indices that were rebalanced, the current newEntries node otherwise.\n type IterableHashEntries = Iterable<Entry<Hash>>;\n const allValues: IterableHashEntries[] = [];\n for (let idx = startIndex; idx <= endIndex; idx++) {\n const rebalancedNode = group.nodes.get(idx);\n if (rebalancedNode) {\n allValues.push(rebalancedNode.entries as IterableHashEntries);\n } else {\n const hash = newEntries[idx][1];\n const existingNode = await tree.getNode(hash);\n allValues.push(existingNode.entries as IterableHashEntries);\n }\n }\n\n const level = this.level - 1;\n const merged = joinIterables(...allValues);\n const rebalanced = partition(\n merged,\n e => e[2],\n tree.minSize - tree.chunkHeaderSize,\n tree.maxSize - tree.chunkHeaderSize,\n entries => {\n const node = tree.newNodeImpl(entries, level);\n return createNewInternalEntryForNode(node, tree.getEntrySize);\n },\n );\n\n newEntries.splice(startIndex, removeCount, ...rebalanced);\n }\n }\n\n if (this.isMutable) {\n this.entries = newEntries;\n this._updateNode(tree);\n return this;\n }\n\n return tree.newInternalNodeImpl(newEntries, this.level);\n }\n\n /**\n * This merges the child node entries with previous or next sibling and then\n * partitions the merged entries.\n */\n async #mergeAndPartition(\n tree: BTreeWrite,\n i: number,\n childNode: DataNodeImpl | InternalNodeImpl,\n ): Promise<InternalNodeImpl> {\n const level = this.level - 1;\n const thisEntries = this.entries;\n\n type IterableHashEntries = Iterable<Entry<Hash>>;\n\n let values: IterableHashEntries;\n let startIndex: number;\n let removeCount: number;\n if (i > 0) {\n const hash = thisEntries[i - 1][1];\n const previousSibling = await tree.getNode(hash);\n values = joinIterables(\n previousSibling.entries as IterableHashEntries,\n childNode.entries as IterableHashEntries,\n );\n startIndex = i - 1;\n removeCount = 2;\n } else if (i < thisEntries.length - 1) {\n const hash = thisEntries[i + 1][1];\n const nextSibling = await tree.getNode(hash);\n values = joinIterables(\n childNode.entries as IterableHashEntries,\n nextSibling.entries as IterableHashEntries,\n );\n startIndex = i;\n removeCount = 2;\n } else {\n values = childNode.entries as IterableHashEntries;\n startIndex = i;\n removeCount = 1;\n }\n\n const newEntries = partition(\n values,\n value => value[2],\n tree.minSize - tree.chunkHeaderSize,\n tree.maxSize - tree.chunkHeaderSize,\n entries => {\n const node = tree.newNodeImpl(entries, level);\n return createNewInternalEntryForNode(node, tree.getEntrySize);\n },\n );\n\n if (this.isMutable) {\n this.entries.splice(startIndex, removeCount, ...newEntries);\n this._updateNode(tree);\n return this;\n }\n\n const entries = readonlySplice(\n thisEntries,\n startIndex,\n removeCount,\n ...newEntries,\n );\n\n return tree.newInternalNodeImpl(entries, this.level);\n }\n\n #replaceChild(\n tree: BTreeWrite,\n index: number,\n newEntry: Entry<Hash>,\n ): InternalNodeImpl {\n if (this.isMutable) {\n this.entries.splice(index, 1, newEntry);\n this._updateNode(tree);\n return this;\n }\n const entries = readonlySplice(this.entries, index, 1, newEntry);\n return tree.newInternalNodeImpl(entries, this.level);\n }\n\n async del(\n key: string,\n tree: BTreeWrite,\n ): Promise<InternalNodeImpl | DataNodeImpl> {\n const i = binarySearch(key, this.entries);\n if (i === this.entries.length) {\n // Key is larger than maxKey of rightmost entry so it is not present.\n return this;\n }\n\n const childHash = this.entries[i][1];\n const oldChildNode = await tree.getNode(childHash);\n const oldHash = oldChildNode.hash;\n\n const childNode = await oldChildNode.del(key, tree);\n if (childNode.hash === oldHash) {\n // Not changed so not found.\n return this;\n }\n\n if (childNode.entries.length === 0) {\n // Subtree is now empty. Remove internal node.\n const entries = readonlySplice(this.entries, i, 1);\n return tree.newInternalNodeImpl(entries, this.level);\n }\n\n if (i === 0 && this.entries.length === 1) {\n // There was only one node at this level and it was removed. We can return\n // the modified subtree.\n return childNode;\n }\n\n // The child node is still a good size.\n if (childNode.getChildNodeSize(tree) > tree.minSize) {\n // No merging needed.\n const entry = createNewInternalEntryForNode(childNode, tree.getEntrySize);\n return this.#replaceChild(tree, i, entry);\n }\n\n // Child node size is too small.\n return this.#mergeAndPartition(tree, i, childNode);\n }\n\n async *keys(tree: BTreeRead): AsyncGenerator<string, void> {\n for (const entry of this.entries) {\n const childNode = await tree.getNode(entry[1]);\n yield* childNode.keys(tree);\n }\n }\n\n async *entriesIter(\n tree: BTreeRead,\n ): AsyncGenerator<Entry<FrozenJSONValue>, void> {\n for (const entry of this.entries) {\n const childNode = await tree.getNode(entry[1]);\n yield* childNode.entriesIter(tree);\n }\n }\n\n getChildren(\n start: number,\n length: number,\n tree: BTreeRead,\n ): Promise<Array<InternalNodeImpl | DataNodeImpl>> {\n const ps: Promise<DataNodeImpl | InternalNodeImpl>[] = [];\n for (let i = start; i < length && i < this.entries.length; i++) {\n ps.push(tree.getNode(this.entries[i][1]));\n }\n return Promise.all(ps);\n }\n\n async getCompositeChildren(\n start: number,\n length: number,\n tree: BTreeRead,\n ): Promise<InternalNodeImpl | DataNodeImpl> {\n const {level} = this;\n\n if (length === 0) {\n return new InternalNodeImpl([], newRandomHash(), level - 1, true);\n }\n\n const output = await this.getChildren(start, start + length, tree);\n\n if (level > 1) {\n const entries: Entry<Hash>[] = [];\n for (const child of output as InternalNodeImpl[]) {\n entries.push(...child.entries);\n }\n return new InternalNodeImpl(entries, newRandomHash(), level - 1, true);\n }\n\n assert(level === 1, 'Expected level to be 1');\n const entries: Entry<FrozenJSONValue>[] = [];\n for (const child of output as DataNodeImpl[]) {\n entries.push(...child.entries);\n }\n return new DataNodeImpl(entries, newRandomHash(), true);\n }\n}\n\nexport function newNodeImpl(\n entries: Array<Entry<FrozenJSONValue>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n): DataNodeImpl;\nexport function newNodeImpl(\n entries: Array<Entry<Hash>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n): InternalNodeImpl;\nexport function newNodeImpl(\n entries: Array<Entry<FrozenJSONValue>> | Array<Entry<Hash>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n): DataNodeImpl | InternalNodeImpl;\nexport function newNodeImpl(\n entries: Array<Entry<FrozenJSONValue>> | Array<Entry<Hash>>,\n hash: Hash,\n level: number,\n isMutable: boolean,\n): DataNodeImpl | InternalNodeImpl {\n if (level === 0) {\n return new DataNodeImpl(\n entries as Entry<FrozenJSONValue>[],\n hash,\n isMutable,\n );\n }\n return new InternalNodeImpl(entries as Entry<Hash>[], hash, level, isMutable);\n}\n\nexport function isDataNodeImpl(\n node: DataNodeImpl | InternalNodeImpl,\n): node is DataNodeImpl {\n return node.level === 0;\n}\n\nexport function partition<T, R>(\n values: Iterable<T>,\n // This is the size of each Entry\n getSizeOfEntry: (v: T) => number,\n min: number,\n max: number,\n create: (entries: T[]) => R,\n): R[] {\n const results: R[] = [];\n let sum = 0;\n let accum: T[] = [];\n // The most recently finalized partition, held back until we know it won't be\n // merged with trailing entries.\n let lastPartition: T[] | null = null;\n let lastSize = 0;\n\n function commitLast() {\n if (lastPartition !== null) {\n results.push(create(lastPartition));\n lastPartition = null;\n }\n }\n\n for (const value of values) {\n const size = getSizeOfEntry(value);\n if (size >= max) {\n if (accum.length > 0) {\n commitLast();\n lastPartition = accum;\n lastSize = sum;\n accum = [];\n sum = 0;\n }\n commitLast();\n lastPartition = [value];\n lastSize = size;\n } else if (sum + size >= min) {\n accum.push(value);\n commitLast();\n lastPartition = accum;\n lastSize = sum + size;\n accum = [];\n sum = 0;\n } else {\n sum += size;\n accum.push(value);\n }\n }\n\n if (sum > 0) {\n if (lastPartition !== null && sum + lastSize <= max) {\n lastPartition.push(...accum);\n commitLast();\n } else {\n commitLast();\n results.push(create(accum));\n }\n } else {\n commitLast();\n }\n\n return results;\n}\n\nexport const emptyDataNode = makeNodeChunkData<ReadonlyJSONValue>(\n 0,\n [],\n FormatVersion.Latest,\n);\nexport const emptyDataNodeImpl = new DataNodeImpl([], emptyHash, false);\n\nexport function createNewInternalEntryForNode(\n node: NodeImpl<unknown>,\n getSizeOfEntry: <K, V>(k: K, v: V) => number,\n): [string, Hash, number] {\n const key = node.maxKey();\n const value = node.hash;\n const size = getSizeOfEntry(key, value);\n return [key, value, size];\n}\n"],"mappings":"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"}
@@ -19,6 +19,13 @@ export declare class BTreeWrite extends BTreeRead {
19
19
  newNodeImpl(entries: Entry<Hash>[], level: number): InternalNodeImpl;
20
20
  newNodeImpl(entries: Entry<Hash>[] | Entry<FrozenJSONValue>[], level: number): InternalNodeImpl | DataNodeImpl;
21
21
  put(key: string, value: FrozenJSONValue): Promise<void>;
22
+ /**
23
+ * Inserts multiple key-value pairs into the BTree efficiently.
24
+ * The entries array must be sorted by key.
25
+ * @param entries - Array of [key, value] tuples, must be sorted by key
26
+ * @returns Promise that resolves when all entries are inserted
27
+ */
28
+ putMany(entries: ReadonlyArray<readonly [string, FrozenJSONValue]>): Promise<void>;
22
29
  del(key: string): Promise<boolean>;
23
30
  clear(): Promise<void>;
24
31
  flush(): Promise<Hash>;
@@ -1 +1 @@
1
- {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/btree/write.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AAItD,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAC,KAAK,IAAI,EAA2B,MAAM,YAAY,CAAC;AAC/D,OAAO,EACL,YAAY,EACZ,KAAK,KAAK,EACV,gBAAgB,EAOjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAC,SAAS,EAAC,MAAM,WAAW,CAAC;AAEpC,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhD,qBAAa,UAAW,SAAQ,SAAS;;IAoBvC,UAAkB,QAAQ,EAAE,KAAK,CAAC;IAElC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGvB,QAAQ,EAAE,KAAK,EACf,aAAa,EAAE,aAAa,EAC5B,IAAI,GAAE,IAAgB,EACtB,OAAO,SAAW,EAClB,OAAO,SAAY,EACnB,YAAY,GAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAuB,EAC3D,eAAe,CAAC,EAAE,MAAM;IAc1B,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,gBAAgB,GAAG,IAAI;IAOvD,mBAAmB,CACjB,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAC3B,KAAK,EAAE,MAAM,GACZ,gBAAgB;IAMnB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,eAAe,CAAC,EAAE,GAAG,YAAY;IAMhE,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY;IAC3E,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,gBAAgB;IACpE,WAAW,CACT,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,eAAe,CAAC,EAAE,EACjD,KAAK,EAAE,MAAM,GACZ,gBAAgB,GAAG,YAAY;IAUlC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BvD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqBlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBvB"}
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/btree/write.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,6BAA6B,CAAC;AAItD,OAAO,KAAK,EAAC,KAAK,EAAC,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,KAAK,aAAa,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAC,KAAK,IAAI,EAA2B,MAAM,YAAY,CAAC;AAC/D,OAAO,EACL,YAAY,EACZ,KAAK,KAAK,EACV,gBAAgB,EAOjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAC,SAAS,EAAC,MAAM,WAAW,CAAC;AAEpC,KAAK,aAAa,GAAG,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhD,qBAAa,UAAW,SAAQ,SAAS;;IAoBvC,UAAkB,QAAQ,EAAE,KAAK,CAAC;IAElC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAGvB,QAAQ,EAAE,KAAK,EACf,aAAa,EAAE,aAAa,EAC5B,IAAI,GAAE,IAAgB,EACtB,OAAO,SAAW,EAClB,OAAO,SAAY,EACnB,YAAY,GAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAuB,EAC3D,eAAe,CAAC,EAAE,MAAM;IAc1B,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,gBAAgB,GAAG,IAAI;IAOvD,mBAAmB,CACjB,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAC3B,KAAK,EAAE,MAAM,GACZ,gBAAgB;IAMnB,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,eAAe,CAAC,EAAE,GAAG,YAAY;IAMhE,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY;IAC3E,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,gBAAgB;IACpE,WAAW,CACT,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,eAAe,CAAC,EAAE,EACjD,KAAK,EAAE,MAAM,GACZ,gBAAgB,GAAG,YAAY;IAUlC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BvD;;;;;OAKG;IACH,OAAO,CACL,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,GACzD,OAAO,CAAC,IAAI,CAAC;IA6GhB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqBlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBvB"}
@@ -75,6 +75,56 @@ var BTreeWrite = class extends BTreeRead {
75
75
  this.rootHash = rootNode.hash;
76
76
  });
77
77
  }
78
+ /**
79
+ * Inserts multiple key-value pairs into the BTree efficiently.
80
+ * The entries array must be sorted by key.
81
+ * @param entries - Array of [key, value] tuples, must be sorted by key
82
+ * @returns Promise that resolves when all entries are inserted
83
+ */
84
+ putMany(entries) {
85
+ return this.#lock.withLock(async () => {
86
+ if (entries.length === 0) return;
87
+ const sizedEntries = entries.map(([k, v], i) => {
88
+ if (i > 0) assert(entries[i - 1][0] < k, `putMany entries must be sorted and unique`);
89
+ return [
90
+ k,
91
+ v,
92
+ this.getEntrySize(k, v)
93
+ ];
94
+ });
95
+ const headerSize = this.chunkHeaderSize;
96
+ const contentMin = this.minSize - headerSize;
97
+ const contentMax = this.maxSize - headerSize;
98
+ if (this.rootHash === emptyHash) {
99
+ const leafPartitions = partition(sizedEntries, (e) => e[2], contentMin, contentMax, (entries) => entries);
100
+ if (leafPartitions.length === 0) return;
101
+ if (leafPartitions.length === 1) {
102
+ this.rootHash = this.newDataNodeImpl(leafPartitions[0]).hash;
103
+ return;
104
+ }
105
+ let currentLevel = leafPartitions.map((entries) => this.newDataNodeImpl(entries));
106
+ let level = 0;
107
+ while (currentLevel.length > 1) {
108
+ level++;
109
+ const parentEntriesLength = currentLevel.length;
110
+ for (let i = 0; i < parentEntriesLength; i++) currentLevel[i] = createNewInternalEntryForNode(currentLevel[i], this.getEntrySize);
111
+ currentLevel = partition(currentLevel, (e) => e[2], contentMin, contentMax, (entries) => entries).map((entries) => this.newInternalNodeImpl(entries, level));
112
+ }
113
+ this.rootHash = currentLevel[0].hash;
114
+ return;
115
+ }
116
+ const rootNode = await (await this.getNode(this.rootHash)).putMany(sizedEntries, this);
117
+ if (rootNode.getChildNodeSize(this) > this.maxSize) {
118
+ const { level } = rootNode;
119
+ const entries = partition(rootNode.entries, (e) => e[2], contentMin, contentMax, (partitionEntries) => {
120
+ return createNewInternalEntryForNode(this.newNodeImpl(partitionEntries, level), this.getEntrySize);
121
+ });
122
+ this.rootHash = this.newInternalNodeImpl(entries, level + 1).hash;
123
+ return;
124
+ }
125
+ this.rootHash = rootNode.hash;
126
+ });
127
+ }
78
128
  del(key) {
79
129
  return this.#lock.withLock(async () => {
80
130
  const newRootNode = await (await this.getNode(this.rootHash)).del(key, this);
@@ -1 +1 @@
1
- {"version":3,"file":"write.js","names":["#lock","#modified","#addToModified"],"sources":["../../../../../replicache/src/btree/write.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {getSizeOfEntry} from '../../../shared/src/size-of-value.ts';\nimport {type Chunk, type CreateChunk, toRefs} from '../dag/chunk.ts';\nimport type {Write} from '../dag/store.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport type {FrozenJSONValue} from '../frozen-json.ts';\nimport {type Hash, emptyHash, newRandomHash} from '../hash.ts';\nimport {\n DataNodeImpl,\n type Entry,\n InternalNodeImpl,\n createNewInternalEntryForNode,\n emptyDataNode,\n isDataNodeImpl,\n newNodeImpl,\n partition,\n toChunkData,\n} from './node.ts';\nimport {BTreeRead} from './read.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nexport class BTreeWrite extends BTreeRead {\n /**\n * This rw lock is used to ensure we do not mutate the btree in parallel. It\n * would be a problem if we didn't have the lock in cases like this:\n *\n * ```ts\n * const p1 = tree.put('a', 0);\n * const p2 = tree.put('b', 1);\n * await p1;\n * await p2;\n * ```\n *\n * because both `p1` and `p2` would start from the old root hash but a put\n * changes the root hash so the two concurrent puts would lead to only one of\n * them actually working, and it is not deterministic which one would finish\n * last.\n */\n readonly #lock = new Lock();\n readonly #modified: Map<Hash, DataNodeImpl | InternalNodeImpl> = new Map();\n\n declare protected _dagRead: Write;\n\n readonly minSize: number;\n readonly maxSize: number;\n\n constructor(\n dagWrite: Write,\n formatVersion: FormatVersion,\n root: Hash = emptyHash,\n minSize = 8 * 1024,\n maxSize = 16 * 1024,\n getEntrySize: <K, V>(k: K, v: V) => number = getSizeOfEntry,\n chunkHeaderSize?: number,\n ) {\n super(dagWrite, formatVersion, root, getEntrySize, chunkHeaderSize);\n\n this.minSize = minSize;\n this.maxSize = maxSize;\n }\n\n #addToModified(node: DataNodeImpl | InternalNodeImpl): void {\n assert(node.isMutable, 'Expected node to be mutable');\n this.#modified.set(node.hash, node);\n this._cache.set(node.hash, node);\n }\n\n updateNode(node: DataNodeImpl | InternalNodeImpl): void {\n assert(node.isMutable, 'Expected node to be mutable');\n this.#modified.delete(node.hash);\n node.hash = newRandomHash();\n this.#addToModified(node);\n }\n\n newInternalNodeImpl(\n entries: Array<Entry<Hash>>,\n level: number,\n ): InternalNodeImpl {\n const n = new InternalNodeImpl(entries, newRandomHash(), level, true);\n this.#addToModified(n);\n return n;\n }\n\n newDataNodeImpl(entries: Entry<FrozenJSONValue>[]): DataNodeImpl {\n const n = new DataNodeImpl(entries, newRandomHash(), true);\n this.#addToModified(n);\n return n;\n }\n\n newNodeImpl(entries: Entry<FrozenJSONValue>[], level: number): DataNodeImpl;\n newNodeImpl(entries: Entry<Hash>[], level: number): InternalNodeImpl;\n newNodeImpl(\n entries: Entry<Hash>[] | Entry<FrozenJSONValue>[],\n level: number,\n ): InternalNodeImpl | DataNodeImpl;\n newNodeImpl(\n entries: Entry<Hash>[] | Entry<FrozenJSONValue>[],\n level: number,\n ): InternalNodeImpl | DataNodeImpl {\n const n = newNodeImpl(entries, newRandomHash(), level, true);\n this.#addToModified(n);\n return n;\n }\n\n put(key: string, value: FrozenJSONValue): Promise<void> {\n return this.#lock.withLock(async () => {\n const oldRootNode = await this.getNode(this.rootHash);\n const entrySize = this.getEntrySize(key, value);\n const rootNode = await oldRootNode.set(key, value, entrySize, this);\n\n // We do the rebalancing in the parent so we need to do it here as well.\n if (rootNode.getChildNodeSize(this) > this.maxSize) {\n const headerSize = this.chunkHeaderSize;\n const {level} = rootNode;\n const entries = partition(\n rootNode.entries,\n value => value[2],\n this.minSize - headerSize,\n this.maxSize - headerSize,\n entries => {\n const node = this.newNodeImpl(entries, level);\n return createNewInternalEntryForNode(node, this.getEntrySize);\n },\n );\n const newRoot = this.newInternalNodeImpl(entries, level + 1);\n this.rootHash = newRoot.hash;\n return;\n }\n\n this.rootHash = rootNode.hash;\n });\n }\n\n del(key: string): Promise<boolean> {\n return this.#lock.withLock(async () => {\n const oldRootNode = await this.getNode(this.rootHash);\n const newRootNode = await oldRootNode.del(key, this);\n\n // No need to rebalance here since if root gets too small there is nothing\n // we can do about that.\n const found = this.rootHash !== newRootNode.hash;\n if (found) {\n // Flatten one layer.\n if (newRootNode.level > 0 && newRootNode.entries.length === 1) {\n this.rootHash = (newRootNode as InternalNodeImpl).entries[0][1];\n } else {\n this.rootHash = newRootNode.hash;\n }\n }\n\n return found;\n });\n }\n\n clear(): Promise<void> {\n return this.#lock.withLock(() => {\n this.#modified.clear();\n this.rootHash = emptyHash;\n });\n }\n\n flush(): Promise<Hash> {\n return this.#lock.withLock(async () => {\n const dagWrite = this._dagRead;\n\n if (this.rootHash === emptyHash) {\n // Write a chunk for the empty tree.\n const chunk = dagWrite.createChunk(emptyDataNode, []);\n await dagWrite.putChunk(chunk as Chunk<ReadonlyJSONValue>);\n return chunk.hash;\n }\n\n const newChunks: Chunk[] = [];\n const newRoot = gatherNewChunks(\n this.rootHash,\n newChunks,\n dagWrite.createChunk,\n this.#modified,\n this._formatVersion,\n );\n await Promise.all(newChunks.map(chunk => dagWrite.putChunk(chunk)));\n this.#modified.clear();\n this.rootHash = newRoot;\n return newRoot;\n });\n }\n}\n\nfunction gatherNewChunks(\n hash: Hash,\n newChunks: Chunk[],\n createChunk: CreateChunk,\n modified: Map<Hash, DataNodeImpl | InternalNodeImpl>,\n formatVersion: FormatVersion,\n): Hash {\n const node = modified.get(hash);\n if (node === undefined) {\n // Not modified, use the original.\n return hash;\n }\n\n if (isDataNodeImpl(node)) {\n const chunk = createChunk(toChunkData(node, formatVersion), []);\n newChunks.push(chunk);\n return chunk.hash;\n }\n\n // The BTree cannot have duplicate keys so the child entry hashes are unique.\n // No need fot a set to dedupe here.\n const refs: Hash[] = [];\n const {entries} = node;\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const childHash = entry[1];\n const newChildHash = gatherNewChunks(\n childHash,\n newChunks,\n createChunk,\n modified,\n formatVersion,\n );\n if (newChildHash !== childHash) {\n // MUTATES the entries!\n // Hashes do not change the size of the entry because all hashes have the same length\n entries[i] = [entry[0], newChildHash, entry[2]];\n }\n refs.push(newChildHash);\n }\n const chunk = createChunk(toChunkData(node, formatVersion), toRefs(refs));\n newChunks.push(chunk);\n return chunk.hash;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"write.js","names":["#lock","#modified","#addToModified"],"sources":["../../../../../replicache/src/btree/write.ts"],"sourcesContent":["import {Lock} from '@rocicorp/lock';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../shared/src/enum.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {getSizeOfEntry} from '../../../shared/src/size-of-value.ts';\nimport {type Chunk, type CreateChunk, toRefs} from '../dag/chunk.ts';\nimport type {Write} from '../dag/store.ts';\nimport type * as FormatVersion from '../format-version-enum.ts';\nimport type {FrozenJSONValue} from '../frozen-json.ts';\nimport {type Hash, emptyHash, newRandomHash} from '../hash.ts';\nimport {\n DataNodeImpl,\n type Entry,\n InternalNodeImpl,\n createNewInternalEntryForNode,\n emptyDataNode,\n isDataNodeImpl,\n newNodeImpl,\n partition,\n toChunkData,\n} from './node.ts';\nimport {BTreeRead} from './read.ts';\n\ntype FormatVersion = Enum<typeof FormatVersion>;\n\nexport class BTreeWrite extends BTreeRead {\n /**\n * This rw lock is used to ensure we do not mutate the btree in parallel. It\n * would be a problem if we didn't have the lock in cases like this:\n *\n * ```ts\n * const p1 = tree.put('a', 0);\n * const p2 = tree.put('b', 1);\n * await p1;\n * await p2;\n * ```\n *\n * because both `p1` and `p2` would start from the old root hash but a put\n * changes the root hash so the two concurrent puts would lead to only one of\n * them actually working, and it is not deterministic which one would finish\n * last.\n */\n readonly #lock = new Lock();\n readonly #modified: Map<Hash, DataNodeImpl | InternalNodeImpl> = new Map();\n\n declare protected _dagRead: Write;\n\n readonly minSize: number;\n readonly maxSize: number;\n\n constructor(\n dagWrite: Write,\n formatVersion: FormatVersion,\n root: Hash = emptyHash,\n minSize = 8 * 1024,\n maxSize = 16 * 1024,\n getEntrySize: <K, V>(k: K, v: V) => number = getSizeOfEntry,\n chunkHeaderSize?: number,\n ) {\n super(dagWrite, formatVersion, root, getEntrySize, chunkHeaderSize);\n\n this.minSize = minSize;\n this.maxSize = maxSize;\n }\n\n #addToModified(node: DataNodeImpl | InternalNodeImpl): void {\n assert(node.isMutable, 'Expected node to be mutable');\n this.#modified.set(node.hash, node);\n this._cache.set(node.hash, node);\n }\n\n updateNode(node: DataNodeImpl | InternalNodeImpl): void {\n assert(node.isMutable, 'Expected node to be mutable');\n this.#modified.delete(node.hash);\n node.hash = newRandomHash();\n this.#addToModified(node);\n }\n\n newInternalNodeImpl(\n entries: Array<Entry<Hash>>,\n level: number,\n ): InternalNodeImpl {\n const n = new InternalNodeImpl(entries, newRandomHash(), level, true);\n this.#addToModified(n);\n return n;\n }\n\n newDataNodeImpl(entries: Entry<FrozenJSONValue>[]): DataNodeImpl {\n const n = new DataNodeImpl(entries, newRandomHash(), true);\n this.#addToModified(n);\n return n;\n }\n\n newNodeImpl(entries: Entry<FrozenJSONValue>[], level: number): DataNodeImpl;\n newNodeImpl(entries: Entry<Hash>[], level: number): InternalNodeImpl;\n newNodeImpl(\n entries: Entry<Hash>[] | Entry<FrozenJSONValue>[],\n level: number,\n ): InternalNodeImpl | DataNodeImpl;\n newNodeImpl(\n entries: Entry<Hash>[] | Entry<FrozenJSONValue>[],\n level: number,\n ): InternalNodeImpl | DataNodeImpl {\n const n = newNodeImpl(entries, newRandomHash(), level, true);\n this.#addToModified(n);\n return n;\n }\n\n put(key: string, value: FrozenJSONValue): Promise<void> {\n return this.#lock.withLock(async () => {\n const oldRootNode = await this.getNode(this.rootHash);\n const entrySize = this.getEntrySize(key, value);\n const rootNode = await oldRootNode.set(key, value, entrySize, this);\n\n // We do the rebalancing in the parent so we need to do it here as well.\n if (rootNode.getChildNodeSize(this) > this.maxSize) {\n const headerSize = this.chunkHeaderSize;\n const {level} = rootNode;\n const entries = partition(\n rootNode.entries,\n value => value[2],\n this.minSize - headerSize,\n this.maxSize - headerSize,\n entries => {\n const node = this.newNodeImpl(entries, level);\n return createNewInternalEntryForNode(node, this.getEntrySize);\n },\n );\n const newRoot = this.newInternalNodeImpl(entries, level + 1);\n this.rootHash = newRoot.hash;\n return;\n }\n\n this.rootHash = rootNode.hash;\n });\n }\n\n /**\n * Inserts multiple key-value pairs into the BTree efficiently.\n * The entries array must be sorted by key.\n * @param entries - Array of [key, value] tuples, must be sorted by key\n * @returns Promise that resolves when all entries are inserted\n */\n putMany(\n entries: ReadonlyArray<readonly [string, FrozenJSONValue]>,\n ): Promise<void> {\n return this.#lock.withLock(async () => {\n if (entries.length === 0) {\n return;\n }\n\n // Validate sortedness and convert to sized entries in one pass\n const sizedEntries: Entry<FrozenJSONValue>[] = entries.map(\n ([k, v], i) => {\n if (i > 0) {\n assert(\n entries[i - 1][0] < k,\n `putMany entries must be sorted and unique`,\n );\n }\n return [k, v, this.getEntrySize(k, v)];\n },\n );\n\n // Compute content size constraints\n const headerSize = this.chunkHeaderSize;\n const contentMin = this.minSize - headerSize;\n const contentMax = this.maxSize - headerSize;\n\n // Fast path: if tree is empty, use bulk loading algorithm\n if (this.rootHash === emptyHash) {\n // Build leaf nodes\n const leafPartitions = partition(\n sizedEntries,\n e => e[2],\n contentMin,\n contentMax,\n entries => entries,\n );\n\n if (leafPartitions.length === 0) {\n return;\n }\n\n if (leafPartitions.length === 1) {\n const leaf = this.newDataNodeImpl(leafPartitions[0]);\n this.rootHash = leaf.hash;\n return;\n }\n\n // Build tree bottom-up - reuse array to avoid allocations\n let currentLevel: Array<DataNodeImpl | InternalNodeImpl | Entry<Hash>> =\n leafPartitions.map(entries => this.newDataNodeImpl(entries));\n let level = 0;\n\n while (currentLevel.length > 1) {\n level++;\n\n // Create entries pointing to current level nodes - reuse array\n const parentEntriesLength = currentLevel.length;\n for (let i = 0; i < parentEntriesLength; i++) {\n currentLevel[i] = createNewInternalEntryForNode(\n currentLevel[i] as DataNodeImpl | InternalNodeImpl,\n this.getEntrySize,\n );\n }\n\n // Partition parent entries\n const parentPartitions = partition(\n currentLevel as Entry<Hash>[],\n e => e[2],\n contentMin,\n contentMax,\n entries => entries,\n );\n\n // Create internal nodes\n currentLevel = parentPartitions.map(entries =>\n this.newInternalNodeImpl(entries, level),\n );\n }\n\n this.rootHash = (\n currentLevel[0] as DataNodeImpl | InternalNodeImpl\n ).hash;\n return;\n }\n\n // Slow path: merge with existing tree\n const oldRootNode = await this.getNode(this.rootHash);\n const rootNode = await oldRootNode.putMany(sizedEntries, this);\n\n // We do the rebalancing in the parent so we need to do it here as well.\n if (rootNode.getChildNodeSize(this) > this.maxSize) {\n const {level} = rootNode;\n const entries: Entry<Hash>[] = partition(\n rootNode.entries,\n e => e[2],\n contentMin,\n contentMax,\n partitionEntries => {\n const node = this.newNodeImpl(partitionEntries, level);\n return createNewInternalEntryForNode(node, this.getEntrySize);\n },\n );\n const newRoot = this.newInternalNodeImpl(entries, level + 1);\n this.rootHash = newRoot.hash;\n return;\n }\n\n this.rootHash = rootNode.hash;\n });\n }\n\n del(key: string): Promise<boolean> {\n return this.#lock.withLock(async () => {\n const oldRootNode = await this.getNode(this.rootHash);\n const newRootNode = await oldRootNode.del(key, this);\n\n // No need to rebalance here since if root gets too small there is nothing\n // we can do about that.\n const found = this.rootHash !== newRootNode.hash;\n if (found) {\n // Flatten one layer.\n if (newRootNode.level > 0 && newRootNode.entries.length === 1) {\n this.rootHash = (newRootNode as InternalNodeImpl).entries[0][1];\n } else {\n this.rootHash = newRootNode.hash;\n }\n }\n\n return found;\n });\n }\n\n clear(): Promise<void> {\n return this.#lock.withLock(() => {\n this.#modified.clear();\n this.rootHash = emptyHash;\n });\n }\n\n flush(): Promise<Hash> {\n return this.#lock.withLock(async () => {\n const dagWrite = this._dagRead;\n\n if (this.rootHash === emptyHash) {\n // Write a chunk for the empty tree.\n const chunk = dagWrite.createChunk(emptyDataNode, []);\n await dagWrite.putChunk(chunk as Chunk<ReadonlyJSONValue>);\n return chunk.hash;\n }\n\n const newChunks: Chunk[] = [];\n const newRoot = gatherNewChunks(\n this.rootHash,\n newChunks,\n dagWrite.createChunk,\n this.#modified,\n this._formatVersion,\n );\n await Promise.all(newChunks.map(chunk => dagWrite.putChunk(chunk)));\n this.#modified.clear();\n this.rootHash = newRoot;\n return newRoot;\n });\n }\n}\n\nfunction gatherNewChunks(\n hash: Hash,\n newChunks: Chunk[],\n createChunk: CreateChunk,\n modified: Map<Hash, DataNodeImpl | InternalNodeImpl>,\n formatVersion: FormatVersion,\n): Hash {\n const node = modified.get(hash);\n if (node === undefined) {\n // Not modified, use the original.\n return hash;\n }\n\n if (isDataNodeImpl(node)) {\n const chunk = createChunk(toChunkData(node, formatVersion), []);\n newChunks.push(chunk);\n return chunk.hash;\n }\n\n // The BTree cannot have duplicate keys so the child entry hashes are unique.\n // No need fot a set to dedupe here.\n const refs: Hash[] = [];\n const {entries} = node;\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const childHash = entry[1];\n const newChildHash = gatherNewChunks(\n childHash,\n newChunks,\n createChunk,\n modified,\n formatVersion,\n );\n if (newChildHash !== childHash) {\n // MUTATES the entries!\n // Hashes do not change the size of the entry because all hashes have the same length\n entries[i] = [entry[0], newChildHash, entry[2]];\n }\n refs.push(newChildHash);\n }\n const chunk = createChunk(toChunkData(node, formatVersion), toRefs(refs));\n newChunks.push(chunk);\n return chunk.hash;\n}\n"],"mappings":";;;;;;;;AAyBA,IAAa,aAAb,cAAgC,UAAU;;;;;;;;;;;;;;;;;CAiBxC,QAAiB,IAAI,MAAM;CAC3B,4BAAiE,IAAI,KAAK;CAI1E;CACA;CAEA,YACE,UACA,eACA,OAAa,WACb,UAAU,IAAI,MACd,UAAU,KAAK,MACf,eAA6C,gBAC7C,iBACA;AACA,QAAM,UAAU,eAAe,MAAM,cAAc,gBAAgB;AAEnE,OAAK,UAAU;AACf,OAAK,UAAU;;CAGjB,eAAe,MAA6C;AAC1D,SAAO,KAAK,WAAW,8BAA8B;AACrD,QAAA,SAAe,IAAI,KAAK,MAAM,KAAK;AACnC,OAAK,OAAO,IAAI,KAAK,MAAM,KAAK;;CAGlC,WAAW,MAA6C;AACtD,SAAO,KAAK,WAAW,8BAA8B;AACrD,QAAA,SAAe,OAAO,KAAK,KAAK;AAChC,OAAK,OAAO,eAAe;AAC3B,QAAA,cAAoB,KAAK;;CAG3B,oBACE,SACA,OACkB;EAClB,MAAM,IAAI,IAAI,iBAAiB,SAAS,eAAe,EAAE,OAAO,KAAK;AACrE,QAAA,cAAoB,EAAE;AACtB,SAAO;;CAGT,gBAAgB,SAAiD;EAC/D,MAAM,IAAI,IAAI,aAAa,SAAS,eAAe,EAAE,KAAK;AAC1D,QAAA,cAAoB,EAAE;AACtB,SAAO;;CAST,YACE,SACA,OACiC;EACjC,MAAM,IAAI,YAAY,SAAS,eAAe,EAAE,OAAO,KAAK;AAC5D,QAAA,cAAoB,EAAE;AACtB,SAAO;;CAGT,IAAI,KAAa,OAAuC;AACtD,SAAO,MAAA,KAAW,SAAS,YAAY;GACrC,MAAM,cAAc,MAAM,KAAK,QAAQ,KAAK,SAAS;GACrD,MAAM,YAAY,KAAK,aAAa,KAAK,MAAM;GAC/C,MAAM,WAAW,MAAM,YAAY,IAAI,KAAK,OAAO,WAAW,KAAK;AAGnE,OAAI,SAAS,iBAAiB,KAAK,GAAG,KAAK,SAAS;IAClD,MAAM,aAAa,KAAK;IACxB,MAAM,EAAC,UAAS;IAChB,MAAM,UAAU,UACd,SAAS,UACT,UAAS,MAAM,IACf,KAAK,UAAU,YACf,KAAK,UAAU,aACf,YAAW;AAET,YAAO,8BADM,KAAK,YAAY,SAAS,MAAM,EACF,KAAK,aAAa;MAEhE;AAED,SAAK,WADW,KAAK,oBAAoB,SAAS,QAAQ,EAAE,CACpC;AACxB;;AAGF,QAAK,WAAW,SAAS;IACzB;;;;;;;;CASJ,QACE,SACe;AACf,SAAO,MAAA,KAAW,SAAS,YAAY;AACrC,OAAI,QAAQ,WAAW,EACrB;GAIF,MAAM,eAAyC,QAAQ,KACpD,CAAC,GAAG,IAAI,MAAM;AACb,QAAI,IAAI,EACN,QACE,QAAQ,IAAI,GAAG,KAAK,GACpB,4CACD;AAEH,WAAO;KAAC;KAAG;KAAG,KAAK,aAAa,GAAG,EAAE;KAAC;KAEzC;GAGD,MAAM,aAAa,KAAK;GACxB,MAAM,aAAa,KAAK,UAAU;GAClC,MAAM,aAAa,KAAK,UAAU;AAGlC,OAAI,KAAK,aAAa,WAAW;IAE/B,MAAM,iBAAiB,UACrB,eACA,MAAK,EAAE,IACP,YACA,aACA,YAAW,QACZ;AAED,QAAI,eAAe,WAAW,EAC5B;AAGF,QAAI,eAAe,WAAW,GAAG;AAE/B,UAAK,WADQ,KAAK,gBAAgB,eAAe,GAAG,CAC/B;AACrB;;IAIF,IAAI,eACF,eAAe,KAAI,YAAW,KAAK,gBAAgB,QAAQ,CAAC;IAC9D,IAAI,QAAQ;AAEZ,WAAO,aAAa,SAAS,GAAG;AAC9B;KAGA,MAAM,sBAAsB,aAAa;AACzC,UAAK,IAAI,IAAI,GAAG,IAAI,qBAAqB,IACvC,cAAa,KAAK,8BAChB,aAAa,IACb,KAAK,aACN;AAaH,oBATyB,UACvB,eACA,MAAK,EAAE,IACP,YACA,aACA,YAAW,QACZ,CAG+B,KAAI,YAClC,KAAK,oBAAoB,SAAS,MAAM,CACzC;;AAGH,SAAK,WACH,aAAa,GACb;AACF;;GAKF,MAAM,WAAW,OADG,MAAM,KAAK,QAAQ,KAAK,SAAS,EAClB,QAAQ,cAAc,KAAK;AAG9D,OAAI,SAAS,iBAAiB,KAAK,GAAG,KAAK,SAAS;IAClD,MAAM,EAAC,UAAS;IAChB,MAAM,UAAyB,UAC7B,SAAS,UACT,MAAK,EAAE,IACP,YACA,aACA,qBAAoB;AAElB,YAAO,8BADM,KAAK,YAAY,kBAAkB,MAAM,EACX,KAAK,aAAa;MAEhE;AAED,SAAK,WADW,KAAK,oBAAoB,SAAS,QAAQ,EAAE,CACpC;AACxB;;AAGF,QAAK,WAAW,SAAS;IACzB;;CAGJ,IAAI,KAA+B;AACjC,SAAO,MAAA,KAAW,SAAS,YAAY;GAErC,MAAM,cAAc,OADA,MAAM,KAAK,QAAQ,KAAK,SAAS,EACf,IAAI,KAAK,KAAK;GAIpD,MAAM,QAAQ,KAAK,aAAa,YAAY;AAC5C,OAAI,MAEF,KAAI,YAAY,QAAQ,KAAK,YAAY,QAAQ,WAAW,EAC1D,MAAK,WAAY,YAAiC,QAAQ,GAAG;OAE7D,MAAK,WAAW,YAAY;AAIhC,UAAO;IACP;;CAGJ,QAAuB;AACrB,SAAO,MAAA,KAAW,eAAe;AAC/B,SAAA,SAAe,OAAO;AACtB,QAAK,WAAW;IAChB;;CAGJ,QAAuB;AACrB,SAAO,MAAA,KAAW,SAAS,YAAY;GACrC,MAAM,WAAW,KAAK;AAEtB,OAAI,KAAK,aAAa,WAAW;IAE/B,MAAM,QAAQ,SAAS,YAAY,eAAe,EAAE,CAAC;AACrD,UAAM,SAAS,SAAS,MAAkC;AAC1D,WAAO,MAAM;;GAGf,MAAM,YAAqB,EAAE;GAC7B,MAAM,UAAU,gBACd,KAAK,UACL,WACA,SAAS,aACT,MAAA,UACA,KAAK,eACN;AACD,SAAM,QAAQ,IAAI,UAAU,KAAI,UAAS,SAAS,SAAS,MAAM,CAAC,CAAC;AACnE,SAAA,SAAe,OAAO;AACtB,QAAK,WAAW;AAChB,UAAO;IACP;;;AAIN,SAAS,gBACP,MACA,WACA,aACA,UACA,eACM;CACN,MAAM,OAAO,SAAS,IAAI,KAAK;AAC/B,KAAI,SAAS,KAAA,EAEX,QAAO;AAGT,KAAI,eAAe,KAAK,EAAE;EACxB,MAAM,QAAQ,YAAY,YAAY,MAAM,cAAc,EAAE,EAAE,CAAC;AAC/D,YAAU,KAAK,MAAM;AACrB,SAAO,MAAM;;CAKf,MAAM,OAAe,EAAE;CACvB,MAAM,EAAC,YAAW;AAClB,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,QAAQ,QAAQ;EACtB,MAAM,YAAY,MAAM;EACxB,MAAM,eAAe,gBACnB,WACA,WACA,aACA,UACA,cACD;AACD,MAAI,iBAAiB,UAGnB,SAAQ,KAAK;GAAC,MAAM;GAAI;GAAc,MAAM;GAAG;AAEjD,OAAK,KAAK,aAAa;;CAEzB,MAAM,QAAQ,YAAY,YAAY,MAAM,cAAc,EAAE,OAAO,KAAK,CAAC;AACzE,WAAU,KAAK,MAAM;AACrB,QAAO,MAAM"}