@rocicorp/zero 1.4.0-canary.2 → 1.4.0-canary.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/out/analyze-query/src/analyze-cli.d.ts +0 -1
  2. package/out/analyze-query/src/analyze-cli.d.ts.map +1 -1
  3. package/out/analyze-query/src/analyze-cli.js +0 -1
  4. package/out/analyze-query/src/analyze-cli.js.map +1 -1
  5. package/out/analyze-query/src/bin-analyze.js +10 -9
  6. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  7. package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
  8. package/out/replicache/src/kv/sqlite-store.js +7 -1
  9. package/out/replicache/src/kv/sqlite-store.js.map +1 -1
  10. package/out/replicache/src/with-transactions.d.ts.map +1 -1
  11. package/out/replicache/src/with-transactions.js +16 -2
  12. package/out/replicache/src/with-transactions.js.map +1 -1
  13. package/out/zero/package.js +8 -2
  14. package/out/zero/package.js.map +1 -1
  15. package/out/zero/src/adapters/kysely.d.ts +2 -0
  16. package/out/zero/src/adapters/kysely.d.ts.map +1 -0
  17. package/out/zero/src/adapters/kysely.js +2 -0
  18. package/out/zero/src/zero-cache-dev.js +2 -0
  19. package/out/zero/src/zero-cache-dev.js.map +1 -1
  20. package/out/zero/src/zero.js +2 -1
  21. package/out/zero-cache/src/auth/write-authorizer.d.ts.map +1 -1
  22. package/out/zero-cache/src/auth/write-authorizer.js +14 -1
  23. package/out/zero-cache/src/auth/write-authorizer.js.map +1 -1
  24. package/out/zero-cache/src/db/migration-lite.js +8 -1
  25. package/out/zero-cache/src/db/migration-lite.js.map +1 -1
  26. package/out/zero-cache/src/db/pg-to-lite.d.ts +1 -1
  27. package/out/zero-cache/src/db/pg-to-lite.d.ts.map +1 -1
  28. package/out/zero-cache/src/db/pg-to-lite.js +13 -13
  29. package/out/zero-cache/src/db/pg-to-lite.js.map +1 -1
  30. package/out/zero-cache/src/observability/metrics.d.ts +36 -6
  31. package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
  32. package/out/zero-cache/src/observability/metrics.js +55 -10
  33. package/out/zero-cache/src/observability/metrics.js.map +1 -1
  34. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  35. package/out/zero-cache/src/server/change-streamer.js +2 -0
  36. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  37. package/out/zero-cache/src/server/runner/run-worker.d.ts.map +1 -1
  38. package/out/zero-cache/src/server/runner/run-worker.js +9 -1
  39. package/out/zero-cache/src/server/runner/run-worker.js.map +1 -1
  40. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts +2 -1
  41. package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
  42. package/out/zero-cache/src/server/runner/zero-dispatcher.js +6 -1
  43. package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
  44. package/out/zero-cache/src/services/analyze.d.ts.map +1 -1
  45. package/out/zero-cache/src/services/analyze.js +1 -1
  46. package/out/zero-cache/src/services/analyze.js.map +1 -1
  47. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/change-source/pg/change-source.js +6 -3
  49. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  50. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  51. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +2 -0
  52. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  53. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  54. package/out/zero-cache/src/services/replicator/change-processor.js +10 -3
  55. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  56. package/out/zero-cache/src/services/view-syncer/client-handler.js +3 -6
  57. package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
  58. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +2 -2
  59. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +5 -8
  60. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  61. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +4 -7
  62. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  63. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  64. package/out/zero-cache/src/services/view-syncer/view-syncer.js +16 -26
  65. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  66. package/out/zero-client/src/client/version.js +1 -1
  67. package/out/zero-client/src/mod.d.ts +1 -0
  68. package/out/zero-client/src/mod.d.ts.map +1 -1
  69. package/out/zero-client/src/mod.js +1 -0
  70. package/out/zero-react/src/zero.js +1 -0
  71. package/out/zero-server/src/adapters/kysely.d.ts +69 -0
  72. package/out/zero-server/src/adapters/kysely.d.ts.map +1 -0
  73. package/out/zero-server/src/adapters/kysely.js +82 -0
  74. package/out/zero-server/src/adapters/kysely.js.map +1 -0
  75. package/out/zero-solid/src/zero.js +1 -0
  76. package/out/zql/src/query/validate-input.d.ts +8 -0
  77. package/out/zql/src/query/validate-input.d.ts.map +1 -1
  78. package/out/zql/src/query/validate-input.js +15 -2
  79. package/out/zql/src/query/validate-input.js.map +1 -1
  80. package/out/zqlite/src/query-builder.js +19 -7
  81. package/out/zqlite/src/query-builder.js.map +1 -1
  82. package/package.json +10 -2
  83. package/out/analyze-query/src/explain-queries.d.ts +0 -4
  84. package/out/analyze-query/src/explain-queries.d.ts.map +0 -1
  85. package/out/analyze-query/src/explain-queries.js +0 -13
  86. package/out/analyze-query/src/explain-queries.js.map +0 -1
  87. package/out/otel/src/test-log-config.d.ts +0 -8
  88. package/out/otel/src/test-log-config.d.ts.map +0 -1
  89. package/out/otel/src/test-log-config.js +0 -12
  90. package/out/otel/src/test-log-config.js.map +0 -1
