@rocicorp/zero 1.6.0-canary.5 → 1.6.0-canary.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/replicache/src/kv/expo-sqlite/store.d.ts +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/expo-sqlite/store.js +6 -7
- package/out/replicache/src/kv/expo-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.d.ts.map +1 -1
- package/out/replicache/src/kv/op-sqlite/store.js +6 -6
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +6 -6
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +104 -22
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/kv/throw-if-closed.d.ts +1 -0
- package/out/replicache/src/kv/throw-if-closed.d.ts.map +1 -1
- package/out/replicache/src/kv/throw-if-closed.js +1 -4
- package/out/replicache/src/kv/throw-if-closed.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/server/change-streamer.js +6 -3
- package/out/zero-cache/src/server/change-streamer.js.map +1 -1
- package/out/zero-cache/src/server/reaper.js +2 -2
- package/out/zero-cache/src/server/reaper.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +4 -4
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +2 -2
- 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 +2 -2
- 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.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +4 -4
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/schema/shard.js +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +5 -0
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js +1 -1
- package/out/zero-cache/src/services/view-syncer/client-handler.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/types/configuration-error.d.ts +4 -0
- package/out/zero-cache/src/types/configuration-error.d.ts.map +1 -0
- package/out/zero-cache/src/types/configuration-error.js +11 -0
- package/out/zero-cache/src/types/configuration-error.js.map +1 -0
- package/out/zero-cache/src/types/pg.d.ts +2 -0
- package/out/zero-cache/src/types/pg.d.ts.map +1 -1
- package/out/zero-cache/src/types/pg.js +34 -1
- package/out/zero-cache/src/types/pg.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zql/src/ivm/union-fan-in.d.ts.map +1 -1
- package/out/zql/src/ivm/union-fan-in.js +3 -1
- package/out/zql/src/ivm/union-fan-in.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration-error.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/configuration-error.ts"],"names":[],"mappings":"AAAA,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region ../zero-cache/src/types/configuration-error.ts
|
|
2
|
+
var ConfigurationError = class extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = "ConfigurationError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { ConfigurationError };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=configuration-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration-error.js","names":[],"sources":["../../../../../zero-cache/src/types/configuration-error.ts"],"sourcesContent":["export class ConfigurationError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ConfigurationError';\n }\n}\n"],"mappings":";AAAA,IAAa,qBAAb,cAAwC,MAAM;CAC5C,YAAY,SAAiB,SAAwB;AACnD,QAAM,SAAS,QAAQ;AACvB,OAAK,OAAO"}
|
|
@@ -84,6 +84,7 @@ export declare function pgClient(lc: LogContext, connectionURI: string, applicat
|
|
|
84
84
|
bigint: PostgresType<bigint>;
|
|
85
85
|
json: PostgresType<JSONValue>;
|
|
86
86
|
}>, opts?: TypeOptions): PostgresDB;
|
|
87
|
+
export declare function connectPgClient(...args: Parameters<typeof pgClient>): Promise<PostgresDB>;
|
|
87
88
|
/**
|
|
88
89
|
* Disables any statement_timeout for the current transaction. By default,
|
|
89
90
|
* Postgres does not impose a statement timeout, but some users and providers
|
|
@@ -98,5 +99,6 @@ export declare function pgClient(lc: LogContext, connectionURI: string, applicat
|
|
|
98
99
|
export declare function disableStatementTimeout(sql: PostgresTransaction): void;
|
|
99
100
|
export declare const typeNameByOID: Record<number, string>;
|
|
100
101
|
export declare function isPostgresError(e: unknown, ...codes: [string, ...string[]]): boolean;
|
|
102
|
+
export declare function isPostgresConfigError(e: unknown): boolean;
|
|
101
103
|
export {};
|
|
102
104
|
//# sourceMappingURL=pg.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/pg.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/types/pg.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACjD,OAAO,QAAQ,EAAE,EAAc,KAAK,YAAY,EAAC,MAAM,UAAU,CAAC;AAClE,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,oCAAoC,CAAC;AAmB9E,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAmD7D;AAED,iBAAS,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAuBhD;AAcD,wBAAgB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAoBvE;AAOD,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA8ErE;AAED,iBAAS,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvD,MAAM,MAAM,WAAW,GAAG;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,uBAAoB,WAAgB;;;;;;;;;;;;;;;;;;2BAwBlD,OAAO;;;;;;2BAOP,OAAO;;;;;;2BAQP,MAAM,GAAG,IAAI;;;;;;2BAUb,MAAM;uBACV,MAAM,GAAG,MAAM;;;CAG9B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC,cAAc,CAAC;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,SAAS,CAAC;CACjB,CAAC,CAAC;AAEH,wBAAgB,QAAQ,CACtB,EAAE,EAAE,UAAU,EACd,aAAa,EAAE,MAAM,EAGrB,eAAe,EAAE,MAAM,EACvB,OAAO,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;CAC/B,CAAC,EACF,IAAI,CAAC,EAAE,WAAW,GACjB,UAAU,CAmDZ;AAED,wBAAsB,eAAe,CACnC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,QAAQ,CAAC,GACnC,OAAO,CAAC,UAAU,CAAC,CAcrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,QAE/D;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOhD,CAAC;AAEF,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,WAE1E;AA8BD,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,OAAO,WAiB/C"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { BigIntJSON } from "../../../shared/src/bigint-json.js";
|
|
2
2
|
import { randInt } from "../../../shared/src/rand.js";
|
|
3
|
+
import { ConfigurationError } from "./configuration-error.js";
|
|
3
4
|
import { DATE, JSONB, NUMERIC, TIME, TIMESTAMP, TIMESTAMPTZ, TIMETZ } from "./pg-types.js";
|
|
4
5
|
import postgres from "postgres";
|
|
6
|
+
import { PG_CONFIGURATION_LIMIT_EXCEEDED, PG_CONNECTION_DOES_NOT_EXIST, PG_CONNECTION_EXCEPTION, PG_CONNECTION_FAILURE, PG_INSUFFICIENT_PRIVILEGE, PG_INVALID_AUTHORIZATION_SPECIFICATION, PG_INVALID_CATALOG_NAME, PG_INVALID_PASSWORD, PG_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, PG_TOO_MANY_CONNECTIONS } from "@drdgvhbh/postgres-error-codes";
|
|
5
7
|
import { PreciseDate } from "@google-cloud/precise-date";
|
|
6
8
|
import { OID } from "@postgresql-typed/oids";
|
|
7
9
|
//#region ../zero-cache/src/types/pg.ts
|
|
@@ -179,11 +181,42 @@ function pgClient(lc, connectionURI, applicationName, options, opts) {
|
|
|
179
181
|
}
|
|
180
182
|
});
|
|
181
183
|
}
|
|
184
|
+
async function connectPgClient(...args) {
|
|
185
|
+
const db = pgClient(...args);
|
|
186
|
+
try {
|
|
187
|
+
await db`SELECT 1`.simple().execute();
|
|
188
|
+
return db;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
if (isPostgresConfigError(e)) throw new ConfigurationError("Unable to connect to Postgres. Check your database configuration.", { cause: e });
|
|
191
|
+
throw e;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
182
194
|
Object.freeze(Object.fromEntries(Object.entries(OID).map(([name, oid]) => [oid, name.startsWith("_") ? `${name.substring(1)}[]` : name])));
|
|
183
195
|
function isPostgresError(e, ...codes) {
|
|
184
196
|
return e instanceof postgres.PostgresError && codes.includes(e.code);
|
|
185
197
|
}
|
|
198
|
+
function isPostgresStartupConnectionError(e) {
|
|
199
|
+
if (!(e instanceof Error) || !("code" in e)) return false;
|
|
200
|
+
const { code } = e;
|
|
201
|
+
return typeof code === "string" && [
|
|
202
|
+
"CONNECT_TIMEOUT",
|
|
203
|
+
"ECONNREFUSED",
|
|
204
|
+
"ECONNRESET",
|
|
205
|
+
"EHOSTUNREACH",
|
|
206
|
+
"ENETUNREACH",
|
|
207
|
+
"ENOTFOUND",
|
|
208
|
+
"EAI_AGAIN",
|
|
209
|
+
"ETIMEDOUT",
|
|
210
|
+
"ERR_TLS_CERT_ALTNAME_INVALID",
|
|
211
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
212
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
213
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE"
|
|
214
|
+
].includes(code);
|
|
215
|
+
}
|
|
216
|
+
function isPostgresConfigError(e) {
|
|
217
|
+
return isPostgresStartupConnectionError(e) || isPostgresError(e, PG_CONNECTION_EXCEPTION, PG_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, PG_CONNECTION_DOES_NOT_EXIST, PG_CONNECTION_FAILURE, PG_INVALID_AUTHORIZATION_SPECIFICATION, PG_INVALID_PASSWORD, PG_INVALID_CATALOG_NAME, PG_INSUFFICIENT_PRIVILEGE, PG_TOO_MANY_CONNECTIONS, PG_CONFIGURATION_LIMIT_EXCEEDED);
|
|
218
|
+
}
|
|
186
219
|
//#endregion
|
|
187
|
-
export { isPostgresError, pgClient };
|
|
220
|
+
export { connectPgClient, isPostgresError, pgClient };
|
|
188
221
|
|
|
189
222
|
//# sourceMappingURL=pg.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pg.js","names":[],"sources":["../../../../../zero-cache/src/types/pg.ts"],"sourcesContent":["import {PreciseDate} from '@google-cloud/precise-date';\nimport {OID} from '@postgresql-typed/oids';\nimport type {LogContext} from '@rocicorp/logger';\nimport postgres, {type Notice, type PostgresType} from 'postgres';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {\n DATE,\n JSON,\n JSONB,\n NUMERIC,\n TIME,\n TIMESTAMP,\n TIMESTAMPTZ,\n TIMETZ,\n} from './pg-types.ts';\n\n// Matches: YEAR-MM-DD HH:MM:SS[.fraction][+-TZ[:MM]][ BC]\nconst pgTimestampRe =\n /^(\\d+)-(\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)([+-]\\d{1,2}(?::\\d{2})?)?(?: BC)?$/;\n\n// exported for testing.\nexport function timestampToFpMillis(timestamp: string): number {\n if (timestamp === 'infinity') return Infinity;\n if (timestamp === '-infinity') return -Infinity;\n const match = timestamp.match(pgTimestampRe);\n if (!match) {\n throw new Error(`Error parsing ${timestamp}`);\n }\n\n const [, yearStr, monthDay, time, tz] = match;\n const bc = timestamp.endsWith(' BC');\n\n let year = Number(yearStr);\n if (bc) {\n // Postgres: 1 BC = JS year 0, 2 BC = JS year -1, N BC = -(N-1)\n year = -(year - 1);\n }\n\n // Format year as ISO 8601 expanded year if needed.\n // https://tc39.es/ecma262/#sec-expanded-years\n let isoYear: string;\n if (year >= 0 && year <= 9999) {\n isoYear = String(year).padStart(4, '0');\n } else if (year >= 0) {\n isoYear = '+' + String(year).padStart(6, '0');\n } else {\n isoYear = '-' + String(Math.abs(year)).padStart(6, '0');\n }\n\n // Build a UTC ISO string so PreciseDate returns microsecond precision.\n const utcString = `${isoYear}-${monthDay}T${time}Z`;\n\n try {\n const fullTime = new PreciseDate(utcString).getFullTime();\n const millis = Number(fullTime / 1_000_000n);\n const nanos = Number(fullTime % 1_000_000n);\n const ret = millis + nanos * 1e-6; // floating point milliseconds\n\n // Add back in the timezone offset. We passed local time as UTC,\n // so a positive offset means we need to subtract, and vice versa.\n if (tz) {\n const positiveOffset = tz.startsWith('+');\n const [hours, minutes] = tz.split(':');\n const offset =\n Math.abs(Number(hours)) * 60 + (minutes ? Number(minutes) : 0);\n const offsetMillis = offset * 60 * 1_000;\n return positiveOffset ? ret - offsetMillis : ret + offsetMillis;\n }\n return ret;\n } catch (e) {\n throw new Error(`Error parsing ${timestamp}`, {cause: e});\n }\n}\n\nfunction serializeTimestamp(val: unknown): string {\n switch (typeof val) {\n case 'string':\n return val; // Let Postgres parse it\n case 'number': {\n if (Number.isInteger(val)) {\n return new PreciseDate(val).toISOString();\n }\n // Convert floating point to bigint nanoseconds.\n const nanoseconds =\n 1_000_000n * BigInt(Math.trunc(val)) +\n BigInt(Math.trunc((val % 1) * 1e6));\n return new PreciseDate(nanoseconds).toISOString();\n }\n // Note: Don't support bigint inputs until we decide what the semantics are (e.g. micros vs nanos)\n // case 'bigint':\n // return new PreciseDate(val).toISOString();\n default:\n if (val instanceof Date) {\n return val.toISOString();\n }\n }\n throw new Error(`Unsupported type \"${typeof val}\" for timestamp: ${val}`);\n}\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nfunction serializeTime(x: unknown, type: 'time' | 'timetz'): string {\n switch (typeof x) {\n case 'string':\n return x; // Let Postgres parse it\n case 'number':\n return millisecondsToPostgresTime(x);\n }\n throw new Error(`Unsupported type \"${typeof x}\" for ${type}: ${x}`);\n}\n\nexport function millisecondsToPostgresTime(milliseconds: number): string {\n if (milliseconds < 0) {\n throw new Error('Milliseconds cannot be negative');\n }\n\n if (milliseconds >= MILLISECONDS_PER_DAY) {\n throw new Error(\n `Milliseconds cannot exceed 24 hours (${MILLISECONDS_PER_DAY}ms)`,\n );\n }\n\n milliseconds = Math.floor(milliseconds); // Ensure it's an integer\n\n const totalSeconds = Math.floor(milliseconds / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const ms = milliseconds % 1000;\n\n return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}+00`;\n}\n\n// Regular expression to match HH:MM:SS, HH:MM:SS.mmm, or HH:MM:SS+00 / HH:MM:SS.mmm+00\n// Supports optional timezone offset\nconst timeRegex =\n /^(\\d{1,2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?(?:([+-])(\\d{1,2})(?::(\\d{2}))?)?$/;\n\nexport function postgresTimeToMilliseconds(timeString: string): number {\n // Validate basic format\n if (!timeString || typeof timeString !== 'string') {\n throw new Error('Invalid time string: must be a non-empty string');\n }\n\n const match = timeString.match(timeRegex);\n\n if (!match) {\n throw new Error(\n `Invalid time format: \"${timeString}\". Expected HH:MM:SS[.mmm][+|-HH[:MM]]`,\n );\n }\n\n // Extract components\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const seconds = parseInt(match[3], 10);\n // Handle optional milliseconds, pad right with zeros if needed\n let milliseconds = 0;\n if (match[4]) {\n // Pad microseconds to 6 digits\n const msString = match[4].padEnd(6, '0');\n // slice milliseconds out of the microseconds\n // e.g. 123456 -> 123, 1234 -> 123,\n milliseconds = parseInt(msString.slice(0, 3), 10);\n }\n\n // Validate ranges\n if (hours < 0 || hours > 24) {\n throw new Error(\n `Invalid hours: ${hours}. Must be between 0 and 24 (24 means end of day)`,\n );\n }\n\n if (minutes < 0 || minutes >= 60) {\n throw new Error(`Invalid minutes: ${minutes}. Must be between 0 and 59`);\n }\n\n if (seconds < 0 || seconds >= 60) {\n throw new Error(`Invalid seconds: ${seconds}. Must be between 0 and 59`);\n }\n\n if (milliseconds < 0 || milliseconds >= 1000) {\n throw new Error(\n `Invalid milliseconds: ${milliseconds}. Must be between 0 and 999`,\n );\n }\n\n // Special case: PostgreSQL allows 24:00:00 to represent end of day\n if (hours === 24 && (minutes !== 0 || seconds !== 0 || milliseconds !== 0)) {\n throw new Error(\n 'Invalid time: when hours is 24, minutes, seconds, and milliseconds must be 0',\n );\n }\n\n // Calculate total milliseconds\n let totalMs =\n hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds;\n\n // Timezone Offset\n if (match[5]) {\n const sign = match[5] === '+' ? 1 : -1;\n const tzHours = parseInt(match[6], 10);\n const tzMinutes = match[7] ? parseInt(match[7], 10) : 0;\n const offsetMs = sign * (tzHours * 3600000 + tzMinutes * 60000);\n totalMs -= offsetMs;\n }\n\n // Normalize to 0-24h only if outside valid range\n if (totalMs > MILLISECONDS_PER_DAY || totalMs < 0) {\n return (\n ((totalMs % MILLISECONDS_PER_DAY) + MILLISECONDS_PER_DAY) %\n MILLISECONDS_PER_DAY\n );\n }\n\n return totalMs;\n}\n\nfunction dateToUTCMidnight(date: string): number {\n if (date === 'infinity') return Infinity;\n if (date === '-infinity') return -Infinity;\n const d = new Date(date);\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\n}\n\n/**\n * The (javascript) types of objects that can be returned by our configured\n * Postgres clients. For initial-sync, these comes from the postgres.js client:\n *\n * https://github.com/porsager/postgres/blob/master/src/types.js\n *\n * and for the replication stream these come from the the node-postgres client:\n *\n * https://github.com/brianc/node-pg-types/blob/master/lib/textParsers.js\n */\nexport type PostgresValueType = JSONValue | Uint8Array;\n\nexport type TypeOptions = {\n /**\n * Sends strings directly as JSON values (i.e. without JSON stringification).\n * The application is responsible for ensuring that string inputs for JSON\n * columns are already stringified. Other data types (e.g. objects) will\n * still be stringified by the pg client.\n */\n sendStringAsJson?: boolean;\n};\n\n/**\n * Configures types for the Postgres.js client library (`postgres`).\n *\n * @param jsonAsString Keep JSON / JSONB values as strings instead of parsing.\n */\nexport const postgresTypeConfig = ({sendStringAsJson}: TypeOptions = {}) => ({\n // Type the type IDs as `number` so that Typescript doesn't complain about\n // referencing external types during type inference.\n types: {\n bigint: postgres.BigInt,\n json: {\n to: JSON,\n from: [JSON, JSONB],\n serialize: sendStringAsJson\n ? (x: unknown) => (typeof x === 'string' ? x : BigIntJSON.stringify(x))\n : BigIntJSON.stringify,\n parse: BigIntJSON.parse,\n },\n // Timestamps are converted to PreciseDate objects.\n timestamp: {\n to: TIMESTAMP,\n from: [TIMESTAMP, TIMESTAMPTZ],\n serialize: serializeTimestamp,\n parse: timestampToFpMillis,\n },\n // Times are converted as strings\n time: {\n to: TIME,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'time'),\n parse: postgresTimeToMilliseconds,\n },\n\n timetz: {\n to: TIMETZ,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'timetz'),\n parse: postgresTimeToMilliseconds,\n },\n\n // The DATE type is stored directly as the PG normalized date string.\n date: {\n to: DATE,\n from: [DATE],\n serialize: (x: string | Date) =>\n (x instanceof Date ? x : new Date(x)).toISOString(),\n parse: dateToUTCMidnight,\n },\n // Returns a `js` number which can lose precision for large numbers.\n // JS number is 53 bits so this should generally not occur.\n // An API will be provided for users to override this type.\n numeric: {\n to: NUMERIC,\n from: [NUMERIC],\n serialize: (x: number) => String(x), // pg expects a string\n parse: (x: string | number) => Number(x),\n },\n },\n});\n\nexport type PostgresDB = postgres.Sql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport type PostgresTransaction = postgres.TransactionSql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport function pgClient(\n lc: LogContext,\n connectionURI: string,\n // A descriptive name for the code that is making the connection, so that we\n // can debug things like deadlocks.\n applicationName: string,\n options?: postgres.Options<{\n bigint: PostgresType<bigint>;\n json: PostgresType<JSONValue>;\n }>,\n opts?: TypeOptions,\n): PostgresDB {\n applicationName = `zero-${applicationName}`;\n\n const onnotice = (n: Notice) => {\n // https://www.postgresql.org/docs/current/plpgsql-errors-and-messages.html#PLPGSQL-STATEMENTS-RAISE\n switch (n.severity) {\n case 'NOTICE':\n return; // silenced\n case 'DEBUG':\n lc.debug?.(n);\n return;\n case 'WARNING':\n lc.warn?.(n);\n return;\n case 'EXCEPTION':\n lc.error?.(n);\n return;\n case 'LOG':\n case 'INFO':\n default:\n lc.info?.(n);\n }\n };\n const url = new URL(connectionURI);\n const sslFlag =\n url.searchParams.get('ssl') ?? url.searchParams.get('sslmode') ?? 'prefer';\n\n let ssl: boolean | 'prefer' | {rejectUnauthorized: boolean};\n if (sslFlag === 'disable' || sslFlag === 'false') {\n ssl = false;\n } else if (sslFlag === 'no-verify') {\n ssl = {rejectUnauthorized: false};\n } else {\n ssl = sslFlag as 'prefer';\n }\n\n // Set connections to expire between 5 and 10 minutes to free up state on PG.\n const maxLifetimeSeconds = randInt(5 * 60, 10 * 60);\n const providedConnection = options?.connection ?? {};\n\n return postgres(connectionURI, {\n ...postgresTypeConfig(opts),\n onnotice,\n ['max_lifetime']: maxLifetimeSeconds,\n ssl,\n ...options,\n connection: {\n ...providedConnection,\n ['application_name']: applicationName,\n },\n });\n}\n\n/**\n * Disables any statement_timeout for the current transaction. By default,\n * Postgres does not impose a statement timeout, but some users and providers\n * set one at the database level (even though it is explicitly discouraged by\n * the Postgres documentation).\n *\n * Zero logic in particular often does not fit into the category of general\n * application logic; for potentially long-running operations like migrations\n * and background cleanup, the statement timeout should be disabled to prevent\n * these operations from timing out.\n */\nexport function disableStatementTimeout(sql: PostgresTransaction) {\n void sql`SET LOCAL statement_timeout = 0;`.execute().catch(() => {});\n}\n\nexport const typeNameByOID: Record<number, string> = Object.freeze(\n Object.fromEntries(\n Object.entries(OID).map(([name, oid]) => [\n oid,\n name.startsWith('_') ? `${name.substring(1)}[]` : name,\n ]),\n ),\n);\n\nexport function isPostgresError(e: unknown, ...codes: [string, ...string[]]) {\n return e instanceof postgres.PostgresError && codes.includes(e.code);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pg.js","names":[],"sources":["../../../../../zero-cache/src/types/pg.ts"],"sourcesContent":["import {\n PG_CONFIGURATION_LIMIT_EXCEEDED,\n PG_CONNECTION_DOES_NOT_EXIST,\n PG_CONNECTION_EXCEPTION,\n PG_CONNECTION_FAILURE,\n PG_INSUFFICIENT_PRIVILEGE,\n PG_INVALID_AUTHORIZATION_SPECIFICATION,\n PG_INVALID_CATALOG_NAME,\n PG_INVALID_PASSWORD,\n PG_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n PG_TOO_MANY_CONNECTIONS,\n} from '@drdgvhbh/postgres-error-codes';\nimport {PreciseDate} from '@google-cloud/precise-date';\nimport {OID} from '@postgresql-typed/oids';\nimport type {LogContext} from '@rocicorp/logger';\nimport postgres, {type Notice, type PostgresType} from 'postgres';\nimport {BigIntJSON, type JSONValue} from '../../../shared/src/bigint-json.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport {ConfigurationError} from './configuration-error.ts';\nimport {\n DATE,\n JSON,\n JSONB,\n NUMERIC,\n TIME,\n TIMESTAMP,\n TIMESTAMPTZ,\n TIMETZ,\n} from './pg-types.ts';\n\n// Matches: YEAR-MM-DD HH:MM:SS[.fraction][+-TZ[:MM]][ BC]\nconst pgTimestampRe =\n /^(\\d+)-(\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?)([+-]\\d{1,2}(?::\\d{2})?)?(?: BC)?$/;\n\n// exported for testing.\nexport function timestampToFpMillis(timestamp: string): number {\n if (timestamp === 'infinity') return Infinity;\n if (timestamp === '-infinity') return -Infinity;\n const match = timestamp.match(pgTimestampRe);\n if (!match) {\n throw new Error(`Error parsing ${timestamp}`);\n }\n\n const [, yearStr, monthDay, time, tz] = match;\n const bc = timestamp.endsWith(' BC');\n\n let year = Number(yearStr);\n if (bc) {\n // Postgres: 1 BC = JS year 0, 2 BC = JS year -1, N BC = -(N-1)\n year = -(year - 1);\n }\n\n // Format year as ISO 8601 expanded year if needed.\n // https://tc39.es/ecma262/#sec-expanded-years\n let isoYear: string;\n if (year >= 0 && year <= 9999) {\n isoYear = String(year).padStart(4, '0');\n } else if (year >= 0) {\n isoYear = '+' + String(year).padStart(6, '0');\n } else {\n isoYear = '-' + String(Math.abs(year)).padStart(6, '0');\n }\n\n // Build a UTC ISO string so PreciseDate returns microsecond precision.\n const utcString = `${isoYear}-${monthDay}T${time}Z`;\n\n try {\n const fullTime = new PreciseDate(utcString).getFullTime();\n const millis = Number(fullTime / 1_000_000n);\n const nanos = Number(fullTime % 1_000_000n);\n const ret = millis + nanos * 1e-6; // floating point milliseconds\n\n // Add back in the timezone offset. We passed local time as UTC,\n // so a positive offset means we need to subtract, and vice versa.\n if (tz) {\n const positiveOffset = tz.startsWith('+');\n const [hours, minutes] = tz.split(':');\n const offset =\n Math.abs(Number(hours)) * 60 + (minutes ? Number(minutes) : 0);\n const offsetMillis = offset * 60 * 1_000;\n return positiveOffset ? ret - offsetMillis : ret + offsetMillis;\n }\n return ret;\n } catch (e) {\n throw new Error(`Error parsing ${timestamp}`, {cause: e});\n }\n}\n\nfunction serializeTimestamp(val: unknown): string {\n switch (typeof val) {\n case 'string':\n return val; // Let Postgres parse it\n case 'number': {\n if (Number.isInteger(val)) {\n return new PreciseDate(val).toISOString();\n }\n // Convert floating point to bigint nanoseconds.\n const nanoseconds =\n 1_000_000n * BigInt(Math.trunc(val)) +\n BigInt(Math.trunc((val % 1) * 1e6));\n return new PreciseDate(nanoseconds).toISOString();\n }\n // Note: Don't support bigint inputs until we decide what the semantics are (e.g. micros vs nanos)\n // case 'bigint':\n // return new PreciseDate(val).toISOString();\n default:\n if (val instanceof Date) {\n return val.toISOString();\n }\n }\n throw new Error(`Unsupported type \"${typeof val}\" for timestamp: ${val}`);\n}\n\nconst MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;\n\nfunction serializeTime(x: unknown, type: 'time' | 'timetz'): string {\n switch (typeof x) {\n case 'string':\n return x; // Let Postgres parse it\n case 'number':\n return millisecondsToPostgresTime(x);\n }\n throw new Error(`Unsupported type \"${typeof x}\" for ${type}: ${x}`);\n}\n\nexport function millisecondsToPostgresTime(milliseconds: number): string {\n if (milliseconds < 0) {\n throw new Error('Milliseconds cannot be negative');\n }\n\n if (milliseconds >= MILLISECONDS_PER_DAY) {\n throw new Error(\n `Milliseconds cannot exceed 24 hours (${MILLISECONDS_PER_DAY}ms)`,\n );\n }\n\n milliseconds = Math.floor(milliseconds); // Ensure it's an integer\n\n const totalSeconds = Math.floor(milliseconds / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n const ms = milliseconds % 1000;\n\n return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}+00`;\n}\n\n// Regular expression to match HH:MM:SS, HH:MM:SS.mmm, or HH:MM:SS+00 / HH:MM:SS.mmm+00\n// Supports optional timezone offset\nconst timeRegex =\n /^(\\d{1,2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?(?:([+-])(\\d{1,2})(?::(\\d{2}))?)?$/;\n\nexport function postgresTimeToMilliseconds(timeString: string): number {\n // Validate basic format\n if (!timeString || typeof timeString !== 'string') {\n throw new Error('Invalid time string: must be a non-empty string');\n }\n\n const match = timeString.match(timeRegex);\n\n if (!match) {\n throw new Error(\n `Invalid time format: \"${timeString}\". Expected HH:MM:SS[.mmm][+|-HH[:MM]]`,\n );\n }\n\n // Extract components\n const hours = parseInt(match[1], 10);\n const minutes = parseInt(match[2], 10);\n const seconds = parseInt(match[3], 10);\n // Handle optional milliseconds, pad right with zeros if needed\n let milliseconds = 0;\n if (match[4]) {\n // Pad microseconds to 6 digits\n const msString = match[4].padEnd(6, '0');\n // slice milliseconds out of the microseconds\n // e.g. 123456 -> 123, 1234 -> 123,\n milliseconds = parseInt(msString.slice(0, 3), 10);\n }\n\n // Validate ranges\n if (hours < 0 || hours > 24) {\n throw new Error(\n `Invalid hours: ${hours}. Must be between 0 and 24 (24 means end of day)`,\n );\n }\n\n if (minutes < 0 || minutes >= 60) {\n throw new Error(`Invalid minutes: ${minutes}. Must be between 0 and 59`);\n }\n\n if (seconds < 0 || seconds >= 60) {\n throw new Error(`Invalid seconds: ${seconds}. Must be between 0 and 59`);\n }\n\n if (milliseconds < 0 || milliseconds >= 1000) {\n throw new Error(\n `Invalid milliseconds: ${milliseconds}. Must be between 0 and 999`,\n );\n }\n\n // Special case: PostgreSQL allows 24:00:00 to represent end of day\n if (hours === 24 && (minutes !== 0 || seconds !== 0 || milliseconds !== 0)) {\n throw new Error(\n 'Invalid time: when hours is 24, minutes, seconds, and milliseconds must be 0',\n );\n }\n\n // Calculate total milliseconds\n let totalMs =\n hours * 3600000 + minutes * 60000 + seconds * 1000 + milliseconds;\n\n // Timezone Offset\n if (match[5]) {\n const sign = match[5] === '+' ? 1 : -1;\n const tzHours = parseInt(match[6], 10);\n const tzMinutes = match[7] ? parseInt(match[7], 10) : 0;\n const offsetMs = sign * (tzHours * 3600000 + tzMinutes * 60000);\n totalMs -= offsetMs;\n }\n\n // Normalize to 0-24h only if outside valid range\n if (totalMs > MILLISECONDS_PER_DAY || totalMs < 0) {\n return (\n ((totalMs % MILLISECONDS_PER_DAY) + MILLISECONDS_PER_DAY) %\n MILLISECONDS_PER_DAY\n );\n }\n\n return totalMs;\n}\n\nfunction dateToUTCMidnight(date: string): number {\n if (date === 'infinity') return Infinity;\n if (date === '-infinity') return -Infinity;\n const d = new Date(date);\n return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());\n}\n\n/**\n * The (javascript) types of objects that can be returned by our configured\n * Postgres clients. For initial-sync, these comes from the postgres.js client:\n *\n * https://github.com/porsager/postgres/blob/master/src/types.js\n *\n * and for the replication stream these come from the the node-postgres client:\n *\n * https://github.com/brianc/node-pg-types/blob/master/lib/textParsers.js\n */\nexport type PostgresValueType = JSONValue | Uint8Array;\n\nexport type TypeOptions = {\n /**\n * Sends strings directly as JSON values (i.e. without JSON stringification).\n * The application is responsible for ensuring that string inputs for JSON\n * columns are already stringified. Other data types (e.g. objects) will\n * still be stringified by the pg client.\n */\n sendStringAsJson?: boolean;\n};\n\n/**\n * Configures types for the Postgres.js client library (`postgres`).\n *\n * @param jsonAsString Keep JSON / JSONB values as strings instead of parsing.\n */\nexport const postgresTypeConfig = ({sendStringAsJson}: TypeOptions = {}) => ({\n // Type the type IDs as `number` so that Typescript doesn't complain about\n // referencing external types during type inference.\n types: {\n bigint: postgres.BigInt,\n json: {\n to: JSON,\n from: [JSON, JSONB],\n serialize: sendStringAsJson\n ? (x: unknown) => (typeof x === 'string' ? x : BigIntJSON.stringify(x))\n : BigIntJSON.stringify,\n parse: BigIntJSON.parse,\n },\n // Timestamps are converted to PreciseDate objects.\n timestamp: {\n to: TIMESTAMP,\n from: [TIMESTAMP, TIMESTAMPTZ],\n serialize: serializeTimestamp,\n parse: timestampToFpMillis,\n },\n // Times are converted as strings\n time: {\n to: TIME,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'time'),\n parse: postgresTimeToMilliseconds,\n },\n\n timetz: {\n to: TIMETZ,\n from: [TIME, TIMETZ],\n serialize: (x: unknown) => serializeTime(x, 'timetz'),\n parse: postgresTimeToMilliseconds,\n },\n\n // The DATE type is stored directly as the PG normalized date string.\n date: {\n to: DATE,\n from: [DATE],\n serialize: (x: string | Date) =>\n (x instanceof Date ? x : new Date(x)).toISOString(),\n parse: dateToUTCMidnight,\n },\n // Returns a `js` number which can lose precision for large numbers.\n // JS number is 53 bits so this should generally not occur.\n // An API will be provided for users to override this type.\n numeric: {\n to: NUMERIC,\n from: [NUMERIC],\n serialize: (x: number) => String(x), // pg expects a string\n parse: (x: string | number) => Number(x),\n },\n },\n});\n\nexport type PostgresDB = postgres.Sql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport type PostgresTransaction = postgres.TransactionSql<{\n bigint: bigint;\n json: JSONValue;\n}>;\n\nexport function pgClient(\n lc: LogContext,\n connectionURI: string,\n // A descriptive name for the code that is making the connection, so that we\n // can debug things like deadlocks.\n applicationName: string,\n options?: postgres.Options<{\n bigint: PostgresType<bigint>;\n json: PostgresType<JSONValue>;\n }>,\n opts?: TypeOptions,\n): PostgresDB {\n applicationName = `zero-${applicationName}`;\n\n const onnotice = (n: Notice) => {\n // https://www.postgresql.org/docs/current/plpgsql-errors-and-messages.html#PLPGSQL-STATEMENTS-RAISE\n switch (n.severity) {\n case 'NOTICE':\n return; // silenced\n case 'DEBUG':\n lc.debug?.(n);\n return;\n case 'WARNING':\n lc.warn?.(n);\n return;\n case 'EXCEPTION':\n lc.error?.(n);\n return;\n case 'LOG':\n case 'INFO':\n default:\n lc.info?.(n);\n }\n };\n const url = new URL(connectionURI);\n const sslFlag =\n url.searchParams.get('ssl') ?? url.searchParams.get('sslmode') ?? 'prefer';\n\n let ssl: boolean | 'prefer' | {rejectUnauthorized: boolean};\n if (sslFlag === 'disable' || sslFlag === 'false') {\n ssl = false;\n } else if (sslFlag === 'no-verify') {\n ssl = {rejectUnauthorized: false};\n } else {\n ssl = sslFlag as 'prefer';\n }\n\n // Set connections to expire between 5 and 10 minutes to free up state on PG.\n const maxLifetimeSeconds = randInt(5 * 60, 10 * 60);\n const providedConnection = options?.connection ?? {};\n\n return postgres(connectionURI, {\n ...postgresTypeConfig(opts),\n onnotice,\n ['max_lifetime']: maxLifetimeSeconds,\n ssl,\n ...options,\n connection: {\n ...providedConnection,\n ['application_name']: applicationName,\n },\n });\n}\n\nexport async function connectPgClient(\n ...args: Parameters<typeof pgClient>\n): Promise<PostgresDB> {\n const db = pgClient(...args);\n try {\n await db`SELECT 1`.simple().execute();\n return db;\n } catch (e) {\n if (isPostgresConfigError(e)) {\n throw new ConfigurationError(\n 'Unable to connect to Postgres. Check your database configuration.',\n {cause: e},\n );\n }\n throw e;\n }\n}\n\n/**\n * Disables any statement_timeout for the current transaction. By default,\n * Postgres does not impose a statement timeout, but some users and providers\n * set one at the database level (even though it is explicitly discouraged by\n * the Postgres documentation).\n *\n * Zero logic in particular often does not fit into the category of general\n * application logic; for potentially long-running operations like migrations\n * and background cleanup, the statement timeout should be disabled to prevent\n * these operations from timing out.\n */\nexport function disableStatementTimeout(sql: PostgresTransaction) {\n void sql`SET LOCAL statement_timeout = 0;`.execute().catch(() => {});\n}\n\nexport const typeNameByOID: Record<number, string> = Object.freeze(\n Object.fromEntries(\n Object.entries(OID).map(([name, oid]) => [\n oid,\n name.startsWith('_') ? `${name.substring(1)}[]` : name,\n ]),\n ),\n);\n\nexport function isPostgresError(e: unknown, ...codes: [string, ...string[]]) {\n return e instanceof postgres.PostgresError && codes.includes(e.code);\n}\n\nfunction isPostgresStartupConnectionError(e: unknown) {\n if (!(e instanceof Error) || !('code' in e)) {\n return false;\n }\n const {code} = e;\n // postgres.js throws plain Error instances for connection failures that\n // happen before Postgres can return a SQLSTATE. These are still startup\n // configuration problems for Zero: wrong host/port, unreachable DB, DNS,\n // TLS certificate mismatch, etc.\n return (\n typeof code === 'string' &&\n [\n 'CONNECT_TIMEOUT',\n 'ECONNREFUSED',\n 'ECONNRESET',\n 'EHOSTUNREACH',\n 'ENETUNREACH',\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'ETIMEDOUT',\n 'ERR_TLS_CERT_ALTNAME_INVALID',\n 'SELF_SIGNED_CERT_IN_CHAIN',\n 'DEPTH_ZERO_SELF_SIGNED_CERT',\n 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n ].includes(code)\n );\n}\n\nexport function isPostgresConfigError(e: unknown) {\n return (\n isPostgresStartupConnectionError(e) ||\n isPostgresError(\n e,\n PG_CONNECTION_EXCEPTION,\n PG_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION,\n PG_CONNECTION_DOES_NOT_EXIST,\n PG_CONNECTION_FAILURE,\n PG_INVALID_AUTHORIZATION_SPECIFICATION,\n PG_INVALID_PASSWORD,\n PG_INVALID_CATALOG_NAME,\n PG_INSUFFICIENT_PRIVILEGE,\n PG_TOO_MANY_CONNECTIONS,\n PG_CONFIGURATION_LIMIT_EXCEEDED,\n )\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"union-fan-in.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/union-fan-in.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AAMvB,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAQ,KAAK,MAAM,EAAC,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAEpD,qBAAa,UAAW,YAAW,QAAQ;;gBAO7B,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE;IAiEhD,OAAO,IAAI,IAAI;IAMf,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"union-fan-in.d.ts","sourceRoot":"","sources":["../../../../../zql/src/ivm/union-fan-in.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAExC,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AACpC,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AAMvB,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAQ,KAAK,MAAM,EAAC,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oBAAoB,CAAC;AAEpD,qBAAa,UAAW,YAAW,QAAQ;;gBAO7B,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE;IAiEhD,OAAO,IAAI,IAAI;IAMf,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC;IAShD,SAAS,IAAI,YAAY;IAIxB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC;IAqEzD,oBAAoB;IAQnB,iBAAiB,CAAC,gBAAgB,EAAE,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC;IA0BjE,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAGhC;AAED,wBAAiB,YAAY,CAC3B,OAAO,EAAE,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,EAAE,EACnC,UAAU,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,KAAK,MAAM,GACvC,gBAAgB,CAAC,IAAI,GAAG,OAAO,CAAC,CAuElC"}
|
|
@@ -47,7 +47,9 @@ var UnionFanIn = class {
|
|
|
47
47
|
for (const input of this.#inputs) input.destroy();
|
|
48
48
|
}
|
|
49
49
|
fetch(req) {
|
|
50
|
-
|
|
50
|
+
const iterables = this.#inputs.map((input) => input.fetch(req));
|
|
51
|
+
const compareRows = this.#schema.compareRows;
|
|
52
|
+
return mergeFetches(iterables, req.reverse ? (l, r) => compareRows(r.row, l.row) : (l, r) => compareRows(l.row, r.row));
|
|
51
53
|
}
|
|
52
54
|
getSchema() {
|
|
53
55
|
return this.#schema;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"union-fan-in.js","names":["#inputs","#schema","#fanOutPushStarted","#pushInternalChange","#accumulatedPushes","#output"],"sources":["../../../../../zql/src/ivm/union-fan-in.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {Writable} from '../../../shared/src/writable.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport type {Constraint} from './constraint.ts';\nimport type {Node} from './data.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Operator,\n type Output,\n} from './operator.ts';\nimport {\n makeAddEmptyRelationships,\n mergeRelationships,\n pushAccumulatedChanges,\n} from './push-accumulated.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {first, type Stream} from './stream.ts';\nimport type {UnionFanOut} from './union-fan-out.ts';\n\nexport class UnionFanIn implements Operator {\n readonly #inputs: readonly Input[];\n readonly #schema: SourceSchema;\n #fanOutPushStarted: boolean = false;\n #output: Output = throwOutput;\n #accumulatedPushes: Change[] = [];\n\n constructor(fanOut: UnionFanOut, inputs: Input[]) {\n this.#inputs = inputs;\n const fanOutSchema = fanOut.getSchema();\n fanOut.setFanIn(this);\n assert(fanOutSchema.sort !== undefined, 'UnionFanIn requires sorted input');\n\n const schema: Writable<SourceSchema> = {\n tableName: fanOutSchema.tableName,\n columns: fanOutSchema.columns,\n primaryKey: fanOutSchema.primaryKey,\n relationships: {\n ...fanOutSchema.relationships,\n },\n isHidden: fanOutSchema.isHidden,\n system: fanOutSchema.system,\n compareRows: fanOutSchema.compareRows,\n sort: fanOutSchema.sort,\n };\n\n // now go through inputs and merge relationships\n const relationshipsFromBranches: Set<string> = new Set();\n for (const input of inputs) {\n const inputSchema = input.getSchema();\n assert(\n schema.tableName === inputSchema.tableName,\n `Table name mismatch in union fan-in: ${schema.tableName} !== ${inputSchema.tableName}`,\n );\n assert(\n schema.primaryKey === inputSchema.primaryKey,\n `Primary key mismatch in union fan-in`,\n );\n assert(\n schema.system === inputSchema.system,\n `System mismatch in union fan-in: ${schema.system} !== ${inputSchema.system}`,\n );\n assert(\n schema.compareRows === inputSchema.compareRows,\n `compareRows mismatch in union fan-in`,\n );\n assert(schema.sort === inputSchema.sort, `Sort mismatch in union fan-in`);\n\n for (const [relName, relSchema] of Object.entries(\n inputSchema.relationships,\n )) {\n if (relName in fanOutSchema.relationships) {\n continue;\n }\n\n // All branches will have unique relationship names except for relationships\n // that come in from `fanOut`.\n assert(\n !relationshipsFromBranches.has(relName),\n `Relationship ${relName} exists in multiple upstream inputs to union fan-in`,\n );\n schema.relationships[relName] = relSchema;\n relationshipsFromBranches.add(relName);\n }\n\n input.setOutput(this);\n }\n\n this.#schema = schema;\n this.#inputs = inputs;\n }\n\n destroy(): void {\n for (const input of this.#inputs) {\n input.destroy();\n }\n }\n\n fetch(req: FetchRequest): Stream<Node | 'yield'> {\n const iterables = this.#inputs.map(input => input.fetch(req));\n return mergeFetches(iterables, (l, r) =>\n this.#schema.compareRows(l.row, r.row),\n );\n }\n\n getSchema(): SourceSchema {\n return this.#schema;\n }\n\n *push(change: Change, pusher: InputBase): Stream<'yield'> {\n if (!this.#fanOutPushStarted) {\n yield* this.#pushInternalChange(change, pusher);\n } else {\n this.#accumulatedPushes.push(change);\n }\n }\n\n /**\n * An internal change means that a change was received inside the fan-out/fan-in sub-graph.\n *\n * These changes always come from children of a flip-join as no other push generating operators\n * currently exist between union-fan-in and union-fan-out. All other pushes\n * enter into union-fan-out before reaching union-fan-in.\n *\n * - normal joins for `exists` come before `union-fan-out`\n * - joins for `related` come after `union-fan-out`\n * - take comes after `union-fan-out`\n *\n * The algorithm for deciding whether or not to forward a push that came from inside the ufo/ufi sub-graph:\n * 1. If the change is a `child` change we can forward it. This is because all child branches in the ufo/ufi sub-graph are unique.\n * 2. If the change is `add` we can forward it iff no `fetches` for the row return any results.\n * If another branch has it, the add was already emitted in the past.\n * 3. If the change is `remove` we can forward it iff no `fetches` for the row return any results.\n * If no other branches have the change, the remove can be sent as the value is no longer present.\n * If other branches have it, the last branch the processes the remove will send the remove.\n * 4. Edits will always come through as child changes as flip join will flip them into children.\n * An edit that would result in a remove or add will have been split into an add/remove pair rather than being an edit.\n */\n *#pushInternalChange(change: Change, pusher: InputBase): Stream<'yield'> {\n if (change[ChangeIndex.TYPE] === ChangeType.CHILD) {\n yield* this.#output.push(change, this);\n return;\n }\n\n assert(\n change[ChangeIndex.TYPE] === ChangeType.ADD ||\n change[ChangeIndex.TYPE] === ChangeType.REMOVE,\n () =>\n `UnionFanIn: expected add or remove change type, got ${change[ChangeIndex.TYPE]}`,\n );\n\n let hadMatch = false;\n for (const input of this.#inputs) {\n if (input === pusher) {\n hadMatch = true;\n continue;\n }\n\n const constraint: Writable<Constraint> = {};\n for (const key of this.#schema.primaryKey) {\n constraint[key] = change[ChangeIndex.NODE].row[key];\n }\n const fetchResult = input.fetch({\n constraint,\n });\n\n if (first(fetchResult) !== undefined) {\n // Another branch has the row, so the add/remove is not needed.\n return;\n }\n }\n\n assert(hadMatch, 'Pusher was not one of the inputs to union-fan-in!');\n\n // No other branches have the row, so we can push the change.\n yield* this.#output.push(change, this);\n }\n\n fanOutStartedPushing() {\n assert(\n this.#fanOutPushStarted === false,\n 'UnionFanIn: fanOutStartedPushing called while already pushing',\n );\n this.#fanOutPushStarted = true;\n }\n\n *fanOutDonePushing(fanOutChangeType: ChangeType): Stream<'yield'> {\n assert(\n this.#fanOutPushStarted,\n 'UnionFanIn: fanOutDonePushing called without fanOutStartedPushing',\n );\n this.#fanOutPushStarted = false;\n if (this.#inputs.length === 0) {\n return;\n }\n\n if (this.#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 yield* pushAccumulatedChanges(\n this.#accumulatedPushes,\n this.#output,\n this,\n fanOutChangeType,\n mergeRelationships,\n makeAddEmptyRelationships(this.#schema),\n );\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n}\n\nexport function* mergeFetches(\n fetches: Iterable<Node | 'yield'>[],\n comparator: (l: Node, r: Node) => number,\n): IterableIterator<Node | 'yield'> {\n const iterators = fetches.map(i => i[Symbol.iterator]());\n let threw = false;\n try {\n const current: (Node | null)[] = [];\n let lastNodeYielded: Node | undefined;\n for (let i = 0; i < iterators.length; i++) {\n const iter = iterators[i];\n let result = iter.next();\n // yield yields when initializing\n while (!result.done && result.value === 'yield') {\n yield result.value;\n result = iter.next();\n }\n current[i] = result.done ? null : (result.value as Node);\n }\n while (current.some(c => c !== null)) {\n const min = current.reduce(\n (acc: [Node, number] | undefined, c, i): [Node, number] | undefined => {\n if (c === null) {\n return acc;\n }\n if (acc === undefined || comparator(c, acc[0]) < 0) {\n return [c, i];\n }\n return acc;\n },\n undefined,\n );\n\n assert(min !== undefined, 'min is undefined');\n const [minNode, minIndex] = min;\n const iter = iterators[minIndex];\n let result = iter.next();\n while (!result.done && result.value === 'yield') {\n yield result.value;\n result = iter.next();\n }\n current[minIndex] = result.done ? null : (result.value as Node);\n if (\n lastNodeYielded !== undefined &&\n comparator(lastNodeYielded, minNode) === 0\n ) {\n continue;\n }\n lastNodeYielded = minNode;\n yield minNode;\n }\n } catch (e) {\n threw = true;\n for (const iter of iterators) {\n try {\n iter.throw?.(e);\n } catch (_cleanupError) {\n // error in the iter.throw cleanup,\n // catch so other iterators are cleaned up\n }\n }\n throw e;\n } finally {\n if (!threw) {\n for (const iter of iterators) {\n try {\n iter.return?.();\n } catch (_cleanupError) {\n // error in the iter.return cleanup,\n // catch so other iterators are cleaned up\n }\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"union-fan-in.js","names":["#inputs","#schema","#fanOutPushStarted","#pushInternalChange","#accumulatedPushes","#output"],"sources":["../../../../../zql/src/ivm/union-fan-in.ts"],"sourcesContent":["import {assert} from '../../../shared/src/asserts.ts';\nimport type {Writable} from '../../../shared/src/writable.ts';\nimport {ChangeIndex} from './change-index.ts';\nimport {ChangeType} from './change-type.ts';\nimport type {Change} from './change.ts';\nimport type {Constraint} from './constraint.ts';\nimport type {Node} from './data.ts';\nimport {\n throwOutput,\n type FetchRequest,\n type Input,\n type InputBase,\n type Operator,\n type Output,\n} from './operator.ts';\nimport {\n makeAddEmptyRelationships,\n mergeRelationships,\n pushAccumulatedChanges,\n} from './push-accumulated.ts';\nimport type {SourceSchema} from './schema.ts';\nimport {first, type Stream} from './stream.ts';\nimport type {UnionFanOut} from './union-fan-out.ts';\n\nexport class UnionFanIn implements Operator {\n readonly #inputs: readonly Input[];\n readonly #schema: SourceSchema;\n #fanOutPushStarted: boolean = false;\n #output: Output = throwOutput;\n #accumulatedPushes: Change[] = [];\n\n constructor(fanOut: UnionFanOut, inputs: Input[]) {\n this.#inputs = inputs;\n const fanOutSchema = fanOut.getSchema();\n fanOut.setFanIn(this);\n assert(fanOutSchema.sort !== undefined, 'UnionFanIn requires sorted input');\n\n const schema: Writable<SourceSchema> = {\n tableName: fanOutSchema.tableName,\n columns: fanOutSchema.columns,\n primaryKey: fanOutSchema.primaryKey,\n relationships: {\n ...fanOutSchema.relationships,\n },\n isHidden: fanOutSchema.isHidden,\n system: fanOutSchema.system,\n compareRows: fanOutSchema.compareRows,\n sort: fanOutSchema.sort,\n };\n\n // now go through inputs and merge relationships\n const relationshipsFromBranches: Set<string> = new Set();\n for (const input of inputs) {\n const inputSchema = input.getSchema();\n assert(\n schema.tableName === inputSchema.tableName,\n `Table name mismatch in union fan-in: ${schema.tableName} !== ${inputSchema.tableName}`,\n );\n assert(\n schema.primaryKey === inputSchema.primaryKey,\n `Primary key mismatch in union fan-in`,\n );\n assert(\n schema.system === inputSchema.system,\n `System mismatch in union fan-in: ${schema.system} !== ${inputSchema.system}`,\n );\n assert(\n schema.compareRows === inputSchema.compareRows,\n `compareRows mismatch in union fan-in`,\n );\n assert(schema.sort === inputSchema.sort, `Sort mismatch in union fan-in`);\n\n for (const [relName, relSchema] of Object.entries(\n inputSchema.relationships,\n )) {\n if (relName in fanOutSchema.relationships) {\n continue;\n }\n\n // All branches will have unique relationship names except for relationships\n // that come in from `fanOut`.\n assert(\n !relationshipsFromBranches.has(relName),\n `Relationship ${relName} exists in multiple upstream inputs to union fan-in`,\n );\n schema.relationships[relName] = relSchema;\n relationshipsFromBranches.add(relName);\n }\n\n input.setOutput(this);\n }\n\n this.#schema = schema;\n this.#inputs = inputs;\n }\n\n destroy(): void {\n for (const input of this.#inputs) {\n input.destroy();\n }\n }\n\n fetch(req: FetchRequest): Stream<Node | 'yield'> {\n const iterables = this.#inputs.map(input => input.fetch(req));\n const compareRows = this.#schema.compareRows;\n const compare = req.reverse\n ? (l: Node, r: Node) => compareRows(r.row, l.row)\n : (l: Node, r: Node) => compareRows(l.row, r.row);\n return mergeFetches(iterables, compare);\n }\n\n getSchema(): SourceSchema {\n return this.#schema;\n }\n\n *push(change: Change, pusher: InputBase): Stream<'yield'> {\n if (!this.#fanOutPushStarted) {\n yield* this.#pushInternalChange(change, pusher);\n } else {\n this.#accumulatedPushes.push(change);\n }\n }\n\n /**\n * An internal change means that a change was received inside the fan-out/fan-in sub-graph.\n *\n * These changes always come from children of a flip-join as no other push generating operators\n * currently exist between union-fan-in and union-fan-out. All other pushes\n * enter into union-fan-out before reaching union-fan-in.\n *\n * - normal joins for `exists` come before `union-fan-out`\n * - joins for `related` come after `union-fan-out`\n * - take comes after `union-fan-out`\n *\n * The algorithm for deciding whether or not to forward a push that came from inside the ufo/ufi sub-graph:\n * 1. If the change is a `child` change we can forward it. This is because all child branches in the ufo/ufi sub-graph are unique.\n * 2. If the change is `add` we can forward it iff no `fetches` for the row return any results.\n * If another branch has it, the add was already emitted in the past.\n * 3. If the change is `remove` we can forward it iff no `fetches` for the row return any results.\n * If no other branches have the change, the remove can be sent as the value is no longer present.\n * If other branches have it, the last branch the processes the remove will send the remove.\n * 4. Edits will always come through as child changes as flip join will flip them into children.\n * An edit that would result in a remove or add will have been split into an add/remove pair rather than being an edit.\n */\n *#pushInternalChange(change: Change, pusher: InputBase): Stream<'yield'> {\n if (change[ChangeIndex.TYPE] === ChangeType.CHILD) {\n yield* this.#output.push(change, this);\n return;\n }\n\n assert(\n change[ChangeIndex.TYPE] === ChangeType.ADD ||\n change[ChangeIndex.TYPE] === ChangeType.REMOVE,\n () =>\n `UnionFanIn: expected add or remove change type, got ${change[ChangeIndex.TYPE]}`,\n );\n\n let hadMatch = false;\n for (const input of this.#inputs) {\n if (input === pusher) {\n hadMatch = true;\n continue;\n }\n\n const constraint: Writable<Constraint> = {};\n for (const key of this.#schema.primaryKey) {\n constraint[key] = change[ChangeIndex.NODE].row[key];\n }\n const fetchResult = input.fetch({\n constraint,\n });\n\n if (first(fetchResult) !== undefined) {\n // Another branch has the row, so the add/remove is not needed.\n return;\n }\n }\n\n assert(hadMatch, 'Pusher was not one of the inputs to union-fan-in!');\n\n // No other branches have the row, so we can push the change.\n yield* this.#output.push(change, this);\n }\n\n fanOutStartedPushing() {\n assert(\n this.#fanOutPushStarted === false,\n 'UnionFanIn: fanOutStartedPushing called while already pushing',\n );\n this.#fanOutPushStarted = true;\n }\n\n *fanOutDonePushing(fanOutChangeType: ChangeType): Stream<'yield'> {\n assert(\n this.#fanOutPushStarted,\n 'UnionFanIn: fanOutDonePushing called without fanOutStartedPushing',\n );\n this.#fanOutPushStarted = false;\n if (this.#inputs.length === 0) {\n return;\n }\n\n if (this.#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 yield* pushAccumulatedChanges(\n this.#accumulatedPushes,\n this.#output,\n this,\n fanOutChangeType,\n mergeRelationships,\n makeAddEmptyRelationships(this.#schema),\n );\n }\n\n setOutput(output: Output): void {\n this.#output = output;\n }\n}\n\nexport function* mergeFetches(\n fetches: Iterable<Node | 'yield'>[],\n comparator: (l: Node, r: Node) => number,\n): IterableIterator<Node | 'yield'> {\n const iterators = fetches.map(i => i[Symbol.iterator]());\n let threw = false;\n try {\n const current: (Node | null)[] = [];\n let lastNodeYielded: Node | undefined;\n for (let i = 0; i < iterators.length; i++) {\n const iter = iterators[i];\n let result = iter.next();\n // yield yields when initializing\n while (!result.done && result.value === 'yield') {\n yield result.value;\n result = iter.next();\n }\n current[i] = result.done ? null : (result.value as Node);\n }\n while (current.some(c => c !== null)) {\n const min = current.reduce(\n (acc: [Node, number] | undefined, c, i): [Node, number] | undefined => {\n if (c === null) {\n return acc;\n }\n if (acc === undefined || comparator(c, acc[0]) < 0) {\n return [c, i];\n }\n return acc;\n },\n undefined,\n );\n\n assert(min !== undefined, 'min is undefined');\n const [minNode, minIndex] = min;\n const iter = iterators[minIndex];\n let result = iter.next();\n while (!result.done && result.value === 'yield') {\n yield result.value;\n result = iter.next();\n }\n current[minIndex] = result.done ? null : (result.value as Node);\n if (\n lastNodeYielded !== undefined &&\n comparator(lastNodeYielded, minNode) === 0\n ) {\n continue;\n }\n lastNodeYielded = minNode;\n yield minNode;\n }\n } catch (e) {\n threw = true;\n for (const iter of iterators) {\n try {\n iter.throw?.(e);\n } catch (_cleanupError) {\n // error in the iter.throw cleanup,\n // catch so other iterators are cleaned up\n }\n }\n throw e;\n } finally {\n if (!threw) {\n for (const iter of iterators) {\n try {\n iter.return?.();\n } catch (_cleanupError) {\n // error in the iter.return cleanup,\n // catch so other iterators are cleaned up\n }\n }\n }\n }\n}\n"],"mappings":"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"}
|