@rocicorp/zero 0.26.2-canary.6 → 0.26.2-canary.7

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 (27) hide show
  1. package/out/zero/package.js +1 -1
  2. package/out/zero/package.js.map +1 -1
  3. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  4. package/out/zero-cache/src/server/change-streamer.js +3 -3
  5. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  6. package/out/zero-cache/src/services/change-source/pg/schema/ddl.d.ts.map +1 -1
  7. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js +5 -24
  8. package/out/zero-cache/src/services/change-source/pg/schema/ddl.js.map +1 -1
  9. package/out/zero-cache/src/services/change-source/pg/schema/init.d.ts.map +1 -1
  10. package/out/zero-cache/src/services/change-source/pg/schema/init.js +1 -7
  11. package/out/zero-cache/src/services/change-source/pg/schema/init.js.map +1 -1
  12. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +2 -1
  13. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  14. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +6 -3
  15. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  16. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  17. package/out/zero-cache/src/services/change-streamer/storer.js +108 -77
  18. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  19. package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
  20. package/out/zero-cache/src/services/litestream/commands.js +3 -0
  21. package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
  22. package/out/zero-cache/src/services/replicator/replication-status.d.ts +2 -1
  23. package/out/zero-cache/src/services/replicator/replication-status.d.ts.map +1 -1
  24. package/out/zero-cache/src/services/replicator/replication-status.js +5 -1
  25. package/out/zero-cache/src/services/replicator/replication-status.js.map +1 -1
  26. package/out/zero-client/src/client/version.js +1 -1
  27. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  var package_default = {
2
2
  name: "@rocicorp/zero",
3
- version: "0.26.2-canary.6",
3
+ version: "0.26.2-canary.7",
4
4
  description: "Zero is a web framework for serverless web development.",
5
5
  author: "Rocicorp, Inc.",
6
6
  repository: {
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"0.26.2-canary.6\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"license\": \"Apache-2.0\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\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\": \"prettier --write .\",\n \"check-format\": \"prettier --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 },\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.15\",\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\": \"^5.3.0\",\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.1.1\",\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 \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"prettier\": \"^3.8.1\",\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.0\",\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 \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~5.9.3\",\n \"vite\": \"8.0.0\",\n \"vitest\": \"4.1.0\",\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 \"expo-sqlite\": \">=15\",\n \"@op-engineering/op-sqlite\": \">=15\"\n },\n \"peerDependenciesMeta\": {\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n }\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 \"./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 \"bin\": {\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 \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ]\n}"],"mappings":""}
1
+ {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"0.26.2-canary.7\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"license\": \"Apache-2.0\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\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\": \"prettier --write .\",\n \"check-format\": \"prettier --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 },\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.15\",\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\": \"^5.3.0\",\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.1.1\",\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 \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"prettier\": \"^3.8.1\",\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.0\",\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 \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~5.9.3\",\n \"vite\": \"8.0.0\",\n \"vitest\": \"4.1.0\",\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 \"expo-sqlite\": \">=15\",\n \"@op-engineering/op-sqlite\": \">=15\"\n },\n \"peerDependenciesMeta\": {\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n }\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 \"./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 \"bin\": {\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 \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\"\n },\n \"engines\": {\n \"node\": \">=22\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ]\n}"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AAsBA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAK/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAyIf"}
1
+ {"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AAyBA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAK/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CA8If"}
@@ -1,6 +1,6 @@
1
1
  import { assert } from "../../../shared/src/asserts.js";
2
2
  import { must } from "../../../shared/src/must.js";
3
- import { DatabaseInitError } from "../../../zqlite/src/db.js";
3
+ import { Database, DatabaseInitError } from "../../../zqlite/src/db.js";
4
4
  import { parentWorker, singleProcessMode } from "../types/processes.js";
5
5
  import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
6
6
  import { getShardConfig } from "../types/shards.js";
@@ -13,7 +13,7 @@ import { deleteLiteDB } from "../db/delete-lite-db.js";
13
13
  import { warmupConnections } from "../db/warmup.js";
14
14
  import { initEventSink, publishCriticalEvent } from "../observability/events.js";
15
15
  import { AutoResetSignal, CHANGE_STREAMER_APP_NAME } from "../services/change-streamer/schema/tables.js";
16
- import { replicationStatusError } from "../services/replicator/replication-status.js";
16
+ import { ReplicationStatusPublisher, replicationStatusError } from "../services/replicator/replication-status.js";
17
17
  import { initializeCustomChangeSource } from "../services/change-source/custom/change-source.js";
18
18
  import { initializePostgresChangeSource } from "../services/change-source/pg/change-source.js";
19
19
  import { BackupMonitor } from "../services/change-streamer/backup-monitor.js";
@@ -43,7 +43,7 @@ async function runWorker(parent, env, ...args) {
43
43
  const context = getServerContext(config);
44
44
  for (const first of [true, false]) try {
45
45
  const { changeSource, subscriptionState } = upstream.type === "pg" ? await initializePostgresChangeSource(lc, upstream.db, shard, replica.file, initialSync, context) : await initializeCustomChangeSource(lc, upstream.db, shard, replica.file, context);
46
- changeStreamer = await initializeStreamer(lc, shard, taskID, address, protocol, changeDB, changeSource, subscriptionState, autoReset ?? false, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeout);
46
+ changeStreamer = await initializeStreamer(lc, shard, taskID, address, protocol, changeDB, changeSource, new ReplicationStatusPublisher(new Database(lc, replica.file, { readonly: true })), subscriptionState, autoReset ?? false, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeout);
47
47
  break;
48
48
  } catch (e) {
49
49
  if (first && e instanceof AutoResetSignal) {
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"sourcesContent":["import {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 {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source.ts';\nimport {BackupMonitor} from '../services/change-streamer/backup-monitor.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 {\n AutoResetSignal,\n CHANGE_STREAMER_APP_NAME,\n} from '../services/change-streamer/schema/tables.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {replicationStatusError} from '../services/replicator/replication-status.ts';\nimport {pgClient} 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\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n assert(args.length > 0, `parent startMs not specified`);\n const parentStartMs = parseInt(args[0]);\n\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\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 litestream,\n } = config;\n\n startOtelAuto(createLogContext(config, {worker: 'change-streamer'}, false));\n const lc = createLogContext(config, {worker: 'change-streamer'}, true);\n initEventSink(lc, config);\n\n // Kick off DB connection warmup in the background.\n const changeDB = pgClient(\n lc,\n change.db,\n {\n max: change.maxConns,\n connection: {['application_name']: CHANGE_STREAMER_APP_NAME},\n },\n {sendStringAsJson: true},\n );\n void warmupConnections(lc, changeDB, 'change');\n\n const {autoReset} = config;\n const shard = getShardConfig(config);\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 initialSync,\n context,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n context,\n );\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n subscriptionState,\n autoReset ?? false,\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\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 continue; // execute again with a fresh initial-sync\n }\n await publishCriticalEvent(\n lc,\n replicationStatusError(lc, 'Initializing', e),\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 const {backupURL, port: metricsPort} = litestream;\n const monitor = backupURL\n ? new BackupMonitor(\n lc,\n backupURL,\n `http://localhost:${metricsPort}/metrics`,\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 Date.now() - parentStartMs,\n )\n : new ReplicaMonitor(lc, replica.file, changeStreamer);\n\n const changeStreamerWebServer = new ChangeStreamerHttpServer(\n lc,\n config,\n {port, startupDelayMs},\n parent,\n changeStreamer,\n monitor instanceof BackupMonitor ? monitor : null,\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 runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;AACf,QAAO,KAAK,SAAS,GAAG,+BAA+B;CACvD,MAAM,gBAAgB,SAAS,KAAK,GAAG;CAEvC,MAAM,SAAS,wBAAwB;EAAC;EAAK,MAAM,KAAK,MAAM,EAAE;EAAC,CAAC;CAClE,MAAM,EACJ,QACA,gBAAgB,EACd,MACA,SACA,UACA,gBACA,iCACA,sCAEF,UACA,QACA,SACA,aACA,eACE;AAEJ,eAAc,iBAAiB,QAAQ,EAAC,QAAQ,mBAAkB,EAAE,MAAM,CAAC;CAC3E,MAAM,KAAK,iBAAiB,QAAQ,EAAC,QAAQ,mBAAkB,EAAE,KAAK;AACtE,eAAc,IAAI,OAAO;CAGzB,MAAM,WAAW,SACf,IACA,OAAO,IACP;EACE,KAAK,OAAO;EACZ,YAAY,GAAE,qBAAqB,0BAAyB;EAC7D,EACD,EAAC,kBAAkB,MAAK,CACzB;AACI,mBAAkB,IAAI,UAAU,SAAS;CAE9C,MAAM,EAAC,cAAa;CACpB,MAAM,QAAQ,eAAe,OAAO;CAEpC,IAAI;CAEJ,MAAM,UAAU,iBAAiB,OAAO;AAExC,MAAK,MAAM,SAAS,CAAC,MAAM,MAAM,CAC/B,KAAI;EAEF,MAAM,EAAC,cAAc,sBACnB,SAAS,SAAS,OACd,MAAM,+BACJ,IACA,SAAS,IACT,OACA,QAAQ,MACR,aACA,QACD,GACD,MAAM,6BACJ,IACA,SAAS,IACT,OACA,QAAQ,MACR,QACD;AAEP,mBAAiB,MAAM,mBACrB,IACA,OACA,QACA,SACA,UACA,UACA,cACA,mBACA,aAAa,OACb,iCACA,oCACA,WACD;AACD;UACO,GAAG;AACV,MAAI,SAAS,aAAa,iBAAiB;AACzC,MAAG,OAAO,qBAAqB,QAAQ,QAAQ,EAAE;AAGjD,gBAAa,QAAQ,KAAK;AAC1B;;AAEF,QAAM,qBACJ,IACA,uBAAuB,IAAI,gBAAgB,EAAE,CAC9C;AACD,MAAI,aAAa,kBACf,OAAM,IAAI,MACR,qCAAqC,QAAQ,KAAK,0CAClD,EAAC,OAAO,GAAE,CACX;AAEH,QAAM;;AAIV,QAAO,gBAAgB,mDAAmD;CAE1E,MAAM,EAAC,WAAW,MAAM,gBAAe;CACvC,MAAM,UAAU,YACZ,IAAI,cACF,IACA,WACA,oBAAoB,YAAY,WAChC,gBAQA,KAAK,KAAK,GAAG,cACd,GACD,IAAI,eAAe,IAAI,QAAQ,MAAM,eAAe;CAExD,MAAM,0BAA0B,IAAI,yBAClC,IACA,QACA;EAAC;EAAM;EAAe,EACtB,QACA,gBACA,mBAAmB,gBAAgB,UAAU,KAC9C;AAED,QAAO,KAAK,CAAC,SAAS,EAAC,OAAO,MAAK,CAAC,CAAC;AAIrC,QAAO,eAAe,IAAI,QAAQ,yBAAyB,QAAQ;;AAIrE,IAAI,CAAC,mBAAmB,CACjB,iBACH,UAAU,KAAK,aAAa,EAAE,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,EAAE,CAAC,CACrE"}
1
+ {"version":3,"file":"change-streamer.js","names":[],"sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {Database, 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 {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source.ts';\nimport {BackupMonitor} from '../services/change-streamer/backup-monitor.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 {\n AutoResetSignal,\n CHANGE_STREAMER_APP_NAME,\n} from '../services/change-streamer/schema/tables.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n replicationStatusError,\n ReplicationStatusPublisher,\n} from '../services/replicator/replication-status.ts';\nimport {pgClient} 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\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n assert(args.length > 0, `parent startMs not specified`);\n const parentStartMs = parseInt(args[0]);\n\n const config = getNormalizedZeroConfig({env, argv: args.slice(1)});\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 litestream,\n } = config;\n\n startOtelAuto(createLogContext(config, {worker: 'change-streamer'}, false));\n const lc = createLogContext(config, {worker: 'change-streamer'}, true);\n initEventSink(lc, config);\n\n // Kick off DB connection warmup in the background.\n const changeDB = pgClient(\n lc,\n change.db,\n {\n max: change.maxConns,\n connection: {['application_name']: CHANGE_STREAMER_APP_NAME},\n },\n {sendStringAsJson: true},\n );\n void warmupConnections(lc, changeDB, 'change');\n\n const {autoReset} = config;\n const shard = getShardConfig(config);\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 initialSync,\n context,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n context,\n );\n\n const replicationStatusPublisher = new ReplicationStatusPublisher(\n new Database(lc, replica.file, {readonly: true}),\n );\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n replicationStatusPublisher,\n subscriptionState,\n autoReset ?? false,\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\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 continue; // execute again with a fresh initial-sync\n }\n await publishCriticalEvent(\n lc,\n replicationStatusError(lc, 'Initializing', e),\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 const {backupURL, port: metricsPort} = litestream;\n const monitor = backupURL\n ? new BackupMonitor(\n lc,\n backupURL,\n `http://localhost:${metricsPort}/metrics`,\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 Date.now() - parentStartMs,\n )\n : new ReplicaMonitor(lc, replica.file, changeStreamer);\n\n const changeStreamerWebServer = new ChangeStreamerHttpServer(\n lc,\n config,\n {port, startupDelayMs},\n parent,\n changeStreamer,\n monitor instanceof BackupMonitor ? monitor : null,\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 runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkCA,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;AACf,QAAO,KAAK,SAAS,GAAG,+BAA+B;CACvD,MAAM,gBAAgB,SAAS,KAAK,GAAG;CAEvC,MAAM,SAAS,wBAAwB;EAAC;EAAK,MAAM,KAAK,MAAM,EAAE;EAAC,CAAC;CAClE,MAAM,EACJ,QACA,gBAAgB,EACd,MACA,SACA,UACA,gBACA,iCACA,sCAEF,UACA,QACA,SACA,aACA,eACE;AAEJ,eAAc,iBAAiB,QAAQ,EAAC,QAAQ,mBAAkB,EAAE,MAAM,CAAC;CAC3E,MAAM,KAAK,iBAAiB,QAAQ,EAAC,QAAQ,mBAAkB,EAAE,KAAK;AACtE,eAAc,IAAI,OAAO;CAGzB,MAAM,WAAW,SACf,IACA,OAAO,IACP;EACE,KAAK,OAAO;EACZ,YAAY,GAAE,qBAAqB,0BAAyB;EAC7D,EACD,EAAC,kBAAkB,MAAK,CACzB;AACI,mBAAkB,IAAI,UAAU,SAAS;CAE9C,MAAM,EAAC,cAAa;CACpB,MAAM,QAAQ,eAAe,OAAO;CAEpC,IAAI;CAEJ,MAAM,UAAU,iBAAiB,OAAO;AAExC,MAAK,MAAM,SAAS,CAAC,MAAM,MAAM,CAC/B,KAAI;EAEF,MAAM,EAAC,cAAc,sBACnB,SAAS,SAAS,OACd,MAAM,+BACJ,IACA,SAAS,IACT,OACA,QAAQ,MACR,aACA,QACD,GACD,MAAM,6BACJ,IACA,SAAS,IACT,OACA,QAAQ,MACR,QACD;AAMP,mBAAiB,MAAM,mBACrB,IACA,OACA,QACA,SACA,UACA,UACA,cAXiC,IAAI,2BACrC,IAAI,SAAS,IAAI,QAAQ,MAAM,EAAC,UAAU,MAAK,CAAC,CACjD,EAWC,mBACA,aAAa,OACb,iCACA,oCACA,WACD;AACD;UACO,GAAG;AACV,MAAI,SAAS,aAAa,iBAAiB;AACzC,MAAG,OAAO,qBAAqB,QAAQ,QAAQ,EAAE;AAGjD,gBAAa,QAAQ,KAAK;AAC1B;;AAEF,QAAM,qBACJ,IACA,uBAAuB,IAAI,gBAAgB,EAAE,CAC9C;AACD,MAAI,aAAa,kBACf,OAAM,IAAI,MACR,qCAAqC,QAAQ,KAAK,0CAClD,EAAC,OAAO,GAAE,CACX;AAEH,QAAM;;AAIV,QAAO,gBAAgB,mDAAmD;CAE1E,MAAM,EAAC,WAAW,MAAM,gBAAe;CACvC,MAAM,UAAU,YACZ,IAAI,cACF,IACA,WACA,oBAAoB,YAAY,WAChC,gBAQA,KAAK,KAAK,GAAG,cACd,GACD,IAAI,eAAe,IAAI,QAAQ,MAAM,eAAe;CAExD,MAAM,0BAA0B,IAAI,yBAClC,IACA,QACA;EAAC;EAAM;EAAe,EACtB,QACA,gBACA,mBAAmB,gBAAgB,UAAU,KAC9C;AAED,QAAO,KAAK,CAAC,SAAS,EAAC,OAAO,MAAK,CAAC,CAAC;AAIrC,QAAO,eAAe,IAAI,QAAQ,yBAAyB,QAAQ;;AAIrE,IAAI,CAAC,mBAAmB,CACjB,iBACH,UAAU,KAAK,aAAa,EAAE,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,EAAE,CAAC,CACrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/ddl.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,CAAC,MAAM,wCAAwC,CAAC;AAC5D,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,6BAA6B,CAAC;AAc7E,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAQlC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAGzB,CAAC;AASH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAE9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAG/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAGpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAIlC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AA0NtE,eAAO,MAAM,IAAI,2HAQP,CAAC;AAEX,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,WAAW,UA0C9D;AAGD,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,MAAM,UAgBzB"}
1
+ {"version":3,"file":"ddl.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/ddl.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,CAAC,MAAM,wCAAwC,CAAC;AAC5D,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,6BAA6B,CAAC;AAc7E,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAQlC,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAGzB,CAAC;AASH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAE9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAG/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAGpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAIlC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAuMtE,eAAO,MAAM,IAAI,2HAQP,CAAC;AAEX,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,WAAW,UA0C9D;AAGD,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,MAAM,UAgBzB"}
@@ -172,6 +172,7 @@ BEGIN
172
172
  LIMIT 1 INTO target;
173
173
 
174
174
  -- Filter DDL updates that are not relevant to the shard (i.e. publications) when possible.
175
+ SELECT true INTO relevant;
175
176
 
176
177
  -- Note: ALTER TABLE statements may *remove* the table from the set of published
177
178
  -- tables, and there is no way to determine if the table "used to be" in the
@@ -184,10 +185,6 @@ BEGIN
184
185
  JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = c.relname
185
186
  WHERE c.oid = target.objid AND pb.pubname = ANY (publications)
186
187
  INTO relevant;
187
- IF relevant IS NULL THEN
188
- PERFORM ${schema}.notice_ignore(tag, target);
189
- RETURN;
190
- END IF;
191
188
 
192
189
  ELSIF target.object_type = 'index' THEN
193
190
  SELECT ns.nspname AS "schema", c.relname AS "name" FROM pg_class AS c
@@ -196,30 +193,18 @@ BEGIN
196
193
  JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = ind.tablename
197
194
  WHERE c.oid = target.objid AND pb.pubname = ANY (publications)
198
195
  INTO relevant;
199
- IF relevant IS NULL THEN
200
- PERFORM ${schema}.notice_ignore(tag, target);
201
- RETURN;
202
- END IF;
203
196
 
204
197
  ELSIF target.object_type = 'publication relation' THEN
205
198
  SELECT pb.pubname FROM pg_publication_rel AS rel
206
199
  JOIN pg_publication AS pb ON pb.oid = rel.prpubid
207
200
  WHERE rel.oid = target.objid AND pb.pubname = ANY (publications)
208
201
  INTO relevant;
209
- IF relevant IS NULL THEN
210
- PERFORM ${schema}.notice_ignore(tag, target);
211
- RETURN;
212
- END IF;
213
202
 
214
203
  ELSIF target.object_type = 'publication namespace' THEN
215
204
  SELECT pb.pubname FROM pg_publication_namespace AS ns
216
205
  JOIN pg_publication AS pb ON pb.oid = ns.pnpubid
217
206
  WHERE ns.oid = target.objid AND pb.pubname = ANY (publications)
218
207
  INTO relevant;
219
- IF relevant IS NULL THEN
220
- PERFORM ${schema}.notice_ignore(tag, target);
221
- RETURN;
222
- END IF;
223
208
 
224
209
  ELSIF target.object_type = 'schema' THEN
225
210
  SELECT ns.nspname AS "schema", c.relname AS "name" FROM pg_class AS c
@@ -227,21 +212,17 @@ BEGIN
227
212
  JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = c.relname
228
213
  WHERE ns.oid = target.objid AND pb.pubname = ANY (publications)
229
214
  INTO relevant;
230
- IF relevant IS NULL THEN
231
- PERFORM ${schema}.notice_ignore(tag, target);
232
- RETURN;
233
- END IF;
234
215
 
235
216
  ELSIF target.object_type = 'publication' THEN
236
217
  SELECT 1 WHERE target.object_identity = ANY (publications)
237
218
  INTO relevant;
238
- IF relevant IS NULL THEN
239
- PERFORM ${schema}.notice_ignore(tag, target);
240
- RETURN;
241
- END IF;
242
219
 
243
220
  -- no-op CREATE IF NOT EXIST statements
244
221
  ELSIF tag LIKE 'CREATE %' AND target.object_type IS NULL THEN
222
+ relevant := NULL;
223
+ END IF;
224
+
225
+ IF relevant IS NULL THEN
245
226
  PERFORM ${schema}.notice_ignore(tag, target);
246
227
  RETURN;
247
228
  END IF;
@@ -1 +1 @@
1
- {"version":3,"file":"ddl.js","names":[],"sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/ddl.ts"],"sourcesContent":["import {literal as lit} from 'pg-format';\nimport {assert} from '../../../../../../shared/src/asserts.ts';\nimport * as v from '../../../../../../shared/src/valita.ts';\nimport {upstreamSchema, type ShardConfig} from '../../../../types/shards.ts';\nimport {id} from '../../../../types/sql.ts';\nimport {\n indexDefinitionsQuery,\n publishedSchema,\n publishedTableQuery,\n} from './published.ts';\n\n// Sent in the 'version' tag of \"ddlStart\" and \"ddlUpdate\" event messages.\n// This is used to ensure that the message constructed in the upstream\n// Trigger function is compatible with the code processing it in the zero-cache.\n//\n// Increment this when changing the format of the contents of the \"ddl\" events.\n// This will allow old / incompatible code to detect the change and abort.\nexport const PROTOCOL_VERSION = 1;\n\nconst triggerEvent = v.object({\n context: v.object({query: v.string()}).rest(v.string()),\n});\n\n// All DDL events contain a snapshot of the current tables and indexes that\n// are published / relevant to the shard.\nexport const ddlEventSchema = triggerEvent.extend({\n version: v.literal(PROTOCOL_VERSION),\n schema: publishedSchema,\n});\n\n// The `ddlStart` message is computed before every DDL event, regardless of\n// whether the subsequent event affects the shard. Downstream processing should\n// capture the contained schema information in order to determine the schema\n// changes necessary to apply a subsequent `ddlUpdate` message. Note that a\n// `ddlUpdate` message may not follow, as updates determined to be irrelevant\n// to the shard will not result in a message. However, all `ddlUpdate` messages\n// are guaranteed to be preceded by a `ddlStart` message.\nexport const ddlStartEventSchema = ddlEventSchema.extend({\n type: v.literal('ddlStart'),\n});\n\nexport type DdlStartEvent = v.Infer<typeof ddlStartEventSchema>;\n\n/**\n * The {@link DdlUpdateEvent} contains an updated schema resulting from\n * a particular ddl event. The event type provides information\n * (i.e. constraints) on the difference from the schema of the preceding\n * {@link DdlStartEvent}.\n *\n * Note that in almost all cases (the exception being `CREATE` events),\n * it is possible that there is no relevant difference between the\n * ddl-start schema and the ddl-update schema, as many aspects of the\n * schema (e.g. column constraints) are not relevant to downstream\n * replication.\n */\nexport const ddlUpdateEventSchema = ddlEventSchema.extend({\n type: v.literal('ddlUpdate'),\n event: v.object({tag: v.string()}),\n});\n\nexport type DdlUpdateEvent = v.Infer<typeof ddlUpdateEventSchema>;\n\n/**\n * The `schemaSnapshot` message is a snapshot of a schema taken in response to\n * a `COMMENT ON PUBLICATION` command, which is a hook recognized by zero\n * to manually emit schema snapshots to support detection of schema changes\n * from `ALTER PUBLICATION` commands on supabase, which does not fire event\n * triggers for them (https://github.com/supabase/supautils/issues/123).\n *\n * The hook is exercised by bookmarking the publication change with\n * `COMMENT ON PUBLICATION` statements within e.g.\n *\n * ```sql\n * BEGIN;\n * COMMENT ON PUBLICATION my_publication IS 'whatever';\n * ALTER PUBLICATION my_publication ...;\n * COMMENT ON PUBLICATION my_publication IS 'whatever';\n * COMMIT;\n * ```\n *\n * The `change-source` will perform the diff between a `schemaSnapshot`\n * events and its preceding `schemaSnapshot` (or `ddlUpdate`) within the\n * transaction.\n *\n * In the case where event trigger support is missing, this results in\n * diffing the `schemaSnapshot`s before and after the `ALTER PUBLICATION`\n * statement, thus effecting the same logic that would have been exercised\n * between the `ddlStart` and `ddlEvent` events fired by a database with\n * fully functional event triggers.\n *\n * Note that if the same transaction is run on a database that *does*\n * support event triggers on `ALTER PUBLICATION` statements, the sequence\n * of emitted messages will be:\n *\n * * `schemaSnapshot`\n * * `ddlStart`\n * * `ddlUpdate`\n * * `schemaSnapshot`\n *\n * Since `schemaSnapshot` messages are diffed with the preceding\n * `schemaSnapshot` or `ddlUpdate` event (if any), there will be no schema\n * difference between the `ddlUpdate` and the second `schemaSnapshot`, and\n * thus the extra `COMMENT` statements will effectively be no-ops.\n */\nexport const schemaSnapshotEventSchema = ddlEventSchema.extend({\n type: v.literal('schemaSnapshot'),\n event: v.object({tag: v.string()}),\n});\n\nexport type SchemaSnapshotEvent = v.Infer<typeof schemaSnapshotEventSchema>;\n\nexport const replicationEventSchema = v.union(\n ddlStartEventSchema,\n ddlUpdateEventSchema,\n schemaSnapshotEventSchema,\n);\n\nexport type ReplicationEvent = v.Infer<typeof replicationEventSchema>;\n\n// Creates a function that appends `_{shard-num}` to the input and\n// quotes the result to be a valid identifier.\nfunction append(shardNum: number) {\n return (name: string) => id(name + '_' + String(shardNum));\n}\n\n/**\n * Event trigger functions contain the core logic that are invoked by triggers.\n *\n * Note that although many of these functions can theoretically be parameterized and\n * shared across shards, it is advantageous to keep the functions in each shard\n * isolated from each other in order to avoid the complexity of shared-function\n * versioning.\n *\n * In a sense, shards (and their triggers and functions) should be thought of as\n * execution environments that can be updated at different schedules. If per-shard\n * triggers called into shared functions, we would have to consider versioning the\n * functions when changing their behavior, backwards compatibility, removal of\n * unused versions, etc. (not unlike versioning of npm packages).\n *\n * Instead, we opt for the simplicity and isolation of having each shard\n * completely own (and maintain) the entirety of its trigger/function stack.\n */\nfunction createEventFunctionStatements(shard: ShardConfig) {\n const {appID, shardNum, publications} = shard;\n const schema = id(upstreamSchema(shard)); // e.g. \"{APP_ID}_{SHARD_ID}\"\n return /*sql*/ `\nCREATE SCHEMA IF NOT EXISTS ${schema};\n\nCREATE OR REPLACE FUNCTION ${schema}.get_trigger_context()\nRETURNS record AS $$\nDECLARE\n result record;\nBEGIN\n SELECT current_query() AS \"query\" into result;\n RETURN result;\nEND\n$$ LANGUAGE plpgsql;\n\n\nCREATE OR REPLACE FUNCTION ${schema}.notice_ignore(tag TEXT, target record)\nRETURNS void AS $$\nBEGIN\n RAISE NOTICE 'zero(%) ignoring % %', ${lit(shardNum)}, tag, row_to_json(target);\nEND\n$$ LANGUAGE plpgsql;\n\n\nCREATE OR REPLACE FUNCTION ${schema}.schema_specs()\nRETURNS TEXT AS $$\nDECLARE\n tables record;\n indexes record;\nBEGIN\n ${publishedTableQuery(publications)} INTO tables;\n ${indexDefinitionsQuery(publications)} INTO indexes;\n RETURN json_build_object(\n 'tables', tables.tables,\n 'indexes', indexes.indexes\n );\nEND\n$$ LANGUAGE plpgsql;\n\n\nCREATE OR REPLACE FUNCTION ${schema}.emit_ddl_start()\nRETURNS event_trigger AS $$\nDECLARE\n schema_specs TEXT;\n message TEXT;\nBEGIN\n SELECT ${schema}.schema_specs() INTO schema_specs;\n\n SELECT json_build_object(\n 'type', 'ddlStart',\n 'version', ${PROTOCOL_VERSION},\n 'schema', schema_specs::json,\n 'context', ${schema}.get_trigger_context()\n ) INTO message;\n\n PERFORM pg_logical_emit_message(true, ${lit(\n `${appID}/${shardNum}`,\n )}, message);\nEND\n$$ LANGUAGE plpgsql;\n\n\nCREATE OR REPLACE FUNCTION ${schema}.emit_ddl_end(tag TEXT)\nRETURNS void AS $$\nDECLARE\n publications TEXT[];\n target RECORD;\n relevant RECORD;\n schema_specs TEXT;\n message TEXT;\n event TEXT;\n event_type TEXT;\n event_prefix TEXT;\nBEGIN\n publications := ARRAY[${lit(publications)}];\n\n SELECT objid, object_type, object_identity \n FROM pg_event_trigger_ddl_commands() \n LIMIT 1 INTO target;\n\n -- Filter DDL updates that are not relevant to the shard (i.e. publications) when possible.\n\n -- Note: ALTER TABLE statements may *remove* the table from the set of published\n -- tables, and there is no way to determine if the table \"used to be\" in the\n -- set. Thus, all ALTER TABLE statements must produce a ddl update, similar to\n -- any DROP * statement.\n IF (target.object_type = 'table' AND tag != 'ALTER TABLE') \n OR target.object_type = 'table column' THEN\n SELECT ns.nspname AS \"schema\", c.relname AS \"name\" FROM pg_class AS c\n JOIN pg_namespace AS ns ON c.relnamespace = ns.oid\n JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = c.relname\n WHERE c.oid = target.objid AND pb.pubname = ANY (publications)\n INTO relevant;\n IF relevant IS NULL THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n\n ELSIF target.object_type = 'index' THEN\n SELECT ns.nspname AS \"schema\", c.relname AS \"name\" FROM pg_class AS c\n JOIN pg_namespace AS ns ON c.relnamespace = ns.oid\n JOIN pg_indexes as ind ON ind.schemaname = ns.nspname AND ind.indexname = c.relname\n JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = ind.tablename\n WHERE c.oid = target.objid AND pb.pubname = ANY (publications)\n INTO relevant;\n IF relevant IS NULL THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n\n ELSIF target.object_type = 'publication relation' THEN\n SELECT pb.pubname FROM pg_publication_rel AS rel\n JOIN pg_publication AS pb ON pb.oid = rel.prpubid\n WHERE rel.oid = target.objid AND pb.pubname = ANY (publications) \n INTO relevant;\n IF relevant IS NULL THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n\n ELSIF target.object_type = 'publication namespace' THEN\n SELECT pb.pubname FROM pg_publication_namespace AS ns\n JOIN pg_publication AS pb ON pb.oid = ns.pnpubid\n WHERE ns.oid = target.objid AND pb.pubname = ANY (publications) \n INTO relevant;\n IF relevant IS NULL THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n\n ELSIF target.object_type = 'schema' THEN\n SELECT ns.nspname AS \"schema\", c.relname AS \"name\" FROM pg_class AS c\n JOIN pg_namespace AS ns ON c.relnamespace = ns.oid\n JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = c.relname\n WHERE ns.oid = target.objid AND pb.pubname = ANY (publications)\n INTO relevant;\n IF relevant IS NULL THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n\n ELSIF target.object_type = 'publication' THEN\n SELECT 1 WHERE target.object_identity = ANY (publications)\n INTO relevant;\n IF relevant IS NULL THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n\n -- no-op CREATE IF NOT EXIST statements\n ELSIF tag LIKE 'CREATE %' AND target.object_type IS NULL THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n\n IF tag = 'COMMENT' THEN\n -- Only make schemaSnapshots for COMMENT ON PUBLICATION\n IF target.object_type != 'publication' THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n event_type := 'schemaSnapshot';\n event_prefix := '/ddl';\n ELSE\n event_type := 'ddlUpdate';\n event_prefix := ''; -- TODO: Use '/ddl' for both when rollback safe\n END IF;\n\n RAISE INFO 'Creating % for % %', event_type, tag, row_to_json(target);\n\n -- Construct and emit the DdlUpdateEvent message.\n SELECT json_build_object('tag', tag) INTO event;\n \n SELECT ${schema}.schema_specs() INTO schema_specs;\n\n SELECT json_build_object(\n 'type', event_type,\n 'version', ${PROTOCOL_VERSION},\n 'schema', schema_specs::json,\n 'event', event::json,\n 'context', ${schema}.get_trigger_context()\n ) INTO message;\n\n PERFORM pg_logical_emit_message(true, ${lit(\n `${appID}/${shardNum}`,\n )} || event_prefix, message);\nEND\n$$ LANGUAGE plpgsql;\n`;\n}\n\n// Exported for testing.\nexport const TAGS = [\n 'CREATE TABLE',\n 'ALTER TABLE',\n 'CREATE INDEX',\n 'DROP TABLE',\n 'DROP INDEX',\n 'ALTER PUBLICATION',\n 'ALTER SCHEMA',\n] as const;\n\nexport function createEventTriggerStatements(shard: ShardConfig) {\n // Better to assert here than get a cryptic syntax error from Postgres.\n assert(shard.publications.length, `shard publications must be non-empty`);\n\n // Unlike functions, which are namespaced in shard-specific schemas,\n // EVENT TRIGGER names are in the global namespace and thus must include\n // the appID and shardNum.\n const {appID, shardNum} = shard;\n const sharded = append(shardNum);\n const schema = id(upstreamSchema(shard));\n\n const triggers = [\n dropEventTriggerStatements(shard.appID, shard.shardNum),\n createEventFunctionStatements(shard),\n ];\n\n // A single ddl_command_start trigger covering all relevant tags.\n triggers.push(/*sql*/ `\nCREATE EVENT TRIGGER ${sharded(`${appID}_ddl_start`)}\n ON ddl_command_start\n WHEN TAG IN (${lit(TAGS)})\n EXECUTE PROCEDURE ${schema}.emit_ddl_start();\n`);\n\n // A per-tag ddl_command_end trigger that dispatches to ${schema}.emit_ddl_end(tag)\n for (const tag of [...TAGS, 'COMMENT']) {\n const tagID = tag.toLowerCase().replace(' ', '_');\n triggers.push(/*sql*/ `\nCREATE OR REPLACE FUNCTION ${schema}.emit_${tagID}() \nRETURNS event_trigger AS $$\nBEGIN\n PERFORM ${schema}.emit_ddl_end(${lit(tag)});\nEND\n$$ LANGUAGE plpgsql;\n\nCREATE EVENT TRIGGER ${sharded(`${appID}_${tagID}`)}\n ON ddl_command_end\n WHEN TAG IN (${lit(tag)})\n EXECUTE PROCEDURE ${schema}.emit_${tagID}();\n`);\n }\n return triggers.join('');\n}\n\n// Exported for testing.\nexport function dropEventTriggerStatements(\n appID: string,\n shardID: string | number,\n) {\n const stmts: string[] = [];\n // A single ddl_command_start trigger covering all relevant tags.\n stmts.push(/*sql*/ `\n DROP EVENT TRIGGER IF EXISTS ${id(`${appID}_ddl_start_${shardID}`)};\n `);\n\n // A per-tag ddl_command_end trigger that dispatches to ${schema}.emit_ddl_end(tag)\n for (const tag of [...TAGS, 'COMMENT']) {\n const tagID = tag.toLowerCase().replace(' ', '_');\n stmts.push(/*sql*/ `\n DROP EVENT TRIGGER IF EXISTS ${id(`${appID}_${tagID}_${shardID}`)};\n `);\n }\n return stmts.join('');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ddl.js","names":[],"sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/ddl.ts"],"sourcesContent":["import {literal as lit} from 'pg-format';\nimport {assert} from '../../../../../../shared/src/asserts.ts';\nimport * as v from '../../../../../../shared/src/valita.ts';\nimport {upstreamSchema, type ShardConfig} from '../../../../types/shards.ts';\nimport {id} from '../../../../types/sql.ts';\nimport {\n indexDefinitionsQuery,\n publishedSchema,\n publishedTableQuery,\n} from './published.ts';\n\n// Sent in the 'version' tag of \"ddlStart\" and \"ddlUpdate\" event messages.\n// This is used to ensure that the message constructed in the upstream\n// Trigger function is compatible with the code processing it in the zero-cache.\n//\n// Increment this when changing the format of the contents of the \"ddl\" events.\n// This will allow old / incompatible code to detect the change and abort.\nexport const PROTOCOL_VERSION = 1;\n\nconst triggerEvent = v.object({\n context: v.object({query: v.string()}).rest(v.string()),\n});\n\n// All DDL events contain a snapshot of the current tables and indexes that\n// are published / relevant to the shard.\nexport const ddlEventSchema = triggerEvent.extend({\n version: v.literal(PROTOCOL_VERSION),\n schema: publishedSchema,\n});\n\n// The `ddlStart` message is computed before every DDL event, regardless of\n// whether the subsequent event affects the shard. Downstream processing should\n// capture the contained schema information in order to determine the schema\n// changes necessary to apply a subsequent `ddlUpdate` message. Note that a\n// `ddlUpdate` message may not follow, as updates determined to be irrelevant\n// to the shard will not result in a message. However, all `ddlUpdate` messages\n// are guaranteed to be preceded by a `ddlStart` message.\nexport const ddlStartEventSchema = ddlEventSchema.extend({\n type: v.literal('ddlStart'),\n});\n\nexport type DdlStartEvent = v.Infer<typeof ddlStartEventSchema>;\n\n/**\n * The {@link DdlUpdateEvent} contains an updated schema resulting from\n * a particular ddl event. The event type provides information\n * (i.e. constraints) on the difference from the schema of the preceding\n * {@link DdlStartEvent}.\n *\n * Note that in almost all cases (the exception being `CREATE` events),\n * it is possible that there is no relevant difference between the\n * ddl-start schema and the ddl-update schema, as many aspects of the\n * schema (e.g. column constraints) are not relevant to downstream\n * replication.\n */\nexport const ddlUpdateEventSchema = ddlEventSchema.extend({\n type: v.literal('ddlUpdate'),\n event: v.object({tag: v.string()}),\n});\n\nexport type DdlUpdateEvent = v.Infer<typeof ddlUpdateEventSchema>;\n\n/**\n * The `schemaSnapshot` message is a snapshot of a schema taken in response to\n * a `COMMENT ON PUBLICATION` command, which is a hook recognized by zero\n * to manually emit schema snapshots to support detection of schema changes\n * from `ALTER PUBLICATION` commands on supabase, which does not fire event\n * triggers for them (https://github.com/supabase/supautils/issues/123).\n *\n * The hook is exercised by bookmarking the publication change with\n * `COMMENT ON PUBLICATION` statements within e.g.\n *\n * ```sql\n * BEGIN;\n * COMMENT ON PUBLICATION my_publication IS 'whatever';\n * ALTER PUBLICATION my_publication ...;\n * COMMENT ON PUBLICATION my_publication IS 'whatever';\n * COMMIT;\n * ```\n *\n * The `change-source` will perform the diff between a `schemaSnapshot`\n * events and its preceding `schemaSnapshot` (or `ddlUpdate`) within the\n * transaction.\n *\n * In the case where event trigger support is missing, this results in\n * diffing the `schemaSnapshot`s before and after the `ALTER PUBLICATION`\n * statement, thus effecting the same logic that would have been exercised\n * between the `ddlStart` and `ddlEvent` events fired by a database with\n * fully functional event triggers.\n *\n * Note that if the same transaction is run on a database that *does*\n * support event triggers on `ALTER PUBLICATION` statements, the sequence\n * of emitted messages will be:\n *\n * * `schemaSnapshot`\n * * `ddlStart`\n * * `ddlUpdate`\n * * `schemaSnapshot`\n *\n * Since `schemaSnapshot` messages are diffed with the preceding\n * `schemaSnapshot` or `ddlUpdate` event (if any), there will be no schema\n * difference between the `ddlUpdate` and the second `schemaSnapshot`, and\n * thus the extra `COMMENT` statements will effectively be no-ops.\n */\nexport const schemaSnapshotEventSchema = ddlEventSchema.extend({\n type: v.literal('schemaSnapshot'),\n event: v.object({tag: v.string()}),\n});\n\nexport type SchemaSnapshotEvent = v.Infer<typeof schemaSnapshotEventSchema>;\n\nexport const replicationEventSchema = v.union(\n ddlStartEventSchema,\n ddlUpdateEventSchema,\n schemaSnapshotEventSchema,\n);\n\nexport type ReplicationEvent = v.Infer<typeof replicationEventSchema>;\n\n// Creates a function that appends `_{shard-num}` to the input and\n// quotes the result to be a valid identifier.\nfunction append(shardNum: number) {\n return (name: string) => id(name + '_' + String(shardNum));\n}\n\n/**\n * Event trigger functions contain the core logic that are invoked by triggers.\n *\n * Note that although many of these functions can theoretically be parameterized and\n * shared across shards, it is advantageous to keep the functions in each shard\n * isolated from each other in order to avoid the complexity of shared-function\n * versioning.\n *\n * In a sense, shards (and their triggers and functions) should be thought of as\n * execution environments that can be updated at different schedules. If per-shard\n * triggers called into shared functions, we would have to consider versioning the\n * functions when changing their behavior, backwards compatibility, removal of\n * unused versions, etc. (not unlike versioning of npm packages).\n *\n * Instead, we opt for the simplicity and isolation of having each shard\n * completely own (and maintain) the entirety of its trigger/function stack.\n */\nfunction createEventFunctionStatements(shard: ShardConfig) {\n const {appID, shardNum, publications} = shard;\n const schema = id(upstreamSchema(shard)); // e.g. \"{APP_ID}_{SHARD_ID}\"\n return /*sql*/ `\nCREATE SCHEMA IF NOT EXISTS ${schema};\n\nCREATE OR REPLACE FUNCTION ${schema}.get_trigger_context()\nRETURNS record AS $$\nDECLARE\n result record;\nBEGIN\n SELECT current_query() AS \"query\" into result;\n RETURN result;\nEND\n$$ LANGUAGE plpgsql;\n\n\nCREATE OR REPLACE FUNCTION ${schema}.notice_ignore(tag TEXT, target record)\nRETURNS void AS $$\nBEGIN\n RAISE NOTICE 'zero(%) ignoring % %', ${lit(shardNum)}, tag, row_to_json(target);\nEND\n$$ LANGUAGE plpgsql;\n\n\nCREATE OR REPLACE FUNCTION ${schema}.schema_specs()\nRETURNS TEXT AS $$\nDECLARE\n tables record;\n indexes record;\nBEGIN\n ${publishedTableQuery(publications)} INTO tables;\n ${indexDefinitionsQuery(publications)} INTO indexes;\n RETURN json_build_object(\n 'tables', tables.tables,\n 'indexes', indexes.indexes\n );\nEND\n$$ LANGUAGE plpgsql;\n\n\nCREATE OR REPLACE FUNCTION ${schema}.emit_ddl_start()\nRETURNS event_trigger AS $$\nDECLARE\n schema_specs TEXT;\n message TEXT;\nBEGIN\n SELECT ${schema}.schema_specs() INTO schema_specs;\n\n SELECT json_build_object(\n 'type', 'ddlStart',\n 'version', ${PROTOCOL_VERSION},\n 'schema', schema_specs::json,\n 'context', ${schema}.get_trigger_context()\n ) INTO message;\n\n PERFORM pg_logical_emit_message(true, ${lit(\n `${appID}/${shardNum}`,\n )}, message);\nEND\n$$ LANGUAGE plpgsql;\n\n\nCREATE OR REPLACE FUNCTION ${schema}.emit_ddl_end(tag TEXT)\nRETURNS void AS $$\nDECLARE\n publications TEXT[];\n target RECORD;\n relevant RECORD;\n schema_specs TEXT;\n message TEXT;\n event TEXT;\n event_type TEXT;\n event_prefix TEXT;\nBEGIN\n publications := ARRAY[${lit(publications)}];\n\n SELECT objid, object_type, object_identity \n FROM pg_event_trigger_ddl_commands() \n LIMIT 1 INTO target;\n\n -- Filter DDL updates that are not relevant to the shard (i.e. publications) when possible.\n SELECT true INTO relevant;\n\n -- Note: ALTER TABLE statements may *remove* the table from the set of published\n -- tables, and there is no way to determine if the table \"used to be\" in the\n -- set. Thus, all ALTER TABLE statements must produce a ddl update, similar to\n -- any DROP * statement.\n IF (target.object_type = 'table' AND tag != 'ALTER TABLE') \n OR target.object_type = 'table column' THEN\n SELECT ns.nspname AS \"schema\", c.relname AS \"name\" FROM pg_class AS c\n JOIN pg_namespace AS ns ON c.relnamespace = ns.oid\n JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = c.relname\n WHERE c.oid = target.objid AND pb.pubname = ANY (publications)\n INTO relevant;\n\n ELSIF target.object_type = 'index' THEN\n SELECT ns.nspname AS \"schema\", c.relname AS \"name\" FROM pg_class AS c\n JOIN pg_namespace AS ns ON c.relnamespace = ns.oid\n JOIN pg_indexes as ind ON ind.schemaname = ns.nspname AND ind.indexname = c.relname\n JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = ind.tablename\n WHERE c.oid = target.objid AND pb.pubname = ANY (publications)\n INTO relevant;\n\n ELSIF target.object_type = 'publication relation' THEN\n SELECT pb.pubname FROM pg_publication_rel AS rel\n JOIN pg_publication AS pb ON pb.oid = rel.prpubid\n WHERE rel.oid = target.objid AND pb.pubname = ANY (publications) \n INTO relevant;\n\n ELSIF target.object_type = 'publication namespace' THEN\n SELECT pb.pubname FROM pg_publication_namespace AS ns\n JOIN pg_publication AS pb ON pb.oid = ns.pnpubid\n WHERE ns.oid = target.objid AND pb.pubname = ANY (publications) \n INTO relevant;\n\n ELSIF target.object_type = 'schema' THEN\n SELECT ns.nspname AS \"schema\", c.relname AS \"name\" FROM pg_class AS c\n JOIN pg_namespace AS ns ON c.relnamespace = ns.oid\n JOIN pg_publication_tables AS pb ON pb.schemaname = ns.nspname AND pb.tablename = c.relname\n WHERE ns.oid = target.objid AND pb.pubname = ANY (publications)\n INTO relevant;\n\n ELSIF target.object_type = 'publication' THEN\n SELECT 1 WHERE target.object_identity = ANY (publications)\n INTO relevant;\n\n -- no-op CREATE IF NOT EXIST statements\n ELSIF tag LIKE 'CREATE %' AND target.object_type IS NULL THEN\n relevant := NULL;\n END IF;\n\n IF relevant IS NULL THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n\n IF tag = 'COMMENT' THEN\n -- Only make schemaSnapshots for COMMENT ON PUBLICATION\n IF target.object_type != 'publication' THEN\n PERFORM ${schema}.notice_ignore(tag, target);\n RETURN;\n END IF;\n event_type := 'schemaSnapshot';\n event_prefix := '/ddl';\n ELSE\n event_type := 'ddlUpdate';\n event_prefix := ''; -- TODO: Use '/ddl' for both when rollback safe\n END IF;\n\n RAISE INFO 'Creating % for % %', event_type, tag, row_to_json(target);\n\n -- Construct and emit the DdlUpdateEvent message.\n SELECT json_build_object('tag', tag) INTO event;\n \n SELECT ${schema}.schema_specs() INTO schema_specs;\n\n SELECT json_build_object(\n 'type', event_type,\n 'version', ${PROTOCOL_VERSION},\n 'schema', schema_specs::json,\n 'event', event::json,\n 'context', ${schema}.get_trigger_context()\n ) INTO message;\n\n PERFORM pg_logical_emit_message(true, ${lit(\n `${appID}/${shardNum}`,\n )} || event_prefix, message);\nEND\n$$ LANGUAGE plpgsql;\n`;\n}\n\n// Exported for testing.\nexport const TAGS = [\n 'CREATE TABLE',\n 'ALTER TABLE',\n 'CREATE INDEX',\n 'DROP TABLE',\n 'DROP INDEX',\n 'ALTER PUBLICATION',\n 'ALTER SCHEMA',\n] as const;\n\nexport function createEventTriggerStatements(shard: ShardConfig) {\n // Better to assert here than get a cryptic syntax error from Postgres.\n assert(shard.publications.length, `shard publications must be non-empty`);\n\n // Unlike functions, which are namespaced in shard-specific schemas,\n // EVENT TRIGGER names are in the global namespace and thus must include\n // the appID and shardNum.\n const {appID, shardNum} = shard;\n const sharded = append(shardNum);\n const schema = id(upstreamSchema(shard));\n\n const triggers = [\n dropEventTriggerStatements(shard.appID, shard.shardNum),\n createEventFunctionStatements(shard),\n ];\n\n // A single ddl_command_start trigger covering all relevant tags.\n triggers.push(/*sql*/ `\nCREATE EVENT TRIGGER ${sharded(`${appID}_ddl_start`)}\n ON ddl_command_start\n WHEN TAG IN (${lit(TAGS)})\n EXECUTE PROCEDURE ${schema}.emit_ddl_start();\n`);\n\n // A per-tag ddl_command_end trigger that dispatches to ${schema}.emit_ddl_end(tag)\n for (const tag of [...TAGS, 'COMMENT']) {\n const tagID = tag.toLowerCase().replace(' ', '_');\n triggers.push(/*sql*/ `\nCREATE OR REPLACE FUNCTION ${schema}.emit_${tagID}() \nRETURNS event_trigger AS $$\nBEGIN\n PERFORM ${schema}.emit_ddl_end(${lit(tag)});\nEND\n$$ LANGUAGE plpgsql;\n\nCREATE EVENT TRIGGER ${sharded(`${appID}_${tagID}`)}\n ON ddl_command_end\n WHEN TAG IN (${lit(tag)})\n EXECUTE PROCEDURE ${schema}.emit_${tagID}();\n`);\n }\n return triggers.join('');\n}\n\n// Exported for testing.\nexport function dropEventTriggerStatements(\n appID: string,\n shardID: string | number,\n) {\n const stmts: string[] = [];\n // A single ddl_command_start trigger covering all relevant tags.\n stmts.push(/*sql*/ `\n DROP EVENT TRIGGER IF EXISTS ${id(`${appID}_ddl_start_${shardID}`)};\n `);\n\n // A per-tag ddl_command_end trigger that dispatches to ${schema}.emit_ddl_end(tag)\n for (const tag of [...TAGS, 'COMMENT']) {\n const tagID = tag.toLowerCase().replace(' ', '_');\n stmts.push(/*sql*/ `\n DROP EVENT TRIGGER IF EXISTS ${id(`${appID}_${tagID}_${shardID}`)};\n `);\n }\n return stmts.join('');\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AASjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,6BAA6B,CAAC;AAa7E;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,WAAW,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAyLD,eAAO,MAAM,sBAAsB,QAMwB,CAAC;AAE5D,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,WAAW,iBAYnB"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AASjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,6BAA6B,CAAC;AAa7E;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,WAAW,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAsLD,eAAO,MAAM,sBAAsB,QAMwB,CAAC;AAE5D,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,WAAW,iBAYnB"}
@@ -35,7 +35,6 @@ function getIncrementalMigrations(shard, replicaVersion) {
35
35
  },
36
36
  minSafeVersion: 3
37
37
  },
38
- 5: {},
39
38
  6: { migrateSchema: async (lc, sql) => {
40
39
  assert(replicaVersion, `replicaVersion is always passed for incremental migrations`);
41
40
  await Promise.all([sql`
@@ -82,7 +81,6 @@ function getIncrementalMigrations(shard, replicaVersion) {
82
81
  ]);
83
82
  lc.info?.(`Upgraded schema to support non-disruptive resyncs`);
84
83
  } },
85
- 9: {},
86
84
  10: { migrateSchema: async (lc, sql) => {
87
85
  await sql.unsafe(`
88
86
  ${getMutationsTableDefinition(upstreamSchema(shard))}
@@ -90,8 +88,6 @@ function getIncrementalMigrations(shard, replicaVersion) {
90
88
  `);
91
89
  lc.info?.("Upgraded schema with new mutations table");
92
90
  } },
93
- 11: {},
94
- 12: {},
95
91
  13: { migrateSchema: async (_, sql) => {
96
92
  await sql`
97
93
  CREATE TABLE IF NOT EXISTS ${sql(upstreamSchema(shard))}."schemaVersions" (
@@ -106,7 +102,6 @@ function getIncrementalMigrations(shard, replicaVersion) {
106
102
  ON CONFLICT DO NOTHING;
107
103
  `;
108
104
  } },
109
- 14: {},
110
105
  15: { migrateSchema: async (_, sql) => {
111
106
  await sql`
112
107
  ALTER TABLE ${sql(upstreamSchema(shard))}.replicas
@@ -114,8 +109,7 @@ function getIncrementalMigrations(shard, replicaVersion) {
114
109
  ADD COLUMN "subscriberContext" JSON
115
110
  `;
116
111
  } },
117
- 16: {},
118
- 17: { migrateSchema: async (lc, sql) => {
112
+ 18: { migrateSchema: async (lc, sql) => {
119
113
  const [{ publications }] = await sql`
120
114
  SELECT publications FROM ${sql(shardConfigTable)}`;
121
115
  await setupTriggers(lc, sql, {
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","names":[],"sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/init.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../../../../shared/src/asserts.ts';\nimport * as v from '../../../../../../shared/src/valita.ts';\nimport {\n getVersionHistory,\n runSchemaMigrations,\n type IncrementalMigrationMap,\n type Migration,\n} from '../../../../db/migration.ts';\nimport type {PostgresDB} from '../../../../types/pg.ts';\nimport {upstreamSchema, type ShardConfig} from '../../../../types/shards.ts';\nimport {id} from '../../../../types/sql.ts';\nimport {AutoResetSignal} from '../../../change-streamer/schema/tables.ts';\nimport {decommissionShard} from '../decommission.ts';\nimport {publishedSchema} from './published.ts';\nimport {\n getMutationsTableDefinition,\n legacyReplicationSlot,\n metadataPublicationName,\n setupTablesAndReplication,\n setupTriggers,\n} from './shard.ts';\n\n/**\n * Ensures that a shard is set up for initial sync.\n */\nexport async function ensureShardSchema(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardConfig,\n): Promise<void> {\n const initialSetup: Migration = {\n migrateSchema: (lc, tx) => setupTablesAndReplication(lc, tx, shard),\n minSafeVersion: 1,\n };\n await runSchemaMigrations(\n lc,\n `upstream-shard-${shard.appID}`,\n upstreamSchema(shard),\n db,\n initialSetup,\n // The incremental migration of any existing replicas will be replaced by\n // the incoming replica being synced, so the replicaVersion here is\n // unnecessary.\n getIncrementalMigrations(shard, 'obsolete'),\n );\n}\n\n/**\n * Updates the schema for an existing shard.\n */\nexport async function updateShardSchema(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardConfig,\n replicaVersion: string,\n): Promise<void> {\n await runSchemaMigrations(\n lc,\n `upstream-shard-${shard.appID}`,\n upstreamSchema(shard),\n db,\n {\n // If the expected existing shard is absent, throw an\n // AutoResetSignal to backtrack and initial sync.\n migrateSchema: () => {\n throw new AutoResetSignal(\n `upstream shard ${upstreamSchema(shard)} is not initialized`,\n );\n },\n },\n getIncrementalMigrations(shard, replicaVersion),\n );\n\n // The decommission check is run in updateShardSchema so that it happens\n // after initial sync, and not when the shard schema is initially set up.\n await decommissionLegacyShard(lc, db, shard);\n}\n\nfunction getIncrementalMigrations(\n shard: ShardConfig,\n replicaVersion?: string,\n): IncrementalMigrationMap {\n const shardConfigTable = `${upstreamSchema(shard)}.shardConfig`;\n\n return {\n 4: {\n migrateSchema: () => {\n throw new AutoResetSignal('resetting to upgrade shard schema');\n },\n minSafeVersion: 3,\n },\n\n // v5 changes the upstream schema organization from \"zero_{SHARD_ID}\" to\n // the \"{APP_ID}_0\". An incremental migration indicates that the previous\n // SHARD_ID was \"0\" and the new APP_ID is \"zero\" (i.e. the default values\n // for those options). In this case, the upstream format is identical, and\n // no migration is necessary. However, the version is bumped to v5 to\n // indicate that it was created with the {APP_ID} configuration and should\n // not be decommissioned as a legacy shard.\n 5: {},\n\n 6: {\n migrateSchema: async (lc, sql) => {\n assert(\n replicaVersion,\n `replicaVersion is always passed for incremental migrations`,\n );\n await Promise.all([\n sql`\n ALTER TABLE ${sql(shardConfigTable)} ADD \"replicaVersion\" TEXT`,\n sql`\n UPDATE ${sql(shardConfigTable)} SET ${sql({replicaVersion})}`,\n ]);\n lc.info?.(\n `Recorded replicaVersion ${replicaVersion} in upstream shardConfig`,\n );\n },\n },\n\n // Updates the DDL event trigger protocol to v2, and adds support for\n // ALTER SCHEMA x RENAME TO y\n 7: {\n migrateSchema: async (lc, sql) => {\n const [{publications}] = await sql<{publications: string[]}[]>`\n SELECT publications FROM ${sql(shardConfigTable)}`;\n await setupTriggers(lc, sql, {...shard, publications});\n lc.info?.(`Upgraded to v2 event triggers`);\n },\n },\n\n // Adds support for non-disruptive resyncs, which tracks multiple\n // replicas with different slot names.\n 8: {\n migrateSchema: async (lc, sql) => {\n const legacyShardConfigSchema = v.object({\n replicaVersion: v.string().nullable(),\n initialSchema: publishedSchema.nullable(),\n });\n const result = await sql`\n SELECT \"replicaVersion\", \"initialSchema\" FROM ${sql(shardConfigTable)}`;\n assert(\n result.length === 1,\n () => `Expected exactly one shardConfig row, got ${result.length}`,\n );\n const {replicaVersion, initialSchema} = v.parse(\n result[0],\n legacyShardConfigSchema,\n 'passthrough',\n );\n\n await Promise.all([\n sql`\n CREATE TABLE ${sql(upstreamSchema(shard))}.replicas (\n \"slot\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL,\n \"initialSchema\" JSON NOT NULL\n );\n `,\n sql`\n INSERT INTO ${sql(upstreamSchema(shard))}.replicas ${sql({\n slot: legacyReplicationSlot(shard),\n version: replicaVersion,\n initialSchema,\n })}\n `,\n sql`\n ALTER TABLE ${sql(shardConfigTable)} DROP \"replicaVersion\", DROP \"initialSchema\"\n `,\n ]);\n lc.info?.(`Upgraded schema to support non-disruptive resyncs`);\n },\n },\n\n // Fixes field ordering of compound indexes. This incremental migration\n // only fixes indexes resulting from new schema changes. A full resync is\n // required to fix existing indexes.\n //\n // The migration has been subsumed by the identical logic for migrating\n // to v12 (i.e. a trigger upgrade).\n 9: {},\n\n // Adds the `mutations` table used to track mutation results.\n 10: {\n migrateSchema: async (lc, sql) => {\n await sql.unsafe(/*sql*/ `\n ${getMutationsTableDefinition(upstreamSchema(shard))}\n ALTER PUBLICATION ${id(metadataPublicationName(shard.appID, shard.shardNum))} ADD TABLE ${id(upstreamSchema(shard))}.\"mutations\";\n `);\n lc.info?.('Upgraded schema with new mutations table');\n },\n },\n\n // Formerly dropped the schemaVersions table, but restored in the v13\n // migration for rollback safety.\n 11: {},\n\n // Upgrade DDL trigger to query schemaOID, needed information for auto-backfill.\n // (subsumed by v14)\n 12: {},\n\n // Recreates the legacy schemaVersions table that was prematurely dropped\n // in the (former) v11 migration. It needs to remain present for at least one\n // release in order to be rollback safe.\n //\n // TODO: Drop the table once a release that no longer reads the table has\n // been rolled out.\n 13: {\n migrateSchema: async (_, sql) => {\n await sql`\n CREATE TABLE IF NOT EXISTS ${sql(upstreamSchema(shard))}.\"schemaVersions\" (\n \"minSupportedVersion\" INT4,\n \"maxSupportedVersion\" INT4,\n \"lock\" BOOL PRIMARY KEY DEFAULT true CHECK (lock)\n );`;\n await sql`\n INSERT INTO ${sql(upstreamSchema(shard))}.\"schemaVersions\" \n (\"lock\", \"minSupportedVersion\", \"maxSupportedVersion\")\n VALUES (true, 1, 1)\n ON CONFLICT DO NOTHING;\n `;\n },\n },\n\n // Upgrade DDL trigger to log more info to PG logs.\n // (subsumed by v16)\n 14: {},\n\n // Add initialSyncContext column to replicas table.\n 15: {\n migrateSchema: async (_, sql) => {\n await sql`\n ALTER TABLE ${sql(upstreamSchema(shard))}.replicas\n ADD COLUMN \"initialSyncContext\" JSON,\n ADD COLUMN \"subscriberContext\" JSON\n `;\n },\n },\n\n // Upgrade DDL trigger to fire on all ALTER TABLE statements\n // to catch the *removal* of a table from the published set.\n // (subsumed by v17)\n 16: {},\n\n // Upgrade DDL triggers to support the COMMENT ON PUBLICATION hook for\n // working around the lack of event trigger support for PUBLICATION\n // changes in supabase.\n //\n // This also adds forwards-compatible support for hierarchical logical\n // message prefixes and unknown ddl event types.\n 17: {\n migrateSchema: async (lc, sql) => {\n const [{publications}] = await sql<{publications: string[]}[]>`\n SELECT publications FROM ${sql(shardConfigTable)}`;\n await setupTriggers(lc, sql, {...shard, publications});\n lc.info?.(`Upgraded DDL event triggers`);\n },\n },\n };\n}\n\n// Referenced in tests.\nexport const CURRENT_SCHEMA_VERSION = Object.keys(\n getIncrementalMigrations({\n appID: 'unused',\n shardNum: 0,\n publications: ['foo'],\n }),\n).reduce((prev, curr) => Math.max(prev, parseInt(curr)), 0);\n\nexport async function decommissionLegacyShard(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardConfig,\n) {\n if (shard.appID !== 'zero') {\n // When migration from non-default shard ids, e.g. \"zero_prod\" => \"prod_0\",\n // clean up the old \"zero_prod\" shard if it is pre-v5. Note that the v5\n // check is important to guard against cleaning up a **new** \"zero_0\" app\n // that coexists with the current App (with app-id === \"0\").\n const versionHistory = await getVersionHistory(db, `zero_${shard.appID}`);\n if (versionHistory !== null && versionHistory.schemaVersion < 5) {\n await decommissionShard(lc, db, 'zero', shard.appID);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"init.js","names":[],"sources":["../../../../../../../../zero-cache/src/services/change-source/pg/schema/init.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../../../../shared/src/asserts.ts';\nimport * as v from '../../../../../../shared/src/valita.ts';\nimport {\n getVersionHistory,\n runSchemaMigrations,\n type IncrementalMigrationMap,\n type Migration,\n} from '../../../../db/migration.ts';\nimport type {PostgresDB} from '../../../../types/pg.ts';\nimport {upstreamSchema, type ShardConfig} from '../../../../types/shards.ts';\nimport {id} from '../../../../types/sql.ts';\nimport {AutoResetSignal} from '../../../change-streamer/schema/tables.ts';\nimport {decommissionShard} from '../decommission.ts';\nimport {publishedSchema} from './published.ts';\nimport {\n getMutationsTableDefinition,\n legacyReplicationSlot,\n metadataPublicationName,\n setupTablesAndReplication,\n setupTriggers,\n} from './shard.ts';\n\n/**\n * Ensures that a shard is set up for initial sync.\n */\nexport async function ensureShardSchema(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardConfig,\n): Promise<void> {\n const initialSetup: Migration = {\n migrateSchema: (lc, tx) => setupTablesAndReplication(lc, tx, shard),\n minSafeVersion: 1,\n };\n await runSchemaMigrations(\n lc,\n `upstream-shard-${shard.appID}`,\n upstreamSchema(shard),\n db,\n initialSetup,\n // The incremental migration of any existing replicas will be replaced by\n // the incoming replica being synced, so the replicaVersion here is\n // unnecessary.\n getIncrementalMigrations(shard, 'obsolete'),\n );\n}\n\n/**\n * Updates the schema for an existing shard.\n */\nexport async function updateShardSchema(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardConfig,\n replicaVersion: string,\n): Promise<void> {\n await runSchemaMigrations(\n lc,\n `upstream-shard-${shard.appID}`,\n upstreamSchema(shard),\n db,\n {\n // If the expected existing shard is absent, throw an\n // AutoResetSignal to backtrack and initial sync.\n migrateSchema: () => {\n throw new AutoResetSignal(\n `upstream shard ${upstreamSchema(shard)} is not initialized`,\n );\n },\n },\n getIncrementalMigrations(shard, replicaVersion),\n );\n\n // The decommission check is run in updateShardSchema so that it happens\n // after initial sync, and not when the shard schema is initially set up.\n await decommissionLegacyShard(lc, db, shard);\n}\n\nfunction getIncrementalMigrations(\n shard: ShardConfig,\n replicaVersion?: string,\n): IncrementalMigrationMap {\n const shardConfigTable = `${upstreamSchema(shard)}.shardConfig`;\n\n return {\n 4: {\n migrateSchema: () => {\n throw new AutoResetSignal('resetting to upgrade shard schema');\n },\n minSafeVersion: 3,\n },\n\n // v5: changes the upstream schema organization from \"zero_{SHARD_ID}\" to\n // the \"{APP_ID}_0\". An incremental migration indicates that the previous\n // SHARD_ID was \"0\" and the new APP_ID is \"zero\" (i.e. the default values\n // for those options). In this case, the upstream format is identical, and\n // no migration is necessary. However, the version is bumped to v5 to\n // indicate that it was created with the {APP_ID} configuration and should\n // not be decommissioned as a legacy shard.\n\n 6: {\n migrateSchema: async (lc, sql) => {\n assert(\n replicaVersion,\n `replicaVersion is always passed for incremental migrations`,\n );\n await Promise.all([\n sql`\n ALTER TABLE ${sql(shardConfigTable)} ADD \"replicaVersion\" TEXT`,\n sql`\n UPDATE ${sql(shardConfigTable)} SET ${sql({replicaVersion})}`,\n ]);\n lc.info?.(\n `Recorded replicaVersion ${replicaVersion} in upstream shardConfig`,\n );\n },\n },\n\n // Updates the DDL event trigger protocol to v2, and adds support for\n // ALTER SCHEMA x RENAME TO y\n 7: {\n migrateSchema: async (lc, sql) => {\n const [{publications}] = await sql<{publications: string[]}[]>`\n SELECT publications FROM ${sql(shardConfigTable)}`;\n await setupTriggers(lc, sql, {...shard, publications});\n lc.info?.(`Upgraded to v2 event triggers`);\n },\n },\n\n // Adds support for non-disruptive resyncs, which tracks multiple\n // replicas with different slot names.\n 8: {\n migrateSchema: async (lc, sql) => {\n const legacyShardConfigSchema = v.object({\n replicaVersion: v.string().nullable(),\n initialSchema: publishedSchema.nullable(),\n });\n const result = await sql`\n SELECT \"replicaVersion\", \"initialSchema\" FROM ${sql(shardConfigTable)}`;\n assert(\n result.length === 1,\n () => `Expected exactly one shardConfig row, got ${result.length}`,\n );\n const {replicaVersion, initialSchema} = v.parse(\n result[0],\n legacyShardConfigSchema,\n 'passthrough',\n );\n\n await Promise.all([\n sql`\n CREATE TABLE ${sql(upstreamSchema(shard))}.replicas (\n \"slot\" TEXT PRIMARY KEY,\n \"version\" TEXT NOT NULL,\n \"initialSchema\" JSON NOT NULL\n );\n `,\n sql`\n INSERT INTO ${sql(upstreamSchema(shard))}.replicas ${sql({\n slot: legacyReplicationSlot(shard),\n version: replicaVersion,\n initialSchema,\n })}\n `,\n sql`\n ALTER TABLE ${sql(shardConfigTable)} DROP \"replicaVersion\", DROP \"initialSchema\"\n `,\n ]);\n lc.info?.(`Upgraded schema to support non-disruptive resyncs`);\n },\n },\n\n // v9: Fixes field ordering of compound indexes. This incremental migration\n // only fixes indexes resulting from new schema changes. A full resync is\n // required to fix existing indexes.\n //\n // The migration has been subsumed by the identical logic for migrating\n // to v12 (i.e. a trigger upgrade).\n\n // Adds the `mutations` table used to track mutation results.\n 10: {\n migrateSchema: async (lc, sql) => {\n await sql.unsafe(/*sql*/ `\n ${getMutationsTableDefinition(upstreamSchema(shard))}\n ALTER PUBLICATION ${id(metadataPublicationName(shard.appID, shard.shardNum))} ADD TABLE ${id(upstreamSchema(shard))}.\"mutations\";\n `);\n lc.info?.('Upgraded schema with new mutations table');\n },\n },\n\n // v11: Formerly dropped the schemaVersions table, but restored in the v13\n // migration for rollback safety.\n\n // v12: Upgrade DDL trigger to query schemaOID, needed information for auto-backfill.\n // (subsumed by v14)\n\n // Recreates the legacy schemaVersions table that was prematurely dropped\n // in the (former) v11 migration. It needs to remain present for at least one\n // release in order to be rollback safe.\n //\n // TODO: Drop the table once a release that no longer reads the table has\n // been rolled out.\n 13: {\n migrateSchema: async (_, sql) => {\n await sql`\n CREATE TABLE IF NOT EXISTS ${sql(upstreamSchema(shard))}.\"schemaVersions\" (\n \"minSupportedVersion\" INT4,\n \"maxSupportedVersion\" INT4,\n \"lock\" BOOL PRIMARY KEY DEFAULT true CHECK (lock)\n );`;\n await sql`\n INSERT INTO ${sql(upstreamSchema(shard))}.\"schemaVersions\" \n (\"lock\", \"minSupportedVersion\", \"maxSupportedVersion\")\n VALUES (true, 1, 1)\n ON CONFLICT DO NOTHING;\n `;\n },\n },\n\n // v14: Upgrade DDL trigger to log more info to PG logs.\n // (subsumed by v16)\n\n // Add initialSyncContext column to replicas table.\n 15: {\n migrateSchema: async (_, sql) => {\n await sql`\n ALTER TABLE ${sql(upstreamSchema(shard))}.replicas\n ADD COLUMN \"initialSyncContext\" JSON,\n ADD COLUMN \"subscriberContext\" JSON\n `;\n },\n },\n\n // v16: Upgrade DDL trigger to fire on all ALTER TABLE statements\n // to catch the *removal* of a table from the published set.\n // (subsumed by v17)\n\n // v17: Upgrade DDL triggers to support the COMMENT ON PUBLICATION hook for\n // working around the lack of event trigger support for PUBLICATION\n // changes in supabase.\n //\n // This also adds forwards-compatible support for hierarchical logical\n // message prefixes and unknown ddl event types.\n // (subsued by v18)\n\n // v18: Pure refactoring of event trigger code.\n 18: {\n migrateSchema: async (lc, sql) => {\n const [{publications}] = await sql<{publications: string[]}[]>`\n SELECT publications FROM ${sql(shardConfigTable)}`;\n await setupTriggers(lc, sql, {...shard, publications});\n lc.info?.(`Upgraded DDL event triggers`);\n },\n },\n };\n}\n\n// Referenced in tests.\nexport const CURRENT_SCHEMA_VERSION = Object.keys(\n getIncrementalMigrations({\n appID: 'unused',\n shardNum: 0,\n publications: ['foo'],\n }),\n).reduce((prev, curr) => Math.max(prev, parseInt(curr)), 0);\n\nexport async function decommissionLegacyShard(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardConfig,\n) {\n if (shard.appID !== 'zero') {\n // When migration from non-default shard ids, e.g. \"zero_prod\" => \"prod_0\",\n // clean up the old \"zero_prod\" shard if it is pre-v5. Note that the v5\n // check is important to guard against cleaning up a **new** \"zero_0\" app\n // that coexists with the current App (with app-id === \"0\").\n const versionHistory = await getVersionHistory(db, `zero_${shard.appID}`);\n if (versionHistory !== null && versionHistory.schemaVersion < 5) {\n await decommissionShard(lc, db, 'zero', shard.appID);\n }\n }\n}\n"],"mappings":"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"}
@@ -3,12 +3,13 @@ import type { PostgresDB } from '../../types/pg.ts';
3
3
  import type { ShardID } from '../../types/shards.ts';
4
4
  import type { ChangeSource } from '../change-source/change-source.ts';
5
5
  import { type ChangeStreamData } from '../change-source/protocol/current/downstream.ts';
6
+ import { type ReplicationStatusPublisher } from '../replicator/replication-status.ts';
6
7
  import type { SubscriptionState } from '../replicator/schema/replication-state.ts';
7
8
  import { type ChangeStreamerService } from './change-streamer.ts';
8
9
  /**
9
10
  * Performs initialization and schema migrations to initialize a ChangeStreamerImpl.
10
11
  */
11
- export declare function initializeStreamer(lc: LogContext, shard: ShardID, taskID: string, discoveryAddress: string, discoveryProtocol: string, changeDB: PostgresDB, changeSource: ChangeSource, subscriptionState: SubscriptionState, autoReset: boolean, backPressureLimitHeapProportion: number, flowControlConsensusPaddingSeconds: number, setTimeoutFn?: typeof setTimeout): Promise<ChangeStreamerService>;
12
+ export declare function initializeStreamer(lc: LogContext, shard: ShardID, taskID: string, discoveryAddress: string, discoveryProtocol: string, changeDB: PostgresDB, changeSource: ChangeSource, replicationStatusPublisher: ReplicationStatusPublisher, subscriptionState: SubscriptionState, autoReset: boolean, backPressureLimitHeapProportion: number, flowControlConsensusPaddingSeconds: number, setTimeoutFn?: typeof setTimeout): Promise<ChangeStreamerService>;
12
13
  /**
13
14
  * Internally all Downstream messages (not just commits) are given a watermark.
14
15
  * These are used for internal ordering for:
@@ -1 +1 @@
1
- {"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAYjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAGnD,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,iDAAiD,CAAC;AAKzD,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,2CAA2C,CAAC;AAMjF,OAAO,EACL,KAAK,qBAAqB,EAG3B,MAAM,sBAAsB,CAAC;AAY9B;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,iBAAiB,EACpC,SAAS,EAAE,OAAO,EAClB,+BAA+B,EAAE,MAAM,EACvC,kCAAkC,EAAE,MAAM,EAC1C,YAAY,oBAAa,GACxB,OAAO,CAAC,qBAAqB,CAAC,CA2BhC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"change-streamer-service.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/change-streamer-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAYjD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAGnD,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,iDAAiD,CAAC;AACzD,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,2CAA2C,CAAC;AAMjF,OAAO,EACL,KAAK,qBAAqB,EAG3B,MAAM,sBAAsB,CAAC;AAY9B;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,YAAY,EAC1B,0BAA0B,EAAE,0BAA0B,EACtD,iBAAiB,EAAE,iBAAiB,EACpC,SAAS,EAAE,OAAO,EAClB,+BAA+B,EAAE,MAAM,EACvC,kCAAkC,EAAE,MAAM,EAC1C,YAAY,oBAAa,GACxB,OAAO,CAAC,qBAAqB,CAAC,CA4BhC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC"}
@@ -19,11 +19,11 @@ import { getDefaultHighWaterMark } from "node:stream";
19
19
  /**
20
20
  * Performs initialization and schema migrations to initialize a ChangeStreamerImpl.
21
21
  */
22
- async function initializeStreamer(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, changeSource, subscriptionState, autoReset, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeoutFn = setTimeout) {
22
+ async function initializeStreamer(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, changeSource, replicationStatusPublisher, subscriptionState, autoReset, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeoutFn = setTimeout) {
23
23
  await initChangeStreamerSchema(lc, changeDB, shard);
24
24
  await ensureReplicationConfig(lc, changeDB, subscriptionState, shard, autoReset, setTimeoutFn);
25
25
  const { replicaVersion } = subscriptionState;
26
- return new ChangeStreamerImpl(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, replicaVersion, changeSource, autoReset, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeoutFn);
26
+ return new ChangeStreamerImpl(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, replicaVersion, changeSource, replicationStatusPublisher, autoReset, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeoutFn);
27
27
  }
28
28
  /**
29
29
  * Upstream-agnostic dispatch of messages in a {@link ChangeStreamMessage} to a
@@ -172,13 +172,14 @@ var ChangeStreamerImpl = class {
172
172
  #source;
173
173
  #storer;
174
174
  #forwarder;
175
+ #replicationStatusPublisher;
175
176
  #autoReset;
176
177
  #state;
177
178
  #initialWatermarks = /* @__PURE__ */ new Set();
178
179
  #serving = resolver();
179
180
  #txCounter = getOrCreateCounter("replication", "transactions", "Count of replicated transactions");
180
181
  #stream;
181
- constructor(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, replicaVersion, source, autoReset, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeoutFn = setTimeout) {
182
+ constructor(lc, shard, taskID, discoveryAddress, discoveryProtocol, changeDB, replicaVersion, source, replicationStatusPublisher, autoReset, backPressureLimitHeapProportion, flowControlConsensusPaddingSeconds, setTimeoutFn = setTimeout) {
182
183
  this.id = `change-streamer`;
183
184
  this.#lc = lc.withContext("component", "change-streamer");
184
185
  this.#shard = shard;
@@ -191,6 +192,7 @@ var ChangeStreamerImpl = class {
191
192
  consumed[2]
192
193
  ]), (err) => this.stop(err), backPressureLimitHeapProportion);
193
194
  this.#forwarder = new Forwarder(lc, { flowControlConsensusPaddingSeconds });
195
+ this.#replicationStatusPublisher = replicationStatusPublisher;
194
196
  this.#autoReset = autoReset;
195
197
  this.#state = new RunningState(this.id, void 0, setTimeoutFn);
196
198
  }
@@ -209,6 +211,7 @@ var ChangeStreamerImpl = class {
209
211
  this.#storer.run().catch((e) => stream.changes.cancel(e));
210
212
  this.#stream = stream;
211
213
  this.#state.resetBackoff();
214
+ this.#replicationStatusPublisher.publish(this.#lc, "Replicating", `Replicating from ${lastWatermark}`);
212
215
  watermark = null;
213
216
  for await (const change of stream.changes) {
214
217
  const [type, msg] = change;