@rocicorp/zero 0.25.9 → 0.25.10-canary.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- const version = "0.25.9";
1
+ const version = "0.25.10-canary.4";
2
2
  const packageJson = {
3
3
  version
4
4
  };
@@ -17,6 +17,7 @@ export type ChangeLogEntry = {
17
17
  export type ReplicationState = {
18
18
  lastWatermark: string;
19
19
  owner: string | null;
20
+ ownerAddress: string | null;
20
21
  };
21
22
  export declare function createReplicationStateTable(shard: ShardID): string;
22
23
  export declare function discoverChangeStreamerAddress(shard: ShardID, sql: PostgresDB): Promise<string | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-streamer/schema/tables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAErC,OAAO,EAAC,UAAU,EAAC,MAAM,0CAA0C,CAAC;AAEpE,OAAO,EAA0B,KAAK,UAAU,EAAC,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,8CAA8C,CAAC;AACzE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,8CAA8C,CAAC;AAOpF,eAAO,MAAM,SAAS,QAAQ,CAAC;AAM/B,MAAM,MAAM,cAAc,GAAG;IAG3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAiBF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,UASzD;AAED,wBAAsB,6BAA6B,CACjD,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC,CAAC;AAsBF,wBAAsB,cAAc,CAClC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,QAAQ,CAAC,cAAc,EAC3B,KAAK,EAAE,OAAO,iBAIf;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,iBAKtE;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,iBAAiB,EAAE,iBAAiB,EACpC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,OAAO,iBAyEnB;AAED,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,QAAQ,CAAC,IAAI,qBAAqB;CACnC"}
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-streamer/schema/tables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAErC,OAAO,EAAC,UAAU,EAAC,MAAM,0CAA0C,CAAC;AAEpE,OAAO,EAA0B,KAAK,UAAU,EAAC,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAY,KAAK,OAAO,EAAC,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,8CAA8C,CAAC;AACzE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,8CAA8C,CAAC;AAOpF,eAAO,MAAM,SAAS,QAAQ,CAAC;AAM/B,MAAM,MAAM,cAAc,GAAG;IAG3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAiBF;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,UASzD;AAED,wBAAsB,6BAA6B,CACjD,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC,CAAC;AAsBF,wBAAsB,cAAc,CAClC,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,QAAQ,CAAC,cAAc,EAC3B,KAAK,EAAE,OAAO,iBAIf;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,iBAKtE;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,UAAU,EACd,iBAAiB,EAAE,iBAAiB,EACpC,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,OAAO,iBAiFnB;AAED,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,QAAQ,CAAC,IAAI,qBAAqB;CACnC"}
@@ -75,7 +75,9 @@ async function ensureReplicationConfig(lc, db, subscriptionState, shard, autoRes
75
75
  const { publications, replicaVersion, watermark } = subscriptionState;
76
76
  const replicaConfig = { publications, replicaVersion };
77
77
  const replicationState = {
78
- lastWatermark: replicaVersion
78
+ lastWatermark: replicaVersion,
79
+ owner: null,
80
+ ownerAddress: null
79
81
  };
80
82
  const schema2 = cdcSchema(shard);
81
83
  await db.begin(async (sql) => {
@@ -97,16 +99,15 @@ async function ensureReplicationConfig(lc, db, subscriptionState, shard, autoRes
97
99
  );
98
100
  stmts.push(
99
101
  sql`TRUNCATE TABLE ${sql(schema2)}."changeLog"`,
100
- sql`TRUNCATE TABLE ${sql(schema2)}."replicationConfig"`,
101
- sql`TRUNCATE TABLE ${sql(schema2)}."replicationState"`
102
+ sql`TRUNCATE TABLE ${sql(schema2)}."replicationConfig"`
102
103
  );
103
104
  }
104
105
  }
105
106
  if (results.length === 0 || stmts.length > 0) {
106
107
  stmts.push(
107
108
  sql`INSERT INTO ${sql(schema2)}."replicationConfig" ${sql(replicaConfig)}`,
108
- sql`INSERT INTO ${sql(schema2)}."replicationState"
109
- ${sql(replicationState)}`
109
+ sql`INSERT INTO ${sql(schema2)}."replicationState" ${sql(replicationState)}
110
+ ON CONFLICT (lock) DO UPDATE SET ${sql(replicationState)}`
110
111
  );
111
112
  return Promise.all(stmts);
112
113
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tables.js","sources":["../../../../../../../zero-cache/src/services/change-streamer/schema/tables.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {ident} from 'pg-format';\nimport type postgres from 'postgres';\nimport {type PendingQuery, type Row} from 'postgres';\nimport {AbortError} from '../../../../../shared/src/abort-error.ts';\nimport {equals} from '../../../../../shared/src/set-utils.ts';\nimport {disableStatementTimeout, type PostgresDB} from '../../../types/pg.ts';\nimport {cdcSchema, type ShardID} from '../../../types/shards.ts';\nimport type {Change} from '../../change-source/protocol/current/data.ts';\nimport type {SubscriptionState} from '../../replicator/schema/replication-state.ts';\n\n// For readability in the sql statements.\nfunction schema(shard: ShardID) {\n return ident(cdcSchema(shard));\n}\n\nexport const PG_SCHEMA = 'cdc';\n\nfunction createSchema(shard: ShardID) {\n return /*sql*/ `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`;\n}\n\nexport type ChangeLogEntry = {\n // A strictly monotonically increasing, lexicographically sortable\n // value that uniquely identifies a position in the change stream.\n watermark: string;\n change: Change;\n};\n\nfunction createChangeLogTable(shard: ShardID) {\n // Note: The \"change\" column used to be JSONB, but that was problematic in that\n // it does not handle the NULL unicode character.\n // https://vladimir.varank.in/notes/2021/01/you-dont-insert-unicode-null-character-as-postgres-jsonb/\n return /*sql*/ `\n CREATE TABLE ${schema(shard)}.\"changeLog\" (\n watermark TEXT,\n pos INT8,\n change JSON NOT NULL,\n precommit TEXT, -- Only exists on commit entries. Purely for debugging.\n PRIMARY KEY (watermark, pos)\n );\n`;\n}\n\n/**\n * Tracks the watermark from which to resume the change stream and the\n * current owner (task ID) acting as the single writer to the changeLog.\n */\nexport type ReplicationState = {\n lastWatermark: string;\n owner: string | null;\n};\n\nexport function createReplicationStateTable(shard: ShardID) {\n return /*sql*/ `\n CREATE TABLE ${schema(shard)}.\"replicationState\" (\n \"lastWatermark\" TEXT NOT NULL,\n \"owner\" TEXT,\n \"ownerAddress\" TEXT,\n \"lock\" INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n`;\n}\n\nexport async function discoverChangeStreamerAddress(\n shard: ShardID,\n sql: PostgresDB,\n): Promise<string | null> {\n const result = await sql<{ownerAddress: string | null}[]> /*sql*/ `\n SELECT \"ownerAddress\" FROM ${sql(cdcSchema(shard))}.\"replicationState\"`;\n return result[0].ownerAddress;\n}\n\n/**\n * This mirrors the analogously named table in the SQLite replica\n * (`services/replicator/schema/replication-state.ts`), and is used\n * to detect when the replica has been reset and is no longer compatible\n * with the current ChangeLog.\n */\nexport type ReplicationConfig = {\n replicaVersion: string;\n publications: readonly string[];\n};\n\nfunction createReplicationConfigTable(shard: ShardID) {\n return /*sql*/ `\n CREATE TABLE ${schema(shard)}.\"replicationConfig\" (\n \"replicaVersion\" TEXT NOT NULL,\n \"publications\" TEXT[] NOT NULL,\n \"resetRequired\" BOOL,\n \"lock\" INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n`;\n}\n\nfunction createTables(shard: ShardID) {\n return (\n createSchema(shard) +\n createChangeLogTable(shard) +\n createReplicationStateTable(shard) +\n createReplicationConfigTable(shard)\n );\n}\n\nexport async function setupCDCTables(\n lc: LogContext,\n db: postgres.TransactionSql,\n shard: ShardID,\n) {\n lc.info?.(`Setting up CDC tables`);\n await db.unsafe(createTables(shard));\n}\n\nexport async function markResetRequired(sql: PostgresDB, shard: ShardID) {\n const schema = cdcSchema(shard);\n await sql`\n UPDATE ${sql(schema)}.\"replicationConfig\"\n SET \"resetRequired\" = true`;\n}\n\nexport async function ensureReplicationConfig(\n lc: LogContext,\n db: PostgresDB,\n subscriptionState: SubscriptionState,\n shard: ShardID,\n autoReset: boolean,\n) {\n const {publications, replicaVersion, watermark} = subscriptionState;\n const replicaConfig = {publications, replicaVersion};\n const replicationState: Partial<ReplicationState> = {\n lastWatermark: replicaVersion,\n };\n const schema = cdcSchema(shard);\n\n await db.begin(async sql => {\n disableStatementTimeout(sql);\n\n const stmts: PendingQuery<Row[]>[] = [];\n const results = await sql<\n {\n replicaVersion: string;\n publications: string[];\n resetRequired: boolean | null;\n }[]\n > /*sql*/ `\n SELECT \"replicaVersion\", \"publications\", \"resetRequired\" \n FROM ${sql(schema)}.\"replicationConfig\"`;\n\n if (results.length) {\n const {replicaVersion, publications} = results[0];\n if (\n replicaVersion !== replicaConfig.replicaVersion ||\n !equals(new Set(publications), new Set(replicaConfig.publications))\n ) {\n if (replicaConfig.replicaVersion !== watermark) {\n throw new AutoResetSignal(\n `Cannot reset change db@${replicaVersion} to ` +\n `service replica@${replicaConfig.replicaVersion} ` +\n `from watermark ${watermark}`,\n );\n }\n lc.info?.(\n `Data in cdc tables @${replicaVersion} is incompatible ` +\n `with replica @${replicaConfig.replicaVersion}. Clearing tables.`,\n );\n stmts.push(\n sql`TRUNCATE TABLE ${sql(schema)}.\"changeLog\"`,\n sql`TRUNCATE TABLE ${sql(schema)}.\"replicationConfig\"`,\n sql`TRUNCATE TABLE ${sql(schema)}.\"replicationState\"`,\n );\n }\n }\n // Initialize (or re-initialize TRUNCATED) tables\n if (results.length === 0 || stmts.length > 0) {\n stmts.push(\n sql`INSERT INTO ${sql(schema)}.\"replicationConfig\" ${sql(replicaConfig)}`,\n sql`INSERT INTO ${sql(schema)}.\"replicationState\" \n ${sql(replicationState)}`,\n );\n return Promise.all(stmts);\n }\n\n const {resetRequired} = results[0];\n if (resetRequired) {\n if (autoReset) {\n throw new AutoResetSignal('reset required by replication stream');\n }\n lc.error?.(\n '\\n\\n\\n' +\n 'Reset required but --auto-reset is not enabled.\\n' +\n 'This can happen for upstream databases that do not support event triggers.\\n' +\n 'To correct this, see https://zero.rocicorp.dev/docs/connecting-to-postgres#schema-changes' +\n '\\n\\n\\n',\n );\n }\n\n return [];\n });\n}\n\nexport class AutoResetSignal extends AbortError {\n readonly name = 'AutoResetSignal';\n}\n"],"names":["schema","replicaVersion","publications"],"mappings":"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"}
1
+ {"version":3,"file":"tables.js","sources":["../../../../../../../zero-cache/src/services/change-streamer/schema/tables.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {ident} from 'pg-format';\nimport type postgres from 'postgres';\nimport {type PendingQuery, type Row} from 'postgres';\nimport {AbortError} from '../../../../../shared/src/abort-error.ts';\nimport {equals} from '../../../../../shared/src/set-utils.ts';\nimport {disableStatementTimeout, type PostgresDB} from '../../../types/pg.ts';\nimport {cdcSchema, type ShardID} from '../../../types/shards.ts';\nimport type {Change} from '../../change-source/protocol/current/data.ts';\nimport type {SubscriptionState} from '../../replicator/schema/replication-state.ts';\n\n// For readability in the sql statements.\nfunction schema(shard: ShardID) {\n return ident(cdcSchema(shard));\n}\n\nexport const PG_SCHEMA = 'cdc';\n\nfunction createSchema(shard: ShardID) {\n return /*sql*/ `CREATE SCHEMA IF NOT EXISTS ${schema(shard)};`;\n}\n\nexport type ChangeLogEntry = {\n // A strictly monotonically increasing, lexicographically sortable\n // value that uniquely identifies a position in the change stream.\n watermark: string;\n change: Change;\n};\n\nfunction createChangeLogTable(shard: ShardID) {\n // Note: The \"change\" column used to be JSONB, but that was problematic in that\n // it does not handle the NULL unicode character.\n // https://vladimir.varank.in/notes/2021/01/you-dont-insert-unicode-null-character-as-postgres-jsonb/\n return /*sql*/ `\n CREATE TABLE ${schema(shard)}.\"changeLog\" (\n watermark TEXT,\n pos INT8,\n change JSON NOT NULL,\n precommit TEXT, -- Only exists on commit entries. Purely for debugging.\n PRIMARY KEY (watermark, pos)\n );\n`;\n}\n\n/**\n * Tracks the watermark from which to resume the change stream and the\n * current owner (task ID) acting as the single writer to the changeLog.\n */\nexport type ReplicationState = {\n lastWatermark: string;\n owner: string | null;\n ownerAddress: string | null;\n};\n\nexport function createReplicationStateTable(shard: ShardID) {\n return /*sql*/ `\n CREATE TABLE ${schema(shard)}.\"replicationState\" (\n \"lastWatermark\" TEXT NOT NULL,\n \"owner\" TEXT,\n \"ownerAddress\" TEXT,\n \"lock\" INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n`;\n}\n\nexport async function discoverChangeStreamerAddress(\n shard: ShardID,\n sql: PostgresDB,\n): Promise<string | null> {\n const result = await sql<{ownerAddress: string | null}[]> /*sql*/ `\n SELECT \"ownerAddress\" FROM ${sql(cdcSchema(shard))}.\"replicationState\"`;\n return result[0].ownerAddress;\n}\n\n/**\n * This mirrors the analogously named table in the SQLite replica\n * (`services/replicator/schema/replication-state.ts`), and is used\n * to detect when the replica has been reset and is no longer compatible\n * with the current ChangeLog.\n */\nexport type ReplicationConfig = {\n replicaVersion: string;\n publications: readonly string[];\n};\n\nfunction createReplicationConfigTable(shard: ShardID) {\n return /*sql*/ `\n CREATE TABLE ${schema(shard)}.\"replicationConfig\" (\n \"replicaVersion\" TEXT NOT NULL,\n \"publications\" TEXT[] NOT NULL,\n \"resetRequired\" BOOL,\n \"lock\" INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n`;\n}\n\nfunction createTables(shard: ShardID) {\n return (\n createSchema(shard) +\n createChangeLogTable(shard) +\n createReplicationStateTable(shard) +\n createReplicationConfigTable(shard)\n );\n}\n\nexport async function setupCDCTables(\n lc: LogContext,\n db: postgres.TransactionSql,\n shard: ShardID,\n) {\n lc.info?.(`Setting up CDC tables`);\n await db.unsafe(createTables(shard));\n}\n\nexport async function markResetRequired(sql: PostgresDB, shard: ShardID) {\n const schema = cdcSchema(shard);\n await sql`\n UPDATE ${sql(schema)}.\"replicationConfig\"\n SET \"resetRequired\" = true`;\n}\n\nexport async function ensureReplicationConfig(\n lc: LogContext,\n db: PostgresDB,\n subscriptionState: SubscriptionState,\n shard: ShardID,\n autoReset: boolean,\n) {\n const {publications, replicaVersion, watermark} = subscriptionState;\n const replicaConfig = {publications, replicaVersion};\n const replicationState: ReplicationState = {\n lastWatermark: replicaVersion,\n owner: null,\n ownerAddress: null,\n };\n const schema = cdcSchema(shard);\n\n await db.begin(async sql => {\n disableStatementTimeout(sql);\n\n const stmts: PendingQuery<Row[]>[] = [];\n const results = await sql<\n {\n replicaVersion: string;\n publications: string[];\n resetRequired: boolean | null;\n }[]\n > /*sql*/ `\n SELECT \"replicaVersion\", \"publications\", \"resetRequired\" \n FROM ${sql(schema)}.\"replicationConfig\"`;\n\n if (results.length) {\n const {replicaVersion, publications} = results[0];\n if (\n replicaVersion !== replicaConfig.replicaVersion ||\n !equals(new Set(publications), new Set(replicaConfig.publications))\n ) {\n if (replicaConfig.replicaVersion !== watermark) {\n throw new AutoResetSignal(\n `Cannot reset change db@${replicaVersion} to ` +\n `service replica@${replicaConfig.replicaVersion} ` +\n `from watermark ${watermark}`,\n );\n }\n lc.info?.(\n `Data in cdc tables @${replicaVersion} is incompatible ` +\n `with replica @${replicaConfig.replicaVersion}. Clearing tables.`,\n );\n // Note: The replicationState table is explicitly not TRUNCATE'd.\n // Any existing row must be overwritten by an UPDATE or\n // INSERT ... ON CONFLICT clause in order to correctly abort\n // any pending transaction by a concurrently running\n // change-streamer. Deleting the existing row and creating\n // a new one, on the other hand, may not properly trigger the\n // SERIALIZATION failure necessary to abort the pending tx.\n stmts.push(\n sql`TRUNCATE TABLE ${sql(schema)}.\"changeLog\"`,\n sql`TRUNCATE TABLE ${sql(schema)}.\"replicationConfig\"`,\n );\n }\n }\n // Initialize (or re-initialize TRUNCATED) tables\n if (results.length === 0 || stmts.length > 0) {\n stmts.push(\n sql`INSERT INTO ${sql(schema)}.\"replicationConfig\" ${sql(replicaConfig)}`,\n sql`INSERT INTO ${sql(schema)}.\"replicationState\" ${sql(replicationState)} \n ON CONFLICT (lock) DO UPDATE SET ${sql(replicationState)}`,\n );\n return Promise.all(stmts);\n }\n\n const {resetRequired} = results[0];\n if (resetRequired) {\n if (autoReset) {\n throw new AutoResetSignal('reset required by replication stream');\n }\n lc.error?.(\n '\\n\\n\\n' +\n 'Reset required but --auto-reset is not enabled.\\n' +\n 'This can happen for upstream databases that do not support event triggers.\\n' +\n 'To correct this, see https://zero.rocicorp.dev/docs/connecting-to-postgres#schema-changes' +\n '\\n\\n\\n',\n );\n }\n\n return [];\n });\n}\n\nexport class AutoResetSignal extends AbortError {\n readonly name = 'AutoResetSignal';\n}\n"],"names":["schema","replicaVersion","publications"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const version = "0.25.9";
1
+ const version = "0.25.10-canary.4";
2
2
  export {
3
3
  version
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"push-accumulated.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/push-accumulated.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAC,SAAS,EAAE,MAAM,EAAC,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,wBAAiB,sBAAsB,CACrC,iBAAiB,EAAE,MAAM,EAAE,EAC3B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,EACjB,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,EAChC,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,EAClE,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAChD,MAAM,CAAC,OAAO,CAAC,CA+JjB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAsFtE;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,YAAY,GACnB,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAoD5B;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC,EAC3D,iBAAiB,EAAE,MAAM,EAAE,QAO5B"}
1
+ {"version":3,"file":"push-accumulated.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/push-accumulated.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAC,SAAS,EAAE,MAAM,EAAC,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,wBAAiB,sBAAsB,CACrC,iBAAiB,EAAE,MAAM,EAAE,EAC3B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,EACjB,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,EAChC,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,EAClE,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAChD,MAAM,CAAC,OAAO,CAAC,CA+JjB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CA2GtE;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,YAAY,GACnB,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAoD5B;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC,EAC3D,iBAAiB,EAAE,MAAM,EAAE,QAO5B"}
@@ -160,6 +160,23 @@ function mergeRelationships(left, right) {
160
160
  }
161
161
  };
162
162
  }
163
+ case "child": {
164
+ assert(
165
+ right.type === "child",
166
+ () => `mergeRelationships: when left.type is child and types match, right.type must be child, got ${right.type}`
167
+ );
168
+ return {
169
+ type: "child",
170
+ node: {
171
+ row: left.node.row,
172
+ relationships: {
173
+ ...right.node.relationships,
174
+ ...left.node.relationships
175
+ }
176
+ },
177
+ child: left.child
178
+ };
179
+ }
163
180
  }
164
181
  }
165
182
  assert(left.type === "edit");
@@ -1 +1 @@
1
- {"version":3,"file":"push-accumulated.js","sources":["../../../../../zql/src/ivm/push-accumulated.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {emptyArray} from '../../../shared/src/sentinels.ts';\nimport type {Change} from './change.ts';\nimport type {Node} from './data.ts';\nimport type {InputBase, Output} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\n\n/**\n * # pushAccumulatedChanges\n *\n * Pushes the changes that were accumulated by\n * [fan-out, fan-in] or [ufo, ufi] sub-graphs.\n *\n * This function is called at the end of the sub-graph.\n *\n * The sub-graphs represents `OR`s.\n *\n * Changes that can enter the subgraphs:\n * 1. child (due to exist joins being above the sub-graph)\n * 2. add\n * 3. remove\n * 4. edit\n *\n * # Changes that can exit into `pushAccumulatedChanges`:\n *\n * ## Child\n * If a `child` change enters a sub-graph, it will flow to all branches.\n * Each branch will either:\n * - preserve the `child` change\n * - stop the `child` change (e.g., filter)\n * - convert it to an `add` or `remove` (e.g., exists filter)\n *\n * ## Add\n * If an `add` change enters a sub-graph, it will flow to all branches.\n * Each branch will either:\n * - preserve the `add` change\n * - hide the change (e.g., filter)\n *\n * ## Remove\n * If a `remove` change enters a sub-graph, it will flow to all branches.\n * Each branch will either:\n * - preserve the `remove` change\n * - hide the change (e.g., filter)\n *\n * ## Edit\n * If an `edit` change enters a sub-graph, it will flow to all branches.\n * Each branch will either:\n * - preserve the `edit` change\n * - convert it to an `add` (e.g., filter where old didn't match but new does)\n * - convert it to a `remove` (e.g., filter where old matched but new doesn't)\n *\n * This results in some invariants:\n * - an add coming in will only create adds coming out\n * - a remove coming in will only create removes coming out\n * - an edit coming in can create adds, removes, and edits coming out\n * - a child coming in can create adds, removes, and children coming out\n *\n * # Return of `pushAccumulatedChanges`\n *\n * This function will only push a single change.\n * Given the above invariants, how is this possible?\n *\n * An add that becomes many `adds` results in a single add\n * as the `add` is the same row across all adds. Branches do not change the row.\n *\n * A remove that becomes many `removes` results in a single remove\n * for the same reason.\n *\n * If a child enters and exits, it takes precedence over all other changes.\n * If a child enters and is converted only to add and remove it exits as an edit.\n * If a child enters and is converted to only add or only remove, it exits as that change.\n *\n * If an edit enters and is converted to add and remove it exits as an edit.\n * If an edit enters and is converted to only add or only remove, it exits as that change.\n * If an edit enters and exits as edits only, it exits as a single edit.\n */\nexport function* pushAccumulatedChanges(\n accumulatedPushes: Change[],\n output: Output,\n pusher: InputBase,\n fanOutChangeType: Change['type'],\n mergeRelationships: (existing: Change, incoming: Change) => Change,\n addEmptyRelationships: (change: Change) => Change,\n): Stream<'yield'> {\n if (accumulatedPushes.length === 0) {\n // It is possible for no forks to pass along the push.\n // E.g., if no filters match in any fork.\n return;\n }\n\n // collapse down to a single change per type\n const candidatesToPush = new Map<Change['type'], Change>();\n for (const change of accumulatedPushes) {\n if (fanOutChangeType === 'child' && change.type !== 'child') {\n assert(\n candidatesToPush.has(change.type) === false,\n () =>\n `Fan-in:child expected at most one ${change.type} when fan-out is of type child`,\n );\n }\n\n const existing = candidatesToPush.get(change.type);\n let mergedChange = change;\n if (existing) {\n // merge in relationships\n mergedChange = mergeRelationships(existing, change);\n }\n candidatesToPush.set(change.type, mergedChange);\n }\n\n accumulatedPushes.length = 0;\n\n const types = [...candidatesToPush.keys()];\n /**\n * Based on the received `fanOutChangeType` only certain output types are valid.\n *\n * - remove must result in all removes\n * - add must result in all adds\n * - edit must result in add or removes or edits\n * - child must result in a single add or single remove or many child changes\n * - Single add or remove because the relationship will be unique to one exist check within the fan-out,fan-in sub-graph\n * - Many child changes because other operators may preserve the child change\n */\n switch (fanOutChangeType) {\n case 'remove':\n assert(\n types.length === 1 && types[0] === 'remove',\n 'Fan-in:remove expected all removes',\n );\n yield* output.push(\n addEmptyRelationships(must(candidatesToPush.get('remove'))),\n pusher,\n );\n return;\n case 'add':\n assert(\n types.length === 1 && types[0] === 'add',\n 'Fan-in:add expected all adds',\n );\n yield* output.push(\n addEmptyRelationships(must(candidatesToPush.get('add'))),\n pusher,\n );\n return;\n case 'edit': {\n assert(\n types.every(\n type => type === 'add' || type === 'remove' || type === 'edit',\n ),\n 'Fan-in:edit expected all adds, removes, or edits',\n );\n const addChange = candidatesToPush.get('add');\n const removeChange = candidatesToPush.get('remove');\n let editChange = candidatesToPush.get('edit');\n\n // If an `edit` is present, it supersedes `add` and `remove`\n // as it semantically represents both.\n if (editChange) {\n if (addChange) {\n editChange = mergeRelationships(editChange, addChange);\n }\n if (removeChange) {\n editChange = mergeRelationships(editChange, removeChange);\n }\n yield* output.push(addEmptyRelationships(editChange), pusher);\n return;\n }\n\n // If `edit` didn't make it through but both `add` and `remove` did,\n // convert back to an edit.\n //\n // When can this happen?\n //\n // EDIT old: a=1, new: a=2\n // |\n // FanOut\n // / \\\n // a=1 a=2\n // | |\n // remove add\n // \\ /\n // FanIn\n //\n // The left filter converts the edit into a remove.\n // The right filter converts the edit into an add.\n if (addChange && removeChange) {\n yield* output.push(\n addEmptyRelationships({\n type: 'edit',\n node: addChange.node,\n oldNode: removeChange.node,\n } as const),\n pusher,\n );\n return;\n }\n\n yield* output.push(\n addEmptyRelationships(must(addChange ?? removeChange)),\n pusher,\n );\n return;\n }\n case 'child': {\n assert(\n types.every(\n type =>\n type === 'add' || // exists can change child to add or remove\n type === 'remove' || // exists can change child to add or remove\n type === 'child', // other operators may preserve the child change\n ),\n 'Fan-in:child expected all adds, removes, or children',\n );\n assert(\n types.length <= 2,\n 'Fan-in:child expected at most 2 types on a child change from fan-out',\n );\n\n // If any branch preserved the original child change, that takes precedence over all other changes.\n const childChange = candidatesToPush.get('child');\n if (childChange) {\n yield* output.push(childChange, pusher);\n return;\n }\n\n const addChange = candidatesToPush.get('add');\n const removeChange = candidatesToPush.get('remove');\n\n assert(\n addChange === undefined || removeChange === undefined,\n 'Fan-in:child expected either add or remove, not both',\n );\n\n yield* output.push(\n addEmptyRelationships(must(addChange ?? removeChange)),\n pusher,\n );\n return;\n }\n default:\n fanOutChangeType satisfies never;\n }\n}\n\n/**\n * Puts relationships from `right` into `left` if they don't already exist in `left`.\n */\nexport function mergeRelationships(left: Change, right: Change): Change {\n // change types will always match\n // unless we have an edit on the left\n // then the right could be edit, add, or remove\n if (left.type === right.type) {\n switch (left.type) {\n case 'add': {\n return {\n type: 'add',\n node: {\n row: left.node.row,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n };\n }\n case 'remove': {\n return {\n type: 'remove',\n node: {\n row: left.node.row,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n };\n }\n case 'edit': {\n assert(right.type === 'edit');\n // merge edits into a single edit\n return {\n type: 'edit',\n node: {\n row: left.node.row,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n oldNode: {\n row: left.oldNode.row,\n relationships: {\n ...right.oldNode.relationships,\n ...left.oldNode.relationships,\n },\n },\n };\n }\n }\n }\n\n // left is always an edit here\n assert(left.type === 'edit');\n switch (right.type) {\n case 'add': {\n return {\n type: 'edit',\n node: {\n ...left.node,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n oldNode: left.oldNode,\n };\n }\n case 'remove': {\n return {\n type: 'edit',\n node: left.node,\n oldNode: {\n ...left.oldNode,\n relationships: {\n ...right.node.relationships,\n ...left.oldNode.relationships,\n },\n },\n };\n }\n }\n\n unreachable();\n}\n\nexport function makeAddEmptyRelationships(\n schema: SourceSchema,\n): (change: Change) => Change {\n return (change: Change): Change => {\n if (Object.keys(schema.relationships).length === 0) {\n return change;\n }\n\n switch (change.type) {\n case 'add':\n case 'remove': {\n const ret = {\n ...change,\n node: {\n ...change.node,\n relationships: {\n ...change.node.relationships,\n },\n },\n };\n\n mergeEmpty(ret.node.relationships, Object.keys(schema.relationships));\n\n return ret;\n }\n case 'edit': {\n const ret = {\n ...change,\n node: {\n ...change.node,\n relationships: {\n ...change.node.relationships,\n },\n },\n oldNode: {\n ...change.oldNode,\n relationships: {\n ...change.oldNode.relationships,\n },\n },\n };\n\n mergeEmpty(ret.node.relationships, Object.keys(schema.relationships));\n mergeEmpty(\n ret.oldNode.relationships,\n Object.keys(schema.relationships),\n );\n\n return ret;\n }\n case 'child':\n return change; // children only have relationships along the path to the change\n }\n };\n}\n\n/**\n * For each relationship in `schema` that does not exist\n * in `relationships`, add it with an empty stream.\n *\n * This modifies the `relationships` object in place.\n */\nexport function mergeEmpty(\n relationships: Record<string, () => Stream<Node | 'yield'>>,\n relationshipNames: string[],\n) {\n for (const relName of relationshipNames) {\n if (relationships[relName] === undefined) {\n relationships[relName] = () => emptyArray;\n }\n }\n}\n"],"names":["mergeRelationships"],"mappings":"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"}
1
+ {"version":3,"file":"push-accumulated.js","sources":["../../../../../zql/src/ivm/push-accumulated.ts"],"sourcesContent":["import {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {emptyArray} from '../../../shared/src/sentinels.ts';\nimport type {Change} from './change.ts';\nimport type {Node} from './data.ts';\nimport type {InputBase, Output} from './operator.ts';\nimport type {SourceSchema} from './schema.ts';\nimport type {Stream} from './stream.ts';\n\n/**\n * # pushAccumulatedChanges\n *\n * Pushes the changes that were accumulated by\n * [fan-out, fan-in] or [ufo, ufi] sub-graphs.\n *\n * This function is called at the end of the sub-graph.\n *\n * The sub-graphs represents `OR`s.\n *\n * Changes that can enter the subgraphs:\n * 1. child (due to exist joins being above the sub-graph)\n * 2. add\n * 3. remove\n * 4. edit\n *\n * # Changes that can exit into `pushAccumulatedChanges`:\n *\n * ## Child\n * If a `child` change enters a sub-graph, it will flow to all branches.\n * Each branch will either:\n * - preserve the `child` change\n * - stop the `child` change (e.g., filter)\n * - convert it to an `add` or `remove` (e.g., exists filter)\n *\n * ## Add\n * If an `add` change enters a sub-graph, it will flow to all branches.\n * Each branch will either:\n * - preserve the `add` change\n * - hide the change (e.g., filter)\n *\n * ## Remove\n * If a `remove` change enters a sub-graph, it will flow to all branches.\n * Each branch will either:\n * - preserve the `remove` change\n * - hide the change (e.g., filter)\n *\n * ## Edit\n * If an `edit` change enters a sub-graph, it will flow to all branches.\n * Each branch will either:\n * - preserve the `edit` change\n * - convert it to an `add` (e.g., filter where old didn't match but new does)\n * - convert it to a `remove` (e.g., filter where old matched but new doesn't)\n *\n * This results in some invariants:\n * - an add coming in will only create adds coming out\n * - a remove coming in will only create removes coming out\n * - an edit coming in can create adds, removes, and edits coming out\n * - a child coming in can create adds, removes, and children coming out\n *\n * # Return of `pushAccumulatedChanges`\n *\n * This function will only push a single change.\n * Given the above invariants, how is this possible?\n *\n * An add that becomes many `adds` results in a single add\n * as the `add` is the same row across all adds. Branches do not change the row.\n *\n * A remove that becomes many `removes` results in a single remove\n * for the same reason.\n *\n * If a child enters and exits, it takes precedence over all other changes.\n * If a child enters and is converted only to add and remove it exits as an edit.\n * If a child enters and is converted to only add or only remove, it exits as that change.\n *\n * If an edit enters and is converted to add and remove it exits as an edit.\n * If an edit enters and is converted to only add or only remove, it exits as that change.\n * If an edit enters and exits as edits only, it exits as a single edit.\n */\nexport function* pushAccumulatedChanges(\n accumulatedPushes: Change[],\n output: Output,\n pusher: InputBase,\n fanOutChangeType: Change['type'],\n mergeRelationships: (existing: Change, incoming: Change) => Change,\n addEmptyRelationships: (change: Change) => Change,\n): Stream<'yield'> {\n if (accumulatedPushes.length === 0) {\n // It is possible for no forks to pass along the push.\n // E.g., if no filters match in any fork.\n return;\n }\n\n // collapse down to a single change per type\n const candidatesToPush = new Map<Change['type'], Change>();\n for (const change of accumulatedPushes) {\n if (fanOutChangeType === 'child' && change.type !== 'child') {\n assert(\n candidatesToPush.has(change.type) === false,\n () =>\n `Fan-in:child expected at most one ${change.type} when fan-out is of type child`,\n );\n }\n\n const existing = candidatesToPush.get(change.type);\n let mergedChange = change;\n if (existing) {\n // merge in relationships\n mergedChange = mergeRelationships(existing, change);\n }\n candidatesToPush.set(change.type, mergedChange);\n }\n\n accumulatedPushes.length = 0;\n\n const types = [...candidatesToPush.keys()];\n /**\n * Based on the received `fanOutChangeType` only certain output types are valid.\n *\n * - remove must result in all removes\n * - add must result in all adds\n * - edit must result in add or removes or edits\n * - child must result in a single add or single remove or many child changes\n * - Single add or remove because the relationship will be unique to one exist check within the fan-out,fan-in sub-graph\n * - Many child changes because other operators may preserve the child change\n */\n switch (fanOutChangeType) {\n case 'remove':\n assert(\n types.length === 1 && types[0] === 'remove',\n 'Fan-in:remove expected all removes',\n );\n yield* output.push(\n addEmptyRelationships(must(candidatesToPush.get('remove'))),\n pusher,\n );\n return;\n case 'add':\n assert(\n types.length === 1 && types[0] === 'add',\n 'Fan-in:add expected all adds',\n );\n yield* output.push(\n addEmptyRelationships(must(candidatesToPush.get('add'))),\n pusher,\n );\n return;\n case 'edit': {\n assert(\n types.every(\n type => type === 'add' || type === 'remove' || type === 'edit',\n ),\n 'Fan-in:edit expected all adds, removes, or edits',\n );\n const addChange = candidatesToPush.get('add');\n const removeChange = candidatesToPush.get('remove');\n let editChange = candidatesToPush.get('edit');\n\n // If an `edit` is present, it supersedes `add` and `remove`\n // as it semantically represents both.\n if (editChange) {\n if (addChange) {\n editChange = mergeRelationships(editChange, addChange);\n }\n if (removeChange) {\n editChange = mergeRelationships(editChange, removeChange);\n }\n yield* output.push(addEmptyRelationships(editChange), pusher);\n return;\n }\n\n // If `edit` didn't make it through but both `add` and `remove` did,\n // convert back to an edit.\n //\n // When can this happen?\n //\n // EDIT old: a=1, new: a=2\n // |\n // FanOut\n // / \\\n // a=1 a=2\n // | |\n // remove add\n // \\ /\n // FanIn\n //\n // The left filter converts the edit into a remove.\n // The right filter converts the edit into an add.\n if (addChange && removeChange) {\n yield* output.push(\n addEmptyRelationships({\n type: 'edit',\n node: addChange.node,\n oldNode: removeChange.node,\n } as const),\n pusher,\n );\n return;\n }\n\n yield* output.push(\n addEmptyRelationships(must(addChange ?? removeChange)),\n pusher,\n );\n return;\n }\n case 'child': {\n assert(\n types.every(\n type =>\n type === 'add' || // exists can change child to add or remove\n type === 'remove' || // exists can change child to add or remove\n type === 'child', // other operators may preserve the child change\n ),\n 'Fan-in:child expected all adds, removes, or children',\n );\n assert(\n types.length <= 2,\n 'Fan-in:child expected at most 2 types on a child change from fan-out',\n );\n\n // If any branch preserved the original child change, that takes precedence over all other changes.\n const childChange = candidatesToPush.get('child');\n if (childChange) {\n yield* output.push(childChange, pusher);\n return;\n }\n\n const addChange = candidatesToPush.get('add');\n const removeChange = candidatesToPush.get('remove');\n\n assert(\n addChange === undefined || removeChange === undefined,\n 'Fan-in:child expected either add or remove, not both',\n );\n\n yield* output.push(\n addEmptyRelationships(must(addChange ?? removeChange)),\n pusher,\n );\n return;\n }\n default:\n fanOutChangeType satisfies never;\n }\n}\n\n/**\n * Puts relationships from `right` into `left` if they don't already exist in `left`.\n */\nexport function mergeRelationships(left: Change, right: Change): Change {\n // change types will always match\n // unless we have an edit on the left\n // then the right could be edit, add, or remove\n if (left.type === right.type) {\n switch (left.type) {\n case 'add': {\n return {\n type: 'add',\n node: {\n row: left.node.row,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n };\n }\n case 'remove': {\n return {\n type: 'remove',\n node: {\n row: left.node.row,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n };\n }\n case 'edit': {\n assert(right.type === 'edit');\n // merge edits into a single edit\n return {\n type: 'edit',\n node: {\n row: left.node.row,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n oldNode: {\n row: left.oldNode.row,\n relationships: {\n ...right.oldNode.relationships,\n ...left.oldNode.relationships,\n },\n },\n };\n }\n case 'child': {\n // Multiple branches may preserve the same child change, each adding\n // different relationships. Merge the relationships, keeping the child\n // (which should be identical across branches).\n assert(\n right.type === 'child',\n () =>\n `mergeRelationships: when left.type is child and types match, right.type must be child, got ${right.type}`,\n );\n return {\n type: 'child',\n node: {\n row: left.node.row,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n child: left.child,\n };\n }\n }\n }\n\n // left is always an edit here\n assert(left.type === 'edit');\n switch (right.type) {\n case 'add': {\n return {\n type: 'edit',\n node: {\n ...left.node,\n relationships: {\n ...right.node.relationships,\n ...left.node.relationships,\n },\n },\n oldNode: left.oldNode,\n };\n }\n case 'remove': {\n return {\n type: 'edit',\n node: left.node,\n oldNode: {\n ...left.oldNode,\n relationships: {\n ...right.node.relationships,\n ...left.oldNode.relationships,\n },\n },\n };\n }\n }\n\n unreachable();\n}\n\nexport function makeAddEmptyRelationships(\n schema: SourceSchema,\n): (change: Change) => Change {\n return (change: Change): Change => {\n if (Object.keys(schema.relationships).length === 0) {\n return change;\n }\n\n switch (change.type) {\n case 'add':\n case 'remove': {\n const ret = {\n ...change,\n node: {\n ...change.node,\n relationships: {\n ...change.node.relationships,\n },\n },\n };\n\n mergeEmpty(ret.node.relationships, Object.keys(schema.relationships));\n\n return ret;\n }\n case 'edit': {\n const ret = {\n ...change,\n node: {\n ...change.node,\n relationships: {\n ...change.node.relationships,\n },\n },\n oldNode: {\n ...change.oldNode,\n relationships: {\n ...change.oldNode.relationships,\n },\n },\n };\n\n mergeEmpty(ret.node.relationships, Object.keys(schema.relationships));\n mergeEmpty(\n ret.oldNode.relationships,\n Object.keys(schema.relationships),\n );\n\n return ret;\n }\n case 'child':\n return change; // children only have relationships along the path to the change\n }\n };\n}\n\n/**\n * For each relationship in `schema` that does not exist\n * in `relationships`, add it with an empty stream.\n *\n * This modifies the `relationships` object in place.\n */\nexport function mergeEmpty(\n relationships: Record<string, () => Stream<Node | 'yield'>>,\n relationshipNames: string[],\n) {\n for (const relName of relationshipNames) {\n if (relationships[relName] === undefined) {\n relationships[relName] = () => emptyArray;\n }\n }\n}\n"],"names":["mergeRelationships"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rocicorp/zero",
3
- "version": "0.25.9",
3
+ "version": "0.25.10-canary.4",
4
4
  "description": "Zero is a web framework for serverless web development.",
5
5
  "author": "Rocicorp, Inc.",
6
6
  "repository": {