@@ -17,7 +17,6 @@ export type AnalyzeCLIOptions = {
17
17
  * await runAnalyzeCLI({schema});
18
18
  * ```
19
19
  *
20
- * @experimental This API is in progress and may change without notice.
21
20
  * Exits the process with code 1 on error.
22
21
  */
23
22
  export declare function runAnalyzeCLI(opts: AnalyzeCLIOptions): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"analyze-cli.d.ts","sourceRoot":"","sources":["../../../../analyze-query/src/analyze-cli.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAC;AAcpC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,gCAAgC,CAAC;AAK3D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CACtC,CAAC;AAmHF;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgG1E"}
1
+ {"version":3,"file":"analyze-cli.d.ts","sourceRoot":"","sources":["../../../../analyze-query/src/analyze-cli.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAC;AAcpC,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,gCAAgC,CAAC;AAK3D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CACtC,CAAC;AAmHF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgG1E"}
@@ -93,7 +93,6 @@ var stderrLogSink = { log(level, context, ...args) {
93
93
  * await runAnalyzeCLI({schema});
94
94
  * ```
95
95
  *
96
- * @experimental This API is in progress and may change without notice.
97
96
  * Exits the process with code 1 on error.
98
97
  */
99
98
  async function runAnalyzeCLI(opts) {
@@ -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 * @experimental This API is in progress and may change without notice.\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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"}
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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"}
@@ -5,6 +5,7 @@ import { clientSchemaFrom } from "../../zero-schema/src/builder/schema-builder.j
5
5
  import { asQueryInternals } from "../../zql/src/query/query-internals.js";
6
6
  import { newQuery } from "../../zql/src/query/query-impl.js";
7
7
  import { clientToServer } from "../../zero-schema/src/name-mapper.js";
8
+ import "../../zql/src/query/query.js";
8
9
  import { QueryDelegateBase } from "../../zql/src/query/query-delegate-base.js";
9
10
  import "../../shared/src/dotenv.js";
10
11
  import { logLevel, logOptions } from "../../otel/src/log-options.js";
@@ -17,12 +18,11 @@ import { Database } from "../../zqlite/src/db.js";
17
18
  import { TableSource } from "../../zqlite/src/table-source.js";
18
19
  import { astToZQL } from "../../ast-to-zql/src/ast-to-zql.js";
19
20
  import { formatOutput } from "../../ast-to-zql/src/format.js";
20
- import { testLogConfig } from "../../otel/src/test-log-config.js";
21
21
  import { computeZqlSpecs, mustGetTableSpec } from "../../zero-cache/src/db/lite-tables.js";
22
22
  import { deployPermissionsOptions, loadSchemaAndPermissions } from "../../zero-cache/src/scripts/permissions.js";
23
23
  import { runAst } from "../../zero-cache/src/services/run-ast.js";
24
24
  import { pgClient } from "../../zero-cache/src/types/pg.js";
25
- import { explainQueries } from "./explain-queries.js";
25
+ import { explainQueries } from "../../zqlite/src/explain-queries.js";
26
26
  import { styleText } from "node:util";
27
27
  import fs from "node:fs";
28
28
  //#region ../analyze-query/src/bin-analyze.ts
@@ -30,7 +30,7 @@ var cfg = parseOptions({
30
30
  schema: deployPermissionsOptions.schema,
31
31
  replicaFile: {
32
32
  ...zeroOptions.replica.file,
33
- desc: [`File path to the SQLite replica to test queries against.`]
33
+ desc: ["File path to the SQLite replica to test queries against."]
34
34
  },
35
35
  ast: {
36
36
  type: valita_exports.string().optional(),
@@ -38,15 +38,15 @@ var cfg = parseOptions({
38
38
  },
39
39
  query: {
40
40
  type: valita_exports.string().optional(),
41
- desc: [`Query to be analyzed in the form of: table.where(...).related(...).etc. `, `Only one of ast/query/hash should be provided.`]
41
+ desc: ["Query to be analyzed in the form of: table.where(...).related(...).etc. ", "Only one of ast/query/hash should be provided."]
42
42
  },
43
43
  hash: {
44
44
  type: valita_exports.string().optional(),
45
45
  desc: [
46
- `Hash of the query to be analyzed. This is used to look up the query in the database. `,
47
- `Only one of ast/query/hash should be provided.`,
48
- `You should run this script from the directory containing your .env file to reduce the amount of`,
49
- `configuration required. The .env file should contain the connection URL to the CVR database.`
46
+ "Hash of the query to be analyzed. This is used to look up the query in the database. ",
47
+ "Only one of ast/query/hash should be provided.",
48
+ "You should run this script from the directory containing your .env file to reduce the amount of",
49
+ "configuration required. The .env file should contain the connection URL to the CVR database."
50
50
  ]
51
51
  },
52
52
  applyPermissions: {
@@ -131,6 +131,7 @@ var config = {
131
131
  runtimeDebugFlags.trackRowCountsVended = true;
132
132
  runtimeDebugFlags.trackRowsVended = config.outputVendedRows;
133
133
  var lc = createLogContext({ log: config.log });
134
+ colorConsole.warn("DEPRECATED: `analyze-query` is deprecated. Please migrate to a project-specific analyze command built with `runAnalyzeCLI` from `@rocicorp/zero/analyze`.");
134
135
  if (!fs.existsSync(config.replicaFile)) {
135
136
  colorConsole.error(`Replica file ${config.replicaFile} does not exist`);
136
137
  process.exit(1);
@@ -150,7 +151,7 @@ var AnalyzeQueryDelegate = class extends QueryDelegateBase {
150
151
  if (source) return source;
151
152
  const tableSpec = mustGetTableSpec(tableSpecs, serverTableName);
152
153
  const { primaryKey } = tableSpec.tableSpec;
153
- source = new TableSource(lc, testLogConfig, db, serverTableName, tableSpec.zqlSpec, primaryKey);
154
+ source = new TableSource(lc, config.log, db, serverTableName, tableSpec.zqlSpec, primaryKey);
154
155
  sources.set(serverTableName, source);
155
156
  return source;
156
157
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bin-analyze.js","names":[],"sources":["../../../../analyze-query/src/bin-analyze.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport fs from 'node:fs';\nimport {styleText} from 'node:util';\nimport {astToZQL} from '../../ast-to-zql/src/ast-to-zql.ts';\nimport {formatOutput} from '../../ast-to-zql/src/format.ts';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {testLogConfig} from '../../otel/src/test-log-config.ts';\nimport {colorConsole, createLogContext} from '../../shared/src/logging.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {\n appOptions,\n shardOptions,\n ZERO_ENV_VAR_PREFIX,\n zeroOptions,\n} from '../../zero-cache/src/config/zero-config.ts';\nimport {\n computeZqlSpecs,\n mustGetTableSpec,\n} from '../../zero-cache/src/db/lite-tables.ts';\nimport {\n deployPermissionsOptions,\n loadSchemaAndPermissions,\n} from '../../zero-cache/src/scripts/permissions.ts';\nimport {runAst} from '../../zero-cache/src/services/run-ast.ts';\nimport {pgClient} from '../../zero-cache/src/types/pg.ts';\nimport {getShardID, upstreamSchema} from '../../zero-cache/src/types/shards.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport {type AST} from '../../zero-protocol/src/ast.ts';\nimport {clientSchemaFrom} from '../../zero-schema/src/builder/schema-builder.ts';\nimport {clientToServer} from '../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {\n Debug,\n runtimeDebugFlags,\n} from '../../zql/src/builder/debug-delegate.ts';\nimport type {Source} from '../../zql/src/ivm/source.ts';\nimport {QueryDelegateBase} from '../../zql/src/query/query-delegate-base.ts';\nimport {newQuery} from '../../zql/src/query/query-impl.ts';\nimport {asQueryInternals} from '../../zql/src/query/query-internals.ts';\nimport type {PullRow, Query} from '../../zql/src/query/query.ts';\nimport {Database} from '../../zqlite/src/db.ts';\nimport {TableSource} from '../../zqlite/src/table-source.ts';\nimport {explainQueries} from './explain-queries.ts';\n\nconst options = {\n schema: deployPermissionsOptions.schema,\n replicaFile: {\n ...zeroOptions.replica.file,\n desc: [`File path to the SQLite replica to test queries against.`],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'AST for the query to be analyzed. Only one of ast/query/hash should be provided.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n `Query to be analyzed in the form of: table.where(...).related(...).etc. `,\n `Only one of ast/query/hash should be provided.`,\n ],\n },\n hash: {\n type: v.string().optional(),\n desc: [\n `Hash of the query to be analyzed. This is used to look up the query in the database. `,\n `Only one of ast/query/hash should be provided.`,\n `You should run this script from the directory containing your .env file to reduce the amount of`,\n `configuration required. The .env file should contain the connection URL to the CVR database.`,\n ],\n },\n applyPermissions: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to apply permissions (from your schema file) to the provided query.',\n ],\n },\n authData: {\n type: v.string().optional(),\n desc: [\n 'JSON encoded payload of the auth data.',\n 'This will be used to fill permission variables if the \"applyPermissions\" option is set',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to output the rows which were read from the replica in order to execute the analyzed query. ',\n 'If the same row is read more than once it will be logged once for each time it was read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to output the rows which would be synced to the client for the analyzed query.',\n ],\n },\n cvr: {\n db: {\n type: v.string().optional(),\n desc: [\n 'Connection URL to the CVR database. If using --hash, either this or --upstream-db',\n 'must be specified.',\n ],\n },\n },\n upstream: {\n db: {\n desc: [\n `Connection URL to the \"upstream\" authoritative postgres database. If using --hash, `,\n 'either this or --cvr-db must be specified.',\n ],\n type: v.string().optional(),\n },\n type: zeroOptions.upstream.type,\n },\n app: appOptions,\n shard: shardOptions,\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\nconst cfg = parseOptions(options, {\n // the command line parses drops all text after the first newline\n // so we need to replace newlines with spaces\n // before parsing\n argv: process.argv.slice(2).map(s => s.replaceAll('\\n', ' ')),\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query',\n content: `Analyze a ZQL query and show information about how it runs against a SQLite replica.\n\n analyze-query uses the same environment variables and flags as zero-cache-dev. If run from your development environment, it will pick up your ZERO_REPLICA_FILE, ZERO_SCHEMA_PATH, and other env vars automatically.\n\n If run in another environment (e.g., production) you will have to specify these flags. In particular, you must have a copy of the appropriate Zero schema file to give to the --schema-path flag.`,\n },\n {\n header: 'Examples',\n content: `# In development\n npx analyze-query --query='issue.related(\"comments\").limit(10)'\n npx analyze-query --ast='\\\\{\"table\": \"artist\",\"limit\": 10\\\\}'\n npx analyze-query --hash=1234567890\n\n # In production\n # First copy schema.ts to your production environment, then run:\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --ast='\\\\{\"table\": \"artist\",\"limit\": 10\\\\}'\n\n # cvr-db is required when using the hash option.\n # It is typically the same as your upstream db.\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --cvr-db='postgres://user:pass@host:port/db' \\\\\n --hash=1234567890\n `,\n },\n ],\n});\nconst config = {\n ...cfg,\n cvr: {\n ...cfg.cvr,\n db: cfg.cvr.db ?? cfg.upstream.db,\n },\n};\n\nruntimeDebugFlags.trackRowCountsVended = true;\nruntimeDebugFlags.trackRowsVended = config.outputVendedRows;\n\nconst lc = createLogContext({\n log: config.log,\n});\n\nif (!fs.existsSync(config.replicaFile)) {\n colorConsole.error(`Replica file ${config.replicaFile} does not exist`);\n process.exit(1);\n}\nconst db = new Database(lc, config.replicaFile);\n\nconst {schema, permissions} = await loadSchemaAndPermissions(\n config.schema.path,\n);\nconst clientSchema = clientSchemaFrom(schema).clientSchema;\n\nconst sources = new Map<string, TableSource>();\nconst clientToServerMapper = clientToServer(schema.tables);\nconst debug = new Debug();\nconst tableSpecs = computeZqlSpecs(lc, db, {includeBackfillingColumns: false});\n\nclass AnalyzeQueryDelegate extends QueryDelegateBase {\n readonly debug = debug;\n readonly defaultQueryComplete = true;\n\n getSource(serverTableName: string): Source | undefined {\n let source = sources.get(serverTableName);\n if (source) {\n return source;\n }\n const tableSpec = mustGetTableSpec(tableSpecs, serverTableName);\n const {primaryKey} = tableSpec.tableSpec;\n\n source = new TableSource(\n lc,\n testLogConfig,\n db,\n serverTableName,\n tableSpec.zqlSpec,\n primaryKey,\n );\n\n sources.set(serverTableName, source);\n return source;\n }\n}\n\nconst host = new AnalyzeQueryDelegate();\n\nlet result: AnalyzeQueryResult;\n\nif (config.ast) {\n // the user likely has a transformed AST since the wire and storage formats are the transformed AST\n result = await runAst(\n lc,\n clientSchema,\n JSON.parse(config.ast),\n true,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n} else if (config.query) {\n result = await runQuery(config.query);\n} else if (config.hash) {\n result = await runHash(config.hash);\n} else {\n colorConsole.error('No query or AST or hash provided');\n process.exit(1);\n}\n\nfunction runQuery(queryString: string): Promise<AnalyzeQueryResult> {\n const z = {\n query: Object.fromEntries(\n Object.entries(schema.tables).map(([name]) => [\n name,\n newQuery(schema, name),\n ]),\n ),\n };\n\n const f = new Function('z', `return z.query.${queryString};`);\n const q: Query<string, Schema, PullRow<string, Schema>> = f(z);\n\n const ast = asQueryInternals(q).ast;\n return runAst(\n lc,\n clientSchema,\n ast,\n false,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n}\n\nasync function runHash(hash: string) {\n const cvrDB = pgClient(\n lc,\n must(config.cvr.db, 'CVR DB must be provided when using the hash option'),\n 'analyze-query-hash-cvr',\n );\n\n const rows = await cvrDB`select \"clientAST\", \"internal\" from ${cvrDB(\n upstreamSchema(getShardID(config)) + '/cvr',\n )}.\"queries\" where \"queryHash\" = ${must(hash)} limit 1;`;\n await cvrDB.end();\n\n colorConsole.log('ZQL from Hash:');\n const ast = rows[0].clientAST as AST;\n colorConsole.log(await formatOutput(ast.table + astToZQL(ast)));\n\n return runAst(\n lc,\n clientSchema,\n ast,\n true,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n}\n\nif (config.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\ncolorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\ncolorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n);\nshowStats();\nif (config.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const source of sources.values()) {\n colorConsole.log(\n styleText('bold', `${source.tableSchema.name}:`),\n debug.getVendedRows()?.[source.tableSchema.name] ?? {},\n );\n }\n}\n\ncolorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n);\nconst nvisitCounts = debug.getNVisitCounts();\nlet totalNVisit = 0;\nfor (const [table, queries] of Object.entries(nvisitCounts)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n}\ncolorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n);\n\ncolorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\nconst plans = explainQueries(debug.getVendedRowCounts() ?? {}, db);\nfor (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\nfunction showStats() {\n let totalRowsConsidered = 0;\n for (const source of sources.values()) {\n const values = Object.values(\n debug.getVendedRowCounts()?.[source.tableSchema.name] ?? {},\n );\n for (const v of values) {\n totalRowsConsidered += v;\n }\n colorConsole.log(\n styleText('bold', source.tableSchema.name + ' vended:'),\n debug.getVendedRowCounts()?.[source.tableSchema.name] ?? {},\n );\n }\n\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsConsidered),\n );\n colorConsole.log(\n styleText('bold', 'time:'),\n colorTime(result.end - result.start),\n 'ms',\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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"}
1
+ {"version":3,"file":"bin-analyze.js","names":[],"sources":["../../../../analyze-query/src/bin-analyze.ts"],"sourcesContent":["import '../../shared/src/dotenv.ts';\n\nimport fs from 'node:fs';\nimport {styleText} from 'node:util';\nimport {astToZQL} from '../../ast-to-zql/src/ast-to-zql.ts';\nimport {formatOutput} from '../../ast-to-zql/src/format.ts';\nimport {logLevel, logOptions} from '../../otel/src/log-options.ts';\nimport {colorConsole, createLogContext} from '../../shared/src/logging.ts';\nimport {must} from '../../shared/src/must.ts';\nimport {parseOptions} from '../../shared/src/options.ts';\nimport * as v from '../../shared/src/valita.ts';\nimport {\n appOptions,\n shardOptions,\n ZERO_ENV_VAR_PREFIX,\n zeroOptions,\n} from '../../zero-cache/src/config/zero-config.ts';\nimport {\n computeZqlSpecs,\n mustGetTableSpec,\n} from '../../zero-cache/src/db/lite-tables.ts';\nimport {\n deployPermissionsOptions,\n loadSchemaAndPermissions,\n} from '../../zero-cache/src/scripts/permissions.ts';\nimport {runAst} from '../../zero-cache/src/services/run-ast.ts';\nimport {pgClient} from '../../zero-cache/src/types/pg.ts';\nimport {getShardID, upstreamSchema} from '../../zero-cache/src/types/shards.ts';\nimport type {AnalyzeQueryResult} from '../../zero-protocol/src/analyze-query-result.ts';\nimport {type AST} from '../../zero-protocol/src/ast.ts';\nimport {clientSchemaFrom} from '../../zero-schema/src/builder/schema-builder.ts';\nimport {clientToServer} from '../../zero-schema/src/name-mapper.ts';\nimport type {Schema} from '../../zero-types/src/schema.ts';\nimport {\n Debug,\n runtimeDebugFlags,\n} from '../../zql/src/builder/debug-delegate.ts';\nimport type {Source} from '../../zql/src/ivm/source.ts';\nimport {QueryDelegateBase} from '../../zql/src/query/query-delegate-base.ts';\nimport {newQuery} from '../../zql/src/query/query-impl.ts';\nimport {asQueryInternals} from '../../zql/src/query/query-internals.ts';\nimport {type PullRow, type Query} from '../../zql/src/query/query.ts';\nimport {Database} from '../../zqlite/src/db.ts';\nimport {explainQueries} from '../../zqlite/src/explain-queries.ts';\nimport {TableSource} from '../../zqlite/src/table-source.ts';\n\nconst options = {\n schema: deployPermissionsOptions.schema,\n replicaFile: {\n ...zeroOptions.replica.file,\n desc: ['File path to the SQLite replica to test queries against.'],\n },\n ast: {\n type: v.string().optional(),\n desc: [\n 'AST for the query to be analyzed. Only one of ast/query/hash should be provided.',\n ],\n },\n query: {\n type: v.string().optional(),\n desc: [\n 'Query to be analyzed in the form of: table.where(...).related(...).etc. ',\n 'Only one of ast/query/hash should be provided.',\n ],\n },\n hash: {\n type: v.string().optional(),\n desc: [\n 'Hash of the query to be analyzed. This is used to look up the query in the database. ',\n 'Only one of ast/query/hash should be provided.',\n 'You should run this script from the directory containing your .env file to reduce the amount of',\n 'configuration required. The .env file should contain the connection URL to the CVR database.',\n ],\n },\n applyPermissions: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to apply permissions (from your schema file) to the provided query.',\n ],\n },\n authData: {\n type: v.string().optional(),\n desc: [\n 'JSON encoded payload of the auth data.',\n 'This will be used to fill permission variables if the \"applyPermissions\" option is set',\n ],\n },\n outputVendedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to output the rows which were read from the replica in order to execute the analyzed query. ',\n 'If the same row is read more than once it will be logged once for each time it was read.',\n ],\n },\n outputSyncedRows: {\n type: v.boolean().default(false),\n desc: [\n 'Whether to output the rows which would be synced to the client for the analyzed query.',\n ],\n },\n cvr: {\n db: {\n type: v.string().optional(),\n desc: [\n 'Connection URL to the CVR database. If using --hash, either this or --upstream-db',\n 'must be specified.',\n ],\n },\n },\n upstream: {\n db: {\n desc: [\n `Connection URL to the \"upstream\" authoritative postgres database. If using --hash, `,\n 'either this or --cvr-db must be specified.',\n ],\n type: v.string().optional(),\n },\n type: zeroOptions.upstream.type,\n },\n app: appOptions,\n shard: shardOptions,\n log: {\n ...logOptions,\n level: logLevel.default('error'),\n },\n};\n\nconst cfg = parseOptions(options, {\n argv: process.argv.slice(2).map(s => s.replaceAll('\\n', ' ')),\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n description: [\n {\n header: 'analyze-query',\n content: `Analyze a ZQL query and show information about how it runs against a SQLite replica.\n\n analyze-query uses the same environment variables and flags as zero-cache-dev. If run from your development environment, it will pick up your ZERO_REPLICA_FILE, ZERO_SCHEMA_PATH, and other env vars automatically.\n\n If run in another environment (e.g., production) you will have to specify these flags. In particular, you must have a copy of the appropriate Zero schema file to give to the --schema-path flag.`,\n },\n {\n header: 'Examples',\n content: `# In development\n npx analyze-query --query='issue.related(\"comments\").limit(10)'\n npx analyze-query --ast='\\\\{\"table\": \"artist\",\"limit\": 10\\\\}'\n npx analyze-query --hash=1234567890\n\n # In production\n # First copy schema.ts to your production environment, then run:\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --query='issue.related(\"comments\").limit(10)'\n\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --ast='\\\\{\"table\": \"artist\",\"limit\": 10\\\\}'\n\n # cvr-db is required when using the hash option.\n # It is typically the same as your upstream db.\n npx analyze-query \\\\\n --schema-path='./schema.ts' \\\\\n --replica-file='/path/to/replica.db' \\\\\n --cvr-db='postgres://user:pass@host:port/db' \\\\\n --hash=1234567890\n `,\n },\n ],\n});\nconst config = {\n ...cfg,\n cvr: {\n ...cfg.cvr,\n db: cfg.cvr.db ?? cfg.upstream.db,\n },\n};\n\nruntimeDebugFlags.trackRowCountsVended = true;\nruntimeDebugFlags.trackRowsVended = config.outputVendedRows;\n\nconst lc = createLogContext({\n log: config.log,\n});\n\ncolorConsole.warn(\n 'DEPRECATED: `analyze-query` is deprecated. Please migrate to a project-specific analyze command built with `runAnalyzeCLI` from `@rocicorp/zero/analyze`.',\n);\n\nif (!fs.existsSync(config.replicaFile)) {\n colorConsole.error(`Replica file ${config.replicaFile} does not exist`);\n process.exit(1);\n}\nconst db = new Database(lc, config.replicaFile);\n\nconst {schema, permissions} = await loadSchemaAndPermissions(\n config.schema.path,\n);\nconst clientSchema = clientSchemaFrom(schema).clientSchema;\n\nconst sources = new Map<string, TableSource>();\nconst clientToServerMapper = clientToServer(schema.tables);\nconst debug = new Debug();\nconst tableSpecs = computeZqlSpecs(lc, db, {includeBackfillingColumns: false});\n\nclass AnalyzeQueryDelegate extends QueryDelegateBase {\n readonly debug = debug;\n readonly defaultQueryComplete = true;\n\n getSource(serverTableName: string): Source | undefined {\n let source = sources.get(serverTableName);\n if (source) {\n return source;\n }\n const tableSpec = mustGetTableSpec(tableSpecs, serverTableName);\n const {primaryKey} = tableSpec.tableSpec;\n\n source = new TableSource(\n lc,\n config.log,\n db,\n serverTableName,\n tableSpec.zqlSpec,\n primaryKey,\n );\n\n sources.set(serverTableName, source);\n return source;\n }\n}\n\nconst host = new AnalyzeQueryDelegate();\n\nlet result: AnalyzeQueryResult;\n\nif (config.ast) {\n result = await runAst(\n lc,\n clientSchema,\n JSON.parse(config.ast),\n true,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n} else if (config.query) {\n result = await runQuery(config.query);\n} else if (config.hash) {\n result = await runHash(config.hash);\n} else {\n colorConsole.error('No query or AST or hash provided');\n process.exit(1);\n}\n\nfunction runQuery(queryString: string): Promise<AnalyzeQueryResult> {\n const z = {\n query: Object.fromEntries(\n Object.entries(schema.tables).map(([name]) => [\n name,\n newQuery(schema, name),\n ]),\n ),\n };\n\n const f = new Function('z', `return z.query.${queryString};`);\n const q: Query<string, Schema, PullRow<string, Schema>> = f(z);\n\n const ast = asQueryInternals(q).ast;\n return runAst(\n lc,\n clientSchema,\n ast,\n false,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n}\n\nasync function runHash(hash: string) {\n const cvrDB = pgClient(\n lc,\n must(config.cvr.db, 'CVR DB must be provided when using the hash option'),\n 'analyze-query-hash-cvr',\n );\n\n const rows = await cvrDB`select \"clientAST\", \"internal\" from ${cvrDB(\n upstreamSchema(getShardID(config)) + '/cvr',\n )}.\"queries\" where \"queryHash\" = ${must(hash)} limit 1;`;\n await cvrDB.end();\n\n colorConsole.log('ZQL from Hash:');\n const ast = rows[0].clientAST as AST;\n colorConsole.log(await formatOutput(ast.table + astToZQL(ast)));\n\n return runAst(\n lc,\n clientSchema,\n ast,\n true,\n {\n applyPermissions: config.applyPermissions,\n auth: config.authData\n ? {type: 'jwt' as const, raw: '', decoded: JSON.parse(config.authData)}\n : undefined,\n clientToServerMapper,\n permissions,\n syncedRows: config.outputSyncedRows,\n db,\n tableSpecs,\n host,\n },\n async () => {},\n );\n}\n\nif (config.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\ncolorConsole.log(styleText(['blue', 'bold'], '=== Query Stats: ===\\n'));\ncolorConsole.log(\n styleText('bold', 'total synced rows:'),\n result.syncedRowCount,\n);\nshowStats();\nif (config.outputVendedRows) {\n colorConsole.log(\n styleText(['blue', 'bold'], '=== JS Row Scan Values: ===\\n'),\n );\n for (const source of sources.values()) {\n colorConsole.log(\n styleText('bold', `${source.tableSchema.name}:`),\n debug.getVendedRows()?.[source.tableSchema.name] ?? {},\n );\n }\n}\n\ncolorConsole.log(\n styleText(['blue', 'bold'], '\\n=== Rows Scanned (by SQLite): ===\\n'),\n);\nconst nvisitCounts = debug.getNVisitCounts();\nlet totalNVisit = 0;\nfor (const [table, queries] of Object.entries(nvisitCounts)) {\n colorConsole.log(styleText('bold', `${table}:`), queries);\n for (const count of Object.values(queries)) {\n totalNVisit += count;\n }\n}\ncolorConsole.log(\n styleText('bold', 'total rows scanned:'),\n colorRowsConsidered(totalNVisit),\n);\n\ncolorConsole.log(styleText(['blue', 'bold'], '\\n\\n=== Query Plans: ===\\n'));\nconst plans = explainQueries(debug.getVendedRowCounts() ?? {}, db);\nfor (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\nfunction showStats() {\n let totalRowsConsidered = 0;\n for (const source of sources.values()) {\n const values = Object.values(\n debug.getVendedRowCounts()?.[source.tableSchema.name] ?? {},\n );\n for (const v of values) {\n totalRowsConsidered += v;\n }\n colorConsole.log(\n styleText('bold', source.tableSchema.name + ' vended:'),\n debug.getVendedRowCounts()?.[source.tableSchema.name] ?? {},\n );\n }\n\n colorConsole.log(\n styleText('bold', 'Rows Read (into JS):'),\n colorRowsConsidered(totalRowsConsidered),\n );\n colorConsole.log(\n styleText('bold', 'time:'),\n colorTime(result.end - result.start),\n 'ms',\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/kv/sqlite-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../../../../replicache/src/kv/sqlite-store.ts"],"names":[],"mappings":"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"}
@@ -138,8 +138,14 @@ var SQLiteWrite = class {
138
138
  release() {
139
139
  if (!this.#closed) {
140
140
  this.#closed = true;
141
- if (!this.#committed) this.#dbDelegate.execSync("ROLLBACK");
141
+ let rollbackError;
142
+ if (!this.#committed) try {
143
+ this.#dbDelegate.execSync("ROLLBACK");
144
+ } catch (e) {
145
+ rollbackError = e;
146
+ }
142
147
  this.#release();
148
+ if (rollbackError !== void 0) throw rollbackError;
143
149
  }
144
150
  }
145
151
  get closed() {
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.js","names":["#filename","#entry","#closed","#release","#preparedStatements","#dbDelegate","#committed"],"sources":["../../../../../replicache/src/kv/sqlite-store.ts"],"sourcesContent":["import {RWLock} from '@rocicorp/lock';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {deepFreeze} from '../frozen-json.ts';\nimport type {Read, Store, Write} from './store.ts';\nimport {\n throwIfStoreClosed,\n throwIfTransactionClosed,\n} from './throw-if-closed.ts';\n\n/**\n * A SQLite prepared statement.\n *\n * `run` executes the statement with optional parameters.\n * `all` executes the statement and returns the result rows.\n * `finalize` releases the statement.\n */\nexport interface PreparedStatement {\n firstValue(params: string[]): Promise<unknown>;\n exec(params: string[]): Promise<void>;\n}\n\nexport interface SQLiteDatabase {\n /**\n * Close the database connection.\n */\n close(): void;\n\n /**\n * Destroy or delete the database (e.g. delete file).\n */\n destroy(): void;\n\n /**\n * Prepare a SQL string, returning a statement you can execute.\n * E.g. `const stmt = db.prepare(\"SELECT * FROM todos WHERE id=?\");`\n */\n prepare(sql: string): PreparedStatement;\n\n // for PRAGMA statements, schema creation and transaction control.\n execSync(sql: string): void;\n}\n\nexport type CreateSQLiteDatabase = (\n filename: string,\n opts?: SQLiteStoreOptions,\n) => SQLiteDatabase;\n\n/**\n * SQLite-based implementation of the Store interface using a configurable delegate.\n * Supports shared connections between multiple store instances with the same name,\n * providing efficient resource utilization and proper transaction isolation.\n * Uses parameterized queries for safety and performance.\n */\nexport class SQLiteStore implements Store {\n readonly #filename: string;\n readonly #entry: StoreEntry;\n\n #closed = false;\n\n constructor(\n name: string,\n create: CreateSQLiteDatabase,\n opts?: SQLiteStoreOptions,\n ) {\n this.#filename = safeFilename(name);\n this.#entry = getOrCreateEntry(name, create, opts);\n }\n\n async read(): Promise<Read> {\n throwIfStoreClosed(this);\n\n const entry = this.#entry;\n const {db, lock, preparedStatements} = entry;\n const release = await lock.read();\n\n // Start shared read transaction if this is the first reader\n // This ensures consistent reads across all concurrent readers\n if (entry.activeReaders === 0) {\n db.execSync('BEGIN');\n }\n entry.activeReaders++;\n\n return new SQLiteStoreRead(() => {\n entry.activeReaders--;\n // Commit shared read transaction when last reader finishes\n if (entry.activeReaders === 0) {\n db.execSync('COMMIT');\n }\n release();\n }, preparedStatements);\n }\n\n async write(): Promise<Write> {\n throwIfStoreClosed(this);\n\n const {lock, db, preparedStatements} = this.#entry;\n const release = await lock.write();\n\n // At this point, RWLock guarantees no active readers\n // The last reader would have already committed the shared transaction\n\n db.execSync('BEGIN IMMEDIATE');\n\n return new SQLiteWrite(release, db, preparedStatements);\n }\n\n async close(): Promise<void> {\n if (this.#closed) {\n return;\n }\n\n const {lock, db} = this.#entry;\n // Wait for all readers and writers to finish.\n const writeRelease = await lock.write();\n\n // Handle reference counting for shared stores - only close database\n // when this is the last store instance using it\n decrementStoreRefCount(this.#filename, db);\n\n this.#closed = true;\n writeRelease();\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\nexport function safeFilename(name: string): string {\n return name.replace(/[^a-zA-Z0-9]/g, '_');\n}\n\nexport type PreparedStatements = {\n has: PreparedStatement;\n get: PreparedStatement;\n put: PreparedStatement;\n del: PreparedStatement;\n};\n\nexport interface SQLiteStoreOptions {\n // Common options\n busyTimeout?: number;\n journalMode?: 'WAL' | 'DELETE';\n synchronous?: 'NORMAL' | 'FULL';\n readUncommitted?: boolean;\n}\n\n/**\n * Common database setup logic shared between expo-sqlite and op-sqlite implementations.\n * Configures SQLite pragmas, creates the entry table, and prepares common statements.\n */\n\nexport function setupDatabase(\n delegate: SQLiteDatabase,\n opts?: SQLiteStoreOptions,\n): PreparedStatements {\n // Configure SQLite pragmas for optimal performance\n delegate.execSync(`PRAGMA busy_timeout = ${opts?.busyTimeout ?? 200}`);\n delegate.execSync(`PRAGMA journal_mode = '${opts?.journalMode ?? 'WAL'}'`);\n delegate.execSync(`PRAGMA synchronous = '${opts?.synchronous ?? 'NORMAL'}'`);\n delegate.execSync(\n `PRAGMA read_uncommitted = ${Boolean(opts?.readUncommitted)}`,\n );\n\n // Create the entry table\n delegate.execSync(`\n CREATE TABLE IF NOT EXISTS entry (\n key TEXT PRIMARY KEY, \n value TEXT NOT NULL\n ) WITHOUT ROWID\n `);\n\n // Prepare common statements\n return {\n has: delegate.prepare(`SELECT 1 FROM entry WHERE key = ? LIMIT 1`),\n get: delegate.prepare('SELECT value FROM entry WHERE key = ?'),\n put: delegate.prepare(\n 'INSERT OR REPLACE INTO entry (key, value) VALUES (?, ?)',\n ),\n del: delegate.prepare('DELETE FROM entry WHERE key = ?'),\n };\n}\n\nexport class SQLiteStoreRead implements Read {\n #release: () => void;\n #closed = false;\n #preparedStatements: PreparedStatements;\n\n constructor(release: () => void, preparedStatements: PreparedStatements) {\n this.#release = release;\n this.#preparedStatements = preparedStatements;\n }\n\n async has(key: string): Promise<boolean> {\n throwIfTransactionClosed(this);\n const value = await this.#preparedStatements.has.firstValue([key]);\n return value !== undefined;\n }\n\n async get(key: string): Promise<ReadonlyJSONValue | undefined> {\n throwIfTransactionClosed(this);\n const value = await this.#preparedStatements.get.firstValue([key]);\n if (!value) {\n return undefined;\n }\n\n const parsedValue = JSON.parse(value as string) as ReadonlyJSONValue;\n return deepFreeze(parsedValue);\n }\n\n release(): void {\n if (!this.#closed) {\n this.#closed = true;\n this.#release();\n }\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\nexport class SQLiteWrite implements Write {\n readonly #release: () => void;\n readonly #dbDelegate: SQLiteDatabase;\n readonly #preparedStatements: PreparedStatements;\n #committed = false;\n #closed = false;\n\n constructor(\n release: () => void,\n dbDelegate: SQLiteDatabase,\n preparedStatements: PreparedStatements,\n ) {\n this.#release = release;\n this.#dbDelegate = dbDelegate;\n this.#preparedStatements = preparedStatements;\n }\n\n async has(key: string): Promise<boolean> {\n throwIfTransactionClosed(this);\n const value = await this.#preparedStatements.has.firstValue([key]);\n return value !== undefined;\n }\n\n async get(key: string): Promise<ReadonlyJSONValue | undefined> {\n throwIfTransactionClosed(this);\n const value = await this.#preparedStatements.get.firstValue([key]);\n if (!value) {\n return undefined;\n }\n\n const parsedValue = JSON.parse(value as string) as ReadonlyJSONValue;\n return deepFreeze(parsedValue);\n }\n\n async put(key: string, value: ReadonlyJSONValue): Promise<void> {\n throwIfTransactionClosed(this);\n await this.#preparedStatements.put.exec([key, JSON.stringify(value)]);\n }\n\n async del(key: string): Promise<void> {\n throwIfTransactionClosed(this);\n await this.#preparedStatements.del.exec([key]);\n }\n\n // oxlint-disable-next-line require-await\n async commit(): Promise<void> {\n throwIfTransactionClosed(this);\n this.#dbDelegate.execSync('COMMIT');\n this.#committed = true;\n }\n\n release(): void {\n if (!this.#closed) {\n this.#closed = true;\n\n if (!this.#committed) {\n this.#dbDelegate.execSync('ROLLBACK');\n }\n\n this.#release();\n }\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\ntype StoreEntry = {\n readonly lock: RWLock;\n readonly db: SQLiteDatabase;\n refCount: number;\n activeReaders: number;\n preparedStatements: PreparedStatements;\n};\n\n// Global map to share database connections between multiple store instances with the same name\nconst stores = new Map<string, StoreEntry>();\n\n/**\n * Gets an existing store entry or creates a new one if it doesn't exist.\n * This implements the shared connection pattern where multiple stores with the same\n * name share the same database connection, lock, and delegate.\n */\nfunction getOrCreateEntry(\n name: string,\n create: (filename: string, opts?: SQLiteStoreOptions) => SQLiteDatabase,\n opts?: SQLiteStoreOptions,\n): StoreEntry {\n const filename = safeFilename(name);\n const entry = stores.get(filename);\n\n if (entry) {\n entry.refCount++;\n return entry;\n }\n\n const dbDelegate = create(filename, opts);\n const preparedStatements = setupDatabase(dbDelegate, opts);\n\n const lock = new RWLock();\n\n const newEntry: StoreEntry = {\n lock,\n db: dbDelegate,\n refCount: 1,\n activeReaders: 0,\n preparedStatements,\n };\n stores.set(filename, newEntry);\n return newEntry;\n}\n\n/**\n * Decrements the reference count for a shared store and cleans up resources\n * when the last reference is released.\n */\n\nfunction decrementStoreRefCount(\n filename: string,\n dbDelegate: SQLiteDatabase,\n): void {\n const entry = stores.get(filename);\n if (entry) {\n entry.refCount--;\n if (entry.refCount <= 0) {\n dbDelegate.close();\n stores.delete(filename);\n }\n }\n}\nexport function clearAllNamedStoresForTesting(): void {\n for (const entry of stores.values()) {\n entry.db.close();\n }\n stores.clear();\n}\n\nexport function dropStore(\n name: string,\n createDelegate: (\n filename: string,\n opts?: SQLiteStoreOptions,\n ) => SQLiteDatabase,\n): Promise<void> {\n const filename = safeFilename(name);\n const entry = stores.get(filename);\n if (entry) {\n try {\n entry.db.close();\n } catch {\n // Ignore close errors\n }\n stores.delete(filename);\n }\n\n // Create a temporary delegate to handle database deletion\n const tempDelegate = createDelegate(filename);\n try {\n // we close the db before destroying it - this\n // caused an issue with expo-sqlite since it requires this\n tempDelegate.close();\n } catch {\n // Ignore close errors\n }\n try {\n tempDelegate.destroy();\n } catch {\n // Destroy errors shouldn't be fatal; the file may already be gone or locked\n }\n\n return Promise.resolve();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-store.js","names":["#filename","#entry","#closed","#release","#preparedStatements","#dbDelegate","#committed"],"sources":["../../../../../replicache/src/kv/sqlite-store.ts"],"sourcesContent":["import {RWLock} from '@rocicorp/lock';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {deepFreeze} from '../frozen-json.ts';\nimport type {Read, Store, Write} from './store.ts';\nimport {\n throwIfStoreClosed,\n throwIfTransactionClosed,\n} from './throw-if-closed.ts';\n\n/**\n * A SQLite prepared statement.\n *\n * `run` executes the statement with optional parameters.\n * `all` executes the statement and returns the result rows.\n * `finalize` releases the statement.\n */\nexport interface PreparedStatement {\n firstValue(params: string[]): Promise<unknown>;\n exec(params: string[]): Promise<void>;\n}\n\nexport interface SQLiteDatabase {\n /**\n * Close the database connection.\n */\n close(): void;\n\n /**\n * Destroy or delete the database (e.g. delete file).\n */\n destroy(): void;\n\n /**\n * Prepare a SQL string, returning a statement you can execute.\n * E.g. `const stmt = db.prepare(\"SELECT * FROM todos WHERE id=?\");`\n */\n prepare(sql: string): PreparedStatement;\n\n // for PRAGMA statements, schema creation and transaction control.\n execSync(sql: string): void;\n}\n\nexport type CreateSQLiteDatabase = (\n filename: string,\n opts?: SQLiteStoreOptions,\n) => SQLiteDatabase;\n\n/**\n * SQLite-based implementation of the Store interface using a configurable delegate.\n * Supports shared connections between multiple store instances with the same name,\n * providing efficient resource utilization and proper transaction isolation.\n * Uses parameterized queries for safety and performance.\n */\nexport class SQLiteStore implements Store {\n readonly #filename: string;\n readonly #entry: StoreEntry;\n\n #closed = false;\n\n constructor(\n name: string,\n create: CreateSQLiteDatabase,\n opts?: SQLiteStoreOptions,\n ) {\n this.#filename = safeFilename(name);\n this.#entry = getOrCreateEntry(name, create, opts);\n }\n\n async read(): Promise<Read> {\n throwIfStoreClosed(this);\n\n const entry = this.#entry;\n const {db, lock, preparedStatements} = entry;\n const release = await lock.read();\n\n // Start shared read transaction if this is the first reader\n // This ensures consistent reads across all concurrent readers\n if (entry.activeReaders === 0) {\n db.execSync('BEGIN');\n }\n entry.activeReaders++;\n\n return new SQLiteStoreRead(() => {\n entry.activeReaders--;\n // Commit shared read transaction when last reader finishes\n if (entry.activeReaders === 0) {\n db.execSync('COMMIT');\n }\n release();\n }, preparedStatements);\n }\n\n async write(): Promise<Write> {\n throwIfStoreClosed(this);\n\n const {lock, db, preparedStatements} = this.#entry;\n const release = await lock.write();\n\n // At this point, RWLock guarantees no active readers\n // The last reader would have already committed the shared transaction\n\n db.execSync('BEGIN IMMEDIATE');\n\n return new SQLiteWrite(release, db, preparedStatements);\n }\n\n async close(): Promise<void> {\n if (this.#closed) {\n return;\n }\n\n const {lock, db} = this.#entry;\n // Wait for all readers and writers to finish.\n const writeRelease = await lock.write();\n\n // Handle reference counting for shared stores - only close database\n // when this is the last store instance using it\n decrementStoreRefCount(this.#filename, db);\n\n this.#closed = true;\n writeRelease();\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\nexport function safeFilename(name: string): string {\n return name.replace(/[^a-zA-Z0-9]/g, '_');\n}\n\nexport type PreparedStatements = {\n has: PreparedStatement;\n get: PreparedStatement;\n put: PreparedStatement;\n del: PreparedStatement;\n};\n\nexport interface SQLiteStoreOptions {\n // Common options\n busyTimeout?: number;\n journalMode?: 'WAL' | 'DELETE';\n synchronous?: 'NORMAL' | 'FULL';\n readUncommitted?: boolean;\n}\n\n/**\n * Common database setup logic shared between expo-sqlite and op-sqlite implementations.\n * Configures SQLite pragmas, creates the entry table, and prepares common statements.\n */\n\nexport function setupDatabase(\n delegate: SQLiteDatabase,\n opts?: SQLiteStoreOptions,\n): PreparedStatements {\n // Configure SQLite pragmas for optimal performance\n delegate.execSync(`PRAGMA busy_timeout = ${opts?.busyTimeout ?? 200}`);\n delegate.execSync(`PRAGMA journal_mode = '${opts?.journalMode ?? 'WAL'}'`);\n delegate.execSync(`PRAGMA synchronous = '${opts?.synchronous ?? 'NORMAL'}'`);\n delegate.execSync(\n `PRAGMA read_uncommitted = ${Boolean(opts?.readUncommitted)}`,\n );\n\n // Create the entry table\n delegate.execSync(`\n CREATE TABLE IF NOT EXISTS entry (\n key TEXT PRIMARY KEY, \n value TEXT NOT NULL\n ) WITHOUT ROWID\n `);\n\n // Prepare common statements\n return {\n has: delegate.prepare(`SELECT 1 FROM entry WHERE key = ? LIMIT 1`),\n get: delegate.prepare('SELECT value FROM entry WHERE key = ?'),\n put: delegate.prepare(\n 'INSERT OR REPLACE INTO entry (key, value) VALUES (?, ?)',\n ),\n del: delegate.prepare('DELETE FROM entry WHERE key = ?'),\n };\n}\n\nexport class SQLiteStoreRead implements Read {\n #release: () => void;\n #closed = false;\n #preparedStatements: PreparedStatements;\n\n constructor(release: () => void, preparedStatements: PreparedStatements) {\n this.#release = release;\n this.#preparedStatements = preparedStatements;\n }\n\n async has(key: string): Promise<boolean> {\n throwIfTransactionClosed(this);\n const value = await this.#preparedStatements.has.firstValue([key]);\n return value !== undefined;\n }\n\n async get(key: string): Promise<ReadonlyJSONValue | undefined> {\n throwIfTransactionClosed(this);\n const value = await this.#preparedStatements.get.firstValue([key]);\n if (!value) {\n return undefined;\n }\n\n const parsedValue = JSON.parse(value as string) as ReadonlyJSONValue;\n return deepFreeze(parsedValue);\n }\n\n release(): void {\n if (!this.#closed) {\n this.#closed = true;\n this.#release();\n }\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\nexport class SQLiteWrite implements Write {\n readonly #release: () => void;\n readonly #dbDelegate: SQLiteDatabase;\n readonly #preparedStatements: PreparedStatements;\n #committed = false;\n #closed = false;\n\n constructor(\n release: () => void,\n dbDelegate: SQLiteDatabase,\n preparedStatements: PreparedStatements,\n ) {\n this.#release = release;\n this.#dbDelegate = dbDelegate;\n this.#preparedStatements = preparedStatements;\n }\n\n async has(key: string): Promise<boolean> {\n throwIfTransactionClosed(this);\n const value = await this.#preparedStatements.has.firstValue([key]);\n return value !== undefined;\n }\n\n async get(key: string): Promise<ReadonlyJSONValue | undefined> {\n throwIfTransactionClosed(this);\n const value = await this.#preparedStatements.get.firstValue([key]);\n if (!value) {\n return undefined;\n }\n\n const parsedValue = JSON.parse(value as string) as ReadonlyJSONValue;\n return deepFreeze(parsedValue);\n }\n\n async put(key: string, value: ReadonlyJSONValue): Promise<void> {\n throwIfTransactionClosed(this);\n await this.#preparedStatements.put.exec([key, JSON.stringify(value)]);\n }\n\n async del(key: string): Promise<void> {\n throwIfTransactionClosed(this);\n await this.#preparedStatements.del.exec([key]);\n }\n\n // oxlint-disable-next-line require-await\n async commit(): Promise<void> {\n throwIfTransactionClosed(this);\n this.#dbDelegate.execSync('COMMIT');\n this.#committed = true;\n }\n\n release(): void {\n if (!this.#closed) {\n this.#closed = true;\n let rollbackError: unknown;\n if (!this.#committed) {\n try {\n this.#dbDelegate.execSync('ROLLBACK');\n } catch (e) {\n rollbackError = e;\n }\n }\n\n this.#release();\n if (rollbackError !== undefined) {\n throw rollbackError;\n }\n }\n }\n\n get closed(): boolean {\n return this.#closed;\n }\n}\n\ntype StoreEntry = {\n readonly lock: RWLock;\n readonly db: SQLiteDatabase;\n refCount: number;\n activeReaders: number;\n preparedStatements: PreparedStatements;\n};\n\n// Global map to share database connections between multiple store instances with the same name\nconst stores = new Map<string, StoreEntry>();\n\n/**\n * Gets an existing store entry or creates a new one if it doesn't exist.\n * This implements the shared connection pattern where multiple stores with the same\n * name share the same database connection, lock, and delegate.\n */\nfunction getOrCreateEntry(\n name: string,\n create: (filename: string, opts?: SQLiteStoreOptions) => SQLiteDatabase,\n opts?: SQLiteStoreOptions,\n): StoreEntry {\n const filename = safeFilename(name);\n const entry = stores.get(filename);\n\n if (entry) {\n entry.refCount++;\n return entry;\n }\n\n const dbDelegate = create(filename, opts);\n const preparedStatements = setupDatabase(dbDelegate, opts);\n\n const lock = new RWLock();\n\n const newEntry: StoreEntry = {\n lock,\n db: dbDelegate,\n refCount: 1,\n activeReaders: 0,\n preparedStatements,\n };\n stores.set(filename, newEntry);\n return newEntry;\n}\n\n/**\n * Decrements the reference count for a shared store and cleans up resources\n * when the last reference is released.\n */\n\nfunction decrementStoreRefCount(\n filename: string,\n dbDelegate: SQLiteDatabase,\n): void {\n const entry = stores.get(filename);\n if (entry) {\n entry.refCount--;\n if (entry.refCount <= 0) {\n dbDelegate.close();\n stores.delete(filename);\n }\n }\n}\nexport function clearAllNamedStoresForTesting(): void {\n for (const entry of stores.values()) {\n entry.db.close();\n }\n stores.clear();\n}\n\nexport function dropStore(\n name: string,\n createDelegate: (\n filename: string,\n opts?: SQLiteStoreOptions,\n ) => SQLiteDatabase,\n): Promise<void> {\n const filename = safeFilename(name);\n const entry = stores.get(filename);\n if (entry) {\n try {\n entry.db.close();\n } catch {\n // Ignore close errors\n }\n stores.delete(filename);\n }\n\n // Create a temporary delegate to handle database deletion\n const tempDelegate = createDelegate(filename);\n try {\n // we close the db before destroying it - this\n // caused an issue with expo-sqlite since it requires this\n tempDelegate.close();\n } catch {\n // Ignore close errors\n }\n try {\n tempDelegate.destroy();\n } catch {\n // Destroy errors shouldn't be fatal; the file may already be gone or locked\n }\n\n return Promise.resolve();\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"with-transactions.d.ts","sourceRoot":"","sources":["../../../../replicache/src/with-transactions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,MAAM;IACrB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,UAAU,SAAS,CAAC,IAAI,SAAS,OAAO;IACtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,UAAU,UAAU,CAAC,KAAK,SAAS,OAAO;IACxC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;CACzB;AAED,wBAAgB,QAAQ,CAAC,IAAI,SAAS,OAAO,EAAE,MAAM,EACnD,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,EACtB,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAC3C,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAgB,yBAAyB,CAAC,KAAK,SAAS,OAAO,EAAE,MAAM,EACrE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EACxB,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAC7C,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAgB,SAAS,CAAC,KAAK,SAAS,OAAO,GAAG,MAAM,EAAE,MAAM,EAC9D,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EACxB,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAC7C,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,MAAM,EACpD,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,EACd,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GACvC,OAAO,CAAC,MAAM,CAAC,CAOjB"}
1
+ {"version":3,"file":"with-transactions.d.ts","sourceRoot":"","sources":["../../../../replicache/src/with-transactions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,MAAM;IACrB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,UAAU,SAAS,CAAC,IAAI,SAAS,OAAO;IACtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,UAAU,UAAU,CAAC,KAAK,SAAS,OAAO;IACxC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;CACzB;AAED,wBAAgB,QAAQ,CAAC,IAAI,SAAS,OAAO,EAAE,MAAM,EACnD,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,EACtB,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAC3C,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAgB,yBAAyB,CAAC,KAAK,SAAS,OAAO,EAAE,MAAM,EACrE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EACxB,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAC7C,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAgB,SAAS,CAAC,KAAK,SAAS,OAAO,GAAG,MAAM,EAAE,MAAM,EAC9D,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EACxB,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAC7C,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,MAAM,EACpD,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,EACd,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GACvC,OAAO,CAAC,MAAM,CAAC,CA6BjB"}
@@ -19,11 +19,25 @@ function withWrite(store, fn) {
19
19
  */
20
20
  async function using(x, fn) {
21
21
  const write = await x;
22
+ let result;
23
+ let operationError;
24
+ try {
25
+ result = await fn(write);
26
+ } catch (e) {
27
+ operationError = e;
28
+ }
22
29
  try {
23
- return await fn(write);
24
- } finally {
25
30
  write.release();
31
+ } catch (releaseError) {
32
+ if (operationError !== void 0) {
33
+ const combinedError = /* @__PURE__ */ new Error(`Transaction operation failed and release also failed: operation error = ${String(operationError)}; release error = ${String(releaseError)}`);
34
+ combinedError.cause = operationError;
35
+ throw combinedError;
36
+ }
37
+ throw releaseError;
26
38
  }
39
+ if (operationError !== void 0) throw operationError;
40
+ return result;
27
41
  }
28
42
  //#endregion
29
43
  export { using, withRead, withWrite, withWriteNoImplicitCommit };
@@ -1 +1 @@
1
- {"version":3,"file":"with-transactions.js","names":[],"sources":["../../../../replicache/src/with-transactions.ts"],"sourcesContent":["export interface Release {\n release(): void;\n}\n\nexport interface Commit {\n commit(): Promise<void>;\n}\n\ninterface ReadStore<Read extends Release> {\n read(): Promise<Read>;\n}\n\ninterface WriteStore<Write extends Release> {\n write(): Promise<Write>;\n}\n\nexport function withRead<Read extends Release, Return>(\n store: ReadStore<Read>,\n fn: (read: Read) => Return | Promise<Return>,\n): Promise<Return> {\n return using(store.read(), fn);\n}\n\nexport function withWriteNoImplicitCommit<Write extends Release, Return>(\n store: WriteStore<Write>,\n fn: (write: Write) => Return | Promise<Return>,\n): Promise<Return> {\n return using(store.write(), fn);\n}\n\nexport function withWrite<Write extends Release & Commit, Return>(\n store: WriteStore<Write>,\n fn: (write: Write) => Return | Promise<Return>,\n): Promise<Return> {\n return using(store.write(), async write => {\n const result = await fn(write);\n await write.commit();\n return result;\n });\n}\n\n/**\n * This function takes a promise for a resource and a function that uses that\n * resource. It will release the resource after the function returns by calling\n * the `release` function\n */\nexport async function using<TX extends Release, Return>(\n x: Promise<TX>,\n fn: (tx: TX) => Return | Promise<Return>,\n): Promise<Return> {\n const write = await x;\n try {\n return await fn(write);\n } finally {\n write.release();\n }\n}\n"],"mappings":";AAgBA,SAAgB,SACd,OACA,IACiB;AACjB,QAAO,MAAM,MAAM,MAAM,EAAE,GAAG;;AAGhC,SAAgB,0BACd,OACA,IACiB;AACjB,QAAO,MAAM,MAAM,OAAO,EAAE,GAAG;;AAGjC,SAAgB,UACd,OACA,IACiB;AACjB,QAAO,MAAM,MAAM,OAAO,EAAE,OAAM,UAAS;EACzC,MAAM,SAAS,MAAM,GAAG,MAAM;AAC9B,QAAM,MAAM,QAAQ;AACpB,SAAO;GACP;;;;;;;AAQJ,eAAsB,MACpB,GACA,IACiB;CACjB,MAAM,QAAQ,MAAM;AACpB,KAAI;AACF,SAAO,MAAM,GAAG,MAAM;WACd;AACR,QAAM,SAAS"}
1
+ {"version":3,"file":"with-transactions.js","names":[],"sources":["../../../../replicache/src/with-transactions.ts"],"sourcesContent":["export interface Release {\n release(): void;\n}\n\nexport interface Commit {\n commit(): Promise<void>;\n}\n\ninterface ReadStore<Read extends Release> {\n read(): Promise<Read>;\n}\n\ninterface WriteStore<Write extends Release> {\n write(): Promise<Write>;\n}\n\nexport function withRead<Read extends Release, Return>(\n store: ReadStore<Read>,\n fn: (read: Read) => Return | Promise<Return>,\n): Promise<Return> {\n return using(store.read(), fn);\n}\n\nexport function withWriteNoImplicitCommit<Write extends Release, Return>(\n store: WriteStore<Write>,\n fn: (write: Write) => Return | Promise<Return>,\n): Promise<Return> {\n return using(store.write(), fn);\n}\n\nexport function withWrite<Write extends Release & Commit, Return>(\n store: WriteStore<Write>,\n fn: (write: Write) => Return | Promise<Return>,\n): Promise<Return> {\n return using(store.write(), async write => {\n const result = await fn(write);\n await write.commit();\n return result;\n });\n}\n\n/**\n * This function takes a promise for a resource and a function that uses that\n * resource. It will release the resource after the function returns by calling\n * the `release` function\n */\nexport async function using<TX extends Release, Return>(\n x: Promise<TX>,\n fn: (tx: TX) => Return | Promise<Return>,\n): Promise<Return> {\n const write = await x;\n let result: Return | undefined;\n let operationError: unknown;\n try {\n result = await fn(write);\n } catch (e) {\n operationError = e;\n }\n\n try {\n write.release();\n } catch (releaseError) {\n if (operationError !== undefined) {\n const combinedError = new Error(\n `Transaction operation failed and release also failed: operation error = ${String(\n operationError,\n )}; release error = ${String(releaseError)}`,\n );\n combinedError.cause = operationError;\n throw combinedError;\n }\n throw releaseError;\n }\n\n if (operationError !== undefined) {\n throw operationError;\n }\n return result as Return;\n}\n"],"mappings":";AAgBA,SAAgB,SACd,OACA,IACiB;AACjB,QAAO,MAAM,MAAM,MAAM,EAAE,GAAG;;AAGhC,SAAgB,0BACd,OACA,IACiB;AACjB,QAAO,MAAM,MAAM,OAAO,EAAE,GAAG;;AAGjC,SAAgB,UACd,OACA,IACiB;AACjB,QAAO,MAAM,MAAM,OAAO,EAAE,OAAM,UAAS;EACzC,MAAM,SAAS,MAAM,GAAG,MAAM;AAC9B,QAAM,MAAM,QAAQ;AACpB,SAAO;GACP;;;;;;;AAQJ,eAAsB,MACpB,GACA,IACiB;CACjB,MAAM,QAAQ,MAAM;CACpB,IAAI;CACJ,IAAI;AACJ,KAAI;AACF,WAAS,MAAM,GAAG,MAAM;UACjB,GAAG;AACV,mBAAiB;;AAGnB,KAAI;AACF,QAAM,SAAS;UACR,cAAc;AACrB,MAAI,mBAAmB,KAAA,GAAW;GAChC,MAAM,gCAAgB,IAAI,MACxB,2EAA2E,OACzE,eACD,CAAC,oBAAoB,OAAO,aAAa,GAC3C;AACD,iBAAc,QAAQ;AACtB,SAAM;;AAER,QAAM;;AAGR,KAAI,mBAAmB,KAAA,EACrB,OAAM;AAER,QAAO"}
@@ -1,6 +1,6 @@
1
1
  var package_default = {
2
2
  name: "@rocicorp/zero",
3
- version: "1.4.0-canary.2",
3
+ version: "1.4.0-canary.4",
4
4
  description: "Zero is a web framework for serverless web development.",
5
5
  homepage: "https://zero.rocicorp.dev",
6
6
  bugs: { "url": "https://bugs.rocicorp.dev" },
@@ -71,6 +71,10 @@ var package_default = {
71
71
  "types": "./out/zero/src/adapters/drizzle.d.ts",
72
72
  "default": "./out/zero/src/adapters/drizzle.js"
73
73
  },
74
+ "./server/adapters/kysely": {
75
+ "types": "./out/zero/src/adapters/kysely.d.ts",
76
+ "default": "./out/zero/src/adapters/kysely.js"
77
+ },
74
78
  "./server/adapters/prisma": {
75
79
  "types": "./out/zero/src/adapters/prisma.d.ts",
76
80
  "default": "./out/zero/src/adapters/prisma.js"
@@ -186,9 +190,11 @@ var package_default = {
186
190
  },
187
191
  peerDependencies: {
188
192
  "@op-engineering/op-sqlite": ">=15",
189
- "expo-sqlite": ">=15"
193
+ "expo-sqlite": ">=15",
194
+ "kysely": "^0.28.16"
190
195
  },
191
196
  peerDependenciesMeta: {
197
+ "kysely": { "optional": true },
192
198
  "expo-sqlite": { "optional": true },
193
199
  "@op-engineering/op-sqlite": { "optional": true }
194
200
  },
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.4.0-canary.2\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --type-aware src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.3.11\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.39.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.0.0\",\n \"@fastify/websocket\": \"^11.0.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/api-logs\": \"^0.203.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"^0.62.0\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"^0.203.0\",\n \"@opentelemetry/resources\": \"^2.0.1\",\n \"@opentelemetry/sdk-metrics\": \"^2.0.1\",\n \"@opentelemetry/sdk-node\": \"^0.203.0\",\n \"@opentelemetry/sdk-trace-node\": \"^2.0.1\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^5.4.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.0.17\",\n \"@standard-schema/spec\": \"^1.0.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.5.12\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.0\",\n \"chokidar\": \"^4.0.1\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.1\",\n \"command-line-usage\": \"^7.0.3\",\n \"compare-utf8\": \"^0.2.0\",\n \"defu\": \"^6.1.4\",\n \"eventemitter3\": \"^5.0.1\",\n \"fastify\": \"^5.0.0\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.9.3\",\n \"js-xxhash\": \"^4.0.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.0\",\n \"nanoid\": \"^5.1.2\",\n \"oxfmt\": \"^0.45.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.5.4\",\n \"tsx\": \"^4.21.0\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.18.1\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"@vitest/runner\": \"4.1.3\",\n \"analyze-query\": \"0.0.0\",\n \"ast-to-zql\": \"0.0.0\",\n \"expo-sqlite\": \">=15\",\n \"replicache\": \"15.2.1\",\n \"shared\": \"0.0.0\",\n \"syncpack\": \"^14.3.0\",\n \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~6.0.2\",\n \"vite\": \"8.0.3\",\n \"vitest\": \"4.1.3\",\n \"zero-cache\": \"0.0.0\",\n \"zero-client\": \"0.0.0\",\n \"zero-pg\": \"0.0.0\",\n \"zero-protocol\": \"0.0.0\",\n \"zero-react\": \"0.0.0\",\n \"zero-server\": \"0.0.0\",\n \"zero-solid\": \"0.0.0\",\n \"zqlite\": \"0.0.0\"\n },\n \"peerDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"expo-sqlite\": \">=15\"\n },\n \"peerDependenciesMeta\": {\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=22\"\n }\n}"],"mappings":""}
1
+ {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.4.0-canary.4\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/kysely\": {\n \"types\": \"./out/zero/src/adapters/kysely.d.ts\",\n \"default\": \"./out/zero/src/adapters/kysely.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --type-aware src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.3.11\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.39.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.0.0\",\n \"@fastify/websocket\": \"^11.0.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/api-logs\": \"^0.203.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"^0.62.0\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"^0.203.0\",\n \"@opentelemetry/resources\": \"^2.0.1\",\n \"@opentelemetry/sdk-metrics\": \"^2.0.1\",\n \"@opentelemetry/sdk-node\": \"^0.203.0\",\n \"@opentelemetry/sdk-trace-node\": \"^2.0.1\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^5.4.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.0.17\",\n \"@standard-schema/spec\": \"^1.0.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.5.12\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.0\",\n \"chokidar\": \"^4.0.1\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.1\",\n \"command-line-usage\": \"^7.0.3\",\n \"compare-utf8\": \"^0.2.0\",\n \"defu\": \"^6.1.4\",\n \"eventemitter3\": \"^5.0.1\",\n \"fastify\": \"^5.0.0\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.9.3\",\n \"js-xxhash\": \"^4.0.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.0\",\n \"nanoid\": \"^5.1.2\",\n \"oxfmt\": \"^0.45.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.5.4\",\n \"tsx\": \"^4.21.0\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.18.1\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"@vitest/runner\": \"4.1.3\",\n \"analyze-query\": \"0.0.0\",\n \"ast-to-zql\": \"0.0.0\",\n \"expo-sqlite\": \">=15\",\n \"replicache\": \"15.2.1\",\n \"shared\": \"0.0.0\",\n \"syncpack\": \"^14.3.0\",\n \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~6.0.2\",\n \"vite\": \"8.0.3\",\n \"vitest\": \"4.1.3\",\n \"zero-cache\": \"0.0.0\",\n \"zero-client\": \"0.0.0\",\n \"zero-pg\": \"0.0.0\",\n \"zero-protocol\": \"0.0.0\",\n \"zero-react\": \"0.0.0\",\n \"zero-server\": \"0.0.0\",\n \"zero-solid\": \"0.0.0\",\n \"zqlite\": \"0.0.0\"\n },\n \"peerDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"expo-sqlite\": \">=15\",\n \"kysely\": \"^0.28.16\"\n },\n \"peerDependenciesMeta\": {\n \"kysely\": {\n \"optional\": true\n },\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=22\"\n }\n}"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export * from '../../../zero-server/src/adapters/kysely.ts';
2
+ //# sourceMappingURL=kysely.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kysely.d.ts","sourceRoot":"","sources":["../../../../src/adapters/kysely.ts"],"names":[],"mappings":"AAAA,cAAc,6CAA6C,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { KyselyConnection, zeroKysely } from "../../../zero-server/src/adapters/kysely.js";
2
+ export { KyselyConnection, zeroKysely };
@@ -11,6 +11,7 @@ import { watch } from "chokidar";
11
11
  //#region src/zero-cache-dev.ts
12
12
  var deployPermissionsScript = "zero-deploy-permissions";
13
13
  var zeroCacheScript = "zero-cache";
14
+ var startupMessageEnv = "ZERO_ENABLE_STARTUP_MESSAGE";
14
15
  function killProcess(childProcess) {
15
16
  if (!childProcess || childProcess.exitCode !== null) return Promise.resolve();
16
17
  const { resolve, promise } = resolver();
@@ -93,6 +94,7 @@ async function main() {
93
94
  ["ZERO_CVR_MAX_CONNS"]: "6",
94
95
  ["ZERO_UPSTREAM_MAX_CONNS"]: "6",
95
96
  ["NODE_ENV"]: "development",
97
+ [startupMessageEnv]: "1",
96
98
  ...process.env,
97
99
  ...zeroCacheEnv
98
100
  },