@rocicorp/zero 1.9.0-head-ef892a12-20260713 → 1.10.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/out/zero/package.js +2 -19
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/db/sqlite-corruption.d.ts +11 -0
  4. package/out/zero-cache/src/db/sqlite-corruption.d.ts.map +1 -0
  5. package/out/zero-cache/src/db/sqlite-corruption.js +191 -0
  6. package/out/zero-cache/src/db/sqlite-corruption.js.map +1 -0
  7. package/out/zero-cache/src/server/backup-watermark-reader.js +1 -1
  8. package/out/zero-cache/src/server/backup-watermark-reader.js.map +1 -1
  9. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  10. package/out/zero-cache/src/server/change-streamer.js +6 -1
  11. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  12. package/out/zero-cache/src/server/logging.d.ts +8 -0
  13. package/out/zero-cache/src/server/logging.d.ts.map +1 -1
  14. package/out/zero-cache/src/server/logging.js +27 -5
  15. package/out/zero-cache/src/server/logging.js.map +1 -1
  16. package/out/zero-cache/src/server/main.d.ts.map +1 -1
  17. package/out/zero-cache/src/server/main.js +6 -1
  18. package/out/zero-cache/src/server/main.js.map +1 -1
  19. package/out/zero-cache/src/server/mutator.js +1 -1
  20. package/out/zero-cache/src/server/mutator.js.map +1 -1
  21. package/out/zero-cache/src/server/otel-log-sink.d.ts +7 -1
  22. package/out/zero-cache/src/server/otel-log-sink.d.ts.map +1 -1
  23. package/out/zero-cache/src/server/otel-log-sink.js +7 -2
  24. package/out/zero-cache/src/server/otel-log-sink.js.map +1 -1
  25. package/out/zero-cache/src/server/reaper.js +1 -1
  26. package/out/zero-cache/src/server/reaper.js.map +1 -1
  27. package/out/zero-cache/src/server/replicator.d.ts.map +1 -1
  28. package/out/zero-cache/src/server/replicator.js +11 -1
  29. package/out/zero-cache/src/server/replicator.js.map +1 -1
  30. package/out/zero-cache/src/server/shadow-syncer.js +1 -1
  31. package/out/zero-cache/src/server/shadow-syncer.js.map +1 -1
  32. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  33. package/out/zero-cache/src/server/syncer.js +6 -1
  34. package/out/zero-cache/src/server/syncer.js.map +1 -1
  35. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  36. package/out/zero-cache/src/services/change-source/common/replica-schema.js +8 -5
  37. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  38. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  39. package/out/zero-cache/src/services/change-source/pg/change-source.js +10 -5
  40. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  41. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +15 -0
  42. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  43. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +63 -10
  44. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  45. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts +21 -0
  46. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.d.ts.map +1 -1
  47. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js +34 -5
  48. package/out/zero-cache/src/services/change-source/pg/logical-replication/stream.js.map +1 -1
  49. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts +60 -93
  50. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  51. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +15 -11
  52. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  53. package/out/zero-cache/src/services/life-cycle.d.ts +1 -1
  54. package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
  55. package/out/zero-cache/src/services/life-cycle.js +15 -3
  56. package/out/zero-cache/src/services/life-cycle.js.map +1 -1
  57. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  58. package/out/zero-cache/src/services/litestream/commands.js +20 -3
  59. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  60. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  61. package/out/zero-cache/src/services/replicator/change-processor.js +28 -15
  62. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  63. package/out/zero-cache/src/services/replicator/write-worker.js +38 -7
  64. package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
  65. package/out/zero-client/src/client/version.js +1 -1
  66. package/package.json +2 -33
