@rocicorp/zero 1.6.0-canary.13 → 1.6.0-canary.14

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.
@@ -1,6 +1,6 @@
1
1
  var package_default = {
2
2
  name: "@rocicorp/zero",
3
- version: "1.6.0-canary.13",
3
+ version: "1.6.0-canary.14",
4
4
  description: "Zero is a web framework for serverless web development.",
5
5
  homepage: "https://zero.rocicorp.dev",
6
6
  bugs: { "url": "https://bugs.rocicorp.dev" },
@@ -1 +1 @@
1
- {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.6.0-canary.13\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/kysely\": {\n \"types\": \"./out/zero/src/adapters/kysely.d.ts\",\n \"default\": \"./out/zero/src/adapters/kysely.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --quiet --config ../../oxlint.config.ts src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.3.11\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.39.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.0.0\",\n \"@fastify/websocket\": \"^11.0.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/api-logs\": \"^0.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.18\",\n \"@standard-schema/spec\": \"^1.0.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.5.12\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.0\",\n \"chokidar\": \"^4.0.1\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.1\",\n \"command-line-usage\": \"^7.0.3\",\n \"compare-utf8\": \"^0.2.0\",\n \"defu\": \"^6.1.4\",\n \"eventemitter3\": \"^5.0.1\",\n \"fastify\": \"^5.0.0\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.9.3\",\n \"js-xxhash\": \"^4.0.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.0\",\n \"nanoid\": \"^5.1.2\",\n \"oxfmt\": \"^0.45.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.5.4\",\n \"tsx\": \"^4.21.0\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.18.1\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"@vitest/runner\": \"^4.1.5\",\n \"analyze-query\": \"0.0.0\",\n \"ast-to-zql\": \"0.0.0\",\n \"expo-sqlite\": \">=15\",\n \"replicache\": \"15.2.1\",\n \"shared\": \"0.0.0\",\n \"syncpack\": \"^14.3.0\",\n \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~6.0.2\",\n \"vite\": \"8.0.3\",\n \"vitest\": \"^4.1.5\",\n \"zero-cache\": \"0.0.0\",\n \"zero-client\": \"0.0.0\",\n \"zero-pg\": \"0.0.0\",\n \"zero-react\": \"0.0.0\",\n \"zero-server\": \"0.0.0\",\n \"zero-solid\": \"0.0.0\",\n \"zqlite\": \"0.0.0\"\n },\n \"peerDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"expo-sqlite\": \">=15\",\n \"kysely\": \"^0.28.16\"\n },\n \"peerDependenciesMeta\": {\n \"kysely\": {\n \"optional\": true\n },\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=22\"\n }\n}"],"mappings":""}
1
+ {"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":["{\n \"name\": \"@rocicorp/zero\",\n \"version\": \"1.6.0-canary.14\",\n \"description\": \"Zero is a web framework for serverless web development.\",\n \"homepage\": \"https://zero.rocicorp.dev\",\n \"bugs\": {\n \"url\": \"https://bugs.rocicorp.dev\"\n },\n \"license\": \"Apache-2.0\",\n \"author\": \"Rocicorp, Inc.\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/rocicorp/mono.git\",\n \"directory\": \"packages/zero\"\n },\n \"bin\": {\n \"analyze-query\": \"./out/zero/src/analyze-query.js\",\n \"ast-to-zql\": \"./out/zero/src/ast-to-zql.js\",\n \"transform-query\": \"./out/zero/src/transform-query.js\",\n \"zero-build-schema\": \"./out/zero/src/build-schema.js\",\n \"zero-cache\": \"./out/zero/src/cli.js\",\n \"zero-cache-dev\": \"./out/zero/src/zero-cache-dev.js\",\n \"zero-deploy-permissions\": \"./out/zero/src/deploy-permissions.js\",\n \"zero-out\": \"./out/zero/src/zero-out.js\"\n },\n \"files\": [\n \"out\",\n \"!*.tsbuildinfo\"\n ],\n \"type\": \"module\",\n \"main\": \"out/zero/src/zero.js\",\n \"module\": \"out/zero/src/zero.js\",\n \"types\": \"out/zero/src/zero.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./out/zero/src/zero.d.ts\",\n \"default\": \"./out/zero/src/zero.js\"\n },\n \"./analyze\": {\n \"types\": \"./out/zero/src/analyze.d.ts\",\n \"default\": \"./out/zero/src/analyze.js\"\n },\n \"./bindings\": {\n \"types\": \"./out/zero/src/bindings.d.ts\",\n \"default\": \"./out/zero/src/bindings.js\"\n },\n \"./change-protocol/v0\": {\n \"types\": \"./out/zero/src/change-protocol/v0.d.ts\",\n \"default\": \"./out/zero/src/change-protocol/v0.js\"\n },\n \"./expo-sqlite\": {\n \"types\": \"./out/zero/src/expo-sqlite.d.ts\",\n \"default\": \"./out/zero/src/expo-sqlite.js\"\n },\n \"./op-sqlite\": {\n \"types\": \"./out/zero/src/op-sqlite.d.ts\",\n \"default\": \"./out/zero/src/op-sqlite.js\"\n },\n \"./pg\": {\n \"types\": \"./out/zero/src/pg.d.ts\",\n \"default\": \"./out/zero/src/pg.js\"\n },\n \"./react\": {\n \"types\": \"./out/zero/src/react.d.ts\",\n \"default\": \"./out/zero/src/react.js\"\n },\n \"./react-native\": {\n \"types\": \"./out/zero/src/react-native.d.ts\",\n \"default\": \"./out/zero/src/react-native.js\"\n },\n \"./server\": {\n \"types\": \"./out/zero/src/server.d.ts\",\n \"default\": \"./out/zero/src/server.js\"\n },\n \"./server/adapters/drizzle\": {\n \"types\": \"./out/zero/src/adapters/drizzle.d.ts\",\n \"default\": \"./out/zero/src/adapters/drizzle.js\"\n },\n \"./server/adapters/kysely\": {\n \"types\": \"./out/zero/src/adapters/kysely.d.ts\",\n \"default\": \"./out/zero/src/adapters/kysely.js\"\n },\n \"./server/adapters/prisma\": {\n \"types\": \"./out/zero/src/adapters/prisma.d.ts\",\n \"default\": \"./out/zero/src/adapters/prisma.js\"\n },\n \"./server/adapters/pg\": {\n \"types\": \"./out/zero/src/adapters/pg.d.ts\",\n \"default\": \"./out/zero/src/adapters/pg.js\"\n },\n \"./server/adapters/postgresjs\": {\n \"types\": \"./out/zero/src/adapters/postgresjs.d.ts\",\n \"default\": \"./out/zero/src/adapters/postgresjs.js\"\n },\n \"./solid\": {\n \"types\": \"./out/zero/src/solid.d.ts\",\n \"default\": \"./out/zero/src/solid.js\"\n },\n \"./sqlite\": {\n \"types\": \"./out/zero/src/sqlite.d.ts\",\n \"default\": \"./out/zero/src/sqlite.js\"\n },\n \"./zqlite\": {\n \"types\": \"./out/zero/src/zqlite.d.ts\",\n \"default\": \"./out/zero/src/zqlite.js\"\n }\n },\n \"scripts\": {\n \"build\": \"node --experimental-strip-types --no-warnings tool/build.ts\",\n \"build:watch\": \"node --experimental-strip-types --no-warnings tool/build.ts --watch\",\n \"check-types\": \"tsc -p tsconfig.client.json && tsc -p tsconfig.server.json\",\n \"check-types:client:watch\": \"tsc -p tsconfig.client.json --watch\",\n \"check-types:server:watch\": \"tsc -p tsconfig.server.json --watch\",\n \"format\": \"oxfmt .\",\n \"check-format\": \"oxfmt --check .\",\n \"lint\": \"oxlint --quiet --config ../../oxlint.config.ts src/\",\n \"docs\": \"node --experimental-strip-types --no-warnings tool/generate-docs.ts\",\n \"docs:server\": \"node --watch --experimental-strip-types --no-warnings tool/generate-docs.ts --server\",\n \"fmt\": \"oxfmt .\",\n \"check-fmt\": \"oxfmt --check .\"\n },\n \"dependencies\": {\n \"@badrap/valita\": \"0.3.11\",\n \"@databases/escape-identifier\": \"^1.0.3\",\n \"@databases/sql\": \"^3.3.0\",\n \"@dotenvx/dotenvx\": \"^1.39.0\",\n \"@drdgvhbh/postgres-error-codes\": \"^0.0.6\",\n \"@fastify/cors\": \"^10.0.0\",\n \"@fastify/websocket\": \"^11.0.0\",\n \"@google-cloud/precise-date\": \"^4.0.0\",\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/api-logs\": \"^0.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.18\",\n \"@standard-schema/spec\": \"^1.0.0\",\n \"@types/basic-auth\": \"^1.1.8\",\n \"@types/ws\": \"^8.5.12\",\n \"basic-auth\": \"^2.0.1\",\n \"chalk-template\": \"^1.1.0\",\n \"chokidar\": \"^4.0.1\",\n \"cloudevents\": \"^10.0.0\",\n \"command-line-args\": \"^6.0.1\",\n \"command-line-usage\": \"^7.0.3\",\n \"compare-utf8\": \"^0.2.0\",\n \"defu\": \"^6.1.4\",\n \"eventemitter3\": \"^5.0.1\",\n \"fastify\": \"^5.0.0\",\n \"is-in-subnet\": \"^4.0.1\",\n \"jose\": \"^5.9.3\",\n \"js-xxhash\": \"^4.0.0\",\n \"json-custom-numbers\": \"^3.1.1\",\n \"kasi\": \"^1.1.0\",\n \"nanoid\": \"^5.1.2\",\n \"oxfmt\": \"^0.45.0\",\n \"parse-prometheus-text-format\": \"^1.1.1\",\n \"pg-format\": \"npm:pg-format-fix@^1.0.5\",\n \"postgres\": \"3.4.7\",\n \"semver\": \"^7.5.4\",\n \"tsx\": \"^4.21.0\",\n \"url-pattern\": \"^1.0.3\",\n \"urlpattern-polyfill\": \"^10.1.0\",\n \"ws\": \"^8.18.1\"\n },\n \"devDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"@vitest/runner\": \"^4.1.5\",\n \"analyze-query\": \"0.0.0\",\n \"ast-to-zql\": \"0.0.0\",\n \"expo-sqlite\": \">=15\",\n \"replicache\": \"15.2.1\",\n \"shared\": \"0.0.0\",\n \"syncpack\": \"^14.3.0\",\n \"typedoc\": \"^0.28.17\",\n \"typedoc-plugin-markdown\": \"^4.10.0\",\n \"typescript\": \"~6.0.2\",\n \"vite\": \"8.0.3\",\n \"vitest\": \"^4.1.5\",\n \"zero-cache\": \"0.0.0\",\n \"zero-client\": \"0.0.0\",\n \"zero-pg\": \"0.0.0\",\n \"zero-react\": \"0.0.0\",\n \"zero-server\": \"0.0.0\",\n \"zero-solid\": \"0.0.0\",\n \"zqlite\": \"0.0.0\"\n },\n \"peerDependencies\": {\n \"@op-engineering/op-sqlite\": \">=15\",\n \"expo-sqlite\": \">=15\",\n \"kysely\": \"^0.28.16\"\n },\n \"peerDependenciesMeta\": {\n \"kysely\": {\n \"optional\": true\n },\n \"expo-sqlite\": {\n \"optional\": true\n },\n \"@op-engineering/op-sqlite\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=22\"\n }\n}"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"cvr-purger.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-purger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAQ3C,KAAK,OAAO,GAAG;IACb,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,qBAAa,SAAU,YAAW,OAAO;;IACvC,QAAQ,CAAC,EAAE,YAAY;gBAYrB,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,EAAC,qBAAqB,EAAE,gBAAgB,EAAE,iBAAiB,EAAC,EAAE,OAAO,EACrE,uBAAuB,SAA4B;IAc/C,GAAG;IAgDT,iBAAiB,CACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,CAAC;IAkF/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAItB"}
1
+ {"version":3,"file":"cvr-purger.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-purger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,uBAAuB,CAAC;AAE9D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAQ3C,KAAK,OAAO,GAAG;IACb,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,qBAAa,SAAU,YAAW,OAAO;;IACvC,QAAQ,CAAC,EAAE,YAAY;gBAYrB,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,KAAK,EAAE,OAAO,EACd,EAAC,qBAAqB,EAAE,gBAAgB,EAAE,iBAAiB,EAAC,EAAE,OAAO,EACrE,uBAAuB,SAA4B;IAc/C,GAAG;IAiDT,iBAAiB,CACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,CAAC;IAkF/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAItB"}
@@ -37,6 +37,7 @@ var CVRPurger = class {
37
37
  while (this.#state.shouldRun()) try {
38
38
  const start = performance.now();
39
39
  const { purged, remaining } = await this.purgeInactiveCVRs(maxCVRsPerPurge);
40
+ this.#state.resetBackoff();
40
41
  if (purgeable !== void 0 && remaining > purgeable) {
41
42
  maxCVRsPerPurge += this.#initialBatchSize;
42
43
  this.#lc.info?.(`increased CVRs per purge to ${maxCVRsPerPurge}`);
@@ -47,7 +48,7 @@ var CVRPurger = class {
47
48
  this.#lc.info?.(`purged ${purged} inactive CVRs (${elapsed.toFixed(2)} ms). Next purge in ${purgeInterval} ms`);
48
49
  await this.#state.sleep(purgeInterval);
49
50
  } catch (e) {
50
- this.#lc.warn?.(`error encountered while garbage collecting CVRs`, e);
51
+ await this.#state.backoff(this.#lc, e);
51
52
  }
52
53
  }
53
54
  purgeInactiveCVRs(maxCVRs) {
@@ -1 +1 @@
1
- {"version":3,"file":"cvr-purger.js","names":["#lc","#db","#schema","#inactivityThresholdMs","#tombstonePurgeThresholdMs","#initialBatchSize","#initialIntervalMs","#state"],"sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-purger.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {READ_COMMITTED} from '../../db/mode-enum.ts';\nimport {runTx} from '../../db/run-transaction.ts';\nimport {type PostgresDB} from '../../types/pg.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {Service} from '../service.ts';\n\nconst MINUTE = 60 * 1000;\nconst MAX_PURGE_INTERVAL_MS = 16 * MINUTE;\n\n// Purge tombstones after 31 days to facilitate up to a 30-day actives metric.\nconst TOMBSTONE_PURGE_THRESHOLD = 31 * 24 * 60 * 60 * 1000;\n\ntype Options = {\n inactivityThresholdMs: number;\n initialBatchSize: number;\n initialIntervalMs: number;\n};\n\nexport class CVRPurger implements Service {\n readonly id = 'reaper';\n\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #inactivityThresholdMs: number;\n readonly #tombstonePurgeThresholdMs: number;\n readonly #initialBatchSize: number;\n readonly #initialIntervalMs: number;\n readonly #state = new RunningState('reaper');\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n {inactivityThresholdMs, initialBatchSize, initialIntervalMs}: Options,\n tombstonePurgeThreshold = TOMBSTONE_PURGE_THRESHOLD,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#inactivityThresholdMs = inactivityThresholdMs;\n this.#tombstonePurgeThresholdMs = Math.max(\n tombstonePurgeThreshold,\n inactivityThresholdMs,\n );\n this.#initialBatchSize = initialBatchSize;\n this.#initialIntervalMs = initialIntervalMs;\n }\n\n async run() {\n let purgeable: number | undefined;\n let maxCVRsPerPurge = this.#initialBatchSize;\n let purgeInterval = this.#initialIntervalMs;\n\n if (this.#initialBatchSize === 0) {\n this.#lc.warn?.(\n `CVR garbage collection is disabled (initialBatchSize = 0)`,\n );\n // Do nothing and just wait to be stopped.\n await this.#state.stopped();\n } else {\n this.#lc.info?.(\n `running cvr-purger with`,\n await this.#db`SHOW statement_timeout`,\n );\n }\n\n while (this.#state.shouldRun()) {\n try {\n const start = performance.now();\n const {purged, remaining} =\n await this.purgeInactiveCVRs(maxCVRsPerPurge);\n\n if (purgeable !== undefined && remaining > purgeable) {\n // If the number of purgeable CVRs has grown even after the purge,\n // increase the number purged per round to achieve a steady state.\n maxCVRsPerPurge += this.#initialBatchSize;\n this.#lc.info?.(`increased CVRs per purge to ${maxCVRsPerPurge}`);\n }\n purgeable = remaining;\n\n purgeInterval =\n purgeable > 0\n ? this.#initialIntervalMs\n : Math.min(purgeInterval * 2, MAX_PURGE_INTERVAL_MS);\n const elapsed = performance.now() - start;\n this.#lc.info?.(\n `purged ${purged} inactive CVRs (${elapsed.toFixed(2)} ms). Next purge in ${purgeInterval} ms`,\n );\n await this.#state.sleep(purgeInterval);\n } catch (e) {\n this.#lc.warn?.(`error encountered while garbage collecting CVRs`, e);\n }\n }\n }\n\n // Exported for testing.\n purgeInactiveCVRs(\n maxCVRs: number,\n ): Promise<{purged: number; remaining: number}> {\n return runTx(\n this.#db,\n async sql => {\n const now = Date.now();\n const threshold = now - this.#inactivityThresholdMs;\n const tombstonePurgeThreshold = now - this.#tombstonePurgeThresholdMs;\n // Implementation note: `FOR UPDATE` will prevent a syncer from\n // concurrently updating the CVR, since the update also performs\n // a `SELECT ... FOR UPDATE`, instead causing that update to\n // fail, which will cause the client to create a new CVR.\n //\n // `SKIP LOCKED` will skip over CVRs that a syncer is already\n // in the process of updating. In this manner, an in-progress\n // update effectively excludes the CVR from the purge.\n const ids = (\n await sql<{clientGroupID: string}[]>`\n SELECT \"clientGroupID\" FROM ${sql(this.#schema)}.instances\n WHERE NOT \"deleted\" AND \"lastActive\" < ${threshold}\n ORDER BY \"lastActive\" ASC\n LIMIT ${maxCVRs}\n FOR UPDATE SKIP LOCKED\n `.values()\n ).flat();\n\n if (ids.length > 0) {\n // Explicitly delete rows from cvr tables from \"bottom\" up. Relying on\n // foreign key cascading deletes can be suboptimal when the foreign key\n // is not a prefix of the primary key (e.g. the \"desires\" foreign key\n // reference to the \"queries\" table is not a prefix of the \"desires\"\n // primary key).\n const stmts = [\n 'desires',\n 'queries',\n 'clients',\n 'rows',\n 'rowsVersion',\n ].map(table =>\n sql`\n DELETE FROM ${sql(this.#schema)}.${sql(table)} \n WHERE \"clientGroupID\" IN ${sql(ids)}`.execute(),\n );\n // Tombstones are written for the `instances` rows, preserving the\n // \"profileID\" and \"lastActive\" columns for computing usage stats.\n //\n // For backwards compatibility (i.e. older zero-caches that do not\n // check the \"deleted\" column) reset the \"version\" to '00' to trigger\n // the ClientNotFound error via\n // view-syncer.ts:checkClientAndCVRVersions()\n stmts.push(\n sql`\n UPDATE ${sql(this.#schema)}.instances\n SET \"deleted\" = TRUE, \n \"version\" = '00', \n \"ttlClock\" = 0,\n \"replicaVersion\" = NULL, \n \"owner\" = NULL,\n \"grantedAt\" = NULL,\n \"clientSchema\" = NULL\n WHERE \"clientGroupID\" IN ${sql(ids)}`.execute(),\n );\n // Tombstone rows are deleted after the tombstonePurgeThreshold.\n stmts.push(\n sql`\n DELETE FROM ${sql(this.#schema)}.instances\n WHERE \"deleted\" AND \"lastActive\" < ${tombstonePurgeThreshold}\n `.execute(),\n );\n await Promise.all(stmts);\n }\n\n const [{remaining}] = await sql<[{remaining: bigint}]>`\n SELECT COUNT(*) AS remaining FROM ${sql(this.#schema)}.instances\n WHERE NOT \"deleted\" AND \"lastActive\" < ${threshold}\n `;\n\n return {purged: ids.length, remaining: Number(remaining)};\n },\n {mode: READ_COMMITTED},\n );\n }\n\n stop(): Promise<void> {\n this.#state.stop(this.#lc);\n return promiseVoid;\n }\n}\n"],"mappings":";;;;;;;AAUA,IAAM,wBAAwB,MADf,KAAK;AAIpB,IAAM,4BAA4B,MAAU,KAAK,KAAK;AAQtD,IAAa,YAAb,MAA0C;CACxC,KAAc;CAEd;CACA;CACA;CACA;CACA;CACA;CACA;CACA,SAAkB,IAAI,aAAa,SAAS;CAE5C,YACE,IACA,IACA,OACA,EAAC,uBAAuB,kBAAkB,qBAC1C,0BAA0B,2BAC1B;AACA,QAAA,KAAW;AACX,QAAA,KAAW;AACX,QAAA,SAAe,UAAU,MAAM;AAC/B,QAAA,wBAA8B;AAC9B,QAAA,4BAAkC,KAAK,IACrC,yBACA,sBACD;AACD,QAAA,mBAAyB;AACzB,QAAA,oBAA0B;;CAG5B,MAAM,MAAM;EACV,IAAI;EACJ,IAAI,kBAAkB,MAAA;EACtB,IAAI,gBAAgB,MAAA;AAEpB,MAAI,MAAA,qBAA2B,GAAG;AAChC,SAAA,GAAS,OACP,4DACD;AAED,SAAM,MAAA,MAAY,SAAS;QAE3B,OAAA,GAAS,OACP,2BACA,MAAM,MAAA,EAAQ,yBACf;AAGH,SAAO,MAAA,MAAY,WAAW,CAC5B,KAAI;GACF,MAAM,QAAQ,YAAY,KAAK;GAC/B,MAAM,EAAC,QAAQ,cACb,MAAM,KAAK,kBAAkB,gBAAgB;AAE/C,OAAI,cAAc,KAAA,KAAa,YAAY,WAAW;AAGpD,uBAAmB,MAAA;AACnB,UAAA,GAAS,OAAO,+BAA+B,kBAAkB;;AAEnE,eAAY;AAEZ,mBACE,YAAY,IACR,MAAA,oBACA,KAAK,IAAI,gBAAgB,GAAG,sBAAsB;GACxD,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,SAAA,GAAS,OACP,UAAU,OAAO,kBAAkB,QAAQ,QAAQ,EAAE,CAAC,sBAAsB,cAAc,KAC3F;AACD,SAAM,MAAA,MAAY,MAAM,cAAc;WAC/B,GAAG;AACV,SAAA,GAAS,OAAO,mDAAmD,EAAE;;;CAM3E,kBACE,SAC8C;AAC9C,SAAO,MACL,MAAA,IACA,OAAM,QAAO;GACX,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,YAAY,MAAM,MAAA;GACxB,MAAM,0BAA0B,MAAM,MAAA;GAStC,MAAM,OACJ,MAAM,GAA8B;wCACN,IAAI,MAAA,OAAa,CAAC;qDACL,UAAU;;oBAE3C,QAAQ;;QAEpB,QAAQ,EACN,MAAM;AAER,OAAI,IAAI,SAAS,GAAG;IAMlB,MAAM,QAAQ;KACZ;KACA;KACA;KACA;KACA;KACD,CAAC,KAAI,UACJ,GAAG;0BACW,IAAI,MAAA,OAAa,CAAC,GAAG,IAAI,MAAM,CAAC;yCACjB,IAAI,IAAI,GAAG,SAAS,CAClD;AAQD,UAAM,KACJ,GAAG;qBACM,IAAI,MAAA,OAAa,CAAC;;;;;;;;yCAQE,IAAI,IAAI,GAAG,SAAS,CAClD;AAED,UAAM,KACJ,GAAG;0BACW,IAAI,MAAA,OAAa,CAAC;mDACO,wBAAwB;YAC/D,SAAS,CACV;AACD,UAAM,QAAQ,IAAI,MAAM;;GAG1B,MAAM,CAAC,EAAC,eAAc,MAAM,GAA0B;4CAClB,IAAI,MAAA,OAAa,CAAC;mDACX,UAAU;;AAGrD,UAAO;IAAC,QAAQ,IAAI;IAAQ,WAAW,OAAO,UAAU;IAAC;KAE3D,EAAC,MAAM,gBAAe,CACvB;;CAGH,OAAsB;AACpB,QAAA,MAAY,KAAK,MAAA,GAAS;AAC1B,SAAO"}
1
+ {"version":3,"file":"cvr-purger.js","names":["#lc","#db","#schema","#inactivityThresholdMs","#tombstonePurgeThresholdMs","#initialBatchSize","#initialIntervalMs","#state"],"sources":["../../../../../../zero-cache/src/services/view-syncer/cvr-purger.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {promiseVoid} from '../../../../shared/src/resolved-promises.ts';\nimport {READ_COMMITTED} from '../../db/mode-enum.ts';\nimport {runTx} from '../../db/run-transaction.ts';\nimport {type PostgresDB} from '../../types/pg.ts';\nimport {cvrSchema, type ShardID} from '../../types/shards.ts';\nimport {RunningState} from '../running-state.ts';\nimport type {Service} from '../service.ts';\n\nconst MINUTE = 60 * 1000;\nconst MAX_PURGE_INTERVAL_MS = 16 * MINUTE;\n\n// Purge tombstones after 31 days to facilitate up to a 30-day actives metric.\nconst TOMBSTONE_PURGE_THRESHOLD = 31 * 24 * 60 * 60 * 1000;\n\ntype Options = {\n inactivityThresholdMs: number;\n initialBatchSize: number;\n initialIntervalMs: number;\n};\n\nexport class CVRPurger implements Service {\n readonly id = 'reaper';\n\n readonly #lc: LogContext;\n readonly #db: PostgresDB;\n readonly #schema: string;\n readonly #inactivityThresholdMs: number;\n readonly #tombstonePurgeThresholdMs: number;\n readonly #initialBatchSize: number;\n readonly #initialIntervalMs: number;\n readonly #state = new RunningState('reaper');\n\n constructor(\n lc: LogContext,\n db: PostgresDB,\n shard: ShardID,\n {inactivityThresholdMs, initialBatchSize, initialIntervalMs}: Options,\n tombstonePurgeThreshold = TOMBSTONE_PURGE_THRESHOLD,\n ) {\n this.#lc = lc;\n this.#db = db;\n this.#schema = cvrSchema(shard);\n this.#inactivityThresholdMs = inactivityThresholdMs;\n this.#tombstonePurgeThresholdMs = Math.max(\n tombstonePurgeThreshold,\n inactivityThresholdMs,\n );\n this.#initialBatchSize = initialBatchSize;\n this.#initialIntervalMs = initialIntervalMs;\n }\n\n async run() {\n let purgeable: number | undefined;\n let maxCVRsPerPurge = this.#initialBatchSize;\n let purgeInterval = this.#initialIntervalMs;\n\n if (this.#initialBatchSize === 0) {\n this.#lc.warn?.(\n `CVR garbage collection is disabled (initialBatchSize = 0)`,\n );\n // Do nothing and just wait to be stopped.\n await this.#state.stopped();\n } else {\n this.#lc.info?.(\n `running cvr-purger with`,\n await this.#db`SHOW statement_timeout`,\n );\n }\n\n while (this.#state.shouldRun()) {\n try {\n const start = performance.now();\n const {purged, remaining} =\n await this.purgeInactiveCVRs(maxCVRsPerPurge);\n this.#state.resetBackoff();\n\n if (purgeable !== undefined && remaining > purgeable) {\n // If the number of purgeable CVRs has grown even after the purge,\n // increase the number purged per round to achieve a steady state.\n maxCVRsPerPurge += this.#initialBatchSize;\n this.#lc.info?.(`increased CVRs per purge to ${maxCVRsPerPurge}`);\n }\n purgeable = remaining;\n\n purgeInterval =\n purgeable > 0\n ? this.#initialIntervalMs\n : Math.min(purgeInterval * 2, MAX_PURGE_INTERVAL_MS);\n const elapsed = performance.now() - start;\n this.#lc.info?.(\n `purged ${purged} inactive CVRs (${elapsed.toFixed(2)} ms). Next purge in ${purgeInterval} ms`,\n );\n await this.#state.sleep(purgeInterval);\n } catch (e) {\n await this.#state.backoff(this.#lc, e);\n }\n }\n }\n\n // Exported for testing.\n purgeInactiveCVRs(\n maxCVRs: number,\n ): Promise<{purged: number; remaining: number}> {\n return runTx(\n this.#db,\n async sql => {\n const now = Date.now();\n const threshold = now - this.#inactivityThresholdMs;\n const tombstonePurgeThreshold = now - this.#tombstonePurgeThresholdMs;\n // Implementation note: `FOR UPDATE` will prevent a syncer from\n // concurrently updating the CVR, since the update also performs\n // a `SELECT ... FOR UPDATE`, instead causing that update to\n // fail, which will cause the client to create a new CVR.\n //\n // `SKIP LOCKED` will skip over CVRs that a syncer is already\n // in the process of updating. In this manner, an in-progress\n // update effectively excludes the CVR from the purge.\n const ids = (\n await sql<{clientGroupID: string}[]>`\n SELECT \"clientGroupID\" FROM ${sql(this.#schema)}.instances\n WHERE NOT \"deleted\" AND \"lastActive\" < ${threshold}\n ORDER BY \"lastActive\" ASC\n LIMIT ${maxCVRs}\n FOR UPDATE SKIP LOCKED\n `.values()\n ).flat();\n\n if (ids.length > 0) {\n // Explicitly delete rows from cvr tables from \"bottom\" up. Relying on\n // foreign key cascading deletes can be suboptimal when the foreign key\n // is not a prefix of the primary key (e.g. the \"desires\" foreign key\n // reference to the \"queries\" table is not a prefix of the \"desires\"\n // primary key).\n const stmts = [\n 'desires',\n 'queries',\n 'clients',\n 'rows',\n 'rowsVersion',\n ].map(table =>\n sql`\n DELETE FROM ${sql(this.#schema)}.${sql(table)} \n WHERE \"clientGroupID\" IN ${sql(ids)}`.execute(),\n );\n // Tombstones are written for the `instances` rows, preserving the\n // \"profileID\" and \"lastActive\" columns for computing usage stats.\n //\n // For backwards compatibility (i.e. older zero-caches that do not\n // check the \"deleted\" column) reset the \"version\" to '00' to trigger\n // the ClientNotFound error via\n // view-syncer.ts:checkClientAndCVRVersions()\n stmts.push(\n sql`\n UPDATE ${sql(this.#schema)}.instances\n SET \"deleted\" = TRUE, \n \"version\" = '00', \n \"ttlClock\" = 0,\n \"replicaVersion\" = NULL, \n \"owner\" = NULL,\n \"grantedAt\" = NULL,\n \"clientSchema\" = NULL\n WHERE \"clientGroupID\" IN ${sql(ids)}`.execute(),\n );\n // Tombstone rows are deleted after the tombstonePurgeThreshold.\n stmts.push(\n sql`\n DELETE FROM ${sql(this.#schema)}.instances\n WHERE \"deleted\" AND \"lastActive\" < ${tombstonePurgeThreshold}\n `.execute(),\n );\n await Promise.all(stmts);\n }\n\n const [{remaining}] = await sql<[{remaining: bigint}]>`\n SELECT COUNT(*) AS remaining FROM ${sql(this.#schema)}.instances\n WHERE NOT \"deleted\" AND \"lastActive\" < ${threshold}\n `;\n\n return {purged: ids.length, remaining: Number(remaining)};\n },\n {mode: READ_COMMITTED},\n );\n }\n\n stop(): Promise<void> {\n this.#state.stop(this.#lc);\n return promiseVoid;\n }\n}\n"],"mappings":";;;;;;;AAUA,IAAM,wBAAwB,MADf,KAAK;AAIpB,IAAM,4BAA4B,MAAU,KAAK,KAAK;AAQtD,IAAa,YAAb,MAA0C;CACxC,KAAc;CAEd;CACA;CACA;CACA;CACA;CACA;CACA;CACA,SAAkB,IAAI,aAAa,SAAS;CAE5C,YACE,IACA,IACA,OACA,EAAC,uBAAuB,kBAAkB,qBAC1C,0BAA0B,2BAC1B;AACA,QAAA,KAAW;AACX,QAAA,KAAW;AACX,QAAA,SAAe,UAAU,MAAM;AAC/B,QAAA,wBAA8B;AAC9B,QAAA,4BAAkC,KAAK,IACrC,yBACA,sBACD;AACD,QAAA,mBAAyB;AACzB,QAAA,oBAA0B;;CAG5B,MAAM,MAAM;EACV,IAAI;EACJ,IAAI,kBAAkB,MAAA;EACtB,IAAI,gBAAgB,MAAA;AAEpB,MAAI,MAAA,qBAA2B,GAAG;AAChC,SAAA,GAAS,OACP,4DACD;AAED,SAAM,MAAA,MAAY,SAAS;QAE3B,OAAA,GAAS,OACP,2BACA,MAAM,MAAA,EAAQ,yBACf;AAGH,SAAO,MAAA,MAAY,WAAW,CAC5B,KAAI;GACF,MAAM,QAAQ,YAAY,KAAK;GAC/B,MAAM,EAAC,QAAQ,cACb,MAAM,KAAK,kBAAkB,gBAAgB;AAC/C,SAAA,MAAY,cAAc;AAE1B,OAAI,cAAc,KAAA,KAAa,YAAY,WAAW;AAGpD,uBAAmB,MAAA;AACnB,UAAA,GAAS,OAAO,+BAA+B,kBAAkB;;AAEnE,eAAY;AAEZ,mBACE,YAAY,IACR,MAAA,oBACA,KAAK,IAAI,gBAAgB,GAAG,sBAAsB;GACxD,MAAM,UAAU,YAAY,KAAK,GAAG;AACpC,SAAA,GAAS,OACP,UAAU,OAAO,kBAAkB,QAAQ,QAAQ,EAAE,CAAC,sBAAsB,cAAc,KAC3F;AACD,SAAM,MAAA,MAAY,MAAM,cAAc;WAC/B,GAAG;AACV,SAAM,MAAA,MAAY,QAAQ,MAAA,IAAU,EAAE;;;CAM5C,kBACE,SAC8C;AAC9C,SAAO,MACL,MAAA,IACA,OAAM,QAAO;GACX,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,YAAY,MAAM,MAAA;GACxB,MAAM,0BAA0B,MAAM,MAAA;GAStC,MAAM,OACJ,MAAM,GAA8B;wCACN,IAAI,MAAA,OAAa,CAAC;qDACL,UAAU;;oBAE3C,QAAQ;;QAEpB,QAAQ,EACN,MAAM;AAER,OAAI,IAAI,SAAS,GAAG;IAMlB,MAAM,QAAQ;KACZ;KACA;KACA;KACA;KACA;KACD,CAAC,KAAI,UACJ,GAAG;0BACW,IAAI,MAAA,OAAa,CAAC,GAAG,IAAI,MAAM,CAAC;yCACjB,IAAI,IAAI,GAAG,SAAS,CAClD;AAQD,UAAM,KACJ,GAAG;qBACM,IAAI,MAAA,OAAa,CAAC;;;;;;;;yCAQE,IAAI,IAAI,GAAG,SAAS,CAClD;AAED,UAAM,KACJ,GAAG;0BACW,IAAI,MAAA,OAAa,CAAC;mDACO,wBAAwB;YAC/D,SAAS,CACV;AACD,UAAM,QAAQ,IAAI,MAAM;;GAG1B,MAAM,CAAC,EAAC,eAAc,MAAM,GAA0B;4CAClB,IAAI,MAAA,OAAa,CAAC;mDACX,UAAU;;AAGrD,UAAO;IAAC,QAAQ,IAAI;IAAQ,WAAW,OAAO,UAAU;IAAC;KAE3D,EAAC,MAAM,gBAAe,CACvB;;CAGH,OAAsB;AACpB,QAAA,MAAY,KAAK,MAAA,GAAS;AAC1B,SAAO"}
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * The current version of Zero.
4
4
  */
5
- var version = "1.6.0-canary.13";
5
+ var version = "1.6.0-canary.14";
6
6
  //#endregion
7
7
  export { version };
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rocicorp/zero",
3
- "version": "1.6.0-canary.13",
3
+ "version": "1.6.0-canary.14",
4
4
  "description": "Zero is a web framework for serverless web development.",
5
5
  "homepage": "https://zero.rocicorp.dev",
6
6
  "bugs": {