@rocicorp/zero 1.4.0-canary.1 → 1.4.0-canary.2
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.
- package/out/analyze-query/src/bin-analyze.js +1 -1
- package/out/analyze-query/src/bin-analyze.js.map +1 -1
- package/out/analyze-query/src/bin-transform.js +1 -1
- package/out/analyze-query/src/bin-transform.js.map +1 -1
- package/out/replicache/src/btree/node.d.ts +1 -1
- package/out/replicache/src/btree/node.d.ts.map +1 -1
- package/out/replicache/src/btree/node.js +34 -21
- package/out/replicache/src/btree/node.js.map +1 -1
- package/out/replicache/src/btree/write.js +1 -2
- package/out/replicache/src/btree/write.js.map +1 -1
- package/out/shared/src/btree-set.d.ts +6 -0
- package/out/shared/src/btree-set.d.ts.map +1 -1
- package/out/shared/src/btree-set.js +34 -0
- package/out/shared/src/btree-set.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/config/zero-config.d.ts +18 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +35 -3
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/scripts/decommission.d.ts.map +1 -1
- package/out/zero-cache/src/scripts/decommission.js +3 -3
- package/out/zero-cache/src/scripts/decommission.js.map +1 -1
- package/out/zero-cache/src/scripts/deploy-permissions.js +1 -1
- package/out/zero-cache/src/scripts/deploy-permissions.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +2 -5
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +6 -1
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/reaper.d.ts.map +1 -1
- package/out/zero-cache/src/server/reaper.js +1 -4
- package/out/zero-cache/src/server/reaper.js.map +1 -1
- package/out/zero-cache/src/server/shadow-syncer.js +35 -0
- package/out/zero-cache/src/server/shadow-syncer.js.map +1 -0
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +2 -8
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/server/worker-urls.d.ts +1 -0
- package/out/zero-cache/src/server/worker-urls.d.ts.map +1 -1
- package/out/zero-cache/src/server/worker-urls.js +2 -1
- package/out/zero-cache/src/server/worker-urls.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +8 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +31 -18
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +44 -46
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +6 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +62 -22
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -3
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/schema/tables.js +1 -1
- package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js +49 -0
- package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -0
- package/out/zero-cache/src/services/statz.js +3 -3
- package/out/zero-cache/src/services/statz.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +1 -0
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +34 -11
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts +16 -1
- package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js +19 -1
- package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +6 -0
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +45 -2
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-set-signature.d.ts +17 -0
- package/out/zero-cache/src/services/view-syncer/row-set-signature.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/row-set-signature.js +29 -0
- package/out/zero-cache/src/services/view-syncer/row-set-signature.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts +1 -0
- package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/cvr.js +1 -0
- package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/init.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/init.js +5 -1
- package/out/zero-cache/src/services/view-syncer/schema/init.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.d.ts +105 -0
- package/out/zero-cache/src/services/view-syncer/schema/types.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/schema/types.js +8 -4
- package/out/zero-cache/src/services/view-syncer/schema/types.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +2 -2
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/pg.d.ts +1 -1
- package/out/zero-cache/src/types/pg.d.ts.map +1 -1
- package/out/zero-cache/src/types/pg.js +8 -2
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-cache/src/types/timeout.d.ts +11 -0
- package/out/zero-cache/src/types/timeout.d.ts.map +1 -0
- package/out/zero-cache/src/types/timeout.js +26 -0
- package/out/zero-cache/src/types/timeout.js.map +1 -0
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-server/src/adapters/postgresjs.d.ts.map +1 -1
- package/out/zero-server/src/adapters/postgresjs.js +1 -1
- package/out/zero-server/src/adapters/postgresjs.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +3 -3
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/query/query-internals.d.ts.map +1 -1
- package/out/zql/src/query/query-internals.js +1 -1
- package/out/zql/src/query/query-internals.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decommission.js","names":[],"sources":["../../../../../zero-cache/src/scripts/decommission.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {logOptions} from '../../../otel/src/log-options.ts';\nimport type {Config} from '../../../shared/src/options.ts';\nimport {appOptions, shardOptions, zeroOptions} from '../config/zero-config.ts';\nimport {decommissionShard} from '../services/change-source/pg/decommission.ts';\nimport {pgClient, type PostgresDB} from '../types/pg.ts';\nimport {cdcSchema, cvrSchema, getShardID} from '../types/shards.ts';\nimport {id} from '../types/sql.ts';\n\nexport const decommissionOptions = {\n app: {\n id: appOptions.id,\n },\n\n shard: {\n num: shardOptions.num,\n },\n\n upstream: {\n db: zeroOptions.upstream.db,\n type: zeroOptions.upstream.type,\n },\n\n cvr: {\n db: zeroOptions.cvr.db,\n },\n\n change: {\n db: zeroOptions.change.db,\n },\n\n log: {level: logOptions.level, format: logOptions.format},\n};\n\nexport type DecommissionConfig = Config<typeof decommissionOptions>;\n\nexport async function decommissionZero(\n lc: LogContext,\n cfg: DecommissionConfig,\n) {\n const {app, shard} = cfg;\n const shardID = getShardID(cfg);\n lc.info?.(`Decommissioning app \"${app.id}\"`);\n\n if (cfg.upstream.type === 'pg') {\n const upstream = pgClient(lc, cfg.upstream.db);\n await decommissionShard(lc, upstream, app.id, shard.num);\n\n lc.debug?.(`Cleaning up upstream metadata from ${hostPort(upstream)}`);\n await upstream.unsafe(`DROP SCHEMA IF EXISTS ${id(app.id)} CASCADE`);\n await upstream.end();\n }\n\n const cvr = pgClient(lc, cfg.cvr.db ?? cfg.upstream.db);\n lc.debug?.(`Cleaning up cvc data from ${hostPort(cvr)}`);\n await cvr.unsafe(`DROP SCHEMA IF EXISTS ${id(cvrSchema(shardID))} CASCADE`);\n await cvr.end();\n\n const cdc = pgClient(lc
|
|
1
|
+
{"version":3,"file":"decommission.js","names":[],"sources":["../../../../../zero-cache/src/scripts/decommission.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {logOptions} from '../../../otel/src/log-options.ts';\nimport type {Config} from '../../../shared/src/options.ts';\nimport {appOptions, shardOptions, zeroOptions} from '../config/zero-config.ts';\nimport {decommissionShard} from '../services/change-source/pg/decommission.ts';\nimport {pgClient, type PostgresDB} from '../types/pg.ts';\nimport {cdcSchema, cvrSchema, getShardID} from '../types/shards.ts';\nimport {id} from '../types/sql.ts';\n\nexport const decommissionOptions = {\n app: {\n id: appOptions.id,\n },\n\n shard: {\n num: shardOptions.num,\n },\n\n upstream: {\n db: zeroOptions.upstream.db,\n type: zeroOptions.upstream.type,\n },\n\n cvr: {\n db: zeroOptions.cvr.db,\n },\n\n change: {\n db: zeroOptions.change.db,\n },\n\n log: {level: logOptions.level, format: logOptions.format},\n};\n\nexport type DecommissionConfig = Config<typeof decommissionOptions>;\n\nexport async function decommissionZero(\n lc: LogContext,\n cfg: DecommissionConfig,\n) {\n const {app, shard} = cfg;\n const shardID = getShardID(cfg);\n lc.info?.(`Decommissioning app \"${app.id}\"`);\n\n if (cfg.upstream.type === 'pg') {\n const upstream = pgClient(lc, cfg.upstream.db, 'decommission-upstream');\n await decommissionShard(lc, upstream, app.id, shard.num);\n\n lc.debug?.(`Cleaning up upstream metadata from ${hostPort(upstream)}`);\n await upstream.unsafe(`DROP SCHEMA IF EXISTS ${id(app.id)} CASCADE`);\n await upstream.end();\n }\n\n const cvr = pgClient(lc, cfg.cvr.db ?? cfg.upstream.db, 'decommission-cvr');\n lc.debug?.(`Cleaning up cvc data from ${hostPort(cvr)}`);\n await cvr.unsafe(`DROP SCHEMA IF EXISTS ${id(cvrSchema(shardID))} CASCADE`);\n await cvr.end();\n\n const cdc = pgClient(\n lc,\n cfg.change.db ?? cfg.upstream.db,\n 'decommission-cdc',\n );\n lc.debug?.(`Cleaning up cdc data from ${hostPort(cdc)}`);\n await cdc.unsafe(`DROP SCHEMA IF EXISTS ${id(cdcSchema(shardID))} CASCADE`);\n await cdc.end();\n\n lc.info?.(`App \"${app.id}\" decommissioned`);\n}\n\nfunction hostPort(db: PostgresDB) {\n const {host, port} = db.options;\n return `${host.join(',')}:${port?.at(0) ?? 5432}`;\n}\n"],"mappings":";;;;;;;AASA,IAAa,sBAAsB;CACjC,KAAK,EACH,IAAI,WAAW,IAChB;CAED,OAAO,EACL,KAAK,aAAa,KACnB;CAED,UAAU;EACR,IAAI,YAAY,SAAS;EACzB,MAAM,YAAY,SAAS;EAC5B;CAED,KAAK,EACH,IAAI,YAAY,IAAI,IACrB;CAED,QAAQ,EACN,IAAI,YAAY,OAAO,IACxB;CAED,KAAK;EAAC,OAAO,WAAW;EAAO,QAAQ,WAAW;EAAO;CAC1D;AAID,eAAsB,iBACpB,IACA,KACA;CACA,MAAM,EAAC,KAAK,UAAS;CACrB,MAAM,UAAU,WAAW,IAAI;AAC/B,IAAG,OAAO,wBAAwB,IAAI,GAAG,GAAG;AAE5C,KAAI,IAAI,SAAS,SAAS,MAAM;EAC9B,MAAM,WAAW,SAAS,IAAI,IAAI,SAAS,IAAI,wBAAwB;AACvE,QAAM,kBAAkB,IAAI,UAAU,IAAI,IAAI,MAAM,IAAI;AAExD,KAAG,QAAQ,sCAAsC,SAAS,SAAS,GAAG;AACtE,QAAM,SAAS,OAAO,yBAAyB,GAAG,IAAI,GAAG,CAAC,UAAU;AACpE,QAAM,SAAS,KAAK;;CAGtB,MAAM,MAAM,SAAS,IAAI,IAAI,IAAI,MAAM,IAAI,SAAS,IAAI,mBAAmB;AAC3E,IAAG,QAAQ,6BAA6B,SAAS,IAAI,GAAG;AACxD,OAAM,IAAI,OAAO,yBAAyB,GAAG,UAAU,QAAQ,CAAC,CAAC,UAAU;AAC3E,OAAM,IAAI,KAAK;CAEf,MAAM,MAAM,SACV,IACA,IAAI,OAAO,MAAM,IAAI,SAAS,IAC9B,mBACD;AACD,IAAG,QAAQ,6BAA6B,SAAS,IAAI,GAAG;AACxD,OAAM,IAAI,OAAO,yBAAyB,GAAG,UAAU,QAAQ,CAAC,CAAC,UAAU;AAC3E,OAAM,IAAI,KAAK;AAEf,IAAG,OAAO,QAAQ,IAAI,GAAG,kBAAkB;;AAG7C,SAAS,SAAS,IAAgB;CAChC,MAAM,EAAC,MAAM,SAAQ,GAAG;AACxB,QAAO,GAAG,KAAK,KAAK,IAAI,CAAC,GAAG,MAAM,GAAG,EAAE,IAAI"}
|
|
@@ -74,7 +74,7 @@ function failWithMessage(msg) {
|
|
|
74
74
|
process.exit(-1);
|
|
75
75
|
}
|
|
76
76
|
async function deployPermissions(upstreamURI, permissions, force) {
|
|
77
|
-
const db = pgClient(lc, upstreamURI);
|
|
77
|
+
const db = pgClient(lc, upstreamURI, "deploy-permissions");
|
|
78
78
|
const { host, port } = db.options;
|
|
79
79
|
colorConsole.debug(`Connecting to upstream@${host}:${port}`);
|
|
80
80
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-permissions.js","names":[],"sources":["../../../../../zero-cache/src/scripts/deploy-permissions.ts"],"sourcesContent":["import '../../../shared/src/dotenv.ts';\n\nimport {writeFile} from 'node:fs/promises';\nimport {ident as id, literal} from 'pg-format';\nimport {colorConsole, createLogContext} from '../../../shared/src/logging.ts';\nimport {parseOptions} from '../../../shared/src/options.ts';\nimport {difference} from '../../../shared/src/set-utils.ts';\nimport {mapCondition} from '../../../zero-protocol/src/ast.ts';\nimport {\n type AssetPermissions,\n type PermissionsConfig,\n type Rule,\n} from '../../../zero-schema/src/compiled-permissions.ts';\nimport {validator} from '../../../zero-schema/src/name-mapper.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../config/zero-config.ts';\nimport {runTx} from '../db/run-transaction.ts';\nimport {getPublicationInfo} from '../services/change-source/pg/schema/published.ts';\nimport {\n ensureGlobalTables,\n SHARD_CONFIG_TABLE,\n} from '../services/change-source/pg/schema/shard.ts';\nimport {liteTableName} from '../types/names.ts';\nimport {pgClient, type PostgresDB} from '../types/pg.ts';\nimport {appSchema, getShardID, upstreamSchema} from '../types/shards.ts';\nimport {\n deployPermissionsOptions,\n loadSchemaAndPermissions,\n} from './permissions.ts';\n\nconst config = parseOptions(deployPermissionsOptions, {\n argv: process.argv.slice(2),\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n});\n\nconst shard = getShardID(config);\nconst app = appSchema(shard);\n\nconst lc = createLogContext(config);\n\nasync function validatePermissions(\n db: PostgresDB,\n permissions: PermissionsConfig,\n) {\n const schema = upstreamSchema(shard);\n\n // Check if the shardConfig table has been initialized.\n const result = await db`\n SELECT relname FROM pg_class\n JOIN pg_namespace ON relnamespace = pg_namespace.oid\n WHERE nspname = ${schema} AND relname = ${SHARD_CONFIG_TABLE}`;\n if (result.length === 0) {\n colorConsole.warn(\n `zero-cache has not yet initialized the upstream database.\\n` +\n `Deploying ${app} permissions without validating against published tables/columns.`,\n );\n return;\n }\n\n // Get the publications for the shard\n const config = await db<{publications: string[]}[]>`\n SELECT publications FROM ${db(schema + '.' + SHARD_CONFIG_TABLE)}\n `;\n if (config.length === 0) {\n colorConsole.warn(\n `zero-cache has not yet initialized the upstream database.\\n` +\n `Deploying ${app} permissions without validating against published tables/columns.`,\n );\n return;\n }\n colorConsole.info(\n `Validating permissions against tables and columns published for \"${app}\".`,\n );\n\n const [{publications: shardPublications}] = config;\n const {tables, publications} = await getPublicationInfo(\n db,\n shardPublications,\n );\n const pubnames = publications.map(p => p.pubname);\n const missing = difference(new Set(shardPublications), new Set(pubnames));\n if (missing.size) {\n colorConsole.warn(\n `Upstream is missing expected publications \"${[...missing]}\".\\n` +\n `You may need to re-initialize your replica.\\n` +\n `Deploying ${app} permissions without validating against published tables/columns.`,\n );\n return;\n }\n const tablesToColumns = new Map(\n tables.map(t => [liteTableName(t), Object.keys(t.columns)]),\n );\n const validate = validator(tablesToColumns);\n try {\n for (const [table, perms] of Object.entries(permissions?.tables ?? {})) {\n const validateRule = ([_, cond]: Rule) => {\n mapCondition(cond, table, validate);\n };\n const validateAsset = (asset: AssetPermissions | undefined) => {\n asset?.select?.forEach(validateRule);\n asset?.delete?.forEach(validateRule);\n asset?.insert?.forEach(validateRule);\n asset?.update?.preMutation?.forEach(validateRule);\n asset?.update?.postMutation?.forEach(validateRule);\n };\n validateAsset(perms.row);\n if (perms.cell) {\n Object.values(perms.cell).forEach(validateAsset);\n }\n }\n } catch (e) {\n failWithMessage(String(e));\n }\n}\n\nfunction failWithMessage(msg: string) {\n colorConsole.error(msg);\n colorConsole.info('\\nUse --force to deploy at your own risk.\\n');\n process.exit(-1);\n}\n\nasync function deployPermissions(\n upstreamURI: string,\n permissions: PermissionsConfig,\n force: boolean,\n) {\n const db = pgClient(lc, upstreamURI);\n const {host, port} = db.options;\n colorConsole.debug(`Connecting to upstream@${host}:${port}`);\n try {\n await ensureGlobalTables(db, shard);\n\n const {hash, changed} = await runTx(db, async tx => {\n if (force) {\n colorConsole.warn(`--force specified. Skipping validation.`);\n } else {\n await validatePermissions(tx, permissions);\n }\n\n const {appID} = shard;\n colorConsole.info(\n `Deploying permissions for --app-id \"${appID}\" to upstream@${db.options.host}`,\n );\n const [{hash: beforeHash}] = await tx<{hash: string}[]>`\n SELECT hash from ${tx(app)}.permissions`;\n const [{hash}] = await tx<{hash: string}[]>`\n UPDATE ${tx(app)}.permissions SET ${db({permissions})} RETURNING hash`;\n\n return {hash: hash.substring(0, 7), changed: beforeHash !== hash};\n });\n if (changed) {\n colorConsole.info(`Deployed new permissions (hash=${hash})`);\n } else {\n colorConsole.info(`Permissions unchanged (hash=${hash})`);\n }\n } finally {\n await db.end();\n }\n}\n\nasync function writePermissionsFile(\n perms: PermissionsConfig,\n file: string,\n format: 'sql' | 'json' | 'pretty',\n) {\n const contents =\n format === 'sql'\n ? `UPDATE ${id(app)}.permissions SET permissions = ${literal(\n JSON.stringify(perms),\n )};`\n : JSON.stringify(perms, null, format === 'pretty' ? 2 : 0);\n await writeFile(file, contents);\n colorConsole.info(`Wrote ${format} permissions to ${config.output.file}`);\n}\n\nconst ret = await loadSchemaAndPermissions(config.schema.path, true);\nif (!ret || Object.keys(ret?.permissions ?? {}).length === 0) {\n colorConsole.warn(\n `No permissions found at ${config.schema.path}, so could not deploy ` +\n `permissions. Replicating data, but no tables will be syncable. ` +\n `Create a schema file with permissions to be able to sync data.`,\n );\n} else {\n colorConsole.warn(\n `Permissions are deprecated and will be removed in an upcoming release. See: https://zero.rocicorp.dev/docs/auth.`,\n );\n\n const {permissions} = ret;\n if (config.output.file) {\n await writePermissionsFile(\n permissions,\n config.output.file,\n config.output.format,\n );\n } else if (config.upstream.type !== 'pg') {\n colorConsole.warn(\n `Permissions deployment is not supported for ${config.upstream.type} upstreams`,\n );\n process.exit(-1);\n } else if (config.upstream.db) {\n await deployPermissions(config.upstream.db, permissions, config.force);\n } else {\n colorConsole.error(`No --output-file or --upstream-db specified`);\n // Shows the usage text.\n parseOptions(deployPermissionsOptions, {\n argv: ['--help'],\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"deploy-permissions.js","names":[],"sources":["../../../../../zero-cache/src/scripts/deploy-permissions.ts"],"sourcesContent":["import '../../../shared/src/dotenv.ts';\n\nimport {writeFile} from 'node:fs/promises';\nimport {ident as id, literal} from 'pg-format';\nimport {colorConsole, createLogContext} from '../../../shared/src/logging.ts';\nimport {parseOptions} from '../../../shared/src/options.ts';\nimport {difference} from '../../../shared/src/set-utils.ts';\nimport {mapCondition} from '../../../zero-protocol/src/ast.ts';\nimport {\n type AssetPermissions,\n type PermissionsConfig,\n type Rule,\n} from '../../../zero-schema/src/compiled-permissions.ts';\nimport {validator} from '../../../zero-schema/src/name-mapper.ts';\nimport {ZERO_ENV_VAR_PREFIX} from '../config/zero-config.ts';\nimport {runTx} from '../db/run-transaction.ts';\nimport {getPublicationInfo} from '../services/change-source/pg/schema/published.ts';\nimport {\n ensureGlobalTables,\n SHARD_CONFIG_TABLE,\n} from '../services/change-source/pg/schema/shard.ts';\nimport {liteTableName} from '../types/names.ts';\nimport {pgClient, type PostgresDB} from '../types/pg.ts';\nimport {appSchema, getShardID, upstreamSchema} from '../types/shards.ts';\nimport {\n deployPermissionsOptions,\n loadSchemaAndPermissions,\n} from './permissions.ts';\n\nconst config = parseOptions(deployPermissionsOptions, {\n argv: process.argv.slice(2),\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n});\n\nconst shard = getShardID(config);\nconst app = appSchema(shard);\n\nconst lc = createLogContext(config);\n\nasync function validatePermissions(\n db: PostgresDB,\n permissions: PermissionsConfig,\n) {\n const schema = upstreamSchema(shard);\n\n // Check if the shardConfig table has been initialized.\n const result = await db`\n SELECT relname FROM pg_class\n JOIN pg_namespace ON relnamespace = pg_namespace.oid\n WHERE nspname = ${schema} AND relname = ${SHARD_CONFIG_TABLE}`;\n if (result.length === 0) {\n colorConsole.warn(\n `zero-cache has not yet initialized the upstream database.\\n` +\n `Deploying ${app} permissions without validating against published tables/columns.`,\n );\n return;\n }\n\n // Get the publications for the shard\n const config = await db<{publications: string[]}[]>`\n SELECT publications FROM ${db(schema + '.' + SHARD_CONFIG_TABLE)}\n `;\n if (config.length === 0) {\n colorConsole.warn(\n `zero-cache has not yet initialized the upstream database.\\n` +\n `Deploying ${app} permissions without validating against published tables/columns.`,\n );\n return;\n }\n colorConsole.info(\n `Validating permissions against tables and columns published for \"${app}\".`,\n );\n\n const [{publications: shardPublications}] = config;\n const {tables, publications} = await getPublicationInfo(\n db,\n shardPublications,\n );\n const pubnames = publications.map(p => p.pubname);\n const missing = difference(new Set(shardPublications), new Set(pubnames));\n if (missing.size) {\n colorConsole.warn(\n `Upstream is missing expected publications \"${[...missing]}\".\\n` +\n `You may need to re-initialize your replica.\\n` +\n `Deploying ${app} permissions without validating against published tables/columns.`,\n );\n return;\n }\n const tablesToColumns = new Map(\n tables.map(t => [liteTableName(t), Object.keys(t.columns)]),\n );\n const validate = validator(tablesToColumns);\n try {\n for (const [table, perms] of Object.entries(permissions?.tables ?? {})) {\n const validateRule = ([_, cond]: Rule) => {\n mapCondition(cond, table, validate);\n };\n const validateAsset = (asset: AssetPermissions | undefined) => {\n asset?.select?.forEach(validateRule);\n asset?.delete?.forEach(validateRule);\n asset?.insert?.forEach(validateRule);\n asset?.update?.preMutation?.forEach(validateRule);\n asset?.update?.postMutation?.forEach(validateRule);\n };\n validateAsset(perms.row);\n if (perms.cell) {\n Object.values(perms.cell).forEach(validateAsset);\n }\n }\n } catch (e) {\n failWithMessage(String(e));\n }\n}\n\nfunction failWithMessage(msg: string) {\n colorConsole.error(msg);\n colorConsole.info('\\nUse --force to deploy at your own risk.\\n');\n process.exit(-1);\n}\n\nasync function deployPermissions(\n upstreamURI: string,\n permissions: PermissionsConfig,\n force: boolean,\n) {\n const db = pgClient(lc, upstreamURI, 'deploy-permissions');\n const {host, port} = db.options;\n colorConsole.debug(`Connecting to upstream@${host}:${port}`);\n try {\n await ensureGlobalTables(db, shard);\n\n const {hash, changed} = await runTx(db, async tx => {\n if (force) {\n colorConsole.warn(`--force specified. Skipping validation.`);\n } else {\n await validatePermissions(tx, permissions);\n }\n\n const {appID} = shard;\n colorConsole.info(\n `Deploying permissions for --app-id \"${appID}\" to upstream@${db.options.host}`,\n );\n const [{hash: beforeHash}] = await tx<{hash: string}[]>`\n SELECT hash from ${tx(app)}.permissions`;\n const [{hash}] = await tx<{hash: string}[]>`\n UPDATE ${tx(app)}.permissions SET ${db({permissions})} RETURNING hash`;\n\n return {hash: hash.substring(0, 7), changed: beforeHash !== hash};\n });\n if (changed) {\n colorConsole.info(`Deployed new permissions (hash=${hash})`);\n } else {\n colorConsole.info(`Permissions unchanged (hash=${hash})`);\n }\n } finally {\n await db.end();\n }\n}\n\nasync function writePermissionsFile(\n perms: PermissionsConfig,\n file: string,\n format: 'sql' | 'json' | 'pretty',\n) {\n const contents =\n format === 'sql'\n ? `UPDATE ${id(app)}.permissions SET permissions = ${literal(\n JSON.stringify(perms),\n )};`\n : JSON.stringify(perms, null, format === 'pretty' ? 2 : 0);\n await writeFile(file, contents);\n colorConsole.info(`Wrote ${format} permissions to ${config.output.file}`);\n}\n\nconst ret = await loadSchemaAndPermissions(config.schema.path, true);\nif (!ret || Object.keys(ret?.permissions ?? {}).length === 0) {\n colorConsole.warn(\n `No permissions found at ${config.schema.path}, so could not deploy ` +\n `permissions. Replicating data, but no tables will be syncable. ` +\n `Create a schema file with permissions to be able to sync data.`,\n );\n} else {\n colorConsole.warn(\n `Permissions are deprecated and will be removed in an upcoming release. See: https://zero.rocicorp.dev/docs/auth.`,\n );\n\n const {permissions} = ret;\n if (config.output.file) {\n await writePermissionsFile(\n permissions,\n config.output.file,\n config.output.format,\n );\n } else if (config.upstream.type !== 'pg') {\n colorConsole.warn(\n `Permissions deployment is not supported for ${config.upstream.type} upstreams`,\n );\n process.exit(-1);\n } else if (config.upstream.db) {\n await deployPermissions(config.upstream.db, permissions, config.force);\n } else {\n colorConsole.error(`No --output-file or --upstream-db specified`);\n // Shows the usage text.\n parseOptions(deployPermissionsOptions, {\n argv: ['--help'],\n envNamePrefix: ZERO_ENV_VAR_PREFIX,\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"change-streamer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/change-streamer.ts"],"names":[],"mappings":"AA6BA,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,CA0Lf"}
|
|
@@ -11,7 +11,7 @@ import { getServerContext } from "../config/server-context.js";
|
|
|
11
11
|
import { deleteLiteDB } from "../db/delete-lite-db.js";
|
|
12
12
|
import { warmupConnections } from "../db/warmup.js";
|
|
13
13
|
import { initEventSink, publishCriticalEvent } from "../observability/events.js";
|
|
14
|
-
import { AutoResetSignal
|
|
14
|
+
import { AutoResetSignal } from "../services/change-streamer/schema/tables.js";
|
|
15
15
|
import { upgradeReplica } from "../services/change-source/common/replica-schema.js";
|
|
16
16
|
import { ReplicationStatusPublisher, replicationStatusError } from "../services/replicator/replication-status.js";
|
|
17
17
|
import { initializeCustomChangeSource } from "../services/change-source/custom/change-source.js";
|
|
@@ -35,10 +35,7 @@ async function runWorker(parent, env, ...argv) {
|
|
|
35
35
|
startOtelAuto(createLogContext(config, "change-streamer", 0, false), "change-streamer", 0);
|
|
36
36
|
const lc = createLogContext(config, "change-streamer");
|
|
37
37
|
initEventSink(lc, config);
|
|
38
|
-
const changeDB = pgClient(lc, change.db, {
|
|
39
|
-
max: change.maxConns,
|
|
40
|
-
connection: { ["application_name"]: CHANGE_STREAMER_APP_NAME }
|
|
41
|
-
}, { sendStringAsJson: true });
|
|
38
|
+
const changeDB = pgClient(lc, change.db, "change-streamer", { max: change.maxConns }, { sendStringAsJson: true });
|
|
42
39
|
warmupConnections(lc, changeDB, "change").catch(() => {});
|
|
43
40
|
const { autoReset, replicationLag } = config;
|
|
44
41
|
const shard = getShardConfig(config);
|
|
@@ -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 {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source.ts';\nimport {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 {initChangeStreamerSchema} from '../services/change-streamer/schema/init.ts';\nimport {\n AutoResetSignal,\n CHANGE_STREAMER_APP_NAME,\n} from '../services/change-streamer/schema/tables.ts';\nimport {PurgeLocker} from '../services/change-streamer/storer.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n BackupNotFoundException,\n restoreReplica,\n} from '../services/litestream/commands.ts';\nimport {\n replicationStatusError,\n ReplicationStatusPublisher,\n} from '../services/replicator/replication-status.ts';\nimport {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 ...argv: string[]\n): Promise<void> {\n const workerStartTime = Date.now();\n const config = getNormalizedZeroConfig({env, argv});\n const {\n taskID,\n changeStreamer: {\n port,\n address,\n protocol,\n startupDelayMs,\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\n },\n upstream,\n change,\n replica,\n initialSync,\n litestream,\n } = config;\n\n startOtelAuto(\n createLogContext(config, 'change-streamer', 0, false),\n 'change-streamer',\n 0,\n );\n const lc = createLogContext(config, 'change-streamer');\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').catch(() => {});\n\n const {autoReset, replicationLag} = config;\n const shard = getShardConfig(config);\n\n // Ensure the change DB schema is initialized/up-to-date, then acquire\n // a lock to prevent change-lock purges. This ensures that (this)\n // change-streamer will be able to resume from the backup.\n await initChangeStreamerSchema(lc, changeDB, shard);\n let purgeLock = await new PurgeLocker(lc, shard, changeDB).acquire();\n\n // Restore from litestream if the change-log has entries.\n if (purgeLock) {\n try {\n await restoreReplica(lc, config, purgeLock);\n } catch (e) {\n // If the restore failed, e.g. due to a corrupt or missing backup, the\n // replication-manager recovers by re-syncing.\n const log = e instanceof BackupNotFoundException ? 'warn' : 'error';\n lc[log]?.(\n `error restoring backup. resyncing the replica: ${String(e)}`,\n e,\n );\n\n // The purgeLock must be released if the backup could not be restored,\n // or it will otherwise prevent the change-db update after the resync\n // completes.\n await purgeLock.release();\n purgeLock = null;\n }\n }\n\n let changeStreamer: ChangeStreamerService | undefined;\n\n const context = getServerContext(config);\n\n for (const first of [true, false]) {\n try {\n // Note: This performs initial sync of the replica if necessary.\n const {changeSource, subscriptionState} =\n upstream.type === 'pg'\n ? await initializePostgresChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n {\n ...initialSync,\n replicationSlotFailover: upstream.pgReplicationSlotFailover,\n },\n context,\n replicationLag.reportIntervalMs,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n context,\n );\n\n const replicationStatusPublisher =\n ReplicationStatusPublisher.forReplicaFile(replica.file);\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n replicationStatusPublisher,\n subscriptionState,\n purgeLock,\n autoReset ?? false,\n {\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\n statementTimeoutMs: change.statementTimeoutMs,\n },\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 // Perform any upgrades to the replica in case it was restored from an\n // earlier version. Note that this upgrade is done by the replicator worker\n // as well (in both the replication-manager and the view-syncer), but the\n // change-streamer independently reads the replica, and it is fine run the\n // upgrade logic redundantly since it is idempotent.\n await upgradeReplica(lc, 'change-streamer-init', replica.file);\n\n const {backupURL, port: metricsPort} = litestream;\n const monitor = backupURL\n ? new BackupMonitor(\n lc,\n replica.file,\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() - workerStartTime,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,MAAM,kBAAkB,KAAK,KAAK;CAClC,MAAM,SAAS,wBAAwB;EAAC;EAAK;EAAK,CAAC;CACnD,MAAM,EACJ,QACA,gBAAgB,EACd,MACA,SACA,UACA,gBACA,iCACA,sCAEF,UACA,QACA,SACA,aACA,eACE;AAEJ,eACE,iBAAiB,QAAQ,mBAAmB,GAAG,MAAM,EACrD,mBACA,EACD;CACD,MAAM,KAAK,iBAAiB,QAAQ,kBAAkB;AACtD,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,CAAC,YAAY,GAAG;CAE9D,MAAM,EAAC,WAAW,mBAAkB;CACpC,MAAM,QAAQ,eAAe,OAAO;AAKpC,OAAM,yBAAyB,IAAI,UAAU,MAAM;CACnD,IAAI,YAAY,MAAM,IAAI,YAAY,IAAI,OAAO,SAAS,CAAC,SAAS;AAGpE,KAAI,UACF,KAAI;AACF,QAAM,eAAe,IAAI,QAAQ,UAAU;UACpC,GAAG;AAIV,KADY,aAAa,0BAA0B,SAAS,WAE1D,kDAAkD,OAAO,EAAE,IAC3D,EACD;AAKD,QAAM,UAAU,SAAS;AACzB,cAAY;;CAIhB,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;GACE,GAAG;GACH,yBAAyB,SAAS;GACnC,EACD,SACA,eAAe,iBAChB,GACD,MAAM,6BACJ,IACA,SAAS,IACT,OACA,QAAQ,MACR,QACD;AAKP,mBAAiB,MAAM,mBACrB,IACA,OACA,QACA,SACA,UACA,UACA,cATA,2BAA2B,eAAe,QAAQ,KAAK,EAWvD,mBACA,WACA,aAAa,OACb;GACE;GACA;GACA,oBAAoB,OAAO;GAC5B,EACD,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;AAO1E,OAAM,eAAe,IAAI,wBAAwB,QAAQ,KAAK;CAE9D,MAAM,EAAC,WAAW,MAAM,gBAAe;CACvC,MAAM,UAAU,YACZ,IAAI,cACF,IACA,QAAQ,MACR,WACA,oBAAoB,YAAY,WAChC,gBAQA,KAAK,KAAK,GAAG,gBACd,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 {DatabaseInitError} from '../../../zqlite/src/db.ts';\nimport {getServerContext} from '../config/server-context.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {deleteLiteDB} from '../db/delete-lite-db.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink, publishCriticalEvent} from '../observability/events.ts';\nimport {upgradeReplica} from '../services/change-source/common/replica-schema.ts';\nimport {initializeCustomChangeSource} from '../services/change-source/custom/change-source.ts';\nimport {initializePostgresChangeSource} from '../services/change-source/pg/change-source.ts';\nimport {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 {initChangeStreamerSchema} from '../services/change-streamer/schema/init.ts';\nimport {AutoResetSignal} from '../services/change-streamer/schema/tables.ts';\nimport {PurgeLocker} from '../services/change-streamer/storer.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {\n BackupNotFoundException,\n restoreReplica,\n} from '../services/litestream/commands.ts';\nimport {\n replicationStatusError,\n ReplicationStatusPublisher,\n} from '../services/replicator/replication-status.ts';\nimport {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 ...argv: string[]\n): Promise<void> {\n const workerStartTime = Date.now();\n const config = getNormalizedZeroConfig({env, argv});\n const {\n taskID,\n changeStreamer: {\n port,\n address,\n protocol,\n startupDelayMs,\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\n },\n upstream,\n change,\n replica,\n initialSync,\n litestream,\n } = config;\n\n startOtelAuto(\n createLogContext(config, 'change-streamer', 0, false),\n 'change-streamer',\n 0,\n );\n const lc = createLogContext(config, 'change-streamer');\n initEventSink(lc, config);\n\n // Kick off DB connection warmup in the background.\n const changeDB = pgClient(\n lc,\n change.db,\n 'change-streamer',\n {\n max: change.maxConns,\n },\n {sendStringAsJson: true},\n );\n void warmupConnections(lc, changeDB, 'change').catch(() => {});\n\n const {autoReset, replicationLag} = config;\n const shard = getShardConfig(config);\n\n // Ensure the change DB schema is initialized/up-to-date, then acquire\n // a lock to prevent change-lock purges. This ensures that (this)\n // change-streamer will be able to resume from the backup.\n await initChangeStreamerSchema(lc, changeDB, shard);\n let purgeLock = await new PurgeLocker(lc, shard, changeDB).acquire();\n\n // Restore from litestream if the change-log has entries.\n if (purgeLock) {\n try {\n await restoreReplica(lc, config, purgeLock);\n } catch (e) {\n // If the restore failed, e.g. due to a corrupt or missing backup, the\n // replication-manager recovers by re-syncing.\n const log = e instanceof BackupNotFoundException ? 'warn' : 'error';\n lc[log]?.(\n `error restoring backup. resyncing the replica: ${String(e)}`,\n e,\n );\n\n // The purgeLock must be released if the backup could not be restored,\n // or it will otherwise prevent the change-db update after the resync\n // completes.\n await purgeLock.release();\n purgeLock = null;\n }\n }\n\n let changeStreamer: ChangeStreamerService | undefined;\n\n const context = getServerContext(config);\n\n for (const first of [true, false]) {\n try {\n // Note: This performs initial sync of the replica if necessary.\n const {changeSource, subscriptionState} =\n upstream.type === 'pg'\n ? await initializePostgresChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n {\n ...initialSync,\n replicationSlotFailover: upstream.pgReplicationSlotFailover,\n },\n context,\n replicationLag.reportIntervalMs,\n )\n : await initializeCustomChangeSource(\n lc,\n upstream.db,\n shard,\n replica.file,\n context,\n );\n\n const replicationStatusPublisher =\n ReplicationStatusPublisher.forReplicaFile(replica.file);\n\n changeStreamer = await initializeStreamer(\n lc,\n shard,\n taskID,\n address,\n protocol,\n changeDB,\n changeSource,\n replicationStatusPublisher,\n subscriptionState,\n purgeLock,\n autoReset ?? false,\n {\n backPressureLimitHeapProportion,\n flowControlConsensusPaddingSeconds,\n statementTimeoutMs: change.statementTimeoutMs,\n },\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 // Perform any upgrades to the replica in case it was restored from an\n // earlier version. Note that this upgrade is done by the replicator worker\n // as well (in both the replication-manager and the view-syncer), but the\n // change-streamer independently reads the replica, and it is fine run the\n // upgrade logic redundantly since it is idempotent.\n await upgradeReplica(lc, 'change-streamer-init', replica.file);\n\n const {backupURL, port: metricsPort} = litestream;\n const monitor = backupURL\n ? new BackupMonitor(\n lc,\n replica.file,\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() - workerStartTime,\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,MAAM,kBAAkB,KAAK,KAAK;CAClC,MAAM,SAAS,wBAAwB;EAAC;EAAK;EAAK,CAAC;CACnD,MAAM,EACJ,QACA,gBAAgB,EACd,MACA,SACA,UACA,gBACA,iCACA,sCAEF,UACA,QACA,SACA,aACA,eACE;AAEJ,eACE,iBAAiB,QAAQ,mBAAmB,GAAG,MAAM,EACrD,mBACA,EACD;CACD,MAAM,KAAK,iBAAiB,QAAQ,kBAAkB;AACtD,eAAc,IAAI,OAAO;CAGzB,MAAM,WAAW,SACf,IACA,OAAO,IACP,mBACA,EACE,KAAK,OAAO,UACb,EACD,EAAC,kBAAkB,MAAK,CACzB;AACI,mBAAkB,IAAI,UAAU,SAAS,CAAC,YAAY,GAAG;CAE9D,MAAM,EAAC,WAAW,mBAAkB;CACpC,MAAM,QAAQ,eAAe,OAAO;AAKpC,OAAM,yBAAyB,IAAI,UAAU,MAAM;CACnD,IAAI,YAAY,MAAM,IAAI,YAAY,IAAI,OAAO,SAAS,CAAC,SAAS;AAGpE,KAAI,UACF,KAAI;AACF,QAAM,eAAe,IAAI,QAAQ,UAAU;UACpC,GAAG;AAIV,KADY,aAAa,0BAA0B,SAAS,WAE1D,kDAAkD,OAAO,EAAE,IAC3D,EACD;AAKD,QAAM,UAAU,SAAS;AACzB,cAAY;;CAIhB,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;GACE,GAAG;GACH,yBAAyB,SAAS;GACnC,EACD,SACA,eAAe,iBAChB,GACD,MAAM,6BACJ,IACA,SAAS,IACT,OACA,QAAQ,MACR,QACD;AAKP,mBAAiB,MAAM,mBACrB,IACA,OACA,QACA,SACA,UACA,UACA,cATA,2BAA2B,eAAe,QAAQ,KAAK,EAWvD,mBACA,WACA,aAAa,OACb;GACE;GACA;GACA,oBAAoB,OAAO;GAC5B,EACD,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;AAO1E,OAAM,eAAe,IAAI,wBAAwB,QAAQ,KAAK;CAE9D,MAAM,EAAC,WAAW,MAAM,gBAAe;CACvC,MAAM,UAAU,YACZ,IAAI,cACF,IACA,QAAQ,MACR,WACA,oBAAoB,YAAY,WAChC,gBAQA,KAAK,KAAK,GAAG,gBACd,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":"main.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/main.ts"],"names":[],"mappings":"AAeA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/main.ts"],"names":[],"mappings":"AAeA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAqB/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC,CA6Kf"}
|
|
@@ -3,7 +3,7 @@ import { childWorker, parentWorker, singleProcessMode } from "../types/processes
|
|
|
3
3
|
import { getNormalizedZeroConfig } from "../config/zero-config.js";
|
|
4
4
|
import { ProcessManager, exitAfter, runUntilKilled } from "../services/life-cycle.js";
|
|
5
5
|
import { createLogContext } from "./logging.js";
|
|
6
|
-
import { CHANGE_STREAMER_URL, REAPER_URL, REPLICATOR_URL, SYNCER_URL } from "./worker-urls.js";
|
|
6
|
+
import { CHANGE_STREAMER_URL, REAPER_URL, REPLICATOR_URL, SHADOW_SYNCER_URL, SYNCER_URL } from "./worker-urls.js";
|
|
7
7
|
import { initEventSink } from "../observability/events.js";
|
|
8
8
|
import { restoreReplica, startReplicaBackupProcess } from "../services/litestream/commands.js";
|
|
9
9
|
import { startOtelAuto } from "./otel-start.js";
|
|
@@ -58,6 +58,11 @@ async function runWorker(parent, env) {
|
|
|
58
58
|
loadWorker(REAPER_URL, "supporting").once("message", reaperStarted);
|
|
59
59
|
await reaperReady;
|
|
60
60
|
}
|
|
61
|
+
if (config.shadowSync.enabled && runChangeStreamer) {
|
|
62
|
+
const { promise: shadowReady, resolve: shadowStarted } = resolver();
|
|
63
|
+
loadWorker(SHADOW_SYNCER_URL, "supporting").once("message", shadowStarted);
|
|
64
|
+
await shadowReady;
|
|
65
|
+
}
|
|
61
66
|
const syncers = [];
|
|
62
67
|
if (numSyncers) {
|
|
63
68
|
const mode = runChangeStreamer && litestream.backupURL ? "serving-copy" : "serving";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","names":[],"sources":["../../../../../zero-cache/src/server/main.ts"],"sourcesContent":["import path from 'node:path';\nimport {resolver} from '@rocicorp/resolver';\nimport {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {\n exitAfter,\n ProcessManager,\n runUntilKilled,\n type WorkerType,\n} from '../services/life-cycle.ts';\nimport {\n restoreReplica,\n startReplicaBackupProcess,\n} from '../services/litestream/commands.ts';\nimport {\n childWorker,\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {\n createNotifierFrom,\n handleSubscriptionsFrom,\n type ReplicaFileMode,\n subscribeTo,\n} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {WorkerDispatcher} from './worker-dispatcher.ts';\nimport {\n CHANGE_STREAMER_URL,\n MUTATOR_URL,\n REAPER_URL,\n REPLICATOR_URL,\n SYNCER_URL,\n} from './worker-urls.ts';\n\nconst clientConnectionBifurcated = false;\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n): Promise<void> {\n const startMs = Date.now();\n const config = getNormalizedZeroConfig({env});\n\n startOtelAuto(\n createLogContext(config, 'dispatcher', 0, false),\n 'dispatcher',\n 0,\n );\n const lc = createLogContext(config, 'dispatcher');\n initEventSink(lc, config);\n\n const processes = new ProcessManager(lc, parent);\n\n const {numSyncWorkers: numSyncers} = config;\n if (config.enableCrudMutations && config.upstream.maxConns < numSyncers) {\n throw new Error(\n `Insufficient upstream connections (${config.upstream.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_UPSTREAM_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n if (config.cvr.maxConns < numSyncers) {\n throw new Error(\n `Insufficient cvr connections (${config.cvr.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_CVR_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n\n const internalFlags: string[] =\n numSyncers === 0\n ? []\n : [\n '--upstream-max-conns-per-worker',\n String(Math.floor(config.upstream.maxConns / numSyncers)),\n '--cvr-max-conns-per-worker',\n String(Math.floor(config.cvr.maxConns / numSyncers)),\n ];\n\n function loadWorker(\n moduleUrl: URL,\n type: WorkerType,\n id?: string | number,\n ...args: string[]\n ): Worker {\n const worker = childWorker(moduleUrl, env, ...args, ...internalFlags);\n const name = path.basename(moduleUrl.pathname) + (id ? ` (${id})` : '');\n return processes.addWorker(worker, type, name);\n }\n\n const {\n taskID,\n changeStreamer: {mode: changeStreamerMode, uri: changeStreamerURI},\n litestream,\n } = config;\n const runChangeStreamer =\n changeStreamerMode === 'dedicated' && changeStreamerURI === undefined;\n\n let changeStreamer: Worker | undefined;\n\n if (!runChangeStreamer) {\n changeStreamer = undefined;\n if (litestream.executable) {\n // For view-syncers, the backup is restored here. For the replication-manager,\n // the backup is restored in the change-streamer worker.\n await restoreReplica(lc, config, null);\n }\n } else {\n const {promise: changeStreamerReady, resolve: changeStreamerStarted} =\n resolver();\n changeStreamer = loadWorker(CHANGE_STREAMER_URL, 'supporting').once(\n 'message',\n changeStreamerStarted,\n );\n\n // Wait for the change-streamer to be ready to guarantee that a replica\n // file is present.\n await changeStreamerReady;\n\n if (litestream.backupURL) {\n // Start a backup replicator and corresponding litestream backup process.\n const {promise: backupReady, resolve} = resolver();\n const mode: ReplicaFileMode = 'backup';\n loadWorker(REPLICATOR_URL, 'supporting', mode, mode).once(\n // Wait for the Replicator's first message (i.e. \"ready\") before starting\n // litestream backup in order to avoid contending on the lock when the\n // replicator first prepares the db file.\n 'message',\n () => {\n processes.addSubprocess(\n startReplicaBackupProcess(lc, config),\n 'supporting',\n 'litestream',\n );\n resolve();\n },\n );\n await backupReady;\n }\n }\n\n if (numSyncers > 0) {\n const {promise: reaperReady, resolve: reaperStarted} = resolver();\n loadWorker(REAPER_URL, 'supporting').once('message', reaperStarted);\n // Before starting the view-syncers, ensure that the reaper has started\n // up, indicating that any CVR db migrations have been performed.\n await reaperReady;\n }\n\n const syncers: Worker[] = [];\n if (numSyncers) {\n const mode: ReplicaFileMode =\n runChangeStreamer && litestream.backupURL ? 'serving-copy' : 'serving';\n const {promise: replicaReady, resolve} = resolver();\n const replicator = loadWorker(\n REPLICATOR_URL,\n 'supporting',\n mode,\n mode,\n ).once('message', () => {\n subscribeTo(lc, replicator);\n resolve();\n });\n await replicaReady;\n\n const notifier = createNotifierFrom(lc, replicator);\n for (let i = 0; i < numSyncers; i++) {\n syncers.push(loadWorker(SYNCER_URL, 'user-facing', i, mode, String(i)));\n }\n syncers.forEach(syncer => handleSubscriptionsFrom(lc, syncer, notifier));\n }\n let mutator: Worker | undefined;\n if (clientConnectionBifurcated) {\n mutator = loadWorker(MUTATOR_URL, 'supporting', 'mutator');\n }\n\n lc.info?.('waiting for workers to be ready ...');\n const logWaiting = setInterval(\n () => lc.info?.(`still waiting for ${processes.initializing().join(', ')}`),\n 10_000,\n );\n await processes.allWorkersReady();\n clearInterval(logWaiting);\n lc.info?.(`all workers ready (${Date.now() - startMs} ms)`);\n\n parent.send(['ready', {ready: true}]);\n\n try {\n await runUntilKilled(\n lc,\n parent,\n new WorkerDispatcher(\n lc,\n taskID,\n parent,\n syncers,\n mutator,\n changeStreamer,\n ),\n );\n } catch (err) {\n processes.logErrorAndExit(err, 'dispatcher');\n }\n\n await processes.done();\n}\n\nif (!singleProcessMode()) {\n void exitAfter(() => runWorker(must(parentWorker), process.env));\n}\n"],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"main.js","names":[],"sources":["../../../../../zero-cache/src/server/main.ts"],"sourcesContent":["import path from 'node:path';\nimport {resolver} from '@rocicorp/resolver';\nimport {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {\n exitAfter,\n ProcessManager,\n runUntilKilled,\n type WorkerType,\n} from '../services/life-cycle.ts';\nimport {\n restoreReplica,\n startReplicaBackupProcess,\n} from '../services/litestream/commands.ts';\nimport {\n childWorker,\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {\n createNotifierFrom,\n handleSubscriptionsFrom,\n type ReplicaFileMode,\n subscribeTo,\n} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {WorkerDispatcher} from './worker-dispatcher.ts';\nimport {\n CHANGE_STREAMER_URL,\n MUTATOR_URL,\n REAPER_URL,\n REPLICATOR_URL,\n SHADOW_SYNCER_URL,\n SYNCER_URL,\n} from './worker-urls.ts';\n\nconst clientConnectionBifurcated = false;\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n): Promise<void> {\n const startMs = Date.now();\n const config = getNormalizedZeroConfig({env});\n\n startOtelAuto(\n createLogContext(config, 'dispatcher', 0, false),\n 'dispatcher',\n 0,\n );\n const lc = createLogContext(config, 'dispatcher');\n initEventSink(lc, config);\n\n const processes = new ProcessManager(lc, parent);\n\n const {numSyncWorkers: numSyncers} = config;\n if (config.enableCrudMutations && config.upstream.maxConns < numSyncers) {\n throw new Error(\n `Insufficient upstream connections (${config.upstream.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_UPSTREAM_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n if (config.cvr.maxConns < numSyncers) {\n throw new Error(\n `Insufficient cvr connections (${config.cvr.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_CVR_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n\n const internalFlags: string[] =\n numSyncers === 0\n ? []\n : [\n '--upstream-max-conns-per-worker',\n String(Math.floor(config.upstream.maxConns / numSyncers)),\n '--cvr-max-conns-per-worker',\n String(Math.floor(config.cvr.maxConns / numSyncers)),\n ];\n\n function loadWorker(\n moduleUrl: URL,\n type: WorkerType,\n id?: string | number,\n ...args: string[]\n ): Worker {\n const worker = childWorker(moduleUrl, env, ...args, ...internalFlags);\n const name = path.basename(moduleUrl.pathname) + (id ? ` (${id})` : '');\n return processes.addWorker(worker, type, name);\n }\n\n const {\n taskID,\n changeStreamer: {mode: changeStreamerMode, uri: changeStreamerURI},\n litestream,\n } = config;\n const runChangeStreamer =\n changeStreamerMode === 'dedicated' && changeStreamerURI === undefined;\n\n let changeStreamer: Worker | undefined;\n\n if (!runChangeStreamer) {\n changeStreamer = undefined;\n if (litestream.executable) {\n // For view-syncers, the backup is restored here. For the replication-manager,\n // the backup is restored in the change-streamer worker.\n await restoreReplica(lc, config, null);\n }\n } else {\n const {promise: changeStreamerReady, resolve: changeStreamerStarted} =\n resolver();\n changeStreamer = loadWorker(CHANGE_STREAMER_URL, 'supporting').once(\n 'message',\n changeStreamerStarted,\n );\n\n // Wait for the change-streamer to be ready to guarantee that a replica\n // file is present.\n await changeStreamerReady;\n\n if (litestream.backupURL) {\n // Start a backup replicator and corresponding litestream backup process.\n const {promise: backupReady, resolve} = resolver();\n const mode: ReplicaFileMode = 'backup';\n loadWorker(REPLICATOR_URL, 'supporting', mode, mode).once(\n // Wait for the Replicator's first message (i.e. \"ready\") before starting\n // litestream backup in order to avoid contending on the lock when the\n // replicator first prepares the db file.\n 'message',\n () => {\n processes.addSubprocess(\n startReplicaBackupProcess(lc, config),\n 'supporting',\n 'litestream',\n );\n resolve();\n },\n );\n await backupReady;\n }\n }\n\n if (numSyncers > 0) {\n const {promise: reaperReady, resolve: reaperStarted} = resolver();\n loadWorker(REAPER_URL, 'supporting').once('message', reaperStarted);\n // Before starting the view-syncers, ensure that the reaper has started\n // up, indicating that any CVR db migrations have been performed.\n await reaperReady;\n }\n\n // Only run the shadow-sync canary on the replication-manager (or in\n // single-node mode, where it also owns upstream). Running on every\n // view-syncer would hammer the upstream with N redundant canaries.\n if (config.shadowSync.enabled && runChangeStreamer) {\n const {promise: shadowReady, resolve: shadowStarted} = resolver();\n loadWorker(SHADOW_SYNCER_URL, 'supporting').once('message', shadowStarted);\n await shadowReady;\n }\n\n const syncers: Worker[] = [];\n if (numSyncers) {\n const mode: ReplicaFileMode =\n runChangeStreamer && litestream.backupURL ? 'serving-copy' : 'serving';\n const {promise: replicaReady, resolve} = resolver();\n const replicator = loadWorker(\n REPLICATOR_URL,\n 'supporting',\n mode,\n mode,\n ).once('message', () => {\n subscribeTo(lc, replicator);\n resolve();\n });\n await replicaReady;\n\n const notifier = createNotifierFrom(lc, replicator);\n for (let i = 0; i < numSyncers; i++) {\n syncers.push(loadWorker(SYNCER_URL, 'user-facing', i, mode, String(i)));\n }\n syncers.forEach(syncer => handleSubscriptionsFrom(lc, syncer, notifier));\n }\n let mutator: Worker | undefined;\n if (clientConnectionBifurcated) {\n mutator = loadWorker(MUTATOR_URL, 'supporting', 'mutator');\n }\n\n lc.info?.('waiting for workers to be ready ...');\n const logWaiting = setInterval(\n () => lc.info?.(`still waiting for ${processes.initializing().join(', ')}`),\n 10_000,\n );\n await processes.allWorkersReady();\n clearInterval(logWaiting);\n lc.info?.(`all workers ready (${Date.now() - startMs} ms)`);\n\n parent.send(['ready', {ready: true}]);\n\n try {\n await runUntilKilled(\n lc,\n parent,\n new WorkerDispatcher(\n lc,\n taskID,\n parent,\n syncers,\n mutator,\n changeStreamer,\n ),\n );\n } catch (err) {\n processes.logErrorAndExit(err, 'dispatcher');\n }\n\n await processes.done();\n}\n\nif (!singleProcessMode()) {\n void exitAfter(() => runWorker(must(parentWorker), process.env));\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reaper.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/reaper.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAQ/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"reaper.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/reaper.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAQ/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
|
|
@@ -24,10 +24,7 @@ async function runWorker(parent, env, ...argv) {
|
|
|
24
24
|
startAnonymousTelemetry(lc, config);
|
|
25
25
|
const { cvr } = config;
|
|
26
26
|
const shard = getShardID(config);
|
|
27
|
-
const cvrDB = pgClient(lc, cvr.db, {
|
|
28
|
-
max: 1,
|
|
29
|
-
connection: { ["application_name"]: `zero-sync-cvr-purger` }
|
|
30
|
-
});
|
|
27
|
+
const cvrDB = pgClient(lc, cvr.db, `sync-cvr-purger`, { max: 1 });
|
|
31
28
|
await initViewSyncerSchema(lc, cvrDB, shard);
|
|
32
29
|
parent.send(["ready", { ready: true }]);
|
|
33
30
|
return runUntilKilled(lc, parent, new CVRPurger(lc, cvrDB, shard, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reaper.js","names":[],"sources":["../../../../../zero-cache/src/server/reaper.ts"],"sourcesContent":["import {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {ActiveUsersGauge} from '../services/view-syncer/active-users-gauge.ts';\nimport {CVRPurger} from '../services/view-syncer/cvr-purger.ts';\nimport {initViewSyncerSchema} from '../services/view-syncer/schema/init.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardID} from '../types/shards.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\nconst MS_PER_HOUR = 1000 * 60 * 60;\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n const config = getNormalizedZeroConfig({env, argv});\n\n startOtelAuto(createLogContext(config, 'reaper', 0, false), 'reaper', 0);\n const lc = createLogContext(config, 'reaper');\n initEventSink(lc, config);\n startAnonymousTelemetry(lc, config);\n\n const {cvr} = config;\n const shard = getShardID(config);\n const cvrDB = pgClient(lc, cvr.db, {\n max: 1,\n
|
|
1
|
+
{"version":3,"file":"reaper.js","names":[],"sources":["../../../../../zero-cache/src/server/reaper.ts"],"sourcesContent":["import {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {ActiveUsersGauge} from '../services/view-syncer/active-users-gauge.ts';\nimport {CVRPurger} from '../services/view-syncer/cvr-purger.ts';\nimport {initViewSyncerSchema} from '../services/view-syncer/schema/init.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardID} from '../types/shards.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\n\nconst MS_PER_HOUR = 1000 * 60 * 60;\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n const config = getNormalizedZeroConfig({env, argv});\n\n startOtelAuto(createLogContext(config, 'reaper', 0, false), 'reaper', 0);\n const lc = createLogContext(config, 'reaper');\n initEventSink(lc, config);\n startAnonymousTelemetry(lc, config);\n\n const {cvr} = config;\n const shard = getShardID(config);\n const cvrDB = pgClient(lc, cvr.db, `sync-cvr-purger`, {\n max: 1,\n });\n await initViewSyncerSchema(lc, cvrDB, shard);\n parent.send(['ready', {ready: true}]);\n\n return runUntilKilled(\n lc,\n parent,\n new CVRPurger(lc, cvrDB, shard, {\n inactivityThresholdMs:\n cvr.garbageCollectionInactivityThresholdHours * MS_PER_HOUR,\n initialBatchSize: cvr.garbageCollectionInitialBatchSize,\n initialIntervalMs: cvr.garbageCollectionInitialIntervalSeconds * 1000,\n }),\n // Periodically computes and exports active users gauge to anonymous telemetry\n new ActiveUsersGauge(lc, cvrDB, shard, {\n // Default 10minutes refresh; can be made configurable later if needed\n updateIntervalMs: 10 * 60 * 1000,\n }),\n );\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(() =>\n runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,IAAM,cAAc,MAAO,KAAK;AAEhC,eAA8B,UAC5B,QACA,KACA,GAAG,MACY;CACf,MAAM,SAAS,wBAAwB;EAAC;EAAK;EAAK,CAAC;AAEnD,eAAc,iBAAiB,QAAQ,UAAU,GAAG,MAAM,EAAE,UAAU,EAAE;CACxE,MAAM,KAAK,iBAAiB,QAAQ,SAAS;AAC7C,eAAc,IAAI,OAAO;AACzB,yBAAwB,IAAI,OAAO;CAEnC,MAAM,EAAC,QAAO;CACd,MAAM,QAAQ,WAAW,OAAO;CAChC,MAAM,QAAQ,SAAS,IAAI,IAAI,IAAI,mBAAmB,EACpD,KAAK,GACN,CAAC;AACF,OAAM,qBAAqB,IAAI,OAAO,MAAM;AAC5C,QAAO,KAAK,CAAC,SAAS,EAAC,OAAO,MAAK,CAAC,CAAC;AAErC,QAAO,eACL,IACA,QACA,IAAI,UAAU,IAAI,OAAO,OAAO;EAC9B,uBACE,IAAI,4CAA4C;EAClD,kBAAkB,IAAI;EACtB,mBAAmB,IAAI,0CAA0C;EAClE,CAAC,EAEF,IAAI,iBAAiB,IAAI,OAAO,OAAO,EAErC,kBAAkB,MAAU,KAC7B,CAAC,CACH;;AAIH,IAAI,CAAC,mBAAmB,CACjB,iBACH,UAAU,KAAK,aAAa,EAAE,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,EAAE,CAAC,CACrE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { must } from "../../../shared/src/must.js";
|
|
2
|
+
import { parentWorker, singleProcessMode } from "../types/processes.js";
|
|
3
|
+
import { getShardConfig } from "../types/shards.js";
|
|
4
|
+
import { getNormalizedZeroConfig } from "../config/zero-config.js";
|
|
5
|
+
import { exitAfter, runUntilKilled } from "../services/life-cycle.js";
|
|
6
|
+
import { createLogContext } from "./logging.js";
|
|
7
|
+
import { getServerContext } from "../config/server-context.js";
|
|
8
|
+
import { initEventSink } from "../observability/events.js";
|
|
9
|
+
import { startOtelAuto } from "./otel-start.js";
|
|
10
|
+
import { ShadowSyncService } from "../services/shadow-sync/shadow-sync-service.js";
|
|
11
|
+
//#region ../zero-cache/src/server/shadow-syncer.ts
|
|
12
|
+
var MS_PER_HOUR = 1e3 * 60 * 60;
|
|
13
|
+
function runWorker(parent, env, ...argv) {
|
|
14
|
+
const config = getNormalizedZeroConfig({
|
|
15
|
+
env,
|
|
16
|
+
argv
|
|
17
|
+
});
|
|
18
|
+
startOtelAuto(createLogContext(config, "shadow-syncer", 0, false), "shadow-syncer", 0);
|
|
19
|
+
const lc = createLogContext(config, "shadow-syncer");
|
|
20
|
+
initEventSink(lc, config);
|
|
21
|
+
const { shadowSync, upstream, initialSync } = config;
|
|
22
|
+
const service = new ShadowSyncService(lc, getShardConfig(config), upstream.db, getServerContext(config), {
|
|
23
|
+
intervalMs: shadowSync.intervalHours * MS_PER_HOUR,
|
|
24
|
+
sampleRate: shadowSync.sampleRate,
|
|
25
|
+
maxRowsPerTable: shadowSync.maxRowsPerTable,
|
|
26
|
+
textCopy: initialSync.textCopy
|
|
27
|
+
});
|
|
28
|
+
parent.send(["ready", { ready: true }]);
|
|
29
|
+
return runUntilKilled(lc, parent, service);
|
|
30
|
+
}
|
|
31
|
+
if (!singleProcessMode()) exitAfter(() => runWorker(must(parentWorker), process.env, ...process.argv.slice(2)));
|
|
32
|
+
//#endregion
|
|
33
|
+
export { runWorker as default };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=shadow-syncer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shadow-syncer.js","names":[],"sources":["../../../../../zero-cache/src/server/shadow-syncer.ts"],"sourcesContent":["import {must} from '../../../shared/src/must.ts';\nimport {getServerContext} from '../config/server-context.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {ShadowSyncService} from '../services/shadow-sync/shadow-sync-service.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\nconst MS_PER_HOUR = 1000 * 60 * 60;\n\nexport default function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...argv: string[]\n): Promise<void> {\n const config = getNormalizedZeroConfig({env, argv});\n\n startOtelAuto(\n createLogContext(config, 'shadow-syncer', 0, false),\n 'shadow-syncer',\n 0,\n );\n const lc = createLogContext(config, 'shadow-syncer');\n initEventSink(lc, config);\n\n const {shadowSync, upstream, initialSync} = config;\n const shard = getShardConfig(config);\n const service = new ShadowSyncService(\n lc,\n shard,\n upstream.db,\n getServerContext(config),\n {\n intervalMs: shadowSync.intervalHours * MS_PER_HOUR,\n sampleRate: shadowSync.sampleRate,\n maxRowsPerTable: shadowSync.maxRowsPerTable,\n textCopy: initialSync.textCopy,\n },\n );\n\n parent.send(['ready', {ready: true}]);\n\n return runUntilKilled(lc, parent, service);\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(() =>\n runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;;;AAeA,IAAM,cAAc,MAAO,KAAK;AAEhC,SAAwB,UACtB,QACA,KACA,GAAG,MACY;CACf,MAAM,SAAS,wBAAwB;EAAC;EAAK;EAAK,CAAC;AAEnD,eACE,iBAAiB,QAAQ,iBAAiB,GAAG,MAAM,EACnD,iBACA,EACD;CACD,MAAM,KAAK,iBAAiB,QAAQ,gBAAgB;AACpD,eAAc,IAAI,OAAO;CAEzB,MAAM,EAAC,YAAY,UAAU,gBAAe;CAE5C,MAAM,UAAU,IAAI,kBAClB,IAFY,eAAe,OAAO,EAIlC,SAAS,IACT,iBAAiB,OAAO,EACxB;EACE,YAAY,WAAW,gBAAgB;EACvC,YAAY,WAAW;EACvB,iBAAiB,WAAW;EAC5B,UAAU,YAAY;EACvB,CACF;AAED,QAAO,KAAK,CAAC,SAAS,EAAC,OAAO,MAAK,CAAC,CAAC;AAErC,QAAO,eAAe,IAAI,QAAQ,QAAQ;;AAI5C,IAAI,CAAC,mBAAmB,CACjB,iBACH,UAAU,KAAK,aAAa,EAAE,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,EAAE,CAAC,CACrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/syncer.ts"],"names":[],"mappings":"AA+BA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAgC/B,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"syncer.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/syncer.ts"],"names":[],"mappings":"AA+BA,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAgC/B,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,GAAG,IAAI,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CAsMf"}
|
|
@@ -59,14 +59,8 @@ function runWorker(parent, env, ...args) {
|
|
|
59
59
|
const { cvr, upstream, enableCrudMutations } = config;
|
|
60
60
|
const replicaFile = replicaFileName(config.replica.file, fileMode);
|
|
61
61
|
lc.debug?.(`running view-syncer on ${replicaFile}`);
|
|
62
|
-
const cvrDB = pgClient(lc, cvr.db, {
|
|
63
|
-
|
|
64
|
-
connection: { ["application_name"]: `zero-sync-worker-${pid}-cvr` }
|
|
65
|
-
});
|
|
66
|
-
const upstreamDB = enableCrudMutations ? pgClient(lc, upstream.db, {
|
|
67
|
-
max: must(upstream.maxConnsPerWorker, "upstream.maxConnsPerWorker must be set"),
|
|
68
|
-
connection: { ["application_name"]: `zero-sync-worker-${pid}-upstream` }
|
|
69
|
-
}) : void 0;
|
|
62
|
+
const cvrDB = pgClient(lc, cvr.db, `sync-worker-${pid}-cvr`, { max: must(cvr.maxConnsPerWorker, "cvr.maxConnsPerWorker must be set") });
|
|
63
|
+
const upstreamDB = enableCrudMutations ? pgClient(lc, upstream.db, `sync-worker-${pid}-upstream`, { max: must(upstream.maxConnsPerWorker, "upstream.maxConnsPerWorker must be set") }) : void 0;
|
|
70
64
|
const dbWarmup = Promise.allSettled([warmupConnections(lc, cvrDB, "cvr"), upstreamDB ? warmupConnections(lc, upstreamDB, "upstream") : promiseVoid]);
|
|
71
65
|
const tmpDir = config.storageDBTmpDir ?? tmpdir();
|
|
72
66
|
const operatorStorage = DatabaseStorage.create(lc, path.join(tmpDir, `sync-worker-${randomUUID()}`));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncer.js","names":[],"sources":["../../../../../zero-cache/src/server/syncer.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {tmpdir} from 'node:os';\nimport path from 'node:path';\nimport {pid} from 'node:process';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {DatabaseStorage} from '../../../zqlite/src/database-storage.ts';\nimport type {ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {CustomQueryTransformer} from '../custom-queries/transform-query.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {MutagenService} from '../services/mutagen/mutagen.ts';\nimport {PusherService} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {\n type ConnectionContextManager,\n ConnectionContextManagerImpl,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport type {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport {PipelineDriver} from '../services/view-syncer/pipeline-driver.ts';\nimport {Snapshotter} from '../services/view-syncer/snapshotter.ts';\nimport {ViewSyncerService} from '../services/view-syncer/view-syncer.ts';\nimport {ProtocolErrorWithLevel} from '../types/error-with-level.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardID} from '../types/shards.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {replicaFileModeSchema, replicaFileName} from '../workers/replicator.ts';\nimport {Syncer} from '../workers/syncer.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {InspectorDelegate} from './inspector-delegate.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {isPriorityOpRunning, runPriorityOp} from './priority-op.ts';\n\nfunction randomID() {\n return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n}\n\nfunction getCustomQueryConfig(\n config: Pick<NormalizedZeroConfig, 'query' | 'getQueries'>,\n) {\n const queryConfig = config.query?.url ? config.query : config.getQueries;\n\n if (!queryConfig?.url) {\n return undefined;\n }\n\n return {\n url: queryConfig.url,\n apiKey: queryConfig.apiKey,\n allowedClientHeaders: queryConfig.allowedClientHeaders,\n forwardCookies: queryConfig.forwardCookies ?? false,\n };\n}\n\nexport default function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n assert(args.length >= 2, `expected [fileMode, workerIndex, ...flags]`);\n const fileMode = v.parse(args[0], replicaFileModeSchema);\n const workerIndex = Number(args[1]);\n const config = getNormalizedZeroConfig({env, argv: args.slice(2)});\n\n startOtelAuto(\n createLogContext(config, 'syncer', workerIndex, false),\n 'syncer',\n workerIndex,\n );\n const lc = createLogContext(config, 'syncer', workerIndex);\n initEventSink(lc, config);\n\n const {cvr, upstream, enableCrudMutations} = config;\n\n const replicaFile = replicaFileName(config.replica.file, fileMode);\n lc.debug?.(`running view-syncer on ${replicaFile}`);\n\n const cvrDB = pgClient(lc, cvr.db, {\n max: must(cvr.maxConnsPerWorker, 'cvr.maxConnsPerWorker must be set'),\n connection: {['application_name']: `zero-sync-worker-${pid}-cvr`},\n });\n\n const upstreamDB = enableCrudMutations\n ? pgClient(lc, upstream.db, {\n max: must(\n upstream.maxConnsPerWorker,\n 'upstream.maxConnsPerWorker must be set',\n ),\n connection: {['application_name']: `zero-sync-worker-${pid}-upstream`},\n })\n : undefined;\n\n const dbWarmup = Promise.allSettled([\n warmupConnections(lc, cvrDB, 'cvr'),\n upstreamDB ? warmupConnections(lc, upstreamDB, 'upstream') : promiseVoid,\n ]);\n\n const tmpDir = config.storageDBTmpDir ?? tmpdir();\n const operatorStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `sync-worker-${randomUUID()}`),\n );\n const writeAuthzStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `mutagen-${randomUUID()}`),\n );\n\n const shard = getShardID(config);\n const customQueryConfig = getCustomQueryConfig(config);\n const pushConfig =\n config.push.url === undefined && config.mutate.url === undefined\n ? undefined\n : {\n ...config.push,\n ...config.mutate,\n url: must(\n config.push.url ?? config.mutate.url,\n 'No push or mutate URL configured',\n ),\n };\n\n /** @deprecated used in JWT validation */\n let validateLegacyJWT: ValidateLegacyJWT | undefined = undefined;\n\n const tokenOptions = tokenConfigOptions(config.auth ?? {});\n if (tokenOptions.length === 1) {\n validateLegacyJWT = async (token, {userID}) => {\n if (!userID) {\n throw new ProtocolErrorWithLevel(\n {\n kind: 'Unauthorized',\n message: 'UserID is required for JWT validation.',\n origin: 'zeroCache',\n },\n 'warn',\n );\n }\n\n const decoded = await verifyToken(config.auth, token, {\n subject: userID,\n ...(config.auth?.issuer && {issuer: config.auth.issuer}),\n ...(config.auth?.audience && {\n audience: config.auth.audience,\n }),\n });\n return {\n type: 'jwt',\n raw: token,\n decoded,\n };\n };\n }\n\n const viewSyncerFactory = (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => {\n const logger = lc\n .withContext('component', 'view-syncer')\n .withContext('clientGroupID', id)\n .withContext('instance', randomID());\n\n const customQueryTransformer =\n customQueryConfig && new CustomQueryTransformer(logger, shard);\n const contextManager = new ConnectionContextManagerImpl(\n logger,\n config.auth.revalidateIntervalSeconds,\n config.auth.retransformIntervalSeconds,\n customQueryConfig,\n pushConfig,\n validateLegacyJWT,\n );\n\n lc.debug?.(\n `creating view syncer. Query Planner Enabled: ${config.enableQueryPlanner}`,\n );\n\n const inspectorDelegate = new InspectorDelegate(customQueryTransformer);\n\n const priorityOpRunningYieldThresholdMs = Math.max(\n config.yieldThresholdMs / 4,\n 2,\n );\n const normalYieldThresholdMs = Math.max(config.yieldThresholdMs, 2);\n\n return new ViewSyncerService(\n config,\n logger,\n shard,\n config.taskID,\n id,\n cvrDB,\n new PipelineDriver(\n logger,\n config.log,\n new Snapshotter(logger, replicaFile, shard),\n shard,\n operatorStorage.createClientGroupStorage(id),\n id,\n inspectorDelegate,\n () =>\n isPriorityOpRunning()\n ? priorityOpRunningYieldThresholdMs\n : normalYieldThresholdMs,\n config.enableQueryPlanner,\n config,\n ),\n sub,\n drainCoordinator,\n config.log.slowHydrateThreshold,\n inspectorDelegate,\n contextManager,\n customQueryTransformer,\n runPriorityOp,\n );\n };\n\n const mutagenFactory = upstreamDB\n ? (id: string) =>\n new MutagenService(\n lc\n .withContext('component', 'mutagen')\n .withContext('clientGroupID', id),\n shard,\n id,\n upstreamDB,\n config,\n writeAuthzStorage,\n )\n : undefined;\n\n const pusherFactory =\n pushConfig === undefined\n ? undefined\n : (id: string, contextManager: ConnectionContextManager) =>\n new PusherService(\n config,\n lc.withContext('clientGroupID', id),\n id,\n contextManager,\n );\n\n const syncer = new Syncer(\n lc,\n config,\n viewSyncerFactory,\n mutagenFactory,\n pusherFactory,\n parent,\n validateLegacyJWT,\n );\n\n startAnonymousTelemetry(lc, config);\n\n void dbWarmup.then(() => parent.send(['ready', {ready: true}]));\n\n return runUntilKilled(lc, parent, syncer);\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(() =>\n runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAS,WAAW;AAClB,QAAO,QAAQ,GAAG,OAAO,iBAAiB,CAAC,SAAS,GAAG;;AAGzD,SAAS,qBACP,QACA;CACA,MAAM,cAAc,OAAO,OAAO,MAAM,OAAO,QAAQ,OAAO;AAE9D,KAAI,CAAC,aAAa,IAChB;AAGF,QAAO;EACL,KAAK,YAAY;EACjB,QAAQ,YAAY;EACpB,sBAAsB,YAAY;EAClC,gBAAgB,YAAY,kBAAkB;EAC/C;;AAGH,SAAwB,UACtB,QACA,KACA,GAAG,MACY;AACf,QAAO,KAAK,UAAU,GAAG,6CAA6C;CACtE,MAAM,WAAW,MAAQ,KAAK,IAAI,sBAAsB;CACxD,MAAM,cAAc,OAAO,KAAK,GAAG;CACnC,MAAM,SAAS,wBAAwB;EAAC;EAAK,MAAM,KAAK,MAAM,EAAE;EAAC,CAAC;AAElE,eACE,iBAAiB,QAAQ,UAAU,aAAa,MAAM,EACtD,UACA,YACD;CACD,MAAM,KAAK,iBAAiB,QAAQ,UAAU,YAAY;AAC1D,eAAc,IAAI,OAAO;CAEzB,MAAM,EAAC,KAAK,UAAU,wBAAuB;CAE7C,MAAM,cAAc,gBAAgB,OAAO,QAAQ,MAAM,SAAS;AAClE,IAAG,QAAQ,0BAA0B,cAAc;CAEnD,MAAM,QAAQ,SAAS,IAAI,IAAI,IAAI;EACjC,KAAK,KAAK,IAAI,mBAAmB,oCAAoC;EACrE,YAAY,GAAE,qBAAqB,oBAAoB,IAAI,OAAM;EAClE,CAAC;CAEF,MAAM,aAAa,sBACf,SAAS,IAAI,SAAS,IAAI;EACxB,KAAK,KACH,SAAS,mBACT,yCACD;EACD,YAAY,GAAE,qBAAqB,oBAAoB,IAAI,YAAW;EACvE,CAAC,GACF,KAAA;CAEJ,MAAM,WAAW,QAAQ,WAAW,CAClC,kBAAkB,IAAI,OAAO,MAAM,EACnC,aAAa,kBAAkB,IAAI,YAAY,WAAW,GAAG,YAC9D,CAAC;CAEF,MAAM,SAAS,OAAO,mBAAmB,QAAQ;CACjD,MAAM,kBAAkB,gBAAgB,OACtC,IACA,KAAK,KAAK,QAAQ,eAAe,YAAY,GAAG,CACjD;CACD,MAAM,oBAAoB,gBAAgB,OACxC,IACA,KAAK,KAAK,QAAQ,WAAW,YAAY,GAAG,CAC7C;CAED,MAAM,QAAQ,WAAW,OAAO;CAChC,MAAM,oBAAoB,qBAAqB,OAAO;CACtD,MAAM,aACJ,OAAO,KAAK,QAAQ,KAAA,KAAa,OAAO,OAAO,QAAQ,KAAA,IACnD,KAAA,IACA;EACE,GAAG,OAAO;EACV,GAAG,OAAO;EACV,KAAK,KACH,OAAO,KAAK,OAAO,OAAO,OAAO,KACjC,mCACD;EACF;;CAGP,IAAI,oBAAmD,KAAA;AAGvD,KADqB,mBAAmB,OAAO,QAAQ,EAAE,CAAC,CACzC,WAAW,EAC1B,qBAAoB,OAAO,OAAO,EAAC,aAAY;AAC7C,MAAI,CAAC,OACH,OAAM,IAAI,uBACR;GACE,MAAM;GACN,SAAS;GACT,QAAQ;GACT,EACD,OACD;AAUH,SAAO;GACL,MAAM;GACN,KAAK;GACL,SAVc,MAAM,YAAY,OAAO,MAAM,OAAO;IACpD,SAAS;IACT,GAAI,OAAO,MAAM,UAAU,EAAC,QAAQ,OAAO,KAAK,QAAO;IACvD,GAAI,OAAO,MAAM,YAAY,EAC3B,UAAU,OAAO,KAAK,UACvB;IACF,CAAC;GAKD;;CAIL,MAAM,qBACJ,IACA,KACA,qBACG;EACH,MAAM,SAAS,GACZ,YAAY,aAAa,cAAc,CACvC,YAAY,iBAAiB,GAAG,CAChC,YAAY,YAAY,UAAU,CAAC;EAEtC,MAAM,yBACJ,qBAAqB,IAAI,uBAAuB,QAAQ,MAAM;EAChE,MAAM,iBAAiB,IAAI,6BACzB,QACA,OAAO,KAAK,2BACZ,OAAO,KAAK,4BACZ,mBACA,YACA,kBACD;AAED,KAAG,QACD,gDAAgD,OAAO,qBACxD;EAED,MAAM,oBAAoB,IAAI,kBAAkB,uBAAuB;EAEvE,MAAM,oCAAoC,KAAK,IAC7C,OAAO,mBAAmB,GAC1B,EACD;EACD,MAAM,yBAAyB,KAAK,IAAI,OAAO,kBAAkB,EAAE;AAEnE,SAAO,IAAI,kBACT,QACA,QACA,OACA,OAAO,QACP,IACA,OACA,IAAI,eACF,QACA,OAAO,KACP,IAAI,YAAY,QAAQ,aAAa,MAAM,EAC3C,OACA,gBAAgB,yBAAyB,GAAG,EAC5C,IACA,yBAEE,qBAAqB,GACjB,oCACA,wBACN,OAAO,oBACP,OACD,EACD,KACA,kBACA,OAAO,IAAI,sBACX,mBACA,gBACA,wBACA,cACD;;CA4BH,MAAM,SAAS,IAAI,OACjB,IACA,QACA,mBA5BqB,cAClB,OACC,IAAI,eACF,GACG,YAAY,aAAa,UAAU,CACnC,YAAY,iBAAiB,GAAG,EACnC,OACA,IACA,YACA,QACA,kBACD,GACH,KAAA,GAGF,eAAe,KAAA,IACX,KAAA,KACC,IAAY,mBACX,IAAI,cACF,QACA,GAAG,YAAY,iBAAiB,GAAG,EACnC,IACA,eACD,EAQP,QACA,kBACD;AAED,yBAAwB,IAAI,OAAO;AAE9B,UAAS,WAAW,OAAO,KAAK,CAAC,SAAS,EAAC,OAAO,MAAK,CAAC,CAAC,CAAC;AAE/D,QAAO,eAAe,IAAI,QAAQ,OAAO;;AAI3C,IAAI,CAAC,mBAAmB,CACjB,iBACH,UAAU,KAAK,aAAa,EAAE,QAAQ,KAAK,GAAG,QAAQ,KAAK,MAAM,EAAE,CAAC,CACrE"}
|
|
1
|
+
{"version":3,"file":"syncer.js","names":[],"sources":["../../../../../zero-cache/src/server/syncer.ts"],"sourcesContent":["import {randomUUID} from 'node:crypto';\nimport {tmpdir} from 'node:os';\nimport path from 'node:path';\nimport {pid} from 'node:process';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {promiseVoid} from '../../../shared/src/resolved-promises.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport {DatabaseStorage} from '../../../zqlite/src/database-storage.ts';\nimport type {ValidateLegacyJWT} from '../auth/auth.ts';\nimport {tokenConfigOptions, verifyToken} from '../auth/jwt.ts';\nimport type {NormalizedZeroConfig} from '../config/normalize.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {CustomQueryTransformer} from '../custom-queries/transform-query.ts';\nimport {warmupConnections} from '../db/warmup.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {exitAfter, runUntilKilled} from '../services/life-cycle.ts';\nimport {MutagenService} from '../services/mutagen/mutagen.ts';\nimport {PusherService} from '../services/mutagen/pusher.ts';\nimport type {ReplicaState} from '../services/replicator/replicator.ts';\nimport {\n type ConnectionContextManager,\n ConnectionContextManagerImpl,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport type {DrainCoordinator} from '../services/view-syncer/drain-coordinator.ts';\nimport {PipelineDriver} from '../services/view-syncer/pipeline-driver.ts';\nimport {Snapshotter} from '../services/view-syncer/snapshotter.ts';\nimport {ViewSyncerService} from '../services/view-syncer/view-syncer.ts';\nimport {ProtocolErrorWithLevel} from '../types/error-with-level.ts';\nimport {pgClient} from '../types/pg.ts';\nimport {\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {getShardID} from '../types/shards.ts';\nimport type {Subscription} from '../types/subscription.ts';\nimport {replicaFileModeSchema, replicaFileName} from '../workers/replicator.ts';\nimport {Syncer} from '../workers/syncer.ts';\nimport {startAnonymousTelemetry} from './anonymous-otel-start.ts';\nimport {InspectorDelegate} from './inspector-delegate.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {isPriorityOpRunning, runPriorityOp} from './priority-op.ts';\n\nfunction randomID() {\n return randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n}\n\nfunction getCustomQueryConfig(\n config: Pick<NormalizedZeroConfig, 'query' | 'getQueries'>,\n) {\n const queryConfig = config.query?.url ? config.query : config.getQueries;\n\n if (!queryConfig?.url) {\n return undefined;\n }\n\n return {\n url: queryConfig.url,\n apiKey: queryConfig.apiKey,\n allowedClientHeaders: queryConfig.allowedClientHeaders,\n forwardCookies: queryConfig.forwardCookies ?? false,\n };\n}\n\nexport default function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n ...args: string[]\n): Promise<void> {\n assert(args.length >= 2, `expected [fileMode, workerIndex, ...flags]`);\n const fileMode = v.parse(args[0], replicaFileModeSchema);\n const workerIndex = Number(args[1]);\n const config = getNormalizedZeroConfig({env, argv: args.slice(2)});\n\n startOtelAuto(\n createLogContext(config, 'syncer', workerIndex, false),\n 'syncer',\n workerIndex,\n );\n const lc = createLogContext(config, 'syncer', workerIndex);\n initEventSink(lc, config);\n\n const {cvr, upstream, enableCrudMutations} = config;\n\n const replicaFile = replicaFileName(config.replica.file, fileMode);\n lc.debug?.(`running view-syncer on ${replicaFile}`);\n\n const cvrDB = pgClient(lc, cvr.db, `sync-worker-${pid}-cvr`, {\n max: must(cvr.maxConnsPerWorker, 'cvr.maxConnsPerWorker must be set'),\n });\n\n const upstreamDB = enableCrudMutations\n ? pgClient(lc, upstream.db, `sync-worker-${pid}-upstream`, {\n max: must(\n upstream.maxConnsPerWorker,\n 'upstream.maxConnsPerWorker must be set',\n ),\n })\n : undefined;\n\n const dbWarmup = Promise.allSettled([\n warmupConnections(lc, cvrDB, 'cvr'),\n upstreamDB ? warmupConnections(lc, upstreamDB, 'upstream') : promiseVoid,\n ]);\n\n const tmpDir = config.storageDBTmpDir ?? tmpdir();\n const operatorStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `sync-worker-${randomUUID()}`),\n );\n const writeAuthzStorage = DatabaseStorage.create(\n lc,\n path.join(tmpDir, `mutagen-${randomUUID()}`),\n );\n\n const shard = getShardID(config);\n const customQueryConfig = getCustomQueryConfig(config);\n const pushConfig =\n config.push.url === undefined && config.mutate.url === undefined\n ? undefined\n : {\n ...config.push,\n ...config.mutate,\n url: must(\n config.push.url ?? config.mutate.url,\n 'No push or mutate URL configured',\n ),\n };\n\n /** @deprecated used in JWT validation */\n let validateLegacyJWT: ValidateLegacyJWT | undefined = undefined;\n\n const tokenOptions = tokenConfigOptions(config.auth ?? {});\n if (tokenOptions.length === 1) {\n validateLegacyJWT = async (token, {userID}) => {\n if (!userID) {\n throw new ProtocolErrorWithLevel(\n {\n kind: 'Unauthorized',\n message: 'UserID is required for JWT validation.',\n origin: 'zeroCache',\n },\n 'warn',\n );\n }\n\n const decoded = await verifyToken(config.auth, token, {\n subject: userID,\n ...(config.auth?.issuer && {issuer: config.auth.issuer}),\n ...(config.auth?.audience && {\n audience: config.auth.audience,\n }),\n });\n return {\n type: 'jwt',\n raw: token,\n decoded,\n };\n };\n }\n\n const viewSyncerFactory = (\n id: string,\n sub: Subscription<ReplicaState>,\n drainCoordinator: DrainCoordinator,\n ) => {\n const logger = lc\n .withContext('component', 'view-syncer')\n .withContext('clientGroupID', id)\n .withContext('instance', randomID());\n\n const customQueryTransformer =\n customQueryConfig && new CustomQueryTransformer(logger, shard);\n const contextManager = new ConnectionContextManagerImpl(\n logger,\n config.auth.revalidateIntervalSeconds,\n config.auth.retransformIntervalSeconds,\n customQueryConfig,\n pushConfig,\n validateLegacyJWT,\n );\n\n lc.debug?.(\n `creating view syncer. Query Planner Enabled: ${config.enableQueryPlanner}`,\n );\n\n const inspectorDelegate = new InspectorDelegate(customQueryTransformer);\n\n const priorityOpRunningYieldThresholdMs = Math.max(\n config.yieldThresholdMs / 4,\n 2,\n );\n const normalYieldThresholdMs = Math.max(config.yieldThresholdMs, 2);\n\n return new ViewSyncerService(\n config,\n logger,\n shard,\n config.taskID,\n id,\n cvrDB,\n new PipelineDriver(\n logger,\n config.log,\n new Snapshotter(logger, replicaFile, shard),\n shard,\n operatorStorage.createClientGroupStorage(id),\n id,\n inspectorDelegate,\n () =>\n isPriorityOpRunning()\n ? priorityOpRunningYieldThresholdMs\n : normalYieldThresholdMs,\n config.enableQueryPlanner,\n config,\n ),\n sub,\n drainCoordinator,\n config.log.slowHydrateThreshold,\n inspectorDelegate,\n contextManager,\n customQueryTransformer,\n runPriorityOp,\n );\n };\n\n const mutagenFactory = upstreamDB\n ? (id: string) =>\n new MutagenService(\n lc\n .withContext('component', 'mutagen')\n .withContext('clientGroupID', id),\n shard,\n id,\n upstreamDB,\n config,\n writeAuthzStorage,\n )\n : undefined;\n\n const pusherFactory =\n pushConfig === undefined\n ? undefined\n : (id: string, contextManager: ConnectionContextManager) =>\n new PusherService(\n config,\n lc.withContext('clientGroupID', id),\n id,\n contextManager,\n );\n\n const syncer = new Syncer(\n lc,\n config,\n viewSyncerFactory,\n mutagenFactory,\n pusherFactory,\n parent,\n validateLegacyJWT,\n );\n\n startAnonymousTelemetry(lc, config);\n\n void dbWarmup.then(() => parent.send(['ready', {ready: true}]));\n\n return runUntilKilled(lc, parent, syncer);\n}\n\n// fork()\nif (!singleProcessMode()) {\n void exitAfter(() =>\n runWorker(must(parentWorker), process.env, ...process.argv.slice(2)),\n );\n}\n"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ export declare const MAIN_URL: URL;
|
|
|
3
3
|
export declare const MUTATOR_URL: URL;
|
|
4
4
|
export declare const REAPER_URL: URL;
|
|
5
5
|
export declare const REPLICATOR_URL: URL;
|
|
6
|
+
export declare const SHADOW_SYNCER_URL: URL;
|
|
6
7
|
export declare const SYNCER_URL: URL;
|
|
7
8
|
export declare const WRITE_WORKER_URL: URL;
|
|
8
9
|
//# sourceMappingURL=worker-urls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-urls.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/worker-urls.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,mBAAmB,KAAkC,CAAC;AACnE,eAAO,MAAM,QAAQ,KAAuB,CAAC;AAC7C,eAAO,MAAM,WAAW,KAA0B,CAAC;AACnD,eAAO,MAAM,UAAU,KAAyB,CAAC;AACjD,eAAO,MAAM,cAAc,KAA6B,CAAC;AACzD,eAAO,MAAM,UAAU,KAAyB,CAAC;AACjD,eAAO,MAAM,gBAAgB,KAA+B,CAAC"}
|
|
1
|
+
{"version":3,"file":"worker-urls.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/worker-urls.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,mBAAmB,KAAkC,CAAC;AACnE,eAAO,MAAM,QAAQ,KAAuB,CAAC;AAC7C,eAAO,MAAM,WAAW,KAA0B,CAAC;AACnD,eAAO,MAAM,UAAU,KAAyB,CAAC;AACjD,eAAO,MAAM,cAAc,KAA6B,CAAC;AACzD,eAAO,MAAM,iBAAiB,KAAgC,CAAC;AAC/D,eAAO,MAAM,UAAU,KAAyB,CAAC;AACjD,eAAO,MAAM,gBAAgB,KAA+B,CAAC"}
|
|
@@ -10,9 +10,10 @@ var MAIN_URL = resolve("./main.ts");
|
|
|
10
10
|
resolve("./mutator.ts");
|
|
11
11
|
var REAPER_URL = resolve("./reaper.ts");
|
|
12
12
|
var REPLICATOR_URL = resolve("./replicator.ts");
|
|
13
|
+
var SHADOW_SYNCER_URL = resolve("./shadow-syncer.ts");
|
|
13
14
|
var SYNCER_URL = resolve("./syncer.ts");
|
|
14
15
|
var WRITE_WORKER_URL = resolve("./write-worker.ts");
|
|
15
16
|
//#endregion
|
|
16
|
-
export { CHANGE_STREAMER_URL, MAIN_URL, REAPER_URL, REPLICATOR_URL, SYNCER_URL, WRITE_WORKER_URL };
|
|
17
|
+
export { CHANGE_STREAMER_URL, MAIN_URL, REAPER_URL, REPLICATOR_URL, SHADOW_SYNCER_URL, SYNCER_URL, WRITE_WORKER_URL };
|
|
17
18
|
|
|
18
19
|
//# sourceMappingURL=worker-urls.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-urls.js","names":[],"sources":["../../../../../zero-cache/src/server/worker-urls.ts"],"sourcesContent":["// This module provides URLs for worker files.\n\nconst tsRe = /\\.ts$/;\n\nfunction resolve(path: string): URL {\n const {url} = import.meta;\n if (url.endsWith('.js')) {\n // When compiled, change .ts to .js\n path = path.replace(tsRe, '.js');\n }\n return new URL(path, url);\n}\n\n// These URLs are part of the build process. See ../../zero/tool/build.ts\n// All these urls must be relative to this file and be located in the same directory.\n\nexport const CHANGE_STREAMER_URL = resolve('./change-streamer.ts');\nexport const MAIN_URL = resolve('./main.ts');\nexport const MUTATOR_URL = resolve('./mutator.ts');\nexport const REAPER_URL = resolve('./reaper.ts');\nexport const REPLICATOR_URL = resolve('./replicator.ts');\nexport const SYNCER_URL = resolve('./syncer.ts');\nexport const WRITE_WORKER_URL = resolve('./write-worker.ts');\n"],"mappings":";AAEA,IAAM,OAAO;AAEb,SAAS,QAAQ,MAAmB;CAClC,MAAM,EAAC,QAAO,OAAO;AACrB,KAAI,IAAI,SAAS,MAAM,CAErB,QAAO,KAAK,QAAQ,MAAM,MAAM;AAElC,QAAO,IAAI,IAAI,MAAM,IAAI;;AAM3B,IAAa,sBAAsB,QAAQ,uBAAuB;AAClE,IAAa,WAAW,QAAQ,YAAY;AACjB,QAAQ,eAAe;AAClD,IAAa,aAAa,QAAQ,cAAc;AAChD,IAAa,iBAAiB,QAAQ,kBAAkB;AACxD,IAAa,aAAa,QAAQ,cAAc;AAChD,IAAa,mBAAmB,QAAQ,oBAAoB"}
|
|
1
|
+
{"version":3,"file":"worker-urls.js","names":[],"sources":["../../../../../zero-cache/src/server/worker-urls.ts"],"sourcesContent":["// This module provides URLs for worker files.\n\nconst tsRe = /\\.ts$/;\n\nfunction resolve(path: string): URL {\n const {url} = import.meta;\n if (url.endsWith('.js')) {\n // When compiled, change .ts to .js\n path = path.replace(tsRe, '.js');\n }\n return new URL(path, url);\n}\n\n// These URLs are part of the build process. See ../../zero/tool/build.ts\n// All these urls must be relative to this file and be located in the same directory.\n\nexport const CHANGE_STREAMER_URL = resolve('./change-streamer.ts');\nexport const MAIN_URL = resolve('./main.ts');\nexport const MUTATOR_URL = resolve('./mutator.ts');\nexport const REAPER_URL = resolve('./reaper.ts');\nexport const REPLICATOR_URL = resolve('./replicator.ts');\nexport const SHADOW_SYNCER_URL = resolve('./shadow-syncer.ts');\nexport const SYNCER_URL = resolve('./syncer.ts');\nexport const WRITE_WORKER_URL = resolve('./write-worker.ts');\n"],"mappings":";AAEA,IAAM,OAAO;AAEb,SAAS,QAAQ,MAAmB;CAClC,MAAM,EAAC,QAAO,OAAO;AACrB,KAAI,IAAI,SAAS,MAAM,CAErB,QAAO,KAAK,QAAQ,MAAM,MAAM;AAElC,QAAO,IAAI,IAAI,MAAM,IAAI;;AAM3B,IAAa,sBAAsB,QAAQ,uBAAuB;AAClE,IAAa,WAAW,QAAQ,YAAY;AACjB,QAAQ,eAAe;AAClD,IAAa,aAAa,QAAQ,cAAc;AAChD,IAAa,iBAAiB,QAAQ,kBAAkB;AACxD,IAAa,oBAAoB,QAAQ,qBAAqB;AAC9D,IAAa,aAAa,QAAQ,cAAc;AAChD,IAAa,mBAAmB,QAAQ,oBAAoB"}
|
|
@@ -6,7 +6,14 @@ type StreamOptions = {
|
|
|
6
6
|
* The number of bytes at which to flush a batch of rows in a
|
|
7
7
|
* backfill message. Defaults to Node's getDefaultHighWatermark().
|
|
8
8
|
*/
|
|
9
|
-
flushThresholdBytes?: number;
|
|
9
|
+
flushThresholdBytes?: number | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Use text-format COPY instead of binary COPY.
|
|
12
|
+
* Binary is faster and handles all types (unknown types are cast to
|
|
13
|
+
* `::text` in the SELECT). This flag exists as an escape hatch to
|
|
14
|
+
* revert to the old code path if needed.
|
|
15
|
+
*/
|
|
16
|
+
textCopy?: boolean | undefined;
|
|
10
17
|
};
|
|
11
18
|
/**
|
|
12
19
|
* Streams a series of `backfill` messages (ending with `backfill-complete`)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backfill-stream.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/backfill-stream.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"backfill-stream.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/backfill-stream.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAkBjD,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EAGf,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAahC,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,mBAAmB,CAAC;AAI/C,KAAK,aAAa,GAAG;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAEzC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC,CAAC;AAYF;;;;;GAKG;AACH,wBAAuB,cAAc,CACnC,EAAE,EAAE,UAAU,EACd,WAAW,EAAE,MAAM,EACnB,EAAC,IAAI,EAAE,YAAY,EAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC,EAC5D,EAAE,EAAE,eAAe,EACnB,IAAI,GAAE,aAAkB,GACvB,cAAc,CAAC,eAAe,GAAG,iBAAiB,CAAC,CA8FrD"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assert } from "../../../../../shared/src/asserts.js";
|
|
1
2
|
import { parse } from "../../../../../shared/src/valita.js";
|
|
2
3
|
import { equals } from "../../../../../shared/src/set-utils.js";
|
|
3
4
|
import { pgClient } from "../../../types/pg.js";
|
|
@@ -5,15 +6,17 @@ import { toStateVersionString } from "./lsn.js";
|
|
|
5
6
|
import { READONLY } from "../../../db/mode-enum.js";
|
|
6
7
|
import { getPublicationInfo } from "./schema/published.js";
|
|
7
8
|
import { SchemaIncompatibilityError } from "../common/backfill-manager.js";
|
|
9
|
+
import { BinaryCopyParser, hasBinaryDecoder, makeBinaryDecoder, textCastDecoder } from "../../../db/pg-copy-binary.js";
|
|
8
10
|
import { TsvParser } from "../../../db/pg-copy.js";
|
|
9
11
|
import { getTypeParsers } from "../../../db/pg-type-parser.js";
|
|
10
12
|
import { TransactionPool, importSnapshot } from "../../../db/transaction-pool.js";
|
|
11
13
|
import { columnMetadataSchema, tableMetadataSchema } from "./backfill-metadata.js";
|
|
12
|
-
import { createReplicationSlot, makeDownloadStatements } from "./initial-sync.js";
|
|
14
|
+
import { createReplicationSlot, makeBinarySelectExprs, makeDownloadStatements } from "./initial-sync.js";
|
|
13
15
|
import postgres from "postgres";
|
|
14
16
|
import { PG_UNDEFINED_COLUMN, PG_UNDEFINED_TABLE } from "@drdgvhbh/postgres-error-codes";
|
|
15
17
|
//#region ../zero-cache/src/services/change-source/pg/backfill-stream.ts
|
|
16
18
|
var POSTGRES_COPY_CHUNK_SIZE = 64 * 1024;
|
|
19
|
+
var SAMPLE_OR_LIMIT_RE = /\sTABLESAMPLE\s+BERNOULLI\b|\sLIMIT\s+\d/i;
|
|
17
20
|
/**
|
|
18
21
|
* Streams a series of `backfill` messages (ending with `backfill-complete`)
|
|
19
22
|
* at a set watermark (i.e. LSN). The data is retrieved via a COPY stream
|
|
@@ -22,20 +25,30 @@ var POSTGRES_COPY_CHUNK_SIZE = 64 * 1024;
|
|
|
22
25
|
*/
|
|
23
26
|
async function* streamBackfill(lc, upstreamURI, { slot, publications }, bf, opts = {}) {
|
|
24
27
|
lc = lc.withContext("component", "backfill").withContext("table", bf.table.name);
|
|
25
|
-
const { flushThresholdBytes = POSTGRES_COPY_CHUNK_SIZE } = opts;
|
|
26
|
-
const db = pgClient(lc, upstreamURI, {
|
|
27
|
-
connection: { ["application_name"]: "backfill-stream" },
|
|
28
|
-
["max_lifetime"]: 7200
|
|
29
|
-
});
|
|
28
|
+
const { flushThresholdBytes = POSTGRES_COPY_CHUNK_SIZE, textCopy = false } = opts;
|
|
29
|
+
const db = pgClient(lc, upstreamURI, "backfill-stream", { ["max_lifetime"]: 7200 });
|
|
30
30
|
let tx;
|
|
31
31
|
let watermark;
|
|
32
32
|
try {
|
|
33
33
|
({tx, watermark} = await createSnapshotTransaction(lc, upstreamURI, db, slot));
|
|
34
34
|
const { tableSpec, backfill } = await validateSchema(tx, publications, bf, watermark);
|
|
35
|
-
const types = await getTypeParsers(db, { returnJsonAsString: true });
|
|
36
35
|
const { relation, columns } = backfill;
|
|
37
36
|
const cols = [...relation.rowKey.columns, ...columns];
|
|
38
|
-
|
|
37
|
+
const stmts = makeDownloadStatements(tableSpec, cols);
|
|
38
|
+
if (textCopy) {
|
|
39
|
+
const types = await getTypeParsers(db, { returnJsonAsString: true });
|
|
40
|
+
yield* stream(lc, tx, backfill, stmts, `COPY (${stmts.select}) TO STDOUT`, new TsvParser(), cols.map((col) => {
|
|
41
|
+
const parser = types.getTypeParser(tableSpec.columns[col].typeOID);
|
|
42
|
+
return (text) => parser(text);
|
|
43
|
+
}), flushThresholdBytes);
|
|
44
|
+
} else {
|
|
45
|
+
const binaryStmts = makeDownloadStatements(tableSpec, cols, void 0, void 0, makeBinarySelectExprs(tableSpec, cols));
|
|
46
|
+
yield* stream(lc, tx, backfill, stmts, `COPY (${binaryStmts.select}) TO STDOUT WITH (FORMAT binary)`, new BinaryCopyParser(), cols.map((col) => {
|
|
47
|
+
const spec = tableSpec.columns[col];
|
|
48
|
+
const decoder = hasBinaryDecoder(spec) ? makeBinaryDecoder(spec) : textCastDecoder;
|
|
49
|
+
return (buf) => decoder(buf);
|
|
50
|
+
}), flushThresholdBytes);
|
|
51
|
+
}
|
|
39
52
|
} catch (e) {
|
|
40
53
|
if (e instanceof postgres.PostgresError && (e.code === PG_UNDEFINED_TABLE || e.code === PG_UNDEFINED_COLUMN)) throw new SchemaIncompatibilityError(bf, String(e), { cause: e });
|
|
41
54
|
throw e;
|
|
@@ -44,7 +57,8 @@ async function* streamBackfill(lc, upstreamURI, { slot, publications }, bf, opts
|
|
|
44
57
|
db.end().catch((e) => lc.warn?.(`error closing backfill connection`, e));
|
|
45
58
|
}
|
|
46
59
|
}
|
|
47
|
-
async function* stream(lc, tx, backfill, {
|
|
60
|
+
async function* stream(lc, tx, backfill, { getTotalRows, getTotalBytes }, copyCommand, parser, decoders, flushThresholdBytes) {
|
|
61
|
+
assert(!SAMPLE_OR_LIMIT_RE.test(copyCommand), `backfill COPY must not sample or limit: ${copyCommand}`);
|
|
48
62
|
const start = performance.now();
|
|
49
63
|
const [rows, bytes] = await tx.processReadTask((sql) => Promise.all([sql.unsafe(getTotalRows), sql.unsafe(getTotalBytes)]));
|
|
50
64
|
const status = {
|
|
@@ -53,9 +67,8 @@ async function* stream(lc, tx, backfill, { select, getTotalRows, getTotalBytes }
|
|
|
53
67
|
totalBytes: Number(bytes[0].totalBytes)
|
|
54
68
|
};
|
|
55
69
|
let elapsed = (performance.now() - start).toFixed(3);
|
|
56
|
-
lc.info?.(`Computed total rows and bytes for: ${
|
|
57
|
-
const copyStream = await tx.processReadTask((sql) => sql.unsafe(
|
|
58
|
-
const tsvParser = new TsvParser();
|
|
70
|
+
lc.info?.(`Computed total rows and bytes for: ${copyCommand} (${elapsed} ms)`, { status });
|
|
71
|
+
const copyStream = await tx.processReadTask((sql) => sql.unsafe(copyCommand).readable());
|
|
59
72
|
let totalBytes = 0;
|
|
60
73
|
let totalMsgs = 0;
|
|
61
74
|
let rowValues = [];
|
|
@@ -63,16 +76,16 @@ async function* stream(lc, tx, backfill, { select, getTotalRows, getTotalBytes }
|
|
|
63
76
|
const logFlushed = () => {
|
|
64
77
|
lc.debug?.(`Flushed ${rowValues.length} rows, ${bufferedBytes} bytes (total: rows=${status.rows}, msgs=${totalMsgs}, bytes=${totalBytes})`);
|
|
65
78
|
};
|
|
66
|
-
let row = Array.from({ length:
|
|
79
|
+
let row = Array.from({ length: decoders.length });
|
|
67
80
|
let col = 0;
|
|
68
81
|
for await (const data of copyStream) {
|
|
69
82
|
const chunk = data;
|
|
70
|
-
for (const
|
|
71
|
-
row[col] =
|
|
72
|
-
if (++col ===
|
|
83
|
+
for (const field of parser.parse(chunk)) {
|
|
84
|
+
row[col] = field === null ? null : decoders[col](field);
|
|
85
|
+
if (++col === decoders.length) {
|
|
73
86
|
rowValues.push(row);
|
|
74
87
|
status.rows++;
|
|
75
|
-
row = Array.from({ length:
|
|
88
|
+
row = Array.from({ length: decoders.length });
|
|
76
89
|
col = 0;
|
|
77
90
|
}
|
|
78
91
|
}
|
|
@@ -119,7 +132,7 @@ async function* stream(lc, tx, backfill, { select, getTotalRows, getTotalBytes }
|
|
|
119
132
|
* LSN.)
|
|
120
133
|
*/
|
|
121
134
|
async function createSnapshotTransaction(lc, upstreamURI, db, slotNamePrefix) {
|
|
122
|
-
const replicationSession = pgClient(lc, upstreamURI, {
|
|
135
|
+
const replicationSession = pgClient(lc, upstreamURI, "backfill-replication-session", {
|
|
123
136
|
["fetch_types"]: false,
|
|
124
137
|
connection: { replication: "database" }
|
|
125
138
|
});
|