@@ -1,6 +1,6 @@
1
1
  var package_default = {
2
2
  name: "@rocicorp/zero",
3
- version: "1.9.0-head-ef892a12-20260713",
3
+ version: "1.10.0-canary.0",
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" },
@@ -171,6 +171,7 @@ var package_default = {
171
171
  "analyze-query": "workspace:*",
172
172
  "ast-to-zql": "workspace:*",
173
173
  "expo-sqlite": ">=15",
174
+ "oxc-parser": "^0.129.0",
174
175
  "replicache": "workspace:*",
175
176
  "shared": "workspace:*",
176
177
  "syncpack": "^14.3.0",
@@ -187,24 +188,6 @@ var package_default = {
187
188
  "zero-solid": "workspace:*",
188
189
  "zqlite": "workspace:*"
189
190
  },
190
- peerDependencies: {
191
- "@op-engineering/op-sqlite": ">=15",
192
- "drizzle-orm": "^0.45.2",
193
- "expo-sqlite": ">=15",
194
- "kysely": "^0.28.17",
195
- "pg": "^8.16.3",
196
- "react": "^19.2.6",
197
- "solid-js": "^1.9.4"
198
- },
199
- peerDependenciesMeta: {
200
- "@op-engineering/op-sqlite": { "optional": true },
201
- "drizzle-orm": { "optional": true },
202
- "expo-sqlite": { "optional": true },
203
- "kysely": { "optional": true },
204
- "pg": { "optional": true },
205
- "react": { "optional": true },
206
- "solid-js": { "optional": true }
207
- },
208
191
  engines: { "node": ">=22" },
209
192
  packageManager: "pnpm@11.5.3"
210
193
  };
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.9.0-head-ef892a12-20260713\",\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 --quiet --config ../../oxlint.config.ts 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.218.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"^0.76.0\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"^0.218.0\",\n \"@opentelemetry/resources\": \"^2.7.1\",\n \"@opentelemetry/sdk-metrics\": \"^2.7.1\",\n \"@opentelemetry/sdk-node\": \"^0.218.0\",\n \"@opentelemetry/sdk-trace-node\": \"^2.7.1\",\n \"@opentelemetry/semantic-conventions\": \"^1.41.1\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^6.1.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.1.2\",\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 \"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.6\",\n \"analyze-query\": \"workspace:*\",\n \"ast-to-zql\": \"workspace:*\",\n \"expo-sqlite\": \">=15\",\n \"replicache\": \"workspace:*\",\n \"shared\": \"workspace:*\",\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.13\",\n \"vitest\": \"^4.1.6\",\n \"zero-cache\": \"workspace:*\",\n \"zero-client\": \"workspace:*\",\n \"zero-pg\": \"workspace:*\",\n \"zero-react\": \"workspace:*\",\n \"zero-server\": \"workspace:*\",\n \"zero-solid\": \"workspace:*\",\n \"zqlite\": \"workspace:*\"\n },\n \"peerDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"drizzle-orm\": \"^0.45.2\",\n \"expo-sqlite\": \">=15\",\n \"kysely\": \"^0.28.17\",\n \"pg\": \"^8.16.3\",\n \"react\": \"^19.2.6\",\n \"solid-js\": \"^1.9.4\"\n },\n \"peerDependenciesMeta\": {\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n },\n \"drizzle-orm\": {\n \"optional\": true\n },\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"kysely\": {\n \"optional\": true\n },\n \"pg\": {\n \"optional\": true\n },\n \"react\": {\n \"optional\": true\n },\n \"solid-js\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@11.5.3\"\n}\n"],"mappings":""}
1
+ {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.10.0-canary.0\",\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 --quiet --config ../../oxlint.config.ts 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.218.0\",\n \"@opentelemetry/auto-instrumentations-node\": \"^0.76.0\",\n \"@opentelemetry/exporter-metrics-otlp-http\": \"^0.218.0\",\n \"@opentelemetry/resources\": \"^2.7.1\",\n \"@opentelemetry/sdk-metrics\": \"^2.7.1\",\n \"@opentelemetry/sdk-node\": \"^0.218.0\",\n \"@opentelemetry/sdk-trace-node\": \"^2.7.1\",\n \"@opentelemetry/semantic-conventions\": \"^1.41.1\",\n \"@postgresql-typed/oids\": \"^0.2.0\",\n \"@rocicorp/lock\": \"^1.0.4\",\n \"@rocicorp/logger\": \"^6.1.0\",\n \"@rocicorp/resolver\": \"^1.0.2\",\n \"@rocicorp/zero-sqlite3\": \"^1.1.2\",\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 \"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.6\",\n \"analyze-query\": \"workspace:*\",\n \"ast-to-zql\": \"workspace:*\",\n \"expo-sqlite\": \">=15\",\n \"oxc-parser\": \"^0.129.0\",\n \"replicache\": \"workspace:*\",\n \"shared\": \"workspace:*\",\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.13\",\n \"vitest\": \"^4.1.6\",\n \"zero-cache\": \"workspace:*\",\n \"zero-client\": \"workspace:*\",\n \"zero-pg\": \"workspace:*\",\n \"zero-react\": \"workspace:*\",\n \"zero-server\": \"workspace:*\",\n \"zero-solid\": \"workspace:*\",\n \"zqlite\": \"workspace:*\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"packageManager\": \"pnpm@11.5.3\"\n}\n"],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import type { LogContext } from '@rocicorp/logger';
2
+ type DiagnosticTarget = {
3
+ readonly debugName: string;
4
+ readonly dbPath: string;
5
+ };
6
+ export declare function isSQLiteCorruption(e: unknown): boolean;
7
+ export declare function registerSQLiteCorruptionDiagnosticTarget(target: DiagnosticTarget): () => void;
8
+ export declare function logLastChanceSQLiteCorruptionDiagnostics(lc: LogContext, cause: unknown): boolean;
9
+ export declare function logSQLiteCorruptionDiagnostics(lc: LogContext, debugName: string, dbPath: string, cause: unknown): void;
10
+ export {};
11
+ //# sourceMappingURL=sqlite-corruption.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-corruption.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/db/sqlite-corruption.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAiCjD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAKF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED,wBAAgB,wCAAwC,CACtD,MAAM,EAAE,gBAAgB,GACvB,MAAM,IAAI,CAIZ;AAED,wBAAgB,wCAAwC,CACtD,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,GACb,OAAO,CAQT;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,UAAU,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GACb,IAAI,CA+CN"}
@@ -0,0 +1,191 @@
1
+ import { Database } from "../../../zqlite/src/db.js";
2
+ import { SqliteError } from "@rocicorp/zero-sqlite3";
3
+ import { existsSync, statSync } from "node:fs";
4
+ //#region ../zero-cache/src/db/sqlite-corruption.ts
5
+ var SQLITE_CORRUPTION_CODES = new Set(["SQLITE_CORRUPT", "SQLITE_NOTADB"]);
6
+ var SQLITE_CORRUPTION_MESSAGE = /database disk image is malformed|file is not a database|database is malformed|malformed database/i;
7
+ var SQLITE_FILE_SUFFIXES = [
8
+ "",
9
+ "-wal",
10
+ "-wal2",
11
+ "-shm",
12
+ "-journal"
13
+ ];
14
+ var MAX_LOGGED_CHECK_ISSUES = 20;
15
+ var diagnosticTargets = /* @__PURE__ */ new Map();
16
+ var nextDiagnosticTargetID = 0;
17
+ function isSQLiteCorruption(e) {
18
+ return getSQLiteCorruptionCode(e) !== void 0;
19
+ }
20
+ function registerSQLiteCorruptionDiagnosticTarget(target) {
21
+ const id = nextDiagnosticTargetID++;
22
+ diagnosticTargets.set(id, target);
23
+ return () => diagnosticTargets.delete(id);
24
+ }
25
+ function logLastChanceSQLiteCorruptionDiagnostics(lc, cause) {
26
+ if (!isSQLiteCorruption(cause) || diagnosticTargets.size === 0) return false;
27
+ for (const { debugName, dbPath } of diagnosticTargets.values()) logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause);
28
+ return true;
29
+ }
30
+ function logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause) {
31
+ const start = Date.now();
32
+ lc.error?.("SQLite replica corruption detected", {
33
+ debugName,
34
+ dbPath,
35
+ cause: summarizeError(cause)
36
+ });
37
+ logDiagnostic(lc, debugName, "file-stats", () => ({ files: getSQLiteFileStats(dbPath) }));
38
+ let db;
39
+ try {
40
+ db = new Database(lc, dbPath, { readonly: true });
41
+ db.pragma("busy_timeout = 1000");
42
+ } catch (e) {
43
+ lc.error?.("SQLite corruption diagnostic failed", {
44
+ debugName,
45
+ step: "open-readonly",
46
+ durationMs: Date.now() - start,
47
+ error: summarizeError(e)
48
+ });
49
+ return;
50
+ }
51
+ try {
52
+ logDiagnostic(lc, debugName, "basic-pragmas", () => ({ pragmas: getBasicPragmas(db) }));
53
+ logDiagnostic(lc, debugName, "replica-metadata", () => ({ metadata: getReplicaMetadata(db) }));
54
+ logDiagnostic(lc, debugName, "quick-check", () => ({ check: runCheck(db, "quick_check") }));
55
+ logDiagnostic(lc, debugName, "integrity-check", () => ({ check: runCheck(db, "integrity_check") }));
56
+ lc.error?.("SQLite corruption diagnostics completed", {
57
+ debugName,
58
+ dbPath,
59
+ durationMs: Date.now() - start
60
+ });
61
+ } finally {
62
+ db.close();
63
+ }
64
+ }
65
+ function getSQLiteCorruptionCode(e) {
66
+ let current = e;
67
+ const seen = /* @__PURE__ */ new Set();
68
+ while (current !== void 0 && current !== null && !seen.has(current)) {
69
+ seen.add(current);
70
+ const code = getErrorCode(current);
71
+ if (code !== void 0 && SQLITE_CORRUPTION_CODES.has(code)) return code;
72
+ if (current instanceof SqliteError || current instanceof Error) {
73
+ if (SQLITE_CORRUPTION_MESSAGE.test(current.message)) return code ?? "SQLITE_CORRUPT";
74
+ current = getErrorCause(current);
75
+ continue;
76
+ }
77
+ break;
78
+ }
79
+ }
80
+ function getErrorCode(e) {
81
+ if (e && typeof e === "object" && "code" in e) {
82
+ const code = e.code;
83
+ return typeof code === "string" ? code : void 0;
84
+ }
85
+ }
86
+ function getErrorCause(e) {
87
+ return e.cause;
88
+ }
89
+ function summarizeError(e, depth = 0) {
90
+ if (!(e instanceof Error)) return String(e);
91
+ const cause = getErrorCause(e);
92
+ return {
93
+ name: e.name,
94
+ message: e.message,
95
+ code: getErrorCode(e),
96
+ cause: cause === void 0 || depth >= 4 ? void 0 : summarizeError(cause, depth + 1)
97
+ };
98
+ }
99
+ function getSQLiteFileStats(dbPath) {
100
+ const stats = [];
101
+ for (const suffix of SQLITE_FILE_SUFFIXES) {
102
+ const path = `${dbPath}${suffix}`;
103
+ if (!existsSync(path)) continue;
104
+ const stat = statSync(path);
105
+ stats.push({
106
+ suffix,
107
+ path,
108
+ sizeBytes: stat.size,
109
+ mtimeMs: stat.mtimeMs
110
+ });
111
+ }
112
+ return stats;
113
+ }
114
+ function getBasicPragmas(db) {
115
+ return {
116
+ pageSize: firstPragmaValue(db, "page_size", "page_size"),
117
+ pageCount: firstPragmaValue(db, "page_count", "page_count"),
118
+ freelistCount: firstPragmaValue(db, "freelist_count", "freelist_count"),
119
+ journalMode: firstPragmaValue(db, "journal_mode", "journal_mode"),
120
+ schemaVersion: firstPragmaValue(db, "schema_version", "schema_version"),
121
+ userVersion: firstPragmaValue(db, "user_version", "user_version"),
122
+ applicationID: firstPragmaValue(db, "application_id", "application_id")
123
+ };
124
+ }
125
+ function getReplicaMetadata(db) {
126
+ return {
127
+ versionHistory: queryDiagnostic(() => db.prepare(`SELECT dataVersion, schemaVersion, minSafeVersion
128
+ FROM "_zero.versionHistory"`).get()),
129
+ subscriptionState: queryDiagnostic(() => db.prepare(`SELECT c.replicaVersion, s.stateVersion as watermark
130
+ FROM "_zero.replicationConfig" as c
131
+ JOIN "_zero.replicationState" as s
132
+ ON c.lock = s.lock`).get()),
133
+ sqliteCatalogCounts: queryDiagnostic(() => db.prepare(`SELECT type, count(*) as count
134
+ FROM sqlite_master
135
+ GROUP BY type
136
+ ORDER BY type`).all())
137
+ };
138
+ }
139
+ function queryDiagnostic(query) {
140
+ try {
141
+ return {
142
+ ok: true,
143
+ value: query()
144
+ };
145
+ } catch (e) {
146
+ return {
147
+ ok: false,
148
+ error: summarizeError(e)
149
+ };
150
+ }
151
+ }
152
+ function firstPragmaValue(db, pragma, column) {
153
+ const [row] = db.pragma(pragma);
154
+ return row?.[column];
155
+ }
156
+ function runCheck(db, pragma) {
157
+ const rows = db.pragma(pragma);
158
+ const issues = rows.map((row) => row[pragma] ?? Object.values(row)[0] ?? "").filter((issue) => issue !== "" && issue !== "ok");
159
+ const loggedIssues = issues.slice(0, MAX_LOGGED_CHECK_ISSUES);
160
+ return {
161
+ status: issues.length === 0 ? "ok" : "failed",
162
+ rowCount: rows.length,
163
+ issueCount: issues.length,
164
+ loggedIssueCount: loggedIssues.length,
165
+ truncated: issues.length > loggedIssues.length,
166
+ issues: loggedIssues
167
+ };
168
+ }
169
+ function logDiagnostic(lc, debugName, step, fn) {
170
+ const start = Date.now();
171
+ try {
172
+ const result = fn();
173
+ lc.error?.("SQLite corruption diagnostic", {
174
+ debugName,
175
+ step,
176
+ durationMs: Date.now() - start,
177
+ result
178
+ });
179
+ } catch (e) {
180
+ lc.error?.("SQLite corruption diagnostic failed", {
181
+ debugName,
182
+ step,
183
+ durationMs: Date.now() - start,
184
+ error: summarizeError(e)
185
+ });
186
+ }
187
+ }
188
+ //#endregion
189
+ export { isSQLiteCorruption, logLastChanceSQLiteCorruptionDiagnostics, logSQLiteCorruptionDiagnostics, registerSQLiteCorruptionDiagnosticTarget };
190
+
191
+ //# sourceMappingURL=sqlite-corruption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-corruption.js","names":[],"sources":["../../../../../zero-cache/src/db/sqlite-corruption.ts"],"sourcesContent":["import {existsSync, statSync} from 'node:fs';\nimport type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {Database} from '../../../zqlite/src/db.ts';\n\nconst SQLITE_CORRUPTION_CODES = new Set(['SQLITE_CORRUPT', 'SQLITE_NOTADB']);\nconst SQLITE_CORRUPTION_MESSAGE =\n /database disk image is malformed|file is not a database|database is malformed|malformed database/i;\nconst SQLITE_FILE_SUFFIXES = ['', '-wal', '-wal2', '-shm', '-journal'] as const;\nconst MAX_LOGGED_CHECK_ISSUES = 20;\n\ntype ErrorSummary = {\n readonly name: string;\n readonly message: string;\n readonly code?: string | undefined;\n readonly cause?: ErrorSummary | string | undefined;\n};\n\ntype SQLiteFileStats = {\n readonly suffix: string;\n readonly path: string;\n readonly sizeBytes: number;\n readonly mtimeMs: number;\n};\n\ntype CheckSummary = {\n readonly status: 'ok' | 'failed';\n readonly rowCount: number;\n readonly issueCount: number;\n readonly loggedIssueCount: number;\n readonly truncated: boolean;\n readonly issues: string[];\n};\n\ntype DiagnosticTarget = {\n readonly debugName: string;\n readonly dbPath: string;\n};\n\nconst diagnosticTargets = new Map<number, DiagnosticTarget>();\nlet nextDiagnosticTargetID = 0;\n\nexport function isSQLiteCorruption(e: unknown): boolean {\n return getSQLiteCorruptionCode(e) !== undefined;\n}\n\nexport function registerSQLiteCorruptionDiagnosticTarget(\n target: DiagnosticTarget,\n): () => void {\n const id = nextDiagnosticTargetID++;\n diagnosticTargets.set(id, target);\n return () => diagnosticTargets.delete(id);\n}\n\nexport function logLastChanceSQLiteCorruptionDiagnostics(\n lc: LogContext,\n cause: unknown,\n): boolean {\n if (!isSQLiteCorruption(cause) || diagnosticTargets.size === 0) {\n return false;\n }\n for (const {debugName, dbPath} of diagnosticTargets.values()) {\n logSQLiteCorruptionDiagnostics(lc, debugName, dbPath, cause);\n }\n return true;\n}\n\nexport function logSQLiteCorruptionDiagnostics(\n lc: LogContext,\n debugName: string,\n dbPath: string,\n cause: unknown,\n): void {\n const start = Date.now();\n lc.error?.('SQLite replica corruption detected', {\n debugName,\n dbPath,\n cause: summarizeError(cause),\n });\n\n logDiagnostic(lc, debugName, 'file-stats', () => ({\n files: getSQLiteFileStats(dbPath),\n }));\n\n let db: Database | undefined;\n try {\n db = new Database(lc, dbPath, {readonly: true});\n db.pragma('busy_timeout = 1000');\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step: 'open-readonly',\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n return;\n }\n\n try {\n logDiagnostic(lc, debugName, 'basic-pragmas', () => ({\n pragmas: getBasicPragmas(db),\n }));\n logDiagnostic(lc, debugName, 'replica-metadata', () => ({\n metadata: getReplicaMetadata(db),\n }));\n logDiagnostic(lc, debugName, 'quick-check', () => ({\n check: runCheck(db, 'quick_check'),\n }));\n logDiagnostic(lc, debugName, 'integrity-check', () => ({\n check: runCheck(db, 'integrity_check'),\n }));\n lc.error?.('SQLite corruption diagnostics completed', {\n debugName,\n dbPath,\n durationMs: Date.now() - start,\n });\n } finally {\n db.close();\n }\n}\n\nfunction getSQLiteCorruptionCode(e: unknown): string | undefined {\n let current = e;\n const seen = new Set<unknown>();\n\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current);\n\n const code = getErrorCode(current);\n if (code !== undefined && SQLITE_CORRUPTION_CODES.has(code)) {\n return code;\n }\n\n if (current instanceof SqliteError || current instanceof Error) {\n if (SQLITE_CORRUPTION_MESSAGE.test(current.message)) {\n return code ?? 'SQLITE_CORRUPT';\n }\n current = getErrorCause(current);\n continue;\n }\n\n break;\n }\n\n return undefined;\n}\n\nfunction getErrorCode(e: unknown): string | undefined {\n if (e && typeof e === 'object' && 'code' in e) {\n const code = (e as {readonly code?: unknown}).code;\n return typeof code === 'string' ? code : undefined;\n }\n return undefined;\n}\n\nfunction getErrorCause(e: Error): unknown {\n return (e as {readonly cause?: unknown}).cause;\n}\n\nfunction summarizeError(e: unknown, depth = 0): ErrorSummary | string {\n if (!(e instanceof Error)) {\n return String(e);\n }\n const cause = getErrorCause(e);\n return {\n name: e.name,\n message: e.message,\n code: getErrorCode(e),\n cause:\n cause === undefined || depth >= 4\n ? undefined\n : summarizeError(cause, depth + 1),\n };\n}\n\nfunction getSQLiteFileStats(dbPath: string): SQLiteFileStats[] {\n const stats: SQLiteFileStats[] = [];\n for (const suffix of SQLITE_FILE_SUFFIXES) {\n const path = `${dbPath}${suffix}`;\n if (!existsSync(path)) {\n continue;\n }\n const stat = statSync(path);\n stats.push({\n suffix,\n path,\n sizeBytes: stat.size,\n mtimeMs: stat.mtimeMs,\n });\n }\n return stats;\n}\n\nfunction getBasicPragmas(db: Database) {\n return {\n pageSize: firstPragmaValue<number>(db, 'page_size', 'page_size'),\n pageCount: firstPragmaValue<number>(db, 'page_count', 'page_count'),\n freelistCount: firstPragmaValue<number>(\n db,\n 'freelist_count',\n 'freelist_count',\n ),\n journalMode: firstPragmaValue<string>(db, 'journal_mode', 'journal_mode'),\n schemaVersion: firstPragmaValue<number>(\n db,\n 'schema_version',\n 'schema_version',\n ),\n userVersion: firstPragmaValue<number>(db, 'user_version', 'user_version'),\n applicationID: firstPragmaValue<number>(\n db,\n 'application_id',\n 'application_id',\n ),\n };\n}\n\nfunction getReplicaMetadata(db: Database) {\n return {\n versionHistory: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT dataVersion, schemaVersion, minSafeVersion\n FROM \"_zero.versionHistory\"`,\n )\n .get(),\n ),\n subscriptionState: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT c.replicaVersion, s.stateVersion as watermark\n FROM \"_zero.replicationConfig\" as c\n JOIN \"_zero.replicationState\" as s\n ON c.lock = s.lock`,\n )\n .get(),\n ),\n sqliteCatalogCounts: queryDiagnostic(() =>\n db\n .prepare(\n `SELECT type, count(*) as count\n FROM sqlite_master\n GROUP BY type\n ORDER BY type`,\n )\n .all(),\n ),\n };\n}\n\nfunction queryDiagnostic<T>(query: () => T):\n | {readonly ok: true; readonly value: T}\n | {\n readonly ok: false;\n readonly error: ErrorSummary | string;\n } {\n try {\n return {ok: true, value: query()};\n } catch (e) {\n return {ok: false, error: summarizeError(e)};\n }\n}\n\nfunction firstPragmaValue<T>(\n db: Database,\n pragma: string,\n column: string,\n): T | undefined {\n const [row] = db.pragma<Record<string, T>>(pragma);\n return row?.[column];\n}\n\nfunction runCheck(db: Database, pragma: 'quick_check' | 'integrity_check') {\n const rows = db.pragma<Record<string, string>>(pragma);\n const issues = rows\n .map(row => row[pragma] ?? Object.values(row)[0] ?? '')\n .filter(issue => issue !== '' && issue !== 'ok');\n const loggedIssues = issues.slice(0, MAX_LOGGED_CHECK_ISSUES);\n return {\n status: issues.length === 0 ? 'ok' : 'failed',\n rowCount: rows.length,\n issueCount: issues.length,\n loggedIssueCount: loggedIssues.length,\n truncated: issues.length > loggedIssues.length,\n issues: loggedIssues,\n } satisfies CheckSummary;\n}\n\nfunction logDiagnostic<T>(\n lc: LogContext,\n debugName: string,\n step: string,\n fn: () => T,\n): void {\n const start = Date.now();\n try {\n const result = fn();\n lc.error?.('SQLite corruption diagnostic', {\n debugName,\n step,\n durationMs: Date.now() - start,\n result,\n });\n } catch (e) {\n lc.error?.('SQLite corruption diagnostic failed', {\n debugName,\n step,\n durationMs: Date.now() - start,\n error: summarizeError(e),\n });\n }\n}\n"],"mappings":"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"}
@@ -28,7 +28,7 @@ async function runWorker(parent, env, ...argv) {
28
28
  });
29
29
  await runUntilKilled(lc, parent ?? process, new VfsBackupWatermarkWorkerService(lc, parent, createReader, litestream.vfsProbeIntervalMs));
30
30
  }
31
- if (shouldStartWorker(parentWorker, singleProcessMode())) exitAfter(lc, () => runWorker(parentWorker, process.env, ...process.argv.slice(2)));
31
+ if (shouldStartWorker(parentWorker, singleProcessMode())) exitAfter(() => lc, () => runWorker(parentWorker, process.env, ...process.argv.slice(2)));
32
32
  //#endregion
33
33
  export { runWorker as default };
34
34
 
@@ -1 +1 @@
1
- {"version":3,"file":"backup-watermark-reader.js","names":[],"sources":["../../../../../zero-cache/src/server/backup-watermark-reader.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {must} from '../../../shared/src/must.ts';\nimport {getZeroConfig} from '../config/zero-config.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n buildLitestreamVfsReplicaURL,\n VfsBackupWatermarkReader,\n} from '../services/litestream/vfs-watermark-reader.ts';\nimport {VfsBackupWatermarkWorkerService} from '../services/litestream/vfs-watermark-worker.ts';\nimport {\n parentWorker,\n shouldStartWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {createLogContext} from './logging.ts';\n\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker | null,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n const config = getZeroConfig({env, argv});\n lc = createLogContext(config, 'backup-watermark-reader');\n const {litestream} = config;\n\n const replicaURL = buildLitestreamVfsReplicaURL({\n backupURL: must(\n litestream.backupURL,\n 'Missing --litestream-backup-url for backup watermark reader',\n ),\n endpoint: litestream.endpoint,\n region: litestream.region,\n });\n\n const createReader = () =>\n new VfsBackupWatermarkReader(lc, {\n replicaURL,\n extensionPath: litestream.vfsExtensionPath,\n logLevel: litestream.logLevel,\n logFile: litestream.vfsLogFile,\n });\n\n await runUntilKilled(\n lc,\n parent ?? process,\n new VfsBackupWatermarkWorkerService(\n lc,\n parent,\n createReader,\n litestream.vfsProbeIntervalMs,\n ),\n );\n}\n\n// Unlike the other workers, the backup watermark reader is *always* run as its\n// own OS process — forked on demand by the `VfsBackupMonitor` (via\n// `forkChildWorker`), or launched directly as a standalone debug tool — so that\n// the native Litestream VFS extension and its process-global `LITESTREAM_*` env\n// stay isolated from the rest of zero-cache. See `shouldStartWorker`.\nif (shouldStartWorker(parentWorker, singleProcessMode())) {\n void exitAfter(lc, () =>\n runWorker(parentWorker, process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;AAiBA,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC,GAAG,cAAc;AAElD,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,MAAM,SAAS,cAAc;EAAC;EAAK;CAAI,CAAC;CACxC,KAAK,iBAAiB,QAAQ,yBAAyB;CACvD,MAAM,EAAC,eAAc;CAErB,MAAM,aAAa,6BAA6B;EAC9C,WAAW,KACT,WAAW,WACX,6DACF;EACA,UAAU,WAAW;EACrB,QAAQ,WAAW;CACrB,CAAC;CAED,MAAM,qBACJ,IAAI,yBAAyB,IAAI;EAC/B;EACA,eAAe,WAAW;EAC1B,UAAU,WAAW;EACrB,SAAS,WAAW;CACtB,CAAC;CAEH,MAAM,eACJ,IACA,UAAU,SACV,IAAI,gCACF,IACA,QACA,cACA,WAAW,kBACb,CACF;AACF;AAOA,IAAI,kBAAkB,cAAc,kBAAkB,CAAC,GACrD,UAAe,UACb,UAAU,cAAc,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CAC/D"}
1
+ {"version":3,"file":"backup-watermark-reader.js","names":[],"sources":["../../../../../zero-cache/src/server/backup-watermark-reader.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {must} from '../../../shared/src/must.ts';\nimport {getZeroConfig} from '../config/zero-config.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n buildLitestreamVfsReplicaURL,\n VfsBackupWatermarkReader,\n} from '../services/litestream/vfs-watermark-reader.ts';\nimport {VfsBackupWatermarkWorkerService} from '../services/litestream/vfs-watermark-worker.ts';\nimport {\n parentWorker,\n shouldStartWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {createLogContext} from './logging.ts';\n\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker | null,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n const config = getZeroConfig({env, argv});\n lc = createLogContext(config, 'backup-watermark-reader');\n const {litestream} = config;\n\n const replicaURL = buildLitestreamVfsReplicaURL({\n backupURL: must(\n litestream.backupURL,\n 'Missing --litestream-backup-url for backup watermark reader',\n ),\n endpoint: litestream.endpoint,\n region: litestream.region,\n });\n\n const createReader = () =>\n new VfsBackupWatermarkReader(lc, {\n replicaURL,\n extensionPath: litestream.vfsExtensionPath,\n logLevel: litestream.logLevel,\n logFile: litestream.vfsLogFile,\n });\n\n await runUntilKilled(\n lc,\n parent ?? process,\n new VfsBackupWatermarkWorkerService(\n lc,\n parent,\n createReader,\n litestream.vfsProbeIntervalMs,\n ),\n );\n}\n\n// Unlike the other workers, the backup watermark reader is *always* run as its\n// own OS process — forked on demand by the `VfsBackupMonitor` (via\n// `forkChildWorker`), or launched directly as a standalone debug tool — so that\n// the native Litestream VFS extension and its process-global `LITESTREAM_*` env\n// stay isolated from the rest of zero-cache. See `shouldStartWorker`.\nif (shouldStartWorker(parentWorker, singleProcessMode())) {\n void exitAfter(\n () => lc,\n () => runWorker(parentWorker, process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;AAiBA,IAAI,KAAK,IAAI,WAAW,QAAQ,CAAC,GAAG,cAAc;AAElD,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,MAAM,SAAS,cAAc;EAAC;EAAK;CAAI,CAAC;CACxC,KAAK,iBAAiB,QAAQ,yBAAyB;CACvD,MAAM,EAAC,eAAc;CAErB,MAAM,aAAa,6BAA6B;EAC9C,WAAW,KACT,WAAW,WACX,6DACF;EACA,UAAU,WAAW;EACrB,QAAQ,WAAW;CACrB,CAAC;CAED,MAAM,qBACJ,IAAI,yBAAyB,IAAI;EAC/B;EACA,eAAe,WAAW;EAC1B,UAAU,WAAW;EACrB,SAAS,WAAW;CACtB,CAAC;CAEH,MAAM,eACJ,IACA,UAAU,SACV,IAAI,gCACF,IACA,QACA,cACA,WAAW,kBACb,CACF;AACF;AAOA,IAAI,kBAAkB,cAAc,kBAAkB,CAAC,GACrD,gBACQ,UACA,UAAU,cAAc,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC,CACrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AA8BA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAQ/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAkMf"}
1
+ {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AA+BA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAQ/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAsMf"}
@@ -5,6 +5,7 @@ import { getShardConfig } from "../types/shards.js";
5
5
  import { getNormalizedZeroConfig } from "../config/zero-config.js";
6
6
  import { DatabaseInitError } from "../../../zqlite/src/db.js";
7
7
  import { connectPgClient } from "../types/pg.js";
8
+ import { registerSQLiteCorruptionDiagnosticTarget } from "../db/sqlite-corruption.js";
8
9
  import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
9
10
  import { createLogContext } from "./logging.js";
10
11
  import { getServerContext } from "../config/server-context.js";
@@ -36,6 +37,10 @@ async function runWorker(parent, env, ...argv) {
36
37
  const { taskID, changeStreamer: { port, address, protocol, startupDelayMs, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds }, upstream, change, replica, initialSync, keepaliveTimeoutMs } = config;
37
38
  startOtelAuto(createLogContext(config, "change-streamer", 0, false), "change-streamer", 0);
38
39
  lc = createLogContext(config, "change-streamer");
40
+ registerSQLiteCorruptionDiagnosticTarget({
41
+ debugName: "change-streamer replica",
42
+ dbPath: replica.file
43
+ });
39
44
  initEventSink(lc, config);
40
45
  const changeDB = await connectPgClient(lc, change.db, "change-streamer", { max: change.maxConns }, { sendStringAsJson: true });
41
46
  warmupConnections(lc, changeDB, "change").catch(() => {});
@@ -96,7 +101,7 @@ async function runWorker(parent, env, ...argv) {
96
101
  parent.send(["ready", { ready: true }]);
97
102
  return runUntilKilled(lc, parent, changeStreamerWebServer, monitor);
98
103
  }
99
- if (!singleProcessMode()) exitAfter(lc, () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)).catch(async (e) => {
104
+ if (!singleProcessMode()) exitAfter(() => lc, () => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)).catch(async (e) => {
100
105
  await publishCriticalEvent(lc, replicationStatusError(lc, "Initializing", e));
101
106
  throw e;
102
107
  }));
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {DatabaseInitError} from '../../../zqlite/src/db.ts';\nimport {getServerContext} from '../config/server-context.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink, publishCriticalEvent} from '../observability/events.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source.ts';\nimport {createBackupCleanupMonitor} from '../services/change-streamer/backup-cleanup-monitor-factory.ts';\nimport {ChangeStreamerHttpServer} from '../services/change-streamer/change-streamer-http.ts';\nimport {initializeStreamer} from '../services/change-streamer/change-streamer-service.ts';\nimport type {ChangeStreamerService} from '../services/change-streamer/change-streamer.ts';\nimport {ReplicaMonitor} from '../services/change-streamer/replica-monitor.ts';\nimport {initChangeStreamerSchema} from '../services/change-streamer/schema/init.ts';\nimport {AutoResetSignal} from '../services/change-streamer/schema/tables.ts';\nimport {PurgeLocker} from '../services/change-streamer/storer.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n BackupNotFoundException,\n restoreReplica,\n} from '../services/litestream/commands.ts';\nimport {\n replicationStatusError,\n ReplicationStatusPublisher,\n} from '../services/replicator/replication-status.ts';\nimport {connectPgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardConfig} from '../types/shards.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n const workerStartTime = Date.now();\n const config = getNormalizedZeroConfig({env, argv});\n const {\n taskID,\n changeStreamer: {\n port,\n address,\n protocol,\n startupDelayMs,\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\n },\n upstream,\n change,\n replica,\n initialSync,\n keepaliveTimeoutMs,\n } = config;\n\n startOtelAuto(\n createLogContext(config, 'change-streamer', 0, false),\n 'change-streamer',\n 0,\n );\n lc = createLogContext(config, 'change-streamer');\n initEventSink(lc, config);\n\n // Kick off DB connection warmup in the background.\n const changeDB = await connectPgClient(\n lc,\n change.db,\n 'change-streamer',\n {\n max: change.maxConns,\n },\n {sendStringAsJson: true},\n );\n void warmupConnections(lc, changeDB, 'change').catch(() => {});\n\n const {autoReset, replicationLag} = config;\n const shard = getShardConfig(config);\n\n // Ensure the change DB schema is initialized/up-to-date, then acquire\n // a lock to prevent change-lock purges. This ensures that (this)\n // change-streamer will be able to resume from the backup.\n await initChangeStreamerSchema(lc, changeDB, shard);\n let purgeLock = await new PurgeLocker(lc, shard, changeDB).acquire();\n\n // Restore from litestream if the change-log has entries.\n if (purgeLock) {\n try {\n await restoreReplica(lc, config, purgeLock);\n } catch (e) {\n // If the restore failed, e.g. due to a corrupt or missing backup, the\n // replication-manager recovers by re-syncing.\n const log = e instanceof BackupNotFoundException ? 'warn' : 'error';\n lc[log]?.(\n `error restoring backup. resyncing the replica: ${String(e)}`,\n e,\n );\n\n // The purgeLock must be released if the backup could not be restored,\n // or it will otherwise prevent the change-db update after the resync\n // completes.\n await purgeLock.release();\n purgeLock = null;\n }\n }\n\n let changeStreamer: ChangeStreamerService | undefined;\n\n const context = getServerContext(config);\n\n for (const first of [true, false]) {\n try {\n // Note: This performs initial sync of the replica if necessary.\n const {changeSource, subscriptionState} =\n upstream.type === 'pg'\n ? await initializePostgresChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n {\n ...initialSync,\n replicationSlotFailover: upstream.pgReplicationSlotFailover,\n },\n context,\n replicationLag.reportIntervalMs,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n context,\n );\n\n const replicationStatusPublisher =\n ReplicationStatusPublisher.forReplicaFile(replica.file);\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n replicationStatusPublisher,\n subscriptionState,\n purgeLock,\n autoReset ?? false,\n {\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\n statementTimeoutMs: change.statementTimeoutMs,\n changeLogBatchSize: change.logBatchSize,\n },\n setTimeout,\n );\n break;\n } catch (e) {\n if (first && e instanceof AutoResetSignal) {\n lc.warn?.(`resetting replica ${replica.file}`, e);\n // TODO: Make deleteLiteDB work with litestream. It will probably have to be\n // a semantic wipe instead of a file delete.\n deleteLiteDB(replica.file);\n // Release the purge lock before retrying. This is safe because the\n // purge lock exists to preserve change-log entries so the new\n // change-streamer can resume from the backup replica's watermark.\n // An AutoResetSignal means we cant resume from the backup replica\n // (e.g. its replication slot is gone), so the change-log entries the lock\n // was protecting are no longer needed. The retry performs a fresh\n // initial sync with a new replication slot, independent of the old\n // change-log. Releasing is also necessary to avoid a\n // self-deadlock when CHANGE_DB == UPSTREAM_DB:\n // CREATE_REPLICATION_SLOT waits for all older transactions to\n // finish, including this lock's open transaction.\n await purgeLock?.release();\n purgeLock = null;\n continue; // execute again with a fresh initial-sync\n }\n if (e instanceof DatabaseInitError) {\n throw new Error(\n `Cannot open ZERO_REPLICA_FILE at \"${replica.file}\". Please check that the path is valid.`,\n {cause: e},\n );\n }\n throw e;\n }\n }\n // impossible: upstream must have advanced in order for replication to be stuck.\n assert(changeStreamer, `resetting replica did not advance replicaVersion`);\n\n // Perform any upgrades to the replica in case it was restored from an\n // earlier version. Note that this upgrade is done by the replicator worker\n // as well (in both the replication-manager and the view-syncer), but the\n // change-streamer independently reads the replica, and it is fine run the\n // upgrade logic redundantly since it is idempotent.\n await upgradeReplica(lc, 'change-streamer-init', replica.file);\n\n const backupMonitor = createBackupCleanupMonitor({\n lc,\n config,\n replicaFile: replica.file,\n changeStreamer,\n // The time between when the zero-cache was started to when the\n // change-streamer is ready to start serves as the initial delay for\n // watermark cleanup (as it either includes a similar replica\n // restoration/preparation step, or an initial-sync, which\n // generally takes longer).\n //\n // Consider: Also account for permanent volumes?\n initialCleanupDelayMs: Date.now() - workerStartTime,\n env,\n });\n const monitor =\n backupMonitor ?? new ReplicaMonitor(lc, replica.file, changeStreamer);\n\n const changeStreamerWebServer = new ChangeStreamerHttpServer(\n lc,\n {port, keepaliveTimeoutMs, startupDelayMs},\n parent,\n changeStreamer,\n backupMonitor,\n );\n\n parent.send(['ready', {ready: true}]);\n\n // Note: The changeStreamer itself is not started here; it is started by the\n // changeStreamerWebServer.\n return runUntilKilled(lc, parent, changeStreamerWebServer, monitor);\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(lc, () =>\n runWorker(must(parentWorker), process.env, ...process.argv.slice(2)).catch(\n async e => {\n await publishCriticalEvent(\n lc,\n replicationStatusError(lc, 'Initializing', e),\n );\n throw e;\n },\n ),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"sourcesContent":["import {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {DatabaseInitError} from '../../../zqlite/src/db.ts';\nimport {getServerContext} from '../config/server-context.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {registerSQLiteCorruptionDiagnosticTarget} from '../db/sqlite-corruption.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink, publishCriticalEvent} from '../observability/events.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source.ts';\nimport {createBackupCleanupMonitor} from '../services/change-streamer/backup-cleanup-monitor-factory.ts';\nimport {ChangeStreamerHttpServer} from '../services/change-streamer/change-streamer-http.ts';\nimport {initializeStreamer} from '../services/change-streamer/change-streamer-service.ts';\nimport type {ChangeStreamerService} from '../services/change-streamer/change-streamer.ts';\nimport {ReplicaMonitor} from '../services/change-streamer/replica-monitor.ts';\nimport {initChangeStreamerSchema} from '../services/change-streamer/schema/init.ts';\nimport {AutoResetSignal} from '../services/change-streamer/schema/tables.ts';\nimport {PurgeLocker} from '../services/change-streamer/storer.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n BackupNotFoundException,\n restoreReplica,\n} from '../services/litestream/commands.ts';\nimport {\n replicationStatusError,\n ReplicationStatusPublisher,\n} from '../services/replicator/replication-status.ts';\nimport {connectPgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardConfig} from '../types/shards.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n const workerStartTime = Date.now();\n const config = getNormalizedZeroConfig({env, argv});\n const {\n taskID,\n changeStreamer: {\n port,\n address,\n protocol,\n startupDelayMs,\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\n },\n upstream,\n change,\n replica,\n initialSync,\n keepaliveTimeoutMs,\n } = config;\n\n startOtelAuto(\n createLogContext(config, 'change-streamer', 0, false),\n 'change-streamer',\n 0,\n );\n lc = createLogContext(config, 'change-streamer');\n registerSQLiteCorruptionDiagnosticTarget({\n debugName: 'change-streamer replica',\n dbPath: replica.file,\n });\n initEventSink(lc, config);\n\n // Kick off DB connection warmup in the background.\n const changeDB = await connectPgClient(\n lc,\n change.db,\n 'change-streamer',\n {\n max: change.maxConns,\n },\n {sendStringAsJson: true},\n );\n void warmupConnections(lc, changeDB, 'change').catch(() => {});\n\n const {autoReset, replicationLag} = config;\n const shard = getShardConfig(config);\n\n // Ensure the change DB schema is initialized/up-to-date, then acquire\n // a lock to prevent change-lock purges. This ensures that (this)\n // change-streamer will be able to resume from the backup.\n await initChangeStreamerSchema(lc, changeDB, shard);\n let purgeLock = await new PurgeLocker(lc, shard, changeDB).acquire();\n\n // Restore from litestream if the change-log has entries.\n if (purgeLock) {\n try {\n await restoreReplica(lc, config, purgeLock);\n } catch (e) {\n // If the restore failed, e.g. due to a corrupt or missing backup, the\n // replication-manager recovers by re-syncing.\n const log = e instanceof BackupNotFoundException ? 'warn' : 'error';\n lc[log]?.(\n `error restoring backup. resyncing the replica: ${String(e)}`,\n e,\n );\n\n // The purgeLock must be released if the backup could not be restored,\n // or it will otherwise prevent the change-db update after the resync\n // completes.\n await purgeLock.release();\n purgeLock = null;\n }\n }\n\n let changeStreamer: ChangeStreamerService | undefined;\n\n const context = getServerContext(config);\n\n for (const first of [true, false]) {\n try {\n // Note: This performs initial sync of the replica if necessary.\n const {changeSource, subscriptionState} =\n upstream.type === 'pg'\n ? await initializePostgresChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n {\n ...initialSync,\n replicationSlotFailover: upstream.pgReplicationSlotFailover,\n },\n context,\n replicationLag.reportIntervalMs,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n context,\n );\n\n const replicationStatusPublisher =\n ReplicationStatusPublisher.forReplicaFile(replica.file);\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n replicationStatusPublisher,\n subscriptionState,\n purgeLock,\n autoReset ?? false,\n {\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\n statementTimeoutMs: change.statementTimeoutMs,\n changeLogBatchSize: change.logBatchSize,\n },\n setTimeout,\n );\n break;\n } catch (e) {\n if (first && e instanceof AutoResetSignal) {\n lc.warn?.(`resetting replica ${replica.file}`, e);\n // TODO: Make deleteLiteDB work with litestream. It will probably have to be\n // a semantic wipe instead of a file delete.\n deleteLiteDB(replica.file);\n // Release the purge lock before retrying. This is safe because the\n // purge lock exists to preserve change-log entries so the new\n // change-streamer can resume from the backup replica's watermark.\n // An AutoResetSignal means we cant resume from the backup replica\n // (e.g. its replication slot is gone), so the change-log entries the lock\n // was protecting are no longer needed. The retry performs a fresh\n // initial sync with a new replication slot, independent of the old\n // change-log. Releasing is also necessary to avoid a\n // self-deadlock when CHANGE_DB == UPSTREAM_DB:\n // CREATE_REPLICATION_SLOT waits for all older transactions to\n // finish, including this lock's open transaction.\n await purgeLock?.release();\n purgeLock = null;\n continue; // execute again with a fresh initial-sync\n }\n if (e instanceof DatabaseInitError) {\n throw new Error(\n `Cannot open ZERO_REPLICA_FILE at \"${replica.file}\". Please check that the path is valid.`,\n {cause: e},\n );\n }\n throw e;\n }\n }\n // impossible: upstream must have advanced in order for replication to be stuck.\n assert(changeStreamer, `resetting replica did not advance replicaVersion`);\n\n // Perform any upgrades to the replica in case it was restored from an\n // earlier version. Note that this upgrade is done by the replicator worker\n // as well (in both the replication-manager and the view-syncer), but the\n // change-streamer independently reads the replica, and it is fine run the\n // upgrade logic redundantly since it is idempotent.\n await upgradeReplica(lc, 'change-streamer-init', replica.file);\n\n const backupMonitor = createBackupCleanupMonitor({\n lc,\n config,\n replicaFile: replica.file,\n changeStreamer,\n // The time between when the zero-cache was started to when the\n // change-streamer is ready to start serves as the initial delay for\n // watermark cleanup (as it either includes a similar replica\n // restoration/preparation step, or an initial-sync, which\n // generally takes longer).\n //\n // Consider: Also account for permanent volumes?\n initialCleanupDelayMs: Date.now() - workerStartTime,\n env,\n });\n const monitor =\n backupMonitor ?? new ReplicaMonitor(lc, replica.file, changeStreamer);\n\n const changeStreamerWebServer = new ChangeStreamerHttpServer(\n lc,\n {port, keepaliveTimeoutMs, startupDelayMs},\n parent,\n changeStreamer,\n backupMonitor,\n );\n\n parent.send(['ready', {ready: true}]);\n\n // Note: The changeStreamer itself is not started here; it is started by the\n // changeStreamerWebServer.\n return runUntilKilled(lc, parent, changeStreamerWebServer, monitor);\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(\n () => lc,\n () =>\n runWorker(\n must(parentWorker),\n process.env,\n ...process.argv.slice(2),\n ).catch(async e => {\n await publishCriticalEvent(\n lc,\n replicationStatusError(lc, 'Initializing', e),\n );\n throw e;\n }),\n );\n}\n"],"mappings":"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"}
@@ -1,6 +1,14 @@
1
1
  import type { LogContext } from '@rocicorp/logger';
2
+ import { type Context, type LogLevel, type LogSink } from '@rocicorp/logger';
2
3
  import { type LogConfig } from '../../../shared/src/logging.ts';
3
4
  export declare function createLogContext({ log }: {
4
5
  log: LogConfig;
5
6
  }, worker: string, workerIndex?: number, includeOtel?: boolean): LogContext;
7
+ export declare function logUncaughtException(lc: LogContext, logSink: LogSink, err: unknown, origin: string, includeLastChanceDiagnostics?: boolean): Promise<void>;
8
+ export declare class CompositeLogSink implements LogSink {
9
+ #private;
10
+ constructor(sinks: LogSink[]);
11
+ log(level: LogLevel, context: Context | undefined, ...args: unknown[]): void;
12
+ flush(): Promise<void>;
13
+ }
6
14
  //# sourceMappingURL=logging.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/logging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,gCAAgC,CAAC;AAIxC,wBAAgB,gBAAgB,CAC9B,EAAC,GAAG,EAAC,EAAE;IAAC,GAAG,EAAE,SAAS,CAAA;CAAC,EACvB,MAAM,EAAE,MAAM,EACd,WAAW,SAAI,EACf,WAAW,UAAO,GACjB,UAAU,CASZ"}
1
+ {"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/logging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAC,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAC,MAAM,kBAAkB,CAAC;AAE3E,OAAO,EAGL,KAAK,SAAS,EACf,MAAM,gCAAgC,CAAC;AAYxC,wBAAgB,gBAAgB,CAC9B,EAAC,GAAG,EAAC,EAAE;IAAC,GAAG,EAAE,SAAS,CAAA;CAAC,EACvB,MAAM,EAAE,MAAM,EACd,WAAW,SAAI,EACf,WAAW,UAAO,GACjB,UAAU,CAyBZ;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,4BAA4B,UAAO,GAClC,OAAO,CAAC,IAAI,CAAC,CAYf;AAWD,qBAAa,gBAAiB,YAAW,OAAO;;gBAGlC,KAAK,EAAE,OAAO,EAAE;IAI5B,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAMtE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
@@ -1,22 +1,41 @@
1
1
  import { createLogContext as createLogContext$1, getLogSink } from "../../../shared/src/logging.js";
2
+ import { logLastChanceSQLiteCorruptionDiagnostics } from "../db/sqlite-corruption.js";
2
3
  import "../services/life-cycle.js";
3
4
  import { otelLogsEnabled } from "../../../otel/src/enabled.js";
4
5
  import { OtelLogSink } from "./otel-log-sink.js";
5
6
  import "@rocicorp/logger";
6
7
  //#region ../zero-cache/src/server/logging.ts
8
+ var bootstrapUncaughtExceptionHandler;
7
9
  function createLogContext({ log }, worker, workerIndex = 0, includeOtel = true) {
8
10
  const logSink = createLogSink(log, includeOtel);
9
11
  const lc = createLogContext$1({ log }, {
10
12
  worker,
11
13
  workerIndex
12
14
  }, logSink);
13
- process.on("uncaughtException", async (err, origin) => {
14
- lc.error?.(origin, err);
15
- await logSink.flush?.();
16
- process.exit(13);
17
- });
15
+ const handleUncaughtException = async (err, origin) => {
16
+ try {
17
+ await logUncaughtException(lc, logSink, err, origin, includeOtel);
18
+ } finally {
19
+ process.exit(13);
20
+ }
21
+ };
22
+ if (bootstrapUncaughtExceptionHandler) {
23
+ process.off("uncaughtException", bootstrapUncaughtExceptionHandler);
24
+ bootstrapUncaughtExceptionHandler = void 0;
25
+ }
26
+ process.on("uncaughtException", handleUncaughtException);
27
+ if (!includeOtel) bootstrapUncaughtExceptionHandler = handleUncaughtException;
18
28
  return lc;
19
29
  }
30
+ async function logUncaughtException(lc, logSink, err, origin, includeLastChanceDiagnostics = true) {
31
+ lc.error?.(origin, err);
32
+ if (includeLastChanceDiagnostics) try {
33
+ logLastChanceSQLiteCorruptionDiagnostics(lc, err);
34
+ } catch (diagnosticError) {
35
+ lc.error?.("SQLite corruption last-chance diagnostic failed", { error: diagnosticError });
36
+ }
37
+ await logSink.flush?.();
38
+ }
20
39
  function createLogSink(config, includeOtel) {
21
40
  const sink = getLogSink(config);
22
41
  if (includeOtel && otelLogsEnabled()) return new CompositeLogSink([new OtelLogSink(), sink]);
@@ -30,6 +49,9 @@ var CompositeLogSink = class {
30
49
  log(level, context, ...args) {
31
50
  for (const sink of this.#sinks) sink.log(level, context, ...args);
32
51
  }
52
+ async flush() {
53
+ await Promise.all(this.#sinks.map((sink) => sink.flush?.()));
54
+ }
33
55
  };
34
56
  //#endregion
35
57
  export { createLogContext };
@@ -1 +1 @@
1
- {"version":3,"file":"logging.js","names":["#sinks"],"sources":["../../../../../zero-cache/src/server/logging.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Context, type LogLevel, type LogSink} from '@rocicorp/logger';\nimport {otelLogsEnabled} from '../../../otel/src/enabled.ts';\nimport {\n createLogContext as createLogContextShared,\n getLogSink,\n type LogConfig,\n} from '../../../shared/src/logging.ts';\nimport {UNHANDLED_EXCEPTION_ERROR_CODE} from '../services/life-cycle.ts';\nimport {OtelLogSink} from './otel-log-sink.ts';\n\nexport function createLogContext(\n {log}: {log: LogConfig},\n worker: string,\n workerIndex = 0,\n includeOtel = true,\n): LogContext {\n const logSink = createLogSink(log, includeOtel);\n const lc = createLogContextShared({log}, {worker, workerIndex}, logSink);\n process.on('uncaughtException', async (err, origin) => {\n lc.error?.(origin, err);\n await logSink.flush?.();\n process.exit(UNHANDLED_EXCEPTION_ERROR_CODE);\n });\n return lc;\n}\n\nfunction createLogSink(config: LogConfig, includeOtel: boolean): LogSink {\n const sink = getLogSink(config);\n if (includeOtel && otelLogsEnabled()) {\n const otelSink = new OtelLogSink();\n return new CompositeLogSink([otelSink, sink]);\n }\n return sink;\n}\n\nclass CompositeLogSink implements LogSink {\n readonly #sinks: LogSink[];\n\n constructor(sinks: LogSink[]) {\n this.#sinks = sinks;\n }\n\n log(level: LogLevel, context: Context | undefined, ...args: unknown[]): void {\n for (const sink of this.#sinks) {\n sink.log(level, context, ...args);\n }\n }\n}\n"],"mappings":";;;;;;AAWA,SAAgB,iBACd,EAAC,OACD,QACA,cAAc,GACd,cAAc,MACF;CACZ,MAAM,UAAU,cAAc,KAAK,WAAW;CAC9C,MAAM,KAAK,mBAAuB,EAAC,IAAG,GAAG;EAAC;EAAQ;CAAW,GAAG,OAAO;CACvE,QAAQ,GAAG,qBAAqB,OAAO,KAAK,WAAW;EACrD,GAAG,QAAQ,QAAQ,GAAG;EACtB,MAAM,QAAQ,QAAQ;EACtB,QAAQ,KAAA,EAAmC;CAC7C,CAAC;CACD,OAAO;AACT;AAEA,SAAS,cAAc,QAAmB,aAA+B;CACvE,MAAM,OAAO,WAAW,MAAM;CAC9B,IAAI,eAAe,gBAAgB,GAEjC,OAAO,IAAI,iBAAiB,CAAC,IADR,YACQ,GAAU,IAAI,CAAC;CAE9C,OAAO;AACT;AAEA,IAAM,mBAAN,MAA0C;CACxC;CAEA,YAAY,OAAkB;EAC5B,KAAKA,SAAS;CAChB;CAEA,IAAI,OAAiB,SAA8B,GAAG,MAAuB;EAC3E,KAAK,MAAM,QAAQ,KAAKA,QACtB,KAAK,IAAI,OAAO,SAAS,GAAG,IAAI;CAEpC;AACF"}
1
+ {"version":3,"file":"logging.js","names":["#sinks"],"sources":["../../../../../zero-cache/src/server/logging.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {type Context, type LogLevel, type LogSink} from '@rocicorp/logger';\nimport {otelLogsEnabled} from '../../../otel/src/enabled.ts';\nimport {\n createLogContext as createLogContextShared,\n getLogSink,\n type LogConfig,\n} from '../../../shared/src/logging.ts';\nimport {logLastChanceSQLiteCorruptionDiagnostics} from '../db/sqlite-corruption.ts';\nimport {UNHANDLED_EXCEPTION_ERROR_CODE} from '../services/life-cycle.ts';\nimport {OtelLogSink} from './otel-log-sink.ts';\n\ntype UncaughtExceptionHandler = (\n err: Error,\n origin: NodeJS.UncaughtExceptionOrigin,\n) => Promise<void>;\n\nlet bootstrapUncaughtExceptionHandler: UncaughtExceptionHandler | undefined;\n\nexport function createLogContext(\n {log}: {log: LogConfig},\n worker: string,\n workerIndex = 0,\n includeOtel = true,\n): LogContext {\n const logSink = createLogSink(log, includeOtel);\n const lc = createLogContextShared({log}, {worker, workerIndex}, logSink);\n const handleUncaughtException: UncaughtExceptionHandler = async (\n err,\n origin,\n ) => {\n // Workers create an includeOtel=false context while bootstrapping OTel,\n // then a primary context. Run full corruption checks from the primary\n // handler only.\n try {\n await logUncaughtException(lc, logSink, err, origin, includeOtel);\n } finally {\n process.exit(UNHANDLED_EXCEPTION_ERROR_CODE);\n }\n };\n if (bootstrapUncaughtExceptionHandler) {\n process.off('uncaughtException', bootstrapUncaughtExceptionHandler);\n bootstrapUncaughtExceptionHandler = undefined;\n }\n process.on('uncaughtException', handleUncaughtException);\n if (!includeOtel) {\n bootstrapUncaughtExceptionHandler = handleUncaughtException;\n }\n return lc;\n}\n\nexport async function logUncaughtException(\n lc: LogContext,\n logSink: LogSink,\n err: unknown,\n origin: string,\n includeLastChanceDiagnostics = true,\n): Promise<void> {\n lc.error?.(origin, err);\n if (includeLastChanceDiagnostics) {\n try {\n logLastChanceSQLiteCorruptionDiagnostics(lc, err);\n } catch (diagnosticError) {\n lc.error?.('SQLite corruption last-chance diagnostic failed', {\n error: diagnosticError,\n });\n }\n }\n await logSink.flush?.();\n}\n\nfunction createLogSink(config: LogConfig, includeOtel: boolean): LogSink {\n const sink = getLogSink(config);\n if (includeOtel && otelLogsEnabled()) {\n const otelSink = new OtelLogSink();\n return new CompositeLogSink([otelSink, sink]);\n }\n return sink;\n}\n\nexport class CompositeLogSink implements LogSink {\n readonly #sinks: LogSink[];\n\n constructor(sinks: LogSink[]) {\n this.#sinks = sinks;\n }\n\n log(level: LogLevel, context: Context | undefined, ...args: unknown[]): void {\n for (const sink of this.#sinks) {\n sink.log(level, context, ...args);\n }\n }\n\n async flush(): Promise<void> {\n await Promise.all(this.#sinks.map(sink => sink.flush?.()));\n }\n}\n"],"mappings":";;;;;;;AAiBA,IAAI;AAEJ,SAAgB,iBACd,EAAC,OACD,QACA,cAAc,GACd,cAAc,MACF;CACZ,MAAM,UAAU,cAAc,KAAK,WAAW;CAC9C,MAAM,KAAK,mBAAuB,EAAC,IAAG,GAAG;EAAC;EAAQ;CAAW,GAAG,OAAO;CACvE,MAAM,0BAAoD,OACxD,KACA,WACG;EAIH,IAAI;GACF,MAAM,qBAAqB,IAAI,SAAS,KAAK,QAAQ,WAAW;EAClE,UAAU;GACR,QAAQ,KAAA,EAAmC;EAC7C;CACF;CACA,IAAI,mCAAmC;EACrC,QAAQ,IAAI,qBAAqB,iCAAiC;EAClE,oCAAoC,KAAA;CACtC;CACA,QAAQ,GAAG,qBAAqB,uBAAuB;CACvD,IAAI,CAAC,aACH,oCAAoC;CAEtC,OAAO;AACT;AAEA,eAAsB,qBACpB,IACA,SACA,KACA,QACA,+BAA+B,MAChB;CACf,GAAG,QAAQ,QAAQ,GAAG;CACtB,IAAI,8BACF,IAAI;EACF,yCAAyC,IAAI,GAAG;CAClD,SAAS,iBAAiB;EACxB,GAAG,QAAQ,mDAAmD,EAC5D,OAAO,gBACT,CAAC;CACH;CAEF,MAAM,QAAQ,QAAQ;AACxB;AAEA,SAAS,cAAc,QAAmB,aAA+B;CACvE,MAAM,OAAO,WAAW,MAAM;CAC9B,IAAI,eAAe,gBAAgB,GAEjC,OAAO,IAAI,iBAAiB,CAAC,IADR,YACQ,GAAU,IAAI,CAAC;CAE9C,OAAO;AACT;AAEA,IAAa,mBAAb,MAAiD;CAC/C;CAEA,YAAY,OAAkB;EAC5B,KAAKA,SAAS;CAChB;CAEA,IAAI,OAAiB,SAA8B,GAAG,MAAuB;EAC3E,KAAK,MAAM,QAAQ,KAAKA,QACtB,KAAK,IAAI,OAAO,SAAS,GAAG,IAAI;CAEpC;CAEA,MAAM,QAAuB;EAC3B,MAAM,QAAQ,IAAI,KAAKA,OAAO,KAAI,SAAQ,KAAK,QAAQ,CAAC,CAAC;CAC3D;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/main.ts"],"names":[],"mappings":"AAiBA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAwB/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC,CA+Kf"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/main.ts"],"names":[],"mappings":"AAkBA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAwB/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC,CAmLf"}
@@ -1,6 +1,7 @@
1
1
  import { must } from "../../../shared/src/must.js";
2
2
  import { childWorker, parentWorker, singleProcessMode } from "../types/processes.js";
3
3
  import { getNormalizedZeroConfig } from "../config/zero-config.js";
4
+ import { registerSQLiteCorruptionDiagnosticTarget } from "../db/sqlite-corruption.js";
4
5
  import { ProcessManager, exitAfter, recordStartupDurationMs, runUntilKilled } from "../services/life-cycle.js";
5
6
  import { createLogContext } from "./logging.js";
6
7
  import { CHANGE_STREAMER_URL, REAPER_URL, REPLICATOR_URL, SHADOW_SYNCER_URL, SYNCER_URL } from "./worker-urls.js";
@@ -19,6 +20,10 @@ async function runWorker(parent, env) {
19
20
  const config = getNormalizedZeroConfig({ env });
20
21
  startOtelAuto(createLogContext(config, "dispatcher", 0, false), "dispatcher", 0);
21
22
  lc = createLogContext(config, "dispatcher");
23
+ registerSQLiteCorruptionDiagnosticTarget({
24
+ debugName: "dispatcher replica",
25
+ dbPath: config.replica.file
26
+ });
22
27
  initEventSink(lc, config);
23
28
  const processes = new ProcessManager(lc, parent);
24
29
  const { numSyncWorkers: numSyncers } = config;
@@ -94,7 +99,7 @@ async function runWorker(parent, env) {
94
99
  }
95
100
  await processes.done();
96
101
  }
97
- if (!singleProcessMode()) exitAfter(lc, () => runWorker(must(parentWorker), process.env));
102
+ if (!singleProcessMode()) exitAfter(() => lc, () => runWorker(must(parentWorker), process.env));
98
103
  //#endregion
99
104
  export { runWorker as default };
100
105