easy-ping 0.2.0 → 0.3.0
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/dist/{adapter-f0OR2DhY.d.cts → adapter-BfLe2J0t.d.cts} +10 -0
- package/dist/{adapter-f0OR2DhY.d.ts → adapter-BfLe2J0t.d.ts} +10 -0
- package/dist/adapters/drizzle.cjs +6 -2
- package/dist/adapters/drizzle.cjs.map +1 -1
- package/dist/adapters/drizzle.d.cts +1 -1
- package/dist/adapters/drizzle.d.ts +1 -1
- package/dist/adapters/drizzle.js +6 -2
- package/dist/adapters/drizzle.js.map +1 -1
- package/dist/adapters/mongodb.cjs +3 -2
- package/dist/adapters/mongodb.cjs.map +1 -1
- package/dist/adapters/mongodb.d.cts +2 -2
- package/dist/adapters/mongodb.d.ts +2 -2
- package/dist/adapters/mongodb.js +3 -2
- package/dist/adapters/mongodb.js.map +1 -1
- package/dist/adapters/postgres.cjs +134 -2
- package/dist/adapters/postgres.cjs.map +1 -1
- package/dist/adapters/postgres.d.cts +45 -2
- package/dist/adapters/postgres.d.ts +45 -2
- package/dist/adapters/postgres.js +37 -4
- package/dist/adapters/postgres.js.map +1 -1
- package/dist/chunk-RBDW4S6F.js +199 -0
- package/dist/chunk-RBDW4S6F.js.map +1 -0
- package/dist/client.cjs +3 -5
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +3 -5
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +17 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +17 -11
- package/dist/index.js.map +1 -1
- package/dist/{plugin-bTJKmnlq.d.ts → plugin-CmPOFVmn.d.ts} +1 -1
- package/dist/{plugin-Bqj9xAOS.d.cts → plugin-f5KaFWSN.d.cts} +1 -1
- package/dist/plugins/digests.d.cts +2 -2
- package/dist/plugins/digests.d.ts +2 -2
- package/dist/plugins/preferences-client.d.cts +2 -2
- package/dist/plugins/preferences-client.d.ts +2 -2
- package/dist/plugins/preferences.d.cts +2 -2
- package/dist/plugins/preferences.d.ts +2 -2
- package/dist/plugins/push.cjs +25 -23
- package/dist/plugins/push.cjs.map +1 -1
- package/dist/plugins/push.d.cts +59 -3
- package/dist/plugins/push.d.ts +59 -3
- package/dist/plugins/push.js +25 -24
- package/dist/plugins/push.js.map +1 -1
- package/dist/providers/web-push.d.cts +2 -2
- package/dist/providers/web-push.d.ts +2 -2
- package/dist/react.cjs +3 -5
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +3 -5
- package/dist/react.js.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.ts +2 -2
- package/dist/schema.js +1 -197
- package/dist/schema.js.map +1 -1
- package/dist/testing.cjs +29 -0
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +29 -0
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -22,6 +22,62 @@ function decodeBase64Url(value) {
|
|
|
22
22
|
|
|
23
23
|
// src/schema/declaration.ts
|
|
24
24
|
var toSnakeCase = (value) => value.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
25
|
+
var coreSchema = {
|
|
26
|
+
notification: {
|
|
27
|
+
tableName: "notification",
|
|
28
|
+
fields: {
|
|
29
|
+
id: { type: "string", required: true },
|
|
30
|
+
userId: { type: "string", required: true },
|
|
31
|
+
type: { type: "string", required: true },
|
|
32
|
+
payload: { type: "json", required: true },
|
|
33
|
+
actorId: { type: "string" },
|
|
34
|
+
groupKey: { type: "string" },
|
|
35
|
+
dedupeKey: { type: "string" },
|
|
36
|
+
seenAt: { type: "date" },
|
|
37
|
+
readAt: { type: "date" },
|
|
38
|
+
archivedAt: { type: "date" },
|
|
39
|
+
createdAt: { type: "date", required: true, defaultNow: true }
|
|
40
|
+
},
|
|
41
|
+
primaryKey: ["id"],
|
|
42
|
+
indexes: [
|
|
43
|
+
{ on: ["userId", "createdAt"], name: "notification_feed_idx" },
|
|
44
|
+
{ on: ["userId", "groupKey"], name: "notification_group_idx" },
|
|
45
|
+
{ on: ["userId", "dedupeKey"], unique: true, name: "notification_dedupe_idx" }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
notificationDelivery: {
|
|
49
|
+
tableName: "notification_delivery",
|
|
50
|
+
fields: {
|
|
51
|
+
id: { type: "string", required: true },
|
|
52
|
+
notificationId: { type: "string", required: true },
|
|
53
|
+
channel: { type: "string", required: true },
|
|
54
|
+
status: { type: "string", required: true, default: "pending" },
|
|
55
|
+
attempts: { type: "number", required: true, default: 0 },
|
|
56
|
+
maxAttempts: { type: "number", required: true, default: 5 },
|
|
57
|
+
notBefore: { type: "date", required: true, defaultNow: true },
|
|
58
|
+
claimedAt: { type: "date" },
|
|
59
|
+
claimedBy: { type: "string" },
|
|
60
|
+
lastError: { type: "string" },
|
|
61
|
+
updatedAt: { type: "date", required: true, defaultNow: true }
|
|
62
|
+
},
|
|
63
|
+
primaryKey: ["id"],
|
|
64
|
+
indexes: [
|
|
65
|
+
{ on: ["status", "notBefore"], name: "delivery_claim_idx" },
|
|
66
|
+
{ on: ["notificationId"], name: "delivery_notification_idx" }
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
notificationPreference: {
|
|
70
|
+
tableName: "notification_preference",
|
|
71
|
+
fields: {
|
|
72
|
+
userId: { type: "string", required: true },
|
|
73
|
+
type: { type: "string", required: true },
|
|
74
|
+
channel: { type: "string", required: true },
|
|
75
|
+
enabled: { type: "boolean", required: true, default: true },
|
|
76
|
+
frequency: { type: "string", required: true, default: "instant" }
|
|
77
|
+
},
|
|
78
|
+
primaryKey: ["userId", "type", "channel"]
|
|
79
|
+
}
|
|
80
|
+
};
|
|
25
81
|
|
|
26
82
|
// src/core/store.ts
|
|
27
83
|
var OPERATORS = ["in", "lt", "lte", "gt", "gte", "not"];
|
|
@@ -231,7 +287,7 @@ function postgresAdapter(query, options = {}) {
|
|
|
231
287
|
if (releases.length === 0) return;
|
|
232
288
|
const now = /* @__PURE__ */ new Date();
|
|
233
289
|
const statement = new Statement().raw(
|
|
234
|
-
`UPDATE ${DELIVERY} d SET status = CASE WHEN v.result = 'sent' THEN 'sent' WHEN v.retryable AND d.attempts + 1 < d.max_attempts THEN 'pending' ELSE 'failed' END, attempts = CASE WHEN v.result
|
|
290
|
+
`UPDATE ${DELIVERY} d SET status = CASE WHEN v.result = 'sent' THEN 'sent' WHEN v.result = 'skipped' THEN 'skipped' WHEN v.retryable AND d.attempts + 1 < d.max_attempts THEN 'pending' ELSE 'failed' END, attempts = CASE WHEN v.result IN ('sent', 'skipped') THEN d.attempts ELSE d.attempts + 1 END, last_error = v.error, not_before = COALESCE(v.not_before, d.not_before), claimed_at = NULL, claimed_by = NULL, updated_at = `
|
|
235
291
|
);
|
|
236
292
|
statement.value(now).raw("::timestamptz FROM (VALUES ");
|
|
237
293
|
releases.forEach(({ id, outcome, nextAttemptAt }, index) => {
|
|
@@ -240,7 +296,10 @@ function postgresAdapter(query, options = {}) {
|
|
|
240
296
|
[id, "text"],
|
|
241
297
|
[outcome.result, "text"],
|
|
242
298
|
[outcome.result === "failed" && outcome.retryable, "boolean"],
|
|
243
|
-
[
|
|
299
|
+
[
|
|
300
|
+
outcome.result === "failed" ? outcome.error.slice(0, 2e3) : outcome.result === "skipped" ? outcome.reason.slice(0, 2e3) : null,
|
|
301
|
+
"text"
|
|
302
|
+
],
|
|
244
303
|
[nextAttemptAt ?? null, "timestamptz"]
|
|
245
304
|
]);
|
|
246
305
|
});
|
|
@@ -347,6 +406,79 @@ function postgresAdapter(query, options = {}) {
|
|
|
347
406
|
};
|
|
348
407
|
}
|
|
349
408
|
|
|
409
|
+
// src/schema/render-sql.ts
|
|
410
|
+
var SQL_TYPE = {
|
|
411
|
+
string: "text",
|
|
412
|
+
number: "integer",
|
|
413
|
+
boolean: "boolean",
|
|
414
|
+
date: "timestamptz",
|
|
415
|
+
json: "jsonb"
|
|
416
|
+
};
|
|
417
|
+
var quote2 = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
418
|
+
var defaultClause = (field) => {
|
|
419
|
+
if (field.defaultNow) return " DEFAULT now()";
|
|
420
|
+
if (field.default === void 0) return "";
|
|
421
|
+
if (typeof field.default === "string") return ` DEFAULT '${field.default.replace(/'/g, "''")}'`;
|
|
422
|
+
return ` DEFAULT ${field.default}`;
|
|
423
|
+
};
|
|
424
|
+
function renderPostgresDdl(schema, prefix = "") {
|
|
425
|
+
const statements = [];
|
|
426
|
+
for (const table of Object.values(schema)) {
|
|
427
|
+
const name = quote2(prefix + table.tableName);
|
|
428
|
+
const columns = Object.entries(table.fields).map(([field, spec]) => {
|
|
429
|
+
const nullability = spec.required ? " NOT NULL" : "";
|
|
430
|
+
return ` ${quote2(toSnakeCase(field))} ${SQL_TYPE[spec.type]}${nullability}${defaultClause(spec)}`;
|
|
431
|
+
});
|
|
432
|
+
if (table.primaryKey?.length) {
|
|
433
|
+
const key = table.primaryKey.map((field) => quote2(toSnakeCase(field))).join(", ");
|
|
434
|
+
columns.push(` PRIMARY KEY (${key})`);
|
|
435
|
+
}
|
|
436
|
+
statements.push(`CREATE TABLE IF NOT EXISTS ${name} (
|
|
437
|
+
${columns.join(",\n")}
|
|
438
|
+
)`);
|
|
439
|
+
for (const declaration of table.indexes ?? []) {
|
|
440
|
+
const indexName = quote2(
|
|
441
|
+
prefix + (declaration.name ?? `${table.tableName}_${declaration.on.join("_")}_idx`)
|
|
442
|
+
);
|
|
443
|
+
const columns2 = declaration.on.map((field) => quote2(toSnakeCase(field))).join(", ");
|
|
444
|
+
const unique = declaration.unique ? "UNIQUE " : "";
|
|
445
|
+
statements.push(`CREATE ${unique}INDEX IF NOT EXISTS ${indexName} ON ${name} (${columns2})`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return statements;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/adapters/postgres/helpers.ts
|
|
452
|
+
function pgTransaction(pool) {
|
|
453
|
+
return async (fn) => {
|
|
454
|
+
const client = await pool.connect();
|
|
455
|
+
try {
|
|
456
|
+
await client.query("BEGIN");
|
|
457
|
+
const result = await fn(async (text, params) => (await client.query(text, params)).rows);
|
|
458
|
+
await client.query("COMMIT");
|
|
459
|
+
return result;
|
|
460
|
+
} catch (error) {
|
|
461
|
+
await client.query("ROLLBACK");
|
|
462
|
+
throw error;
|
|
463
|
+
} finally {
|
|
464
|
+
client.release();
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
async function createPostgresTables(query, options = {}) {
|
|
469
|
+
const schemas = [coreSchema, ...options.plugins ?? []];
|
|
470
|
+
let count = 0;
|
|
471
|
+
for (const schema of schemas) {
|
|
472
|
+
for (const statement of renderPostgresDdl(schema, options.prefix)) {
|
|
473
|
+
await query(statement, []);
|
|
474
|
+
count += 1;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return count;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
exports.createPostgresTables = createPostgresTables;
|
|
481
|
+
exports.pgTransaction = pgTransaction;
|
|
350
482
|
exports.postgresAdapter = postgresAdapter;
|
|
351
483
|
//# sourceMappingURL=postgres.cjs.map
|
|
352
484
|
//# sourceMappingURL=postgres.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/base64url.ts","../../src/schema/declaration.ts","../../src/core/store.ts","../../src/adapters/postgres/statement.ts","../../src/adapters/postgres/adapter.ts"],"names":[],"mappings":"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","file":"postgres.cjs","sourcesContent":["// Web APIs, not Buffer: this runs on Workers and Vercel Edge.\n\nexport function encodeBase64Url(input: string | Uint8Array): string {\n const bytes = typeof input === \"string\" ? new TextEncoder().encode(input) : input;\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n// Uint8Array<ArrayBuffer>: bare Uint8Array widens and is not a BufferSource.\nexport function decodeBase64UrlBytes(value: string): Uint8Array<ArrayBuffer> | null {\n try {\n const binary = atob(value.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n } catch {\n return null;\n }\n}\n\nexport function decodeBase64Url(value: string): string | null {\n const bytes = decodeBase64UrlBytes(value);\n return bytes ? new TextDecoder().decode(bytes) : null;\n}\n","import type { SchemaDeclaration } from \"../core/plugin\";\n\nexport const toSnakeCase = (value: string) => value.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);\n\n/**\n * Core tables in the format plugins use. Less expressive than the hand-written\n * Drizzle schema (no partial indexes, ordering or cascade) — a drift test\n * asserts the two agree on the fields they share.\n */\nexport const coreSchema = {\n notification: {\n tableName: \"notification\",\n fields: {\n id: { type: \"string\", required: true },\n userId: { type: \"string\", required: true },\n type: { type: \"string\", required: true },\n payload: { type: \"json\", required: true },\n actorId: { type: \"string\" },\n groupKey: { type: \"string\" },\n dedupeKey: { type: \"string\" },\n seenAt: { type: \"date\" },\n readAt: { type: \"date\" },\n archivedAt: { type: \"date\" },\n createdAt: { type: \"date\", required: true, defaultNow: true },\n },\n primaryKey: [\"id\"],\n indexes: [\n { on: [\"userId\", \"createdAt\"], name: \"notification_feed_idx\" },\n { on: [\"userId\", \"groupKey\"], name: \"notification_group_idx\" },\n { on: [\"userId\", \"dedupeKey\"], unique: true, name: \"notification_dedupe_idx\" },\n ],\n },\n\n notificationDelivery: {\n tableName: \"notification_delivery\",\n fields: {\n id: { type: \"string\", required: true },\n notificationId: { type: \"string\", required: true },\n channel: { type: \"string\", required: true },\n status: { type: \"string\", required: true, default: \"pending\" },\n attempts: { type: \"number\", required: true, default: 0 },\n maxAttempts: { type: \"number\", required: true, default: 5 },\n notBefore: { type: \"date\", required: true, defaultNow: true },\n claimedAt: { type: \"date\" },\n claimedBy: { type: \"string\" },\n lastError: { type: \"string\" },\n updatedAt: { type: \"date\", required: true, defaultNow: true },\n },\n primaryKey: [\"id\"],\n indexes: [\n { on: [\"status\", \"notBefore\"], name: \"delivery_claim_idx\" },\n { on: [\"notificationId\"], name: \"delivery_notification_idx\" },\n ],\n },\n\n notificationPreference: {\n tableName: \"notification_preference\",\n fields: {\n userId: { type: \"string\", required: true },\n type: { type: \"string\", required: true },\n channel: { type: \"string\", required: true },\n enabled: { type: \"boolean\", required: true, default: true },\n frequency: { type: \"string\", required: true, default: \"instant\" },\n },\n primaryKey: [\"userId\", \"type\", \"channel\"],\n },\n} satisfies SchemaDeclaration;\n","import { toSnakeCase } from \"../schema/declaration\";\nimport { ConfigError } from \"./errors\";\nimport type { SchemaDeclaration, TableDeclaration } from \"./plugin\";\n\nexport type Scalar = string | number | boolean | Date | null;\n\n/**\n * One operator per condition. Operators are tagged objects so a bare value is\n * always an equality test; json columns therefore cannot be filtered, since an\n * object value would be ambiguous.\n */\nexport type WhereCondition =\n | Scalar\n | { in: readonly (string | number)[] }\n | { lt: Date | number }\n | { lte: Date | number }\n | { gt: Date | number }\n | { gte: Date | number }\n | { not: Scalar };\n\nexport type WhereClause = Record<string, WhereCondition>;\n\nexport type QueryOptions = {\n limit?: number;\n orderBy?: { field: string; direction: \"asc\" | \"desc\" };\n};\n\nexport type UpsertOptions = { onConflict: readonly string[] };\n\n/**\n * Table access for plugins, scoped to the tables that plugin declared.\n *\n * Plugins could always declare tables via schema() but had no way to read\n * them, so preferences had to bolt its queries onto the core adapter. That\n * does not generalise to digests or push.\n */\nexport type PluginStore = {\n find<T = Record<string, unknown>>(\n table: string,\n where?: WhereClause,\n options?: QueryOptions,\n ): Promise<T[]>;\n insert(table: string, rows: readonly Record<string, unknown>[]): Promise<number>;\n upsert(\n table: string,\n rows: readonly Record<string, unknown>[],\n options: UpsertOptions,\n ): Promise<number>;\n update(table: string, where: WhereClause, set: Record<string, unknown>): Promise<number>;\n remove(table: string, where: WhereClause): Promise<number>;\n};\n\nexport type TableStorage = {\n readonly naming?: \"snake_case\" | \"preserve\";\n readonly serializesJson?: boolean;\n\n queryTable(\n table: string,\n where: WhereClause,\n options: QueryOptions,\n ): Promise<Record<string, unknown>[]>;\n insertRows(\n table: string,\n rows: readonly Record<string, unknown>[],\n onConflict?: readonly string[],\n ): Promise<number>;\n updateRows(table: string, where: WhereClause, set: Record<string, unknown>): Promise<number>;\n deleteRows(table: string, where: WhereClause): Promise<number>;\n};\n\nconst OPERATORS = [\"in\", \"lt\", \"lte\", \"gt\", \"gte\", \"not\"] as const;\n\nexport const isOperator = (value: unknown): boolean =>\n typeof value === \"object\" &&\n value !== null &&\n !(value instanceof Date) &&\n OPERATORS.some((op) => op in value);\n\n/**\n * Every table and column a plugin touches is checked against its own\n * declaration. Without this a plugin could read the notification table, and\n * unvalidated identifiers would reach the SQL builder.\n */\nexport function createPluginStore(\n pluginId: string,\n schema: SchemaDeclaration | undefined,\n storage: TableStorage,\n prefix: string,\n): PluginStore {\n const byTableName = new Map<string, TableDeclaration>();\n for (const declaration of Object.values(schema ?? {})) {\n byTableName.set(declaration.tableName, declaration);\n }\n\n function resolve(table: string): TableDeclaration {\n const declaration = byTableName.get(table);\n if (!declaration) {\n throw new ConfigError(\n `plugin \"${pluginId}\" accessed table \"${table}\", which it does not declare in schema().`,\n );\n }\n return declaration;\n }\n\n function checkFields(table: TableDeclaration, fields: Iterable<string>, context: string) {\n for (const field of fields) {\n const spec = table.fields[field];\n if (!spec) {\n throw new ConfigError(\n `plugin \"${pluginId}\" used unknown field \"${field}\" on \"${table.tableName}\" (${context}).`,\n );\n }\n if (context === \"where\" && spec.type === \"json\") {\n throw new ConfigError(\n `plugin \"${pluginId}\" cannot filter on json field \"${field}\"; ` +\n \"a bare object value is indistinguishable from an operator.\",\n );\n }\n }\n }\n\n const qualified = (table: TableDeclaration) => prefix + table.tableName;\n\n // A document store keeps the declared names; a SQL adapter wants columns.\n const column = (field: string) => (storage.naming === \"preserve\" ? field : toSnakeCase(field));\n\n /**\n * json columns must be handed to the driver as text; postgres-js cannot bind\n * a plain object and the insert fails outright.\n */\n const serialize = (table: TableDeclaration, row: Record<string, unknown>) => {\n if (storage.serializesJson === false) return row;\n\n const out: Record<string, unknown> = {};\n for (const [field, value] of Object.entries(row)) {\n out[field] =\n table.fields[field]?.type === \"json\" && value !== null && value !== undefined\n ? JSON.stringify(value)\n : value;\n }\n return out;\n };\n\n return {\n async find(table, where = {}, options = {}) {\n const declaration = resolve(table);\n checkFields(declaration, Object.keys(where), \"where\");\n if (options.orderBy) checkFields(declaration, [options.orderBy.field], \"orderBy\");\n\n const rows = await storage.queryTable(qualified(declaration), where, options);\n\n // SELECT * returns snake_case columns; plugins declare camelCase fields\n // and type their reads that way. Without this every property is\n // undefined at runtime while typechecking perfectly.\n return rows.map((row) => {\n const mapped: Record<string, unknown> = {};\n for (const field of Object.keys(declaration.fields)) {\n const value = row[column(field)];\n // Drivers differ: some hand back parsed jsonb, some raw text.\n mapped[field] =\n declaration.fields[field]?.type === \"json\" && typeof value === \"string\"\n ? JSON.parse(value)\n : value;\n }\n return mapped;\n }) as never;\n },\n\n async insert(table, rows) {\n const declaration = resolve(table);\n for (const row of rows) checkFields(declaration, Object.keys(row), \"insert\");\n return storage.insertRows(\n qualified(declaration),\n rows.map((row) => serialize(declaration, row)),\n );\n },\n\n async upsert(table, rows, options) {\n const declaration = resolve(table);\n for (const row of rows) checkFields(declaration, Object.keys(row), \"upsert\");\n checkFields(declaration, options.onConflict, \"onConflict\");\n return storage.insertRows(\n qualified(declaration),\n rows.map((row) => serialize(declaration, row)),\n options.onConflict,\n );\n },\n\n async update(table, where, set) {\n const declaration = resolve(table);\n checkFields(declaration, Object.keys(where), \"where\");\n checkFields(declaration, Object.keys(set), \"update\");\n return storage.updateRows(qualified(declaration), where, serialize(declaration, set));\n },\n\n async remove(table, where) {\n const declaration = resolve(table);\n checkFields(declaration, Object.keys(where), \"where\");\n return storage.deleteRows(qualified(declaration), where);\n },\n };\n}\n","/**\n * Builds a parameterised statement: SQL text with `$1, $2 …` placeholders plus\n * the values to bind, which is the one calling convention every Postgres\n * driver agrees on.\n *\n * Values are never interpolated. Identifiers are quoted, and the only\n * identifiers that reach here are either constants in this file or names the\n * PluginStore has already validated against the plugin's own schema.\n */\nexport class Statement {\n #text = \"\";\n readonly params: unknown[] = [];\n\n /** Raw SQL. Never pass user input through this — use `value`. */\n raw(fragment: string): this {\n this.#text += fragment;\n return this;\n }\n\n /** Binds a value and writes its placeholder. */\n value(value: unknown): this {\n // Dates bind as ISO strings: every driver accepts that for timestamptz,\n // whereas a Date object is only handled by some of them.\n this.params.push(value instanceof Date ? value.toISOString() : value);\n this.#text += `$${this.params.length}`;\n return this;\n }\n\n /** `$1, $2, $3` — a comma-separated run of bound values. */\n list(values: readonly unknown[], separator = \", \"): this {\n values.forEach((value, index) => {\n if (index > 0) this.raw(separator);\n this.value(value);\n });\n return this;\n }\n\n /** `($1::text, $2::timestamptz)` — one VALUES tuple with explicit casts. */\n tuple(cells: readonly (readonly [unknown, string])[]): this {\n this.raw(\"(\");\n cells.forEach(([value, cast], index) => {\n if (index > 0) this.raw(\", \");\n this.value(value).raw(`::${cast}`);\n });\n return this.raw(\")\");\n }\n\n get text(): string {\n return this.#text;\n }\n}\n\n/** Double-quoted so a table or column name can never be read as SQL. */\nexport const quote = (identifier: string): string => `\"${identifier.replace(/\"/g, '\"\"')}\"`;\n","import type {\n ClaimArgs,\n ClaimedDelivery,\n DatabaseAdapter,\n DeliveryRelease,\n FeedPage,\n FeedQuery,\n InsertNotification,\n} from \"../../core/adapter\";\nimport { decodeBase64Url, encodeBase64Url } from \"../../core/base64url\";\nimport { isOperator, type QueryOptions, type WhereClause } from \"../../core/store\";\nimport type { Channel, DeliveryRecord, DeliveryStatus, NotificationRecord } from \"../../core/types\";\nimport { toSnakeCase } from \"../../schema/declaration\";\nimport { quote, Statement } from \"./statement\";\n\ntype Row = Record<string, unknown>;\n\n/**\n * Run one parameterised statement and return its rows.\n *\n * This is the whole contract — the lowest common denominator every Postgres\n * driver already exposes, which is what makes this adapter work with `pg`,\n * `postgres.js`, Kysely, Neon, or anything else without an ORM in between.\n */\nexport type SqlQuery = (text: string, params: readonly unknown[]) => Promise<readonly Row[]>;\n\nexport type PostgresAdapterOptions = {\n /** Table-name prefix. Must match the instance's `tablePrefix`. */\n prefix?: string;\n /**\n * Runs `fn` inside a transaction, passing it a query function bound to that\n * transaction. Supply it and `createNotifications` becomes atomic — a\n * notification and its deliveries land together or neither does. Without it\n * the adapter still works; it just loses that guarantee, the same tradeoff\n * the Mongo adapter makes when no client is passed.\n */\n transaction?: <T>(fn: (query: SqlQuery) => Promise<T>) => Promise<T>;\n};\n\nconst str = (value: unknown): string => String(value);\nconst nullableStr = (value: unknown): string | null => (value == null ? null : String(value));\nconst num = (value: unknown): number => Number(value);\nconst date = (value: unknown): Date => (value instanceof Date ? value : new Date(String(value)));\nconst nullableDate = (value: unknown): Date | null =>\n value == null ? null : value instanceof Date ? value : new Date(String(value));\n\nconst encodeCursor = (createdAt: Date, id: string) =>\n encodeBase64Url(`${createdAt.toISOString()}|${id}`);\n\nfunction decodeCursor(cursor: string): { createdAt: Date; id: string } | null {\n const decoded = decodeBase64Url(cursor);\n if (!decoded) return null;\n\n const [iso, id] = decoded.split(\"|\");\n if (!iso || !id) return null;\n\n const createdAt = new Date(iso);\n return Number.isNaN(createdAt.getTime()) ? null : { createdAt, id };\n}\n\nfunction toNotification(row: Row): NotificationRecord {\n return {\n id: str(row.id),\n userId: str(row.user_id),\n type: str(row.type),\n payload: typeof row.payload === \"string\" ? JSON.parse(row.payload) : row.payload,\n actorId: nullableStr(row.actor_id),\n groupKey: nullableStr(row.group_key),\n dedupeKey: nullableStr(row.dedupe_key),\n seenAt: nullableDate(row.seen_at),\n readAt: nullableDate(row.read_at),\n archivedAt: nullableDate(row.archived_at),\n createdAt: date(row.created_at),\n };\n}\n\nfunction toDelivery(row: Row): DeliveryRecord {\n return {\n id: str(row.id),\n notificationId: str(row.notification_id),\n channel: str(row.channel) as Channel,\n status: str(row.status) as DeliveryStatus,\n attempts: num(row.attempts),\n maxAttempts: num(row.max_attempts),\n notBefore: date(row.not_before),\n claimedAt: nullableDate(row.claimed_at),\n claimedBy: nullableStr(row.claimed_by),\n lastError: nullableStr(row.last_error),\n updatedAt: date(row.updated_at),\n };\n}\n\n/** Appends ` WHERE …` for a PluginStore clause. Values are always bound. */\nfunction appendWhere(statement: Statement, where: WhereClause): void {\n const entries = Object.entries(where);\n if (entries.length === 0) return;\n\n statement.raw(\" WHERE \");\n\n entries.forEach(([field, condition], index) => {\n if (index > 0) statement.raw(\" AND \");\n const column = quote(toSnakeCase(field));\n\n if (condition === null) {\n statement.raw(`${column} IS NULL`);\n return;\n }\n\n if (!isOperator(condition)) {\n statement.raw(`${column} = `).value(condition);\n return;\n }\n\n const operator = condition as Record<string, unknown>;\n\n if (\"in\" in operator) {\n const list = operator.in as readonly (string | number)[];\n // An empty IN () is a syntax error, and matching nothing is the honest\n // reading of \"in this empty set\".\n if (list.length === 0) statement.raw(\"FALSE\");\n else statement.raw(`${column} IN (`).list(list).raw(\")\");\n return;\n }\n\n if (\"lt\" in operator) statement.raw(`${column} < `).value(operator.lt);\n else if (\"lte\" in operator) statement.raw(`${column} <= `).value(operator.lte);\n else if (\"gt\" in operator) statement.raw(`${column} > `).value(operator.gt);\n else if (\"gte\" in operator) statement.raw(`${column} >= `).value(operator.gte);\n else if (\"not\" in operator) {\n if (operator.not === null) statement.raw(`${column} IS NOT NULL`);\n else statement.raw(`${column} IS DISTINCT FROM `).value(operator.not);\n } else statement.raw(\"TRUE\");\n });\n}\n\n/**\n * Postgres, through any driver — no ORM required.\n *\n * `drizzleAdapter` predates this and is kept for people already on Drizzle,\n * but it only ever used Drizzle as a SQL builder. This takes the query\n * function directly instead, so a plain `pg` Pool, `postgres.js`, Kysely or a\n * serverless driver all work without pulling an ORM into the dependency tree.\n *\n * The SQL is deliberately Postgres-specific — `ON CONFLICT`, `FOR UPDATE SKIP\n * LOCKED` and `IS DISTINCT FROM` have no portable equivalent, and the claim\n * primitive depends on the second of those. See RFC 0003.\n */\nexport function postgresAdapter(\n query: SqlQuery,\n options: PostgresAdapterOptions = {},\n): DatabaseAdapter {\n const prefix = options.prefix ?? \"\";\n\n const NOTIFICATION = quote(`${prefix}notification`);\n const DELIVERY = quote(`${prefix}notification_delivery`);\n\n const run = async (statement: Statement, exec: SqlQuery = query) =>\n exec(statement.text, statement.params);\n\n /** Uses a transaction when one was supplied, and runs plainly otherwise. */\n const atomically = <T>(fn: (exec: SqlQuery) => Promise<T>): Promise<T> =>\n options.transaction ? options.transaction(fn) : fn(query);\n\n return {\n name: \"postgres\",\n naming: \"snake_case\",\n serializesJson: true,\n\n async createNotifications(input: readonly InsertNotification[]) {\n if (input.length === 0) return { created: [], deduped: [] };\n\n // One clock. created_at has a DEFAULT now() for hand-written SQL, but\n // now() is the *database* clock while every cutoff compared against it\n // (markSeen, getFailedDeliveries) comes from the app. In production those\n // are different hosts, and NTP skew put the newest rows outside markSeen.\n const now = new Date();\n\n return atomically(async (exec) => {\n const insert = new Statement().raw(\n `INSERT INTO ${NOTIFICATION} ` +\n \"(id, user_id, type, payload, actor_id, group_key, dedupe_key, created_at) VALUES \",\n );\n\n input.forEach((row, index) => {\n if (index > 0) insert.raw(\", \");\n insert.tuple([\n [row.id, \"text\"],\n [row.userId, \"text\"],\n [row.type, \"text\"],\n [JSON.stringify(row.payload ?? null), \"jsonb\"],\n [row.actorId ?? null, \"text\"],\n [row.groupKey ?? null, \"text\"],\n [row.dedupeKey ?? null, \"text\"],\n [now, \"timestamptz\"],\n ]);\n });\n\n // Rows with a NULL dedupe_key never conflict — Postgres treats NULLs as\n // distinct — so unlimited undeduped notifications coexist.\n insert.raw(\" ON CONFLICT (user_id, dedupe_key) DO NOTHING RETURNING id\");\n\n const inserted = await run(insert, exec);\n const created = new Set(inserted.map((row) => str(row.id)));\n\n const deliveries = input\n .filter((row) => created.has(row.id))\n .flatMap((row) => row.deliveries.map((delivery) => ({ row, delivery })));\n\n if (deliveries.length > 0) {\n const insertDeliveries = new Statement().raw(\n `INSERT INTO ${DELIVERY} ` +\n \"(id, notification_id, channel, max_attempts, not_before, updated_at) VALUES \",\n );\n\n deliveries.forEach(({ row, delivery }, index) => {\n if (index > 0) insertDeliveries.raw(\", \");\n insertDeliveries.tuple([\n [delivery.id, \"text\"],\n [row.id, \"text\"],\n [delivery.channel, \"text\"],\n [delivery.maxAttempts, \"integer\"],\n [delivery.notBefore, \"timestamptz\"],\n [now, \"timestamptz\"],\n ]);\n });\n\n await run(insertDeliveries, exec);\n }\n\n return {\n created: [...created],\n deduped: input.filter((row) => !created.has(row.id)).map((row) => row.id),\n };\n });\n },\n\n async claimPendingDeliveries(args: ClaimArgs): Promise<readonly ClaimedDelivery[]> {\n const now = args.now ?? new Date();\n const staleBefore = new Date(now.getTime() - args.leaseMs);\n\n // SKIP LOCKED is what lets concurrent sweeps step around each other\n // rather than block. The CTE joins the notification in the same round\n // trip — claiming 20 rows then looking each one up is the N+1 this\n // primitive exists to avoid. See RFC 0003 §5.\n const statement = new Statement()\n .raw(`WITH claimed AS (UPDATE ${DELIVERY} SET status = 'claimed', claimed_at = `)\n .value(now)\n .raw(\"::timestamptz, claimed_by = \")\n .value(args.claimToken)\n .raw(`::text WHERE id IN (SELECT id FROM ${DELIVERY} WHERE (status = 'pending' OR `)\n .raw(\"(status = 'claimed' AND claimed_at < \")\n .value(staleBefore)\n .raw(\"::timestamptz)) AND not_before <= \")\n .value(now)\n .raw(\"::timestamptz AND attempts < max_attempts\");\n\n if (args.channels && args.channels.length > 0) {\n statement.raw(\" AND channel IN (\").list(args.channels).raw(\")\");\n }\n if (args.ids && args.ids.length > 0) {\n statement.raw(\" AND id IN (\").list(args.ids).raw(\")\");\n }\n\n statement\n .raw(\" ORDER BY not_before ASC, id ASC LIMIT \")\n .value(args.limit)\n .raw(\" FOR UPDATE SKIP LOCKED)\")\n .raw(\" RETURNING id, notification_id, channel, attempts, max_attempts)\")\n .raw(\n \" SELECT c.id, c.notification_id, c.channel, c.attempts, c.max_attempts,\" +\n \" n.user_id, n.type, n.payload, n.actor_id FROM claimed c\" +\n ` JOIN ${NOTIFICATION} n ON n.id = c.notification_id`,\n );\n\n const rows = await run(statement);\n\n return rows.map((row) => ({\n id: str(row.id),\n notificationId: str(row.notification_id),\n channel: str(row.channel) as Channel,\n attempts: num(row.attempts),\n maxAttempts: num(row.max_attempts),\n notification: {\n userId: str(row.user_id),\n type: str(row.type),\n payload: typeof row.payload === \"string\" ? JSON.parse(row.payload) : row.payload,\n actorId: nullableStr(row.actor_id),\n },\n }));\n },\n\n async releaseDeliveries(releases: readonly DeliveryRelease[]) {\n if (releases.length === 0) return;\n const now = new Date();\n\n // Written unconditionally — no claimed_by predicate. If the lease expired\n // and another worker re-sent, the duplicate already happened; recording\n // the true terminal state beats wedging the row in 'claimed'. RFC 0003 §6.\n const statement = new Statement().raw(\n `UPDATE ${DELIVERY} d SET status = CASE` +\n \" WHEN v.result = 'sent' THEN 'sent'\" +\n \" WHEN v.retryable AND d.attempts + 1 < d.max_attempts THEN 'pending'\" +\n \" ELSE 'failed' END,\" +\n \" attempts = CASE WHEN v.result = 'sent' THEN d.attempts ELSE d.attempts + 1 END,\" +\n \" last_error = v.error,\" +\n \" not_before = COALESCE(v.not_before, d.not_before),\" +\n \" claimed_at = NULL, claimed_by = NULL, updated_at = \",\n );\n\n statement.value(now).raw(\"::timestamptz FROM (VALUES \");\n\n releases.forEach(({ id, outcome, nextAttemptAt }, index) => {\n if (index > 0) statement.raw(\", \");\n statement.tuple([\n [id, \"text\"],\n [outcome.result, \"text\"],\n [outcome.result === \"failed\" && outcome.retryable, \"boolean\"],\n [outcome.result === \"failed\" ? outcome.error.slice(0, 2000) : null, \"text\"],\n [nextAttemptAt ?? null, \"timestamptz\"],\n ]);\n });\n\n statement.raw(\") AS v(id, result, retryable, error, not_before) WHERE d.id = v.id\");\n\n await run(statement);\n },\n\n async listNotifications(feed: FeedQuery): Promise<FeedPage> {\n const cursor = feed.cursor ? decodeCursor(feed.cursor) : null;\n\n const statement = new Statement()\n .raw(`SELECT * FROM ${NOTIFICATION} WHERE user_id = `)\n .value(feed.userId)\n .raw(\"::text AND archived_at IS NULL\");\n\n if (feed.unreadOnly) statement.raw(\" AND read_at IS NULL\");\n\n if (cursor) {\n statement\n .raw(\" AND (created_at, id) < (\")\n .value(cursor.createdAt)\n .raw(\"::timestamptz, \")\n .value(cursor.id)\n .raw(\"::text)\");\n }\n\n statement.raw(\" ORDER BY created_at DESC, id DESC LIMIT \").value(feed.limit + 1);\n\n const rows = await run(statement);\n const page = rows.slice(0, feed.limit).map(toNotification);\n const last = page.at(-1);\n\n return {\n notifications: page,\n nextCursor: rows.length > feed.limit && last ? encodeCursor(last.createdAt, last.id) : null,\n };\n },\n\n async countUnseen(userId: string) {\n const rows = await run(\n new Statement()\n .raw(`SELECT count(*)::int AS count FROM ${NOTIFICATION} WHERE user_id = `)\n .value(userId)\n .raw(\"::text AND seen_at IS NULL AND archived_at IS NULL\"),\n );\n return num(rows[0]?.count ?? 0);\n },\n\n async markSeen(userId: string, before: Date) {\n await run(\n new Statement()\n .raw(`UPDATE ${NOTIFICATION} SET seen_at = `)\n .value(new Date())\n .raw(\"::timestamptz WHERE user_id = \")\n .value(userId)\n .raw(\"::text AND seen_at IS NULL AND created_at <= \")\n .value(before)\n .raw(\"::timestamptz\"),\n );\n },\n\n async markRead(userId: string, notificationIds: readonly string[]) {\n if (notificationIds.length === 0) return 0;\n\n // Scoped by user_id as well as id — a caller must never be able to flip\n // someone else's row by guessing an id. RFC 0002 §2.\n // No `read_at IS NULL` filter: re-marking must be idempotent. Returning 0\n // for an already-read row made the route 404, which made the client roll\n // its optimistic update back and show the item as unread again.\n const rows = await run(\n new Statement()\n .raw(`UPDATE ${NOTIFICATION} SET read_at = COALESCE(read_at, `)\n .value(new Date())\n .raw(\"::timestamptz) WHERE user_id = \")\n .value(userId)\n .raw(\"::text AND id IN (\")\n .list(notificationIds)\n .raw(\") RETURNING id\"),\n );\n return rows.length;\n },\n\n async markAllRead(userId: string) {\n const rows = await run(\n new Statement()\n .raw(`UPDATE ${NOTIFICATION} SET read_at = `)\n .value(new Date())\n .raw(\"::timestamptz WHERE user_id = \")\n .value(userId)\n .raw(\"::text AND read_at IS NULL RETURNING id\"),\n );\n return rows.length;\n },\n\n async getFailedDeliveries(args: { since: Date; limit: number }) {\n const rows = await run(\n new Statement()\n .raw(`SELECT * FROM ${DELIVERY} WHERE status = 'failed' AND updated_at >= `)\n .value(args.since)\n .raw(\"::timestamptz ORDER BY updated_at DESC LIMIT \")\n .value(args.limit),\n );\n return rows.map(toDelivery);\n },\n\n async queryTable(table: string, where: WhereClause, queryOptions: QueryOptions) {\n const statement = new Statement().raw(`SELECT * FROM ${quote(table)}`);\n appendWhere(statement, where);\n\n if (queryOptions.orderBy) {\n const direction = queryOptions.orderBy.direction === \"desc\" ? \"DESC\" : \"ASC\";\n statement.raw(` ORDER BY ${quote(toSnakeCase(queryOptions.orderBy.field))} ${direction}`);\n }\n if (queryOptions.limit) statement.raw(\" LIMIT \").value(queryOptions.limit);\n\n return [...(await run(statement))];\n },\n\n async insertRows(\n table: string,\n rows: readonly Record<string, unknown>[],\n onConflict?: readonly string[],\n ) {\n if (rows.length === 0) return 0;\n\n // Column order is taken from the first row and every row is projected\n // onto it, so a ragged batch cannot shift values into other columns.\n const columns = Object.keys(rows[0] ?? {});\n if (columns.length === 0) return 0;\n\n const statement = new Statement().raw(\n `INSERT INTO ${quote(table)} (${columns.map((c) => quote(toSnakeCase(c))).join(\", \")}) VALUES `,\n );\n\n rows.forEach((row, index) => {\n if (index > 0) statement.raw(\", \");\n statement\n .raw(\"(\")\n .list(columns.map((column) => row[column]))\n .raw(\")\");\n });\n\n if (onConflict && onConflict.length > 0) {\n const assignments = columns\n .filter((column) => !onConflict.includes(column))\n .map((column) => {\n const quoted = quote(toSnakeCase(column));\n return `${quoted} = EXCLUDED.${quoted}`;\n });\n\n statement.raw(\n ` ON CONFLICT (${onConflict.map((f) => quote(toSnakeCase(f))).join(\", \")}) DO UPDATE SET ${assignments.join(\", \")}`,\n );\n }\n\n statement.raw(\" RETURNING 1 AS ok\");\n return (await run(statement)).length;\n },\n\n async updateRows(table: string, where: WhereClause, set: Record<string, unknown>) {\n const assignments = Object.entries(set);\n if (assignments.length === 0) return 0;\n\n const statement = new Statement().raw(`UPDATE ${quote(table)} SET `);\n\n assignments.forEach(([field, value], index) => {\n if (index > 0) statement.raw(\", \");\n statement.raw(`${quote(toSnakeCase(field))} = `).value(value);\n });\n\n appendWhere(statement, where);\n statement.raw(\" RETURNING 1 AS ok\");\n\n return (await run(statement)).length;\n },\n\n async deleteRows(table: string, where: WhereClause) {\n const statement = new Statement().raw(`DELETE FROM ${quote(table)}`);\n appendWhere(statement, where);\n statement.raw(\" RETURNING 1 AS ok\");\n\n return (await run(statement)).length;\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/base64url.ts","../../src/schema/declaration.ts","../../src/core/store.ts","../../src/adapters/postgres/statement.ts","../../src/adapters/postgres/adapter.ts","../../src/schema/render-sql.ts","../../src/adapters/postgres/helpers.ts"],"names":["quote","columns"],"mappings":";;;AAEO,SAAS,gBAAgB,KAAA,EAAoC;AAClE,EAAA,MAAM,KAAA,GAAQ,OAAO,KAAA,KAAU,QAAA,GAAW,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,GAAI,KAAA;AAC5E,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,EAAO,MAAA,IAAU,MAAA,CAAO,aAAa,IAAI,CAAA;AAC5D,EAAA,OAAO,IAAA,CAAK,MAAM,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC/E;AAGO,SAAS,qBAAqB,KAAA,EAA+C;AAClF,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC,CAAA;AAC/D,IAAA,OAAO,UAAA,CAAW,KAAK,MAAA,EAAQ,CAAC,SAAS,IAAA,CAAK,UAAA,CAAW,CAAC,CAAC,CAAA;AAAA,EAC7D,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAEO,SAAS,gBAAgB,KAAA,EAA8B;AAC5D,EAAA,MAAM,KAAA,GAAQ,qBAAqB,KAAK,CAAA;AACxC,EAAA,OAAO,QAAQ,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,KAAK,CAAA,GAAI,IAAA;AACnD;;;ACpBO,IAAM,WAAA,GAAc,CAAC,KAAA,KAAkB,KAAA,CAAM,OAAA,CAAQ,QAAA,EAAU,CAAC,CAAA,KAAM,CAAA,CAAA,EAAI,CAAA,CAAE,WAAA,EAAa,CAAA,CAAE,CAAA;AAO3F,IAAM,UAAA,GAAa;AAAA,EACxB,YAAA,EAAc;AAAA,IACZ,SAAA,EAAW,cAAA;AAAA,IACX,MAAA,EAAQ;AAAA,MACN,EAAA,EAAI,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MACrC,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MACzC,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MACvC,OAAA,EAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,UAAU,IAAA,EAAK;AAAA,MACxC,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,MAC1B,QAAA,EAAU,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,MAC3B,SAAA,EAAW,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,MAC5B,MAAA,EAAQ,EAAE,IAAA,EAAM,MAAA,EAAO;AAAA,MACvB,MAAA,EAAQ,EAAE,IAAA,EAAM,MAAA,EAAO;AAAA,MACvB,UAAA,EAAY,EAAE,IAAA,EAAM,MAAA,EAAO;AAAA,MAC3B,WAAW,EAAE,IAAA,EAAM,QAAQ,QAAA,EAAU,IAAA,EAAM,YAAY,IAAA;AAAK,KAC9D;AAAA,IACA,UAAA,EAAY,CAAC,IAAI,CAAA;AAAA,IACjB,OAAA,EAAS;AAAA,MACP,EAAE,EAAA,EAAI,CAAC,UAAU,WAAW,CAAA,EAAG,MAAM,uBAAA,EAAwB;AAAA,MAC7D,EAAE,EAAA,EAAI,CAAC,UAAU,UAAU,CAAA,EAAG,MAAM,wBAAA,EAAyB;AAAA,MAC7D,EAAE,IAAI,CAAC,QAAA,EAAU,WAAW,CAAA,EAAG,MAAA,EAAQ,IAAA,EAAM,IAAA,EAAM,yBAAA;AAA0B;AAC/E,GACF;AAAA,EAEA,oBAAA,EAAsB;AAAA,IACpB,SAAA,EAAW,uBAAA;AAAA,IACX,MAAA,EAAQ;AAAA,MACN,EAAA,EAAI,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MACrC,cAAA,EAAgB,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MACjD,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MAC1C,QAAQ,EAAE,IAAA,EAAM,UAAU,QAAA,EAAU,IAAA,EAAM,SAAS,SAAA,EAAU;AAAA,MAC7D,UAAU,EAAE,IAAA,EAAM,UAAU,QAAA,EAAU,IAAA,EAAM,SAAS,CAAA,EAAE;AAAA,MACvD,aAAa,EAAE,IAAA,EAAM,UAAU,QAAA,EAAU,IAAA,EAAM,SAAS,CAAA,EAAE;AAAA,MAC1D,WAAW,EAAE,IAAA,EAAM,QAAQ,QAAA,EAAU,IAAA,EAAM,YAAY,IAAA,EAAK;AAAA,MAC5D,SAAA,EAAW,EAAE,IAAA,EAAM,MAAA,EAAO;AAAA,MAC1B,SAAA,EAAW,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,MAC5B,SAAA,EAAW,EAAE,IAAA,EAAM,QAAA,EAAS;AAAA,MAC5B,WAAW,EAAE,IAAA,EAAM,QAAQ,QAAA,EAAU,IAAA,EAAM,YAAY,IAAA;AAAK,KAC9D;AAAA,IACA,UAAA,EAAY,CAAC,IAAI,CAAA;AAAA,IACjB,OAAA,EAAS;AAAA,MACP,EAAE,EAAA,EAAI,CAAC,UAAU,WAAW,CAAA,EAAG,MAAM,oBAAA,EAAqB;AAAA,MAC1D,EAAE,EAAA,EAAI,CAAC,gBAAgB,CAAA,EAAG,MAAM,2BAAA;AAA4B;AAC9D,GACF;AAAA,EAEA,sBAAA,EAAwB;AAAA,IACtB,SAAA,EAAW,yBAAA;AAAA,IACX,MAAA,EAAQ;AAAA,MACN,MAAA,EAAQ,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MACzC,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MACvC,OAAA,EAAS,EAAE,IAAA,EAAM,QAAA,EAAU,UAAU,IAAA,EAAK;AAAA,MAC1C,SAAS,EAAE,IAAA,EAAM,WAAW,QAAA,EAAU,IAAA,EAAM,SAAS,IAAA,EAAK;AAAA,MAC1D,WAAW,EAAE,IAAA,EAAM,UAAU,QAAA,EAAU,IAAA,EAAM,SAAS,SAAA;AAAU,KAClE;AAAA,IACA,UAAA,EAAY,CAAC,QAAA,EAAU,MAAA,EAAQ,SAAS;AAAA;AAE5C,CAAA;;;ACIA,IAAM,YAAY,CAAC,IAAA,EAAM,MAAM,KAAA,EAAO,IAAA,EAAM,OAAO,KAAK,CAAA;AAEjD,IAAM,aAAa,CAAC,KAAA,KACzB,OAAO,KAAA,KAAU,YACjB,KAAA,KAAU,IAAA,IACV,EAAE,KAAA,YAAiB,SACnB,SAAA,CAAU,IAAA,CAAK,CAAC,EAAA,KAAO,MAAM,KAAK,CAAA;;;ACnE7B,IAAM,YAAN,MAAgB;AAAA,EACrB,KAAA,GAAQ,EAAA;AAAA,EACC,SAAoB,EAAC;AAAA;AAAA,EAG9B,IAAI,QAAA,EAAwB;AAC1B,IAAA,IAAA,CAAK,KAAA,IAAS,QAAA;AACd,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,KAAA,EAAsB;AAG1B,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,KAAA,YAAiB,OAAO,KAAA,CAAM,WAAA,KAAgB,KAAK,CAAA;AACpE,IAAA,IAAA,CAAK,KAAA,IAAS,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,CAAA;AACpC,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,IAAA,CAAK,MAAA,EAA4B,SAAA,GAAY,IAAA,EAAY;AACvD,IAAA,MAAA,CAAO,OAAA,CAAQ,CAAC,KAAA,EAAO,KAAA,KAAU;AAC/B,MAAA,IAAI,KAAA,GAAQ,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,SAAS,CAAA;AACjC,MAAA,IAAA,CAAK,MAAM,KAAK,CAAA;AAAA,IAClB,CAAC,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,KAAA,EAAsD;AAC1D,IAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,IAAA,KAAA,CAAM,QAAQ,CAAC,CAAC,KAAA,EAAO,IAAI,GAAG,KAAA,KAAU;AACtC,MAAA,IAAI,KAAA,GAAQ,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,IAAI,CAAA;AAC5B,MAAA,IAAA,CAAK,MAAM,KAAK,CAAA,CAAE,GAAA,CAAI,CAAA,EAAA,EAAK,IAAI,CAAA,CAAE,CAAA;AAAA,IACnC,CAAC,CAAA;AACD,IAAA,OAAO,IAAA,CAAK,IAAI,GAAG,CAAA;AAAA,EACrB;AAAA,EAEA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AACF,CAAA;AAGO,IAAM,KAAA,GAAQ,CAAC,UAAA,KAA+B,CAAA,CAAA,EAAI,WAAW,OAAA,CAAQ,IAAA,EAAM,IAAI,CAAC,CAAA,CAAA,CAAA;;;ACdvF,IAAM,GAAA,GAAM,CAAC,KAAA,KAA2B,MAAA,CAAO,KAAK,CAAA;AACpD,IAAM,cAAc,CAAC,KAAA,KAAmC,SAAS,IAAA,GAAO,IAAA,GAAO,OAAO,KAAK,CAAA;AAC3F,IAAM,GAAA,GAAM,CAAC,KAAA,KAA2B,MAAA,CAAO,KAAK,CAAA;AACpD,IAAM,IAAA,GAAO,CAAC,KAAA,KAA0B,KAAA,YAAiB,IAAA,GAAO,QAAQ,IAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AAC9F,IAAM,YAAA,GAAe,CAAC,KAAA,KACpB,KAAA,IAAS,IAAA,GAAO,IAAA,GAAO,KAAA,YAAiB,IAAA,GAAO,KAAA,GAAQ,IAAI,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AAE/E,IAAM,YAAA,GAAe,CAAC,SAAA,EAAiB,EAAA,KACrC,eAAA,CAAgB,CAAA,EAAG,SAAA,CAAU,WAAA,EAAa,CAAA,CAAA,EAAI,EAAE,CAAA,CAAE,CAAA;AAEpD,SAAS,aAAa,MAAA,EAAwD;AAC5E,EAAA,MAAM,OAAA,GAAU,gBAAgB,MAAM,CAAA;AACtC,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,EAAA,MAAM,CAAC,GAAA,EAAK,EAAE,CAAA,GAAI,OAAA,CAAQ,MAAM,GAAG,CAAA;AACnC,EAAA,IAAI,CAAC,GAAA,IAAO,CAAC,EAAA,EAAI,OAAO,IAAA;AAExB,EAAA,MAAM,SAAA,GAAY,IAAI,IAAA,CAAK,GAAG,CAAA;AAC9B,EAAA,OAAO,MAAA,CAAO,MAAM,SAAA,CAAU,OAAA,EAAS,CAAA,GAAI,IAAA,GAAO,EAAE,SAAA,EAAW,EAAA,EAAG;AACpE;AAEA,SAAS,eAAe,GAAA,EAA8B;AACpD,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAAA,IACd,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,OAAO,CAAA;AAAA,IACvB,IAAA,EAAM,GAAA,CAAI,GAAA,CAAI,IAAI,CAAA;AAAA,IAClB,OAAA,EAAS,OAAO,GAAA,CAAI,OAAA,KAAY,QAAA,GAAW,KAAK,KAAA,CAAM,GAAA,CAAI,OAAO,CAAA,GAAI,GAAA,CAAI,OAAA;AAAA,IACzE,OAAA,EAAS,WAAA,CAAY,GAAA,CAAI,QAAQ,CAAA;AAAA,IACjC,QAAA,EAAU,WAAA,CAAY,GAAA,CAAI,SAAS,CAAA;AAAA,IACnC,SAAA,EAAW,WAAA,CAAY,GAAA,CAAI,UAAU,CAAA;AAAA,IACrC,MAAA,EAAQ,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA;AAAA,IAChC,MAAA,EAAQ,YAAA,CAAa,GAAA,CAAI,OAAO,CAAA;AAAA,IAChC,UAAA,EAAY,YAAA,CAAa,GAAA,CAAI,WAAW,CAAA;AAAA,IACxC,SAAA,EAAW,IAAA,CAAK,GAAA,CAAI,UAAU;AAAA,GAChC;AACF;AAEA,SAAS,WAAW,GAAA,EAA0B;AAC5C,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAAA,IACd,cAAA,EAAgB,GAAA,CAAI,GAAA,CAAI,eAAe,CAAA;AAAA,IACvC,OAAA,EAAS,GAAA,CAAI,GAAA,CAAI,OAAO,CAAA;AAAA,IACxB,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,MAAM,CAAA;AAAA,IACtB,QAAA,EAAU,GAAA,CAAI,GAAA,CAAI,QAAQ,CAAA;AAAA,IAC1B,WAAA,EAAa,GAAA,CAAI,GAAA,CAAI,YAAY,CAAA;AAAA,IACjC,SAAA,EAAW,IAAA,CAAK,GAAA,CAAI,UAAU,CAAA;AAAA,IAC9B,SAAA,EAAW,YAAA,CAAa,GAAA,CAAI,UAAU,CAAA;AAAA,IACtC,SAAA,EAAW,WAAA,CAAY,GAAA,CAAI,UAAU,CAAA;AAAA,IACrC,SAAA,EAAW,WAAA,CAAY,GAAA,CAAI,UAAU,CAAA;AAAA,IACrC,SAAA,EAAW,IAAA,CAAK,GAAA,CAAI,UAAU;AAAA,GAChC;AACF;AAGA,SAAS,WAAA,CAAY,WAAsB,KAAA,EAA0B;AACnE,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA;AACpC,EAAA,IAAI,OAAA,CAAQ,WAAW,CAAA,EAAG;AAE1B,EAAA,SAAA,CAAU,IAAI,SAAS,CAAA;AAEvB,EAAA,OAAA,CAAQ,QAAQ,CAAC,CAAC,KAAA,EAAO,SAAS,GAAG,KAAA,KAAU;AAC7C,IAAA,IAAI,KAAA,GAAQ,CAAA,EAAG,SAAA,CAAU,GAAA,CAAI,OAAO,CAAA;AACpC,IAAA,MAAM,MAAA,GAAS,KAAA,CAAM,WAAA,CAAY,KAAK,CAAC,CAAA;AAEvC,IAAA,IAAI,cAAc,IAAA,EAAM;AACtB,MAAA,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,MAAM,CAAA,QAAA,CAAU,CAAA;AACjC,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,UAAA,CAAW,SAAS,CAAA,EAAG;AAC1B,MAAA,SAAA,CAAU,IAAI,CAAA,EAAG,MAAM,CAAA,GAAA,CAAK,CAAA,CAAE,MAAM,SAAS,CAAA;AAC7C,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,QAAA,GAAW,SAAA;AAEjB,IAAA,IAAI,QAAQ,QAAA,EAAU;AACpB,MAAA,MAAM,OAAO,QAAA,CAAS,EAAA;AAGtB,MAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,SAAA,CAAU,IAAI,OAAO,CAAA;AAAA,WACvC,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,MAAM,CAAA,KAAA,CAAO,EAAE,IAAA,CAAK,IAAI,CAAA,CAAE,GAAA,CAAI,GAAG,CAAA;AACvD,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,IAAA,IAAQ,QAAA,EAAU,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,MAAM,CAAA,GAAA,CAAK,CAAA,CAAE,KAAA,CAAM,QAAA,CAAS,EAAE,CAAA;AAAA,SAAA,IAC5D,KAAA,IAAS,QAAA,EAAU,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,MAAM,CAAA,IAAA,CAAM,CAAA,CAAE,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA;AAAA,SAAA,IACpE,IAAA,IAAQ,QAAA,EAAU,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,MAAM,CAAA,GAAA,CAAK,CAAA,CAAE,KAAA,CAAM,QAAA,CAAS,EAAE,CAAA;AAAA,SAAA,IACjE,KAAA,IAAS,QAAA,EAAU,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,MAAM,CAAA,IAAA,CAAM,CAAA,CAAE,KAAA,CAAM,QAAA,CAAS,GAAG,CAAA;AAAA,SAAA,IACpE,SAAS,QAAA,EAAU;AAC1B,MAAA,IAAI,SAAS,GAAA,KAAQ,IAAA,YAAgB,GAAA,CAAI,CAAA,EAAG,MAAM,CAAA,YAAA,CAAc,CAAA;AAAA,WAC3D,SAAA,CAAU,IAAI,CAAA,EAAG,MAAM,oBAAoB,CAAA,CAAE,KAAA,CAAM,SAAS,GAAG,CAAA;AAAA,IACtE,CAAA,MAAO,SAAA,CAAU,GAAA,CAAI,MAAM,CAAA;AAAA,EAC7B,CAAC,CAAA;AACH;AAcO,SAAS,eAAA,CACd,KAAA,EACA,OAAA,GAAkC,EAAC,EAClB;AACjB,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,EAAA;AAEjC,EAAA,MAAM,YAAA,GAAe,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,YAAA,CAAc,CAAA;AAClD,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,qBAAA,CAAuB,CAAA;AAEvD,EAAA,MAAM,GAAA,GAAM,OAAO,SAAA,EAAsB,IAAA,GAAiB,UACxD,IAAA,CAAK,SAAA,CAAU,IAAA,EAAM,SAAA,CAAU,MAAM,CAAA;AAGvC,EAAA,MAAM,UAAA,GAAa,CAAI,EAAA,KACrB,OAAA,CAAQ,WAAA,GAAc,QAAQ,WAAA,CAAY,EAAE,CAAA,GAAI,EAAA,CAAG,KAAK,CAAA;AAE1D,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,UAAA;AAAA,IACN,MAAA,EAAQ,YAAA;AAAA,IACR,cAAA,EAAgB,IAAA;AAAA,IAEhB,MAAM,oBAAoB,KAAA,EAAsC;AAC9D,MAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,EAAE,SAAS,EAAC,EAAG,OAAA,EAAS,EAAC,EAAE;AAM1D,MAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AAErB,MAAA,OAAO,UAAA,CAAW,OAAO,IAAA,KAAS;AAChC,QAAA,MAAM,MAAA,GAAS,IAAI,SAAA,EAAU,CAAE,GAAA;AAAA,UAC7B,eAAe,YAAY,CAAA,kFAAA;AAAA,SAE7B;AAEA,QAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,GAAA,EAAK,KAAA,KAAU;AAC5B,UAAA,IAAI,KAAA,GAAQ,CAAA,EAAG,MAAA,CAAO,GAAA,CAAI,IAAI,CAAA;AAC9B,UAAA,MAAA,CAAO,KAAA,CAAM;AAAA,YACX,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,CAAA;AAAA,YACf,CAAC,GAAA,CAAI,MAAA,EAAQ,MAAM,CAAA;AAAA,YACnB,CAAC,GAAA,CAAI,IAAA,EAAM,MAAM,CAAA;AAAA,YACjB,CAAC,IAAA,CAAK,SAAA,CAAU,IAAI,OAAA,IAAW,IAAI,GAAG,OAAO,CAAA;AAAA,YAC7C,CAAC,GAAA,CAAI,OAAA,IAAW,IAAA,EAAM,MAAM,CAAA;AAAA,YAC5B,CAAC,GAAA,CAAI,QAAA,IAAY,IAAA,EAAM,MAAM,CAAA;AAAA,YAC7B,CAAC,GAAA,CAAI,SAAA,IAAa,IAAA,EAAM,MAAM,CAAA;AAAA,YAC9B,CAAC,KAAK,aAAa;AAAA,WACpB,CAAA;AAAA,QACH,CAAC,CAAA;AAID,QAAA,MAAA,CAAO,IAAI,4DAA4D,CAAA;AAEvE,QAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,MAAA,EAAQ,IAAI,CAAA;AACvC,QAAA,MAAM,OAAA,GAAU,IAAI,GAAA,CAAI,QAAA,CAAS,GAAA,CAAI,CAAC,GAAA,KAAQ,GAAA,CAAI,GAAA,CAAI,EAAE,CAAC,CAAC,CAAA;AAE1D,QAAA,MAAM,UAAA,GAAa,MAChB,MAAA,CAAO,CAAC,QAAQ,OAAA,CAAQ,GAAA,CAAI,GAAA,CAAI,EAAE,CAAC,CAAA,CACnC,QAAQ,CAAC,GAAA,KAAQ,GAAA,CAAI,UAAA,CAAW,GAAA,CAAI,CAAC,cAAc,EAAE,GAAA,EAAK,QAAA,EAAS,CAAE,CAAC,CAAA;AAEzE,QAAA,IAAI,UAAA,CAAW,SAAS,CAAA,EAAG;AACzB,UAAA,MAAM,gBAAA,GAAmB,IAAI,SAAA,EAAU,CAAE,GAAA;AAAA,YACvC,eAAe,QAAQ,CAAA,6EAAA;AAAA,WAEzB;AAEA,UAAA,UAAA,CAAW,QAAQ,CAAC,EAAE,GAAA,EAAK,QAAA,IAAY,KAAA,KAAU;AAC/C,YAAA,IAAI,KAAA,GAAQ,CAAA,EAAG,gBAAA,CAAiB,GAAA,CAAI,IAAI,CAAA;AACxC,YAAA,gBAAA,CAAiB,KAAA,CAAM;AAAA,cACrB,CAAC,QAAA,CAAS,EAAA,EAAI,MAAM,CAAA;AAAA,cACpB,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,CAAA;AAAA,cACf,CAAC,QAAA,CAAS,OAAA,EAAS,MAAM,CAAA;AAAA,cACzB,CAAC,QAAA,CAAS,WAAA,EAAa,SAAS,CAAA;AAAA,cAChC,CAAC,QAAA,CAAS,SAAA,EAAW,aAAa,CAAA;AAAA,cAClC,CAAC,KAAK,aAAa;AAAA,aACpB,CAAA;AAAA,UACH,CAAC,CAAA;AAED,UAAA,MAAM,GAAA,CAAI,kBAAkB,IAAI,CAAA;AAAA,QAClC;AAEA,QAAA,OAAO;AAAA,UACL,OAAA,EAAS,CAAC,GAAG,OAAO,CAAA;AAAA,UACpB,SAAS,KAAA,CAAM,MAAA,CAAO,CAAC,GAAA,KAAQ,CAAC,OAAA,CAAQ,GAAA,CAAI,GAAA,CAAI,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,GAAA,KAAQ,IAAI,EAAE;AAAA,SAC1E;AAAA,MACF,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,MAAM,uBAAuB,IAAA,EAAsD;AACjF,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,oBAAO,IAAI,IAAA,EAAK;AACjC,MAAA,MAAM,cAAc,IAAI,IAAA,CAAK,IAAI,OAAA,EAAQ,GAAI,KAAK,OAAO,CAAA;AAMzD,MAAA,MAAM,YAAY,IAAI,SAAA,EAAU,CAC7B,GAAA,CAAI,2BAA2B,QAAQ,CAAA,sCAAA,CAAwC,CAAA,CAC/E,KAAA,CAAM,GAAG,CAAA,CACT,GAAA,CAAI,8BAA8B,CAAA,CAClC,MAAM,IAAA,CAAK,UAAU,CAAA,CACrB,GAAA,CAAI,sCAAsC,QAAQ,CAAA,8BAAA,CAAgC,CAAA,CAClF,GAAA,CAAI,uCAAuC,CAAA,CAC3C,KAAA,CAAM,WAAW,CAAA,CACjB,IAAI,oCAAoC,CAAA,CACxC,MAAM,GAAG,CAAA,CACT,IAAI,2CAA2C,CAAA;AAElD,MAAA,IAAI,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,EAAG;AAC7C,QAAA,SAAA,CAAU,GAAA,CAAI,mBAAmB,CAAA,CAAE,IAAA,CAAK,KAAK,QAAQ,CAAA,CAAE,IAAI,GAAG,CAAA;AAAA,MAChE;AACA,MAAA,IAAI,IAAA,CAAK,GAAA,IAAO,IAAA,CAAK,GAAA,CAAI,SAAS,CAAA,EAAG;AACnC,QAAA,SAAA,CAAU,GAAA,CAAI,cAAc,CAAA,CAAE,IAAA,CAAK,KAAK,GAAG,CAAA,CAAE,IAAI,GAAG,CAAA;AAAA,MACtD;AAEA,MAAA,SAAA,CACG,GAAA,CAAI,yCAAyC,CAAA,CAC7C,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA,CAChB,GAAA,CAAI,0BAA0B,CAAA,CAC9B,GAAA,CAAI,kEAAkE,CAAA,CACtE,GAAA;AAAA,QACC,wIAEW,YAAY,CAAA,8BAAA;AAAA,OACzB;AAEF,MAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,SAAS,CAAA;AAEhC,MAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,MAAS;AAAA,QACxB,EAAA,EAAI,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA;AAAA,QACd,cAAA,EAAgB,GAAA,CAAI,GAAA,CAAI,eAAe,CAAA;AAAA,QACvC,OAAA,EAAS,GAAA,CAAI,GAAA,CAAI,OAAO,CAAA;AAAA,QACxB,QAAA,EAAU,GAAA,CAAI,GAAA,CAAI,QAAQ,CAAA;AAAA,QAC1B,WAAA,EAAa,GAAA,CAAI,GAAA,CAAI,YAAY,CAAA;AAAA,QACjC,YAAA,EAAc;AAAA,UACZ,MAAA,EAAQ,GAAA,CAAI,GAAA,CAAI,OAAO,CAAA;AAAA,UACvB,IAAA,EAAM,GAAA,CAAI,GAAA,CAAI,IAAI,CAAA;AAAA,UAClB,OAAA,EAAS,OAAO,GAAA,CAAI,OAAA,KAAY,QAAA,GAAW,KAAK,KAAA,CAAM,GAAA,CAAI,OAAO,CAAA,GAAI,GAAA,CAAI,OAAA;AAAA,UACzE,OAAA,EAAS,WAAA,CAAY,GAAA,CAAI,QAAQ;AAAA;AACnC,OACF,CAAE,CAAA;AAAA,IACJ,CAAA;AAAA,IAEA,MAAM,kBAAkB,QAAA,EAAsC;AAC5D,MAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AAC3B,MAAA,MAAM,GAAA,uBAAU,IAAA,EAAK;AAKrB,MAAA,MAAM,SAAA,GAAY,IAAI,SAAA,EAAU,CAAE,GAAA;AAAA,QAChC,UAAU,QAAQ,CAAA,kZAAA;AAAA,OAUpB;AAEA,MAAA,SAAA,CAAU,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,6BAA6B,CAAA;AAEtD,MAAA,QAAA,CAAS,QAAQ,CAAC,EAAE,IAAI,OAAA,EAAS,aAAA,IAAiB,KAAA,KAAU;AAC1D,QAAA,IAAI,KAAA,GAAQ,CAAA,EAAG,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AACjC,QAAA,SAAA,CAAU,KAAA,CAAM;AAAA,UACd,CAAC,IAAI,MAAM,CAAA;AAAA,UACX,CAAC,OAAA,CAAQ,MAAA,EAAQ,MAAM,CAAA;AAAA,UACvB,CAAC,OAAA,CAAQ,MAAA,KAAW,QAAA,IAAY,OAAA,CAAQ,WAAW,SAAS,CAAA;AAAA,UAC5D;AAAA,YACE,QAAQ,MAAA,KAAW,QAAA,GACf,OAAA,CAAQ,KAAA,CAAM,MAAM,CAAA,EAAG,GAAI,CAAA,GAC3B,OAAA,CAAQ,WAAW,SAAA,GACjB,OAAA,CAAQ,OAAO,KAAA,CAAM,CAAA,EAAG,GAAI,CAAA,GAC5B,IAAA;AAAA,YACN;AAAA,WACF;AAAA,UACA,CAAC,aAAA,IAAiB,IAAA,EAAM,aAAa;AAAA,SACtC,CAAA;AAAA,MACH,CAAC,CAAA;AAED,MAAA,SAAA,CAAU,IAAI,oEAAoE,CAAA;AAElF,MAAA,MAAM,IAAI,SAAS,CAAA;AAAA,IACrB,CAAA;AAAA,IAEA,MAAM,kBAAkB,IAAA,EAAoC;AAC1D,MAAA,MAAM,SAAS,IAAA,CAAK,MAAA,GAAS,YAAA,CAAa,IAAA,CAAK,MAAM,CAAA,GAAI,IAAA;AAEzD,MAAA,MAAM,SAAA,GAAY,IAAI,SAAA,EAAU,CAC7B,IAAI,CAAA,cAAA,EAAiB,YAAY,CAAA,iBAAA,CAAmB,CAAA,CACpD,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA,CACjB,IAAI,gCAAgC,CAAA;AAEvC,MAAA,IAAI,IAAA,CAAK,UAAA,EAAY,SAAA,CAAU,GAAA,CAAI,sBAAsB,CAAA;AAEzD,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,SAAA,CACG,GAAA,CAAI,2BAA2B,CAAA,CAC/B,KAAA,CAAM,OAAO,SAAS,CAAA,CACtB,GAAA,CAAI,iBAAiB,EACrB,KAAA,CAAM,MAAA,CAAO,EAAE,CAAA,CACf,IAAI,SAAS,CAAA;AAAA,MAClB;AAEA,MAAA,SAAA,CAAU,IAAI,2CAA2C,CAAA,CAAE,KAAA,CAAM,IAAA,CAAK,QAAQ,CAAC,CAAA;AAE/E,MAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,SAAS,CAAA;AAChC,MAAA,MAAM,IAAA,GAAO,KAAK,KAAA,CAAM,CAAA,EAAG,KAAK,KAAK,CAAA,CAAE,IAAI,cAAc,CAAA;AACzD,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,EAAA,CAAG,EAAE,CAAA;AAEvB,MAAA,OAAO;AAAA,QACL,aAAA,EAAe,IAAA;AAAA,QACf,UAAA,EAAY,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,KAAA,IAAS,IAAA,GAAO,YAAA,CAAa,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,EAAE,CAAA,GAAI;AAAA,OACzF;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,YAAY,MAAA,EAAgB;AAChC,MAAA,MAAM,OAAO,MAAM,GAAA;AAAA,QACjB,IAAI,SAAA,EAAU,CACX,GAAA,CAAI,CAAA,mCAAA,EAAsC,YAAY,CAAA,iBAAA,CAAmB,CAAA,CACzE,KAAA,CAAM,MAAM,CAAA,CACZ,GAAA,CAAI,oDAAoD;AAAA,OAC7D;AACA,MAAA,OAAO,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA,EAAG,SAAS,CAAC,CAAA;AAAA,IAChC,CAAA;AAAA,IAEA,MAAM,QAAA,CAAS,MAAA,EAAgB,MAAA,EAAc;AAC3C,MAAA,MAAM,GAAA;AAAA,QACJ,IAAI,SAAA,EAAU,CACX,GAAA,CAAI,CAAA,OAAA,EAAU,YAAY,CAAA,eAAA,CAAiB,CAAA,CAC3C,KAAA,iBAAM,IAAI,IAAA,EAAM,CAAA,CAChB,IAAI,gCAAgC,CAAA,CACpC,KAAA,CAAM,MAAM,CAAA,CACZ,GAAA,CAAI,+CAA+C,CAAA,CACnD,KAAA,CAAM,MAAM,CAAA,CACZ,GAAA,CAAI,eAAe;AAAA,OACxB;AAAA,IACF,CAAA;AAAA,IAEA,MAAM,QAAA,CAAS,MAAA,EAAgB,eAAA,EAAoC;AACjE,MAAA,IAAI,eAAA,CAAgB,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA;AAOzC,MAAA,MAAM,OAAO,MAAM,GAAA;AAAA,QACjB,IAAI,SAAA,EAAU,CACX,GAAA,CAAI,CAAA,OAAA,EAAU,YAAY,CAAA,iCAAA,CAAmC,CAAA,CAC7D,KAAA,iBAAM,IAAI,IAAA,EAAM,CAAA,CAChB,IAAI,iCAAiC,CAAA,CACrC,KAAA,CAAM,MAAM,CAAA,CACZ,GAAA,CAAI,oBAAoB,CAAA,CACxB,IAAA,CAAK,eAAe,CAAA,CACpB,GAAA,CAAI,gBAAgB;AAAA,OACzB;AACA,MAAA,OAAO,IAAA,CAAK,MAAA;AAAA,IACd,CAAA;AAAA,IAEA,MAAM,YAAY,MAAA,EAAgB;AAChC,MAAA,MAAM,OAAO,MAAM,GAAA;AAAA,QACjB,IAAI,WAAU,CACX,GAAA,CAAI,UAAU,YAAY,CAAA,eAAA,CAAiB,EAC3C,KAAA,iBAAM,IAAI,MAAM,CAAA,CAChB,IAAI,gCAAgC,CAAA,CACpC,MAAM,MAAM,CAAA,CACZ,IAAI,yCAAyC;AAAA,OAClD;AACA,MAAA,OAAO,IAAA,CAAK,MAAA;AAAA,IACd,CAAA;AAAA,IAEA,MAAM,oBAAoB,IAAA,EAAsC;AAC9D,MAAA,MAAM,OAAO,MAAM,GAAA;AAAA,QACjB,IAAI,SAAA,EAAU,CACX,GAAA,CAAI,CAAA,cAAA,EAAiB,QAAQ,CAAA,2CAAA,CAA6C,CAAA,CAC1E,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA,CAChB,GAAA,CAAI,+CAA+C,CAAA,CACnD,KAAA,CAAM,KAAK,KAAK;AAAA,OACrB;AACA,MAAA,OAAO,IAAA,CAAK,IAAI,UAAU,CAAA;AAAA,IAC5B,CAAA;AAAA,IAEA,MAAM,UAAA,CAAW,KAAA,EAAe,KAAA,EAAoB,YAAA,EAA4B;AAC9E,MAAA,MAAM,SAAA,GAAY,IAAI,SAAA,EAAU,CAAE,IAAI,CAAA,cAAA,EAAiB,KAAA,CAAM,KAAK,CAAC,CAAA,CAAE,CAAA;AACrE,MAAA,WAAA,CAAY,WAAW,KAAK,CAAA;AAE5B,MAAA,IAAI,aAAa,OAAA,EAAS;AACxB,QAAA,MAAM,SAAA,GAAY,YAAA,CAAa,OAAA,CAAQ,SAAA,KAAc,SAAS,MAAA,GAAS,KAAA;AACvE,QAAA,SAAA,CAAU,GAAA,CAAI,CAAA,UAAA,EAAa,KAAA,CAAM,WAAA,CAAY,YAAA,CAAa,OAAA,CAAQ,KAAK,CAAC,CAAC,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAA;AAAA,MAC1F;AACA,MAAA,IAAI,YAAA,CAAa,OAAO,SAAA,CAAU,GAAA,CAAI,SAAS,CAAA,CAAE,KAAA,CAAM,aAAa,KAAK,CAAA;AAEzE,MAAA,OAAO,CAAC,GAAI,MAAM,GAAA,CAAI,SAAS,CAAE,CAAA;AAAA,IACnC,CAAA;AAAA,IAEA,MAAM,UAAA,CACJ,KAAA,EACA,IAAA,EACA,UAAA,EACA;AACA,MAAA,IAAI,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA;AAI9B,MAAA,MAAM,UAAU,MAAA,CAAO,IAAA,CAAK,KAAK,CAAC,CAAA,IAAK,EAAE,CAAA;AACzC,MAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA;AAEjC,MAAA,MAAM,SAAA,GAAY,IAAI,SAAA,EAAU,CAAE,GAAA;AAAA,QAChC,eAAe,KAAA,CAAM,KAAK,CAAC,CAAA,EAAA,EAAK,QAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,KAAA,CAAM,YAAY,CAAC,CAAC,CAAC,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,SAAA;AAAA,OACtF;AAEA,MAAA,IAAA,CAAK,OAAA,CAAQ,CAAC,GAAA,EAAK,KAAA,KAAU;AAC3B,QAAA,IAAI,KAAA,GAAQ,CAAA,EAAG,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AACjC,QAAA,SAAA,CACG,GAAA,CAAI,GAAG,CAAA,CACP,IAAA,CAAK,QAAQ,GAAA,CAAI,CAAC,MAAA,KAAW,GAAA,CAAI,MAAM,CAAC,CAAC,CAAA,CACzC,IAAI,GAAG,CAAA;AAAA,MACZ,CAAC,CAAA;AAED,MAAA,IAAI,UAAA,IAAc,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG;AACvC,QAAA,MAAM,WAAA,GAAc,OAAA,CACjB,MAAA,CAAO,CAAC,MAAA,KAAW,CAAC,UAAA,CAAW,QAAA,CAAS,MAAM,CAAC,CAAA,CAC/C,GAAA,CAAI,CAAC,MAAA,KAAW;AACf,UAAA,MAAM,MAAA,GAAS,KAAA,CAAM,WAAA,CAAY,MAAM,CAAC,CAAA;AACxC,UAAA,OAAO,CAAA,EAAG,MAAM,CAAA,YAAA,EAAe,MAAM,CAAA,CAAA;AAAA,QACvC,CAAC,CAAA;AAEH,QAAA,SAAA,CAAU,GAAA;AAAA,UACR,iBAAiB,UAAA,CAAW,GAAA,CAAI,CAAC,CAAA,KAAM,KAAA,CAAM,YAAY,CAAC,CAAC,CAAC,CAAA,CAAE,KAAK,IAAI,CAAC,mBAAmB,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,SACnH;AAAA,MACF;AAEA,MAAA,SAAA,CAAU,IAAI,oBAAoB,CAAA;AAClC,MAAA,OAAA,CAAQ,MAAM,GAAA,CAAI,SAAS,CAAA,EAAG,MAAA;AAAA,IAChC,CAAA;AAAA,IAEA,MAAM,UAAA,CAAW,KAAA,EAAe,KAAA,EAAoB,GAAA,EAA8B;AAChF,MAAA,MAAM,WAAA,GAAc,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA;AACtC,MAAA,IAAI,WAAA,CAAY,MAAA,KAAW,CAAA,EAAG,OAAO,CAAA;AAErC,MAAA,MAAM,SAAA,GAAY,IAAI,SAAA,EAAU,CAAE,IAAI,CAAA,OAAA,EAAU,KAAA,CAAM,KAAK,CAAC,CAAA,KAAA,CAAO,CAAA;AAEnE,MAAA,WAAA,CAAY,QAAQ,CAAC,CAAC,KAAA,EAAO,KAAK,GAAG,KAAA,KAAU;AAC7C,QAAA,IAAI,KAAA,GAAQ,CAAA,EAAG,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA;AACjC,QAAA,SAAA,CAAU,GAAA,CAAI,CAAA,EAAG,KAAA,CAAM,WAAA,CAAY,KAAK,CAAC,CAAC,CAAA,GAAA,CAAK,CAAA,CAAE,KAAA,CAAM,KAAK,CAAA;AAAA,MAC9D,CAAC,CAAA;AAED,MAAA,WAAA,CAAY,WAAW,KAAK,CAAA;AAC5B,MAAA,SAAA,CAAU,IAAI,oBAAoB,CAAA;AAElC,MAAA,OAAA,CAAQ,MAAM,GAAA,CAAI,SAAS,CAAA,EAAG,MAAA;AAAA,IAChC,CAAA;AAAA,IAEA,MAAM,UAAA,CAAW,KAAA,EAAe,KAAA,EAAoB;AAClD,MAAA,MAAM,SAAA,GAAY,IAAI,SAAA,EAAU,CAAE,IAAI,CAAA,YAAA,EAAe,KAAA,CAAM,KAAK,CAAC,CAAA,CAAE,CAAA;AACnE,MAAA,WAAA,CAAY,WAAW,KAAK,CAAA;AAC5B,MAAA,SAAA,CAAU,IAAI,oBAAoB,CAAA;AAElC,MAAA,OAAA,CAAQ,MAAM,GAAA,CAAI,SAAS,CAAA,EAAG,MAAA;AAAA,IAChC;AAAA,GACF;AACF;;;AC9fA,IAAM,QAAA,GAAqD;AAAA,EACzD,MAAA,EAAQ,MAAA;AAAA,EACR,MAAA,EAAQ,SAAA;AAAA,EACR,OAAA,EAAS,SAAA;AAAA,EACT,IAAA,EAAM,aAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAEA,IAAMA,MAAAA,GAAQ,CAAC,KAAA,KAAkB,CAAA,CAAA,EAAI,MAAM,OAAA,CAAQ,IAAA,EAAM,IAAI,CAAC,CAAA,CAAA,CAAA;AAE9D,IAAM,aAAA,GAAgB,CAAC,KAAA,KAAoC;AACzD,EAAA,IAAI,KAAA,CAAM,YAAY,OAAO,gBAAA;AAC7B,EAAA,IAAI,KAAA,CAAM,OAAA,KAAY,MAAA,EAAW,OAAO,EAAA;AACxC,EAAA,IAAI,OAAO,KAAA,CAAM,OAAA,KAAY,QAAA,EAAU,OAAO,CAAA,UAAA,EAAa,KAAA,CAAM,OAAA,CAAQ,OAAA,CAAQ,IAAA,EAAM,IAAI,CAAC,CAAA,CAAA,CAAA;AAC5F,EAAA,OAAO,CAAA,SAAA,EAAY,MAAM,OAAO,CAAA,CAAA;AAClC,CAAA;AAGO,SAAS,iBAAA,CAAkB,MAAA,EAA2B,MAAA,GAAS,EAAA,EAAc;AAClF,EAAA,MAAM,aAAuB,EAAC;AAE9B,EAAA,KAAA,MAAW,KAAA,IAAS,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,EAAG;AACzC,IAAA,MAAM,IAAA,GAAOA,MAAAA,CAAM,MAAA,GAAS,KAAA,CAAM,SAAS,CAAA;AAE3C,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,KAAA,CAAM,MAAM,CAAA,CAAE,GAAA,CAAI,CAAC,CAAC,KAAA,EAAO,IAAI,CAAA,KAAM;AAClE,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,QAAA,GAAW,WAAA,GAAc,EAAA;AAClD,MAAA,OAAO,KAAKA,MAAAA,CAAM,WAAA,CAAY,KAAK,CAAC,CAAC,CAAA,CAAA,EAAI,QAAA,CAAS,IAAA,CAAK,IAAI,CAAC,CAAA,EAAG,WAAW,CAAA,EAAG,aAAA,CAAc,IAAI,CAAC,CAAA,CAAA;AAAA,IAClG,CAAC,CAAA;AAED,IAAA,IAAI,KAAA,CAAM,YAAY,MAAA,EAAQ;AAC5B,MAAA,MAAM,GAAA,GAAM,KAAA,CAAM,UAAA,CAAW,GAAA,CAAI,CAAC,KAAA,KAAUA,MAAAA,CAAM,WAAA,CAAY,KAAK,CAAC,CAAC,CAAA,CAAE,KAAK,IAAI,CAAA;AAChF,MAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,eAAA,EAAkB,GAAG,CAAA,CAAA,CAAG,CAAA;AAAA,IACvC;AAEA,IAAA,UAAA,CAAW,IAAA,CAAK,8BAA8B,IAAI,CAAA;AAAA,EAAO,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAC;AAAA,CAAA,CAAK,CAAA;AAEjF,IAAA,KAAA,MAAW,WAAA,IAAe,KAAA,CAAM,OAAA,IAAW,EAAC,EAAG;AAC7C,MAAA,MAAM,SAAA,GAAYA,MAAAA;AAAA,QAChB,MAAA,IAAU,WAAA,CAAY,IAAA,IAAQ,CAAA,EAAG,KAAA,CAAM,SAAS,CAAA,CAAA,EAAI,WAAA,CAAY,EAAA,CAAG,IAAA,CAAK,GAAG,CAAC,CAAA,IAAA,CAAA;AAAA,OAC9E;AACA,MAAA,MAAMC,QAAAA,GAAU,WAAA,CAAY,EAAA,CAAG,GAAA,CAAI,CAAC,KAAA,KAAUD,MAAAA,CAAM,WAAA,CAAY,KAAK,CAAC,CAAC,CAAA,CAAE,KAAK,IAAI,CAAA;AAClF,MAAA,MAAM,MAAA,GAAS,WAAA,CAAY,MAAA,GAAS,SAAA,GAAY,EAAA;AAChD,MAAA,UAAA,CAAW,IAAA,CAAK,UAAU,MAAM,CAAA,oBAAA,EAAuB,SAAS,CAAA,IAAA,EAAO,IAAI,CAAA,EAAA,EAAKC,QAAO,CAAA,CAAA,CAAG,CAAA;AAAA,IAC5F;AAAA,EACF;AAEA,EAAA,OAAO,UAAA;AACT;;;ACzBO,SAAS,cAAc,IAAA,EAAkB;AAC9C,EAAA,OAAO,OAAU,EAAA,KAAoD;AACnE,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,OAAA,EAAQ;AAClC,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,CAAO,MAAM,OAAO,CAAA;AAC1B,MAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAAG,OAAO,IAAA,EAAM,MAAA,KAAA,CAAY,MAAM,MAAA,CAAO,KAAA,CAAM,IAAA,EAAM,MAAM,CAAA,EAAG,IAAI,CAAA;AACvF,MAAA,MAAM,MAAA,CAAO,MAAM,QAAQ,CAAA;AAC3B,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,MAAA,CAAO,MAAM,UAAU,CAAA;AAC7B,MAAA,MAAM,KAAA;AAAA,IACR,CAAA,SAAE;AACA,MAAA,MAAA,CAAO,OAAA,EAAQ;AAAA,IACjB;AAAA,EACF,CAAA;AACF;AAoBA,eAAsB,oBAAA,CACpB,KAAA,EACA,OAAA,GAA+B,EAAC,EACf;AACjB,EAAA,MAAM,UAAwC,CAAC,UAAA,EAAY,GAAI,OAAA,CAAQ,OAAA,IAAW,EAAG,CAAA;AAErF,EAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,KAAA,MAAW,SAAA,IAAa,iBAAA,CAAkB,MAAA,EAAQ,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjE,MAAA,MAAM,KAAA,CAAM,SAAA,EAAW,EAAE,CAAA;AACzB,MAAA,KAAA,IAAS,CAAA;AAAA,IACX;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT","file":"postgres.cjs","sourcesContent":["// Web APIs, not Buffer: this runs on Workers and Vercel Edge.\n\nexport function encodeBase64Url(input: string | Uint8Array): string {\n const bytes = typeof input === \"string\" ? new TextEncoder().encode(input) : input;\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n// Uint8Array<ArrayBuffer>: bare Uint8Array widens and is not a BufferSource.\nexport function decodeBase64UrlBytes(value: string): Uint8Array<ArrayBuffer> | null {\n try {\n const binary = atob(value.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n } catch {\n return null;\n }\n}\n\nexport function decodeBase64Url(value: string): string | null {\n const bytes = decodeBase64UrlBytes(value);\n return bytes ? new TextDecoder().decode(bytes) : null;\n}\n","import type { SchemaDeclaration } from \"../core/plugin\";\n\nexport const toSnakeCase = (value: string) => value.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);\n\n/**\n * Core tables in the format plugins use. Less expressive than the hand-written\n * Drizzle schema (no partial indexes, ordering or cascade) — a drift test\n * asserts the two agree on the fields they share.\n */\nexport const coreSchema = {\n notification: {\n tableName: \"notification\",\n fields: {\n id: { type: \"string\", required: true },\n userId: { type: \"string\", required: true },\n type: { type: \"string\", required: true },\n payload: { type: \"json\", required: true },\n actorId: { type: \"string\" },\n groupKey: { type: \"string\" },\n dedupeKey: { type: \"string\" },\n seenAt: { type: \"date\" },\n readAt: { type: \"date\" },\n archivedAt: { type: \"date\" },\n createdAt: { type: \"date\", required: true, defaultNow: true },\n },\n primaryKey: [\"id\"],\n indexes: [\n { on: [\"userId\", \"createdAt\"], name: \"notification_feed_idx\" },\n { on: [\"userId\", \"groupKey\"], name: \"notification_group_idx\" },\n { on: [\"userId\", \"dedupeKey\"], unique: true, name: \"notification_dedupe_idx\" },\n ],\n },\n\n notificationDelivery: {\n tableName: \"notification_delivery\",\n fields: {\n id: { type: \"string\", required: true },\n notificationId: { type: \"string\", required: true },\n channel: { type: \"string\", required: true },\n status: { type: \"string\", required: true, default: \"pending\" },\n attempts: { type: \"number\", required: true, default: 0 },\n maxAttempts: { type: \"number\", required: true, default: 5 },\n notBefore: { type: \"date\", required: true, defaultNow: true },\n claimedAt: { type: \"date\" },\n claimedBy: { type: \"string\" },\n lastError: { type: \"string\" },\n updatedAt: { type: \"date\", required: true, defaultNow: true },\n },\n primaryKey: [\"id\"],\n indexes: [\n { on: [\"status\", \"notBefore\"], name: \"delivery_claim_idx\" },\n { on: [\"notificationId\"], name: \"delivery_notification_idx\" },\n ],\n },\n\n notificationPreference: {\n tableName: \"notification_preference\",\n fields: {\n userId: { type: \"string\", required: true },\n type: { type: \"string\", required: true },\n channel: { type: \"string\", required: true },\n enabled: { type: \"boolean\", required: true, default: true },\n frequency: { type: \"string\", required: true, default: \"instant\" },\n },\n primaryKey: [\"userId\", \"type\", \"channel\"],\n },\n} satisfies SchemaDeclaration;\n","import { toSnakeCase } from \"../schema/declaration\";\nimport { ConfigError } from \"./errors\";\nimport type { SchemaDeclaration, TableDeclaration } from \"./plugin\";\n\nexport type Scalar = string | number | boolean | Date | null;\n\n/**\n * One operator per condition. Operators are tagged objects so a bare value is\n * always an equality test; json columns therefore cannot be filtered, since an\n * object value would be ambiguous.\n */\nexport type WhereCondition =\n | Scalar\n | { in: readonly (string | number)[] }\n | { lt: Date | number }\n | { lte: Date | number }\n | { gt: Date | number }\n | { gte: Date | number }\n | { not: Scalar };\n\nexport type WhereClause = Record<string, WhereCondition>;\n\nexport type QueryOptions = {\n limit?: number;\n orderBy?: { field: string; direction: \"asc\" | \"desc\" };\n};\n\nexport type UpsertOptions = { onConflict: readonly string[] };\n\n/**\n * Table access for plugins, scoped to the tables that plugin declared.\n *\n * Plugins could always declare tables via schema() but had no way to read\n * them, so preferences had to bolt its queries onto the core adapter. That\n * does not generalise to digests or push.\n */\nexport type PluginStore = {\n find<T = Record<string, unknown>>(\n table: string,\n where?: WhereClause,\n options?: QueryOptions,\n ): Promise<T[]>;\n insert(table: string, rows: readonly Record<string, unknown>[]): Promise<number>;\n upsert(\n table: string,\n rows: readonly Record<string, unknown>[],\n options: UpsertOptions,\n ): Promise<number>;\n update(table: string, where: WhereClause, set: Record<string, unknown>): Promise<number>;\n remove(table: string, where: WhereClause): Promise<number>;\n};\n\nexport type TableStorage = {\n readonly naming?: \"snake_case\" | \"preserve\";\n readonly serializesJson?: boolean;\n\n queryTable(\n table: string,\n where: WhereClause,\n options: QueryOptions,\n ): Promise<Record<string, unknown>[]>;\n insertRows(\n table: string,\n rows: readonly Record<string, unknown>[],\n onConflict?: readonly string[],\n ): Promise<number>;\n updateRows(table: string, where: WhereClause, set: Record<string, unknown>): Promise<number>;\n deleteRows(table: string, where: WhereClause): Promise<number>;\n};\n\nconst OPERATORS = [\"in\", \"lt\", \"lte\", \"gt\", \"gte\", \"not\"] as const;\n\nexport const isOperator = (value: unknown): boolean =>\n typeof value === \"object\" &&\n value !== null &&\n !(value instanceof Date) &&\n OPERATORS.some((op) => op in value);\n\n/**\n * Every table and column a plugin touches is checked against its own\n * declaration. Without this a plugin could read the notification table, and\n * unvalidated identifiers would reach the SQL builder.\n */\nexport function createPluginStore(\n pluginId: string,\n schema: SchemaDeclaration | undefined,\n storage: TableStorage,\n prefix: string,\n): PluginStore {\n const byTableName = new Map<string, TableDeclaration>();\n for (const declaration of Object.values(schema ?? {})) {\n byTableName.set(declaration.tableName, declaration);\n }\n\n function resolve(table: string): TableDeclaration {\n const declaration = byTableName.get(table);\n if (!declaration) {\n throw new ConfigError(\n `plugin \"${pluginId}\" accessed table \"${table}\", which it does not declare in schema().`,\n );\n }\n return declaration;\n }\n\n function checkFields(table: TableDeclaration, fields: Iterable<string>, context: string) {\n for (const field of fields) {\n const spec = table.fields[field];\n if (!spec) {\n throw new ConfigError(\n `plugin \"${pluginId}\" used unknown field \"${field}\" on \"${table.tableName}\" (${context}).`,\n );\n }\n if (context === \"where\" && spec.type === \"json\") {\n throw new ConfigError(\n `plugin \"${pluginId}\" cannot filter on json field \"${field}\"; ` +\n \"a bare object value is indistinguishable from an operator.\",\n );\n }\n }\n }\n\n const qualified = (table: TableDeclaration) => prefix + table.tableName;\n\n // A document store keeps the declared names; a SQL adapter wants columns.\n const column = (field: string) => (storage.naming === \"preserve\" ? field : toSnakeCase(field));\n\n /**\n * json columns must be handed to the driver as text; postgres-js cannot bind\n * a plain object and the insert fails outright.\n */\n const serialize = (table: TableDeclaration, row: Record<string, unknown>) => {\n if (storage.serializesJson === false) return row;\n\n const out: Record<string, unknown> = {};\n for (const [field, value] of Object.entries(row)) {\n out[field] =\n table.fields[field]?.type === \"json\" && value !== null && value !== undefined\n ? JSON.stringify(value)\n : value;\n }\n return out;\n };\n\n return {\n async find(table, where = {}, options = {}) {\n const declaration = resolve(table);\n checkFields(declaration, Object.keys(where), \"where\");\n if (options.orderBy) checkFields(declaration, [options.orderBy.field], \"orderBy\");\n\n const rows = await storage.queryTable(qualified(declaration), where, options);\n\n // SELECT * returns snake_case columns; plugins declare camelCase fields\n // and type their reads that way. Without this every property is\n // undefined at runtime while typechecking perfectly.\n return rows.map((row) => {\n const mapped: Record<string, unknown> = {};\n for (const field of Object.keys(declaration.fields)) {\n const value = row[column(field)];\n // Drivers differ: some hand back parsed jsonb, some raw text.\n mapped[field] =\n declaration.fields[field]?.type === \"json\" && typeof value === \"string\"\n ? JSON.parse(value)\n : value;\n }\n return mapped;\n }) as never;\n },\n\n async insert(table, rows) {\n const declaration = resolve(table);\n for (const row of rows) checkFields(declaration, Object.keys(row), \"insert\");\n return storage.insertRows(\n qualified(declaration),\n rows.map((row) => serialize(declaration, row)),\n );\n },\n\n async upsert(table, rows, options) {\n const declaration = resolve(table);\n for (const row of rows) checkFields(declaration, Object.keys(row), \"upsert\");\n checkFields(declaration, options.onConflict, \"onConflict\");\n return storage.insertRows(\n qualified(declaration),\n rows.map((row) => serialize(declaration, row)),\n options.onConflict,\n );\n },\n\n async update(table, where, set) {\n const declaration = resolve(table);\n checkFields(declaration, Object.keys(where), \"where\");\n checkFields(declaration, Object.keys(set), \"update\");\n return storage.updateRows(qualified(declaration), where, serialize(declaration, set));\n },\n\n async remove(table, where) {\n const declaration = resolve(table);\n checkFields(declaration, Object.keys(where), \"where\");\n return storage.deleteRows(qualified(declaration), where);\n },\n };\n}\n","/**\n * Builds a parameterised statement: SQL text with `$1, $2 …` placeholders plus\n * the values to bind, which is the one calling convention every Postgres\n * driver agrees on.\n *\n * Values are never interpolated. Identifiers are quoted, and the only\n * identifiers that reach here are either constants in this file or names the\n * PluginStore has already validated against the plugin's own schema.\n */\nexport class Statement {\n #text = \"\";\n readonly params: unknown[] = [];\n\n /** Raw SQL. Never pass user input through this — use `value`. */\n raw(fragment: string): this {\n this.#text += fragment;\n return this;\n }\n\n /** Binds a value and writes its placeholder. */\n value(value: unknown): this {\n // Dates bind as ISO strings: every driver accepts that for timestamptz,\n // whereas a Date object is only handled by some of them.\n this.params.push(value instanceof Date ? value.toISOString() : value);\n this.#text += `$${this.params.length}`;\n return this;\n }\n\n /** `$1, $2, $3` — a comma-separated run of bound values. */\n list(values: readonly unknown[], separator = \", \"): this {\n values.forEach((value, index) => {\n if (index > 0) this.raw(separator);\n this.value(value);\n });\n return this;\n }\n\n /** `($1::text, $2::timestamptz)` — one VALUES tuple with explicit casts. */\n tuple(cells: readonly (readonly [unknown, string])[]): this {\n this.raw(\"(\");\n cells.forEach(([value, cast], index) => {\n if (index > 0) this.raw(\", \");\n this.value(value).raw(`::${cast}`);\n });\n return this.raw(\")\");\n }\n\n get text(): string {\n return this.#text;\n }\n}\n\n/** Double-quoted so a table or column name can never be read as SQL. */\nexport const quote = (identifier: string): string => `\"${identifier.replace(/\"/g, '\"\"')}\"`;\n","import type {\n ClaimArgs,\n ClaimedDelivery,\n DatabaseAdapter,\n DeliveryRelease,\n FeedPage,\n FeedQuery,\n InsertNotification,\n} from \"../../core/adapter\";\nimport { decodeBase64Url, encodeBase64Url } from \"../../core/base64url\";\nimport { isOperator, type QueryOptions, type WhereClause } from \"../../core/store\";\nimport type { Channel, DeliveryRecord, DeliveryStatus, NotificationRecord } from \"../../core/types\";\nimport { toSnakeCase } from \"../../schema/declaration\";\nimport { quote, Statement } from \"./statement\";\n\ntype Row = Record<string, unknown>;\n\n/**\n * Run one parameterised statement and return its rows.\n *\n * This is the whole contract — the lowest common denominator every Postgres\n * driver already exposes, which is what makes this adapter work with `pg`,\n * `postgres.js`, Kysely, Neon, or anything else without an ORM in between.\n */\nexport type SqlQuery = (text: string, params: readonly unknown[]) => Promise<readonly Row[]>;\n\nexport type PostgresAdapterOptions = {\n /** Table-name prefix. Must match the instance's `tablePrefix`. */\n prefix?: string;\n /**\n * Runs `fn` inside a transaction, passing it a query function bound to that\n * transaction. Supply it and `createNotifications` becomes atomic — a\n * notification and its deliveries land together or neither does. Without it\n * the adapter still works; it just loses that guarantee, the same tradeoff\n * the Mongo adapter makes when no client is passed.\n */\n transaction?: <T>(fn: (query: SqlQuery) => Promise<T>) => Promise<T>;\n};\n\nconst str = (value: unknown): string => String(value);\nconst nullableStr = (value: unknown): string | null => (value == null ? null : String(value));\nconst num = (value: unknown): number => Number(value);\nconst date = (value: unknown): Date => (value instanceof Date ? value : new Date(String(value)));\nconst nullableDate = (value: unknown): Date | null =>\n value == null ? null : value instanceof Date ? value : new Date(String(value));\n\nconst encodeCursor = (createdAt: Date, id: string) =>\n encodeBase64Url(`${createdAt.toISOString()}|${id}`);\n\nfunction decodeCursor(cursor: string): { createdAt: Date; id: string } | null {\n const decoded = decodeBase64Url(cursor);\n if (!decoded) return null;\n\n const [iso, id] = decoded.split(\"|\");\n if (!iso || !id) return null;\n\n const createdAt = new Date(iso);\n return Number.isNaN(createdAt.getTime()) ? null : { createdAt, id };\n}\n\nfunction toNotification(row: Row): NotificationRecord {\n return {\n id: str(row.id),\n userId: str(row.user_id),\n type: str(row.type),\n payload: typeof row.payload === \"string\" ? JSON.parse(row.payload) : row.payload,\n actorId: nullableStr(row.actor_id),\n groupKey: nullableStr(row.group_key),\n dedupeKey: nullableStr(row.dedupe_key),\n seenAt: nullableDate(row.seen_at),\n readAt: nullableDate(row.read_at),\n archivedAt: nullableDate(row.archived_at),\n createdAt: date(row.created_at),\n };\n}\n\nfunction toDelivery(row: Row): DeliveryRecord {\n return {\n id: str(row.id),\n notificationId: str(row.notification_id),\n channel: str(row.channel) as Channel,\n status: str(row.status) as DeliveryStatus,\n attempts: num(row.attempts),\n maxAttempts: num(row.max_attempts),\n notBefore: date(row.not_before),\n claimedAt: nullableDate(row.claimed_at),\n claimedBy: nullableStr(row.claimed_by),\n lastError: nullableStr(row.last_error),\n updatedAt: date(row.updated_at),\n };\n}\n\n/** Appends ` WHERE …` for a PluginStore clause. Values are always bound. */\nfunction appendWhere(statement: Statement, where: WhereClause): void {\n const entries = Object.entries(where);\n if (entries.length === 0) return;\n\n statement.raw(\" WHERE \");\n\n entries.forEach(([field, condition], index) => {\n if (index > 0) statement.raw(\" AND \");\n const column = quote(toSnakeCase(field));\n\n if (condition === null) {\n statement.raw(`${column} IS NULL`);\n return;\n }\n\n if (!isOperator(condition)) {\n statement.raw(`${column} = `).value(condition);\n return;\n }\n\n const operator = condition as Record<string, unknown>;\n\n if (\"in\" in operator) {\n const list = operator.in as readonly (string | number)[];\n // An empty IN () is a syntax error, and matching nothing is the honest\n // reading of \"in this empty set\".\n if (list.length === 0) statement.raw(\"FALSE\");\n else statement.raw(`${column} IN (`).list(list).raw(\")\");\n return;\n }\n\n if (\"lt\" in operator) statement.raw(`${column} < `).value(operator.lt);\n else if (\"lte\" in operator) statement.raw(`${column} <= `).value(operator.lte);\n else if (\"gt\" in operator) statement.raw(`${column} > `).value(operator.gt);\n else if (\"gte\" in operator) statement.raw(`${column} >= `).value(operator.gte);\n else if (\"not\" in operator) {\n if (operator.not === null) statement.raw(`${column} IS NOT NULL`);\n else statement.raw(`${column} IS DISTINCT FROM `).value(operator.not);\n } else statement.raw(\"TRUE\");\n });\n}\n\n/**\n * Postgres, through any driver — no ORM required.\n *\n * `drizzleAdapter` predates this and is kept for people already on Drizzle,\n * but it only ever used Drizzle as a SQL builder. This takes the query\n * function directly instead, so a plain `pg` Pool, `postgres.js`, Kysely or a\n * serverless driver all work without pulling an ORM into the dependency tree.\n *\n * The SQL is deliberately Postgres-specific — `ON CONFLICT`, `FOR UPDATE SKIP\n * LOCKED` and `IS DISTINCT FROM` have no portable equivalent, and the claim\n * primitive depends on the second of those. See RFC 0003.\n */\nexport function postgresAdapter(\n query: SqlQuery,\n options: PostgresAdapterOptions = {},\n): DatabaseAdapter {\n const prefix = options.prefix ?? \"\";\n\n const NOTIFICATION = quote(`${prefix}notification`);\n const DELIVERY = quote(`${prefix}notification_delivery`);\n\n const run = async (statement: Statement, exec: SqlQuery = query) =>\n exec(statement.text, statement.params);\n\n /** Uses a transaction when one was supplied, and runs plainly otherwise. */\n const atomically = <T>(fn: (exec: SqlQuery) => Promise<T>): Promise<T> =>\n options.transaction ? options.transaction(fn) : fn(query);\n\n return {\n name: \"postgres\",\n naming: \"snake_case\",\n serializesJson: true,\n\n async createNotifications(input: readonly InsertNotification[]) {\n if (input.length === 0) return { created: [], deduped: [] };\n\n // One clock. created_at has a DEFAULT now() for hand-written SQL, but\n // now() is the *database* clock while every cutoff compared against it\n // (markSeen, getFailedDeliveries) comes from the app. In production those\n // are different hosts, and NTP skew put the newest rows outside markSeen.\n const now = new Date();\n\n return atomically(async (exec) => {\n const insert = new Statement().raw(\n `INSERT INTO ${NOTIFICATION} ` +\n \"(id, user_id, type, payload, actor_id, group_key, dedupe_key, created_at) VALUES \",\n );\n\n input.forEach((row, index) => {\n if (index > 0) insert.raw(\", \");\n insert.tuple([\n [row.id, \"text\"],\n [row.userId, \"text\"],\n [row.type, \"text\"],\n [JSON.stringify(row.payload ?? null), \"jsonb\"],\n [row.actorId ?? null, \"text\"],\n [row.groupKey ?? null, \"text\"],\n [row.dedupeKey ?? null, \"text\"],\n [now, \"timestamptz\"],\n ]);\n });\n\n // Rows with a NULL dedupe_key never conflict — Postgres treats NULLs as\n // distinct — so unlimited undeduped notifications coexist.\n insert.raw(\" ON CONFLICT (user_id, dedupe_key) DO NOTHING RETURNING id\");\n\n const inserted = await run(insert, exec);\n const created = new Set(inserted.map((row) => str(row.id)));\n\n const deliveries = input\n .filter((row) => created.has(row.id))\n .flatMap((row) => row.deliveries.map((delivery) => ({ row, delivery })));\n\n if (deliveries.length > 0) {\n const insertDeliveries = new Statement().raw(\n `INSERT INTO ${DELIVERY} ` +\n \"(id, notification_id, channel, max_attempts, not_before, updated_at) VALUES \",\n );\n\n deliveries.forEach(({ row, delivery }, index) => {\n if (index > 0) insertDeliveries.raw(\", \");\n insertDeliveries.tuple([\n [delivery.id, \"text\"],\n [row.id, \"text\"],\n [delivery.channel, \"text\"],\n [delivery.maxAttempts, \"integer\"],\n [delivery.notBefore, \"timestamptz\"],\n [now, \"timestamptz\"],\n ]);\n });\n\n await run(insertDeliveries, exec);\n }\n\n return {\n created: [...created],\n deduped: input.filter((row) => !created.has(row.id)).map((row) => row.id),\n };\n });\n },\n\n async claimPendingDeliveries(args: ClaimArgs): Promise<readonly ClaimedDelivery[]> {\n const now = args.now ?? new Date();\n const staleBefore = new Date(now.getTime() - args.leaseMs);\n\n // SKIP LOCKED is what lets concurrent sweeps step around each other\n // rather than block. The CTE joins the notification in the same round\n // trip — claiming 20 rows then looking each one up is the N+1 this\n // primitive exists to avoid. See RFC 0003 §5.\n const statement = new Statement()\n .raw(`WITH claimed AS (UPDATE ${DELIVERY} SET status = 'claimed', claimed_at = `)\n .value(now)\n .raw(\"::timestamptz, claimed_by = \")\n .value(args.claimToken)\n .raw(`::text WHERE id IN (SELECT id FROM ${DELIVERY} WHERE (status = 'pending' OR `)\n .raw(\"(status = 'claimed' AND claimed_at < \")\n .value(staleBefore)\n .raw(\"::timestamptz)) AND not_before <= \")\n .value(now)\n .raw(\"::timestamptz AND attempts < max_attempts\");\n\n if (args.channels && args.channels.length > 0) {\n statement.raw(\" AND channel IN (\").list(args.channels).raw(\")\");\n }\n if (args.ids && args.ids.length > 0) {\n statement.raw(\" AND id IN (\").list(args.ids).raw(\")\");\n }\n\n statement\n .raw(\" ORDER BY not_before ASC, id ASC LIMIT \")\n .value(args.limit)\n .raw(\" FOR UPDATE SKIP LOCKED)\")\n .raw(\" RETURNING id, notification_id, channel, attempts, max_attempts)\")\n .raw(\n \" SELECT c.id, c.notification_id, c.channel, c.attempts, c.max_attempts,\" +\n \" n.user_id, n.type, n.payload, n.actor_id FROM claimed c\" +\n ` JOIN ${NOTIFICATION} n ON n.id = c.notification_id`,\n );\n\n const rows = await run(statement);\n\n return rows.map((row) => ({\n id: str(row.id),\n notificationId: str(row.notification_id),\n channel: str(row.channel) as Channel,\n attempts: num(row.attempts),\n maxAttempts: num(row.max_attempts),\n notification: {\n userId: str(row.user_id),\n type: str(row.type),\n payload: typeof row.payload === \"string\" ? JSON.parse(row.payload) : row.payload,\n actorId: nullableStr(row.actor_id),\n },\n }));\n },\n\n async releaseDeliveries(releases: readonly DeliveryRelease[]) {\n if (releases.length === 0) return;\n const now = new Date();\n\n // Written unconditionally — no claimed_by predicate. If the lease expired\n // and another worker re-sent, the duplicate already happened; recording\n // the true terminal state beats wedging the row in 'claimed'. RFC 0003 §6.\n const statement = new Statement().raw(\n `UPDATE ${DELIVERY} d SET status = CASE` +\n \" WHEN v.result = 'sent' THEN 'sent'\" +\n \" WHEN v.result = 'skipped' THEN 'skipped'\" +\n \" WHEN v.retryable AND d.attempts + 1 < d.max_attempts THEN 'pending'\" +\n \" ELSE 'failed' END,\" +\n \" attempts = CASE WHEN v.result IN ('sent', 'skipped')\" +\n \" THEN d.attempts ELSE d.attempts + 1 END,\" +\n \" last_error = v.error,\" +\n \" not_before = COALESCE(v.not_before, d.not_before),\" +\n \" claimed_at = NULL, claimed_by = NULL, updated_at = \",\n );\n\n statement.value(now).raw(\"::timestamptz FROM (VALUES \");\n\n releases.forEach(({ id, outcome, nextAttemptAt }, index) => {\n if (index > 0) statement.raw(\", \");\n statement.tuple([\n [id, \"text\"],\n [outcome.result, \"text\"],\n [outcome.result === \"failed\" && outcome.retryable, \"boolean\"],\n [\n outcome.result === \"failed\"\n ? outcome.error.slice(0, 2000)\n : outcome.result === \"skipped\"\n ? outcome.reason.slice(0, 2000)\n : null,\n \"text\",\n ],\n [nextAttemptAt ?? null, \"timestamptz\"],\n ]);\n });\n\n statement.raw(\") AS v(id, result, retryable, error, not_before) WHERE d.id = v.id\");\n\n await run(statement);\n },\n\n async listNotifications(feed: FeedQuery): Promise<FeedPage> {\n const cursor = feed.cursor ? decodeCursor(feed.cursor) : null;\n\n const statement = new Statement()\n .raw(`SELECT * FROM ${NOTIFICATION} WHERE user_id = `)\n .value(feed.userId)\n .raw(\"::text AND archived_at IS NULL\");\n\n if (feed.unreadOnly) statement.raw(\" AND read_at IS NULL\");\n\n if (cursor) {\n statement\n .raw(\" AND (created_at, id) < (\")\n .value(cursor.createdAt)\n .raw(\"::timestamptz, \")\n .value(cursor.id)\n .raw(\"::text)\");\n }\n\n statement.raw(\" ORDER BY created_at DESC, id DESC LIMIT \").value(feed.limit + 1);\n\n const rows = await run(statement);\n const page = rows.slice(0, feed.limit).map(toNotification);\n const last = page.at(-1);\n\n return {\n notifications: page,\n nextCursor: rows.length > feed.limit && last ? encodeCursor(last.createdAt, last.id) : null,\n };\n },\n\n async countUnseen(userId: string) {\n const rows = await run(\n new Statement()\n .raw(`SELECT count(*)::int AS count FROM ${NOTIFICATION} WHERE user_id = `)\n .value(userId)\n .raw(\"::text AND seen_at IS NULL AND archived_at IS NULL\"),\n );\n return num(rows[0]?.count ?? 0);\n },\n\n async markSeen(userId: string, before: Date) {\n await run(\n new Statement()\n .raw(`UPDATE ${NOTIFICATION} SET seen_at = `)\n .value(new Date())\n .raw(\"::timestamptz WHERE user_id = \")\n .value(userId)\n .raw(\"::text AND seen_at IS NULL AND created_at <= \")\n .value(before)\n .raw(\"::timestamptz\"),\n );\n },\n\n async markRead(userId: string, notificationIds: readonly string[]) {\n if (notificationIds.length === 0) return 0;\n\n // Scoped by user_id as well as id — a caller must never be able to flip\n // someone else's row by guessing an id. RFC 0002 §2.\n // No `read_at IS NULL` filter: re-marking must be idempotent. Returning 0\n // for an already-read row made the route 404, which made the client roll\n // its optimistic update back and show the item as unread again.\n const rows = await run(\n new Statement()\n .raw(`UPDATE ${NOTIFICATION} SET read_at = COALESCE(read_at, `)\n .value(new Date())\n .raw(\"::timestamptz) WHERE user_id = \")\n .value(userId)\n .raw(\"::text AND id IN (\")\n .list(notificationIds)\n .raw(\") RETURNING id\"),\n );\n return rows.length;\n },\n\n async markAllRead(userId: string) {\n const rows = await run(\n new Statement()\n .raw(`UPDATE ${NOTIFICATION} SET read_at = `)\n .value(new Date())\n .raw(\"::timestamptz WHERE user_id = \")\n .value(userId)\n .raw(\"::text AND read_at IS NULL RETURNING id\"),\n );\n return rows.length;\n },\n\n async getFailedDeliveries(args: { since: Date; limit: number }) {\n const rows = await run(\n new Statement()\n .raw(`SELECT * FROM ${DELIVERY} WHERE status = 'failed' AND updated_at >= `)\n .value(args.since)\n .raw(\"::timestamptz ORDER BY updated_at DESC LIMIT \")\n .value(args.limit),\n );\n return rows.map(toDelivery);\n },\n\n async queryTable(table: string, where: WhereClause, queryOptions: QueryOptions) {\n const statement = new Statement().raw(`SELECT * FROM ${quote(table)}`);\n appendWhere(statement, where);\n\n if (queryOptions.orderBy) {\n const direction = queryOptions.orderBy.direction === \"desc\" ? \"DESC\" : \"ASC\";\n statement.raw(` ORDER BY ${quote(toSnakeCase(queryOptions.orderBy.field))} ${direction}`);\n }\n if (queryOptions.limit) statement.raw(\" LIMIT \").value(queryOptions.limit);\n\n return [...(await run(statement))];\n },\n\n async insertRows(\n table: string,\n rows: readonly Record<string, unknown>[],\n onConflict?: readonly string[],\n ) {\n if (rows.length === 0) return 0;\n\n // Column order is taken from the first row and every row is projected\n // onto it, so a ragged batch cannot shift values into other columns.\n const columns = Object.keys(rows[0] ?? {});\n if (columns.length === 0) return 0;\n\n const statement = new Statement().raw(\n `INSERT INTO ${quote(table)} (${columns.map((c) => quote(toSnakeCase(c))).join(\", \")}) VALUES `,\n );\n\n rows.forEach((row, index) => {\n if (index > 0) statement.raw(\", \");\n statement\n .raw(\"(\")\n .list(columns.map((column) => row[column]))\n .raw(\")\");\n });\n\n if (onConflict && onConflict.length > 0) {\n const assignments = columns\n .filter((column) => !onConflict.includes(column))\n .map((column) => {\n const quoted = quote(toSnakeCase(column));\n return `${quoted} = EXCLUDED.${quoted}`;\n });\n\n statement.raw(\n ` ON CONFLICT (${onConflict.map((f) => quote(toSnakeCase(f))).join(\", \")}) DO UPDATE SET ${assignments.join(\", \")}`,\n );\n }\n\n statement.raw(\" RETURNING 1 AS ok\");\n return (await run(statement)).length;\n },\n\n async updateRows(table: string, where: WhereClause, set: Record<string, unknown>) {\n const assignments = Object.entries(set);\n if (assignments.length === 0) return 0;\n\n const statement = new Statement().raw(`UPDATE ${quote(table)} SET `);\n\n assignments.forEach(([field, value], index) => {\n if (index > 0) statement.raw(\", \");\n statement.raw(`${quote(toSnakeCase(field))} = `).value(value);\n });\n\n appendWhere(statement, where);\n statement.raw(\" RETURNING 1 AS ok\");\n\n return (await run(statement)).length;\n },\n\n async deleteRows(table: string, where: WhereClause) {\n const statement = new Statement().raw(`DELETE FROM ${quote(table)}`);\n appendWhere(statement, where);\n statement.raw(\" RETURNING 1 AS ok\");\n\n return (await run(statement)).length;\n },\n };\n}\n","import type { FieldDeclaration, SchemaDeclaration } from \"../core/plugin\";\nimport { toSnakeCase } from \"./declaration\";\n\nconst SQL_TYPE: Record<FieldDeclaration[\"type\"], string> = {\n string: \"text\",\n number: \"integer\",\n boolean: \"boolean\",\n date: \"timestamptz\",\n json: \"jsonb\",\n};\n\nconst quote = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n\nconst defaultClause = (field: FieldDeclaration): string => {\n if (field.defaultNow) return \" DEFAULT now()\";\n if (field.default === undefined) return \"\";\n if (typeof field.default === \"string\") return ` DEFAULT '${field.default.replace(/'/g, \"''\")}'`;\n return ` DEFAULT ${field.default}`;\n};\n\n/** SchemaDeclaration to Postgres DDL, from the same source as the Drizzle renderer. */\nexport function renderPostgresDdl(schema: SchemaDeclaration, prefix = \"\"): string[] {\n const statements: string[] = [];\n\n for (const table of Object.values(schema)) {\n const name = quote(prefix + table.tableName);\n\n const columns = Object.entries(table.fields).map(([field, spec]) => {\n const nullability = spec.required ? \" NOT NULL\" : \"\";\n return ` ${quote(toSnakeCase(field))} ${SQL_TYPE[spec.type]}${nullability}${defaultClause(spec)}`;\n });\n\n if (table.primaryKey?.length) {\n const key = table.primaryKey.map((field) => quote(toSnakeCase(field))).join(\", \");\n columns.push(` PRIMARY KEY (${key})`);\n }\n\n statements.push(`CREATE TABLE IF NOT EXISTS ${name} (\\n${columns.join(\",\\n\")}\\n)`);\n\n for (const declaration of table.indexes ?? []) {\n const indexName = quote(\n prefix + (declaration.name ?? `${table.tableName}_${declaration.on.join(\"_\")}_idx`),\n );\n const columns = declaration.on.map((field) => quote(toSnakeCase(field))).join(\", \");\n const unique = declaration.unique ? \"UNIQUE \" : \"\";\n statements.push(`CREATE ${unique}INDEX IF NOT EXISTS ${indexName} ON ${name} (${columns})`);\n }\n }\n\n return statements;\n}\n","import type { SchemaDeclaration } from \"../../core/plugin\";\nimport { coreSchema, renderPostgresDdl } from \"../../schema\";\nimport type { SqlQuery } from \"./adapter\";\n\n/**\n * The slice of `pg`'s Pool this needs, declared structurally so the library\n * takes no dependency on the driver. `postgres.js` and Kysely have their own\n * transaction helpers and do not need this one.\n */\nexport type PgPoolLike = {\n connect(): Promise<{\n query(text: string, params?: readonly unknown[]): Promise<{ rows: Record<string, unknown>[] }>;\n release(): void;\n }>;\n};\n\n/**\n * The `transaction` option for `node-postgres`, so callers stop hand-writing\n * the same sixteen lines of BEGIN/COMMIT/ROLLBACK/release.\n *\n * postgresAdapter(query, { transaction: pgTransaction(pool) })\n *\n * The `finally` is the part worth having in one place: drop the release and\n * every send leaks a connection until the pool is exhausted.\n */\nexport function pgTransaction(pool: PgPoolLike) {\n return async <T>(fn: (query: SqlQuery) => Promise<T>): Promise<T> => {\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n const result = await fn(async (text, params) => (await client.query(text, params)).rows);\n await client.query(\"COMMIT\");\n return result;\n } catch (error) {\n await client.query(\"ROLLBACK\");\n throw error;\n } finally {\n client.release();\n }\n };\n}\n\nexport type CreateTablesOptions = {\n /** Must match the prefix given to the adapter. */\n prefix?: string;\n /**\n * Schemas owned by plugins, exported alongside each plugin — `pushSchema`\n * from `easy-ping/plugins/push`, for instance.\n */\n plugins?: readonly SchemaDeclaration[];\n};\n\n/**\n * Creates the core tables, plus any plugin tables passed, and returns how many\n * statements ran.\n *\n * Every statement is `IF NOT EXISTS`, so this is safe to run on every boot.\n * It is a bootstrap, not a migration: it never alters a table that already\n * exists. Once a database is live, use `planPostgresMigration` instead.\n */\nexport async function createPostgresTables(\n query: SqlQuery,\n options: CreateTablesOptions = {},\n): Promise<number> {\n const schemas: readonly SchemaDeclaration[] = [coreSchema, ...(options.plugins ?? [])];\n\n let count = 0;\n for (const schema of schemas) {\n for (const statement of renderPostgresDdl(schema, options.prefix)) {\n await query(statement, []);\n count += 1;\n }\n }\n return count;\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
1
|
+
import { D as DatabaseAdapter } from '../adapter-BfLe2J0t.cjs';
|
|
2
|
+
import { S as SchemaDeclaration } from '../plugin-f5KaFWSN.cjs';
|
|
2
3
|
|
|
3
4
|
type Row = Record<string, unknown>;
|
|
4
5
|
/**
|
|
@@ -35,4 +36,46 @@ type PostgresAdapterOptions = {
|
|
|
35
36
|
*/
|
|
36
37
|
declare function postgresAdapter(query: SqlQuery, options?: PostgresAdapterOptions): DatabaseAdapter;
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The slice of `pg`'s Pool this needs, declared structurally so the library
|
|
41
|
+
* takes no dependency on the driver. `postgres.js` and Kysely have their own
|
|
42
|
+
* transaction helpers and do not need this one.
|
|
43
|
+
*/
|
|
44
|
+
type PgPoolLike = {
|
|
45
|
+
connect(): Promise<{
|
|
46
|
+
query(text: string, params?: readonly unknown[]): Promise<{
|
|
47
|
+
rows: Record<string, unknown>[];
|
|
48
|
+
}>;
|
|
49
|
+
release(): void;
|
|
50
|
+
}>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The `transaction` option for `node-postgres`, so callers stop hand-writing
|
|
54
|
+
* the same sixteen lines of BEGIN/COMMIT/ROLLBACK/release.
|
|
55
|
+
*
|
|
56
|
+
* postgresAdapter(query, { transaction: pgTransaction(pool) })
|
|
57
|
+
*
|
|
58
|
+
* The `finally` is the part worth having in one place: drop the release and
|
|
59
|
+
* every send leaks a connection until the pool is exhausted.
|
|
60
|
+
*/
|
|
61
|
+
declare function pgTransaction(pool: PgPoolLike): <T>(fn: (query: SqlQuery) => Promise<T>) => Promise<T>;
|
|
62
|
+
type CreateTablesOptions = {
|
|
63
|
+
/** Must match the prefix given to the adapter. */
|
|
64
|
+
prefix?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Schemas owned by plugins, exported alongside each plugin — `pushSchema`
|
|
67
|
+
* from `easy-ping/plugins/push`, for instance.
|
|
68
|
+
*/
|
|
69
|
+
plugins?: readonly SchemaDeclaration[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Creates the core tables, plus any plugin tables passed, and returns how many
|
|
73
|
+
* statements ran.
|
|
74
|
+
*
|
|
75
|
+
* Every statement is `IF NOT EXISTS`, so this is safe to run on every boot.
|
|
76
|
+
* It is a bootstrap, not a migration: it never alters a table that already
|
|
77
|
+
* exists. Once a database is live, use `planPostgresMigration` instead.
|
|
78
|
+
*/
|
|
79
|
+
declare function createPostgresTables(query: SqlQuery, options?: CreateTablesOptions): Promise<number>;
|
|
80
|
+
|
|
81
|
+
export { type CreateTablesOptions, type PgPoolLike, type PostgresAdapterOptions, type SqlQuery, createPostgresTables, pgTransaction, postgresAdapter };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { D as DatabaseAdapter } from '../adapter-
|
|
1
|
+
import { D as DatabaseAdapter } from '../adapter-BfLe2J0t.js';
|
|
2
|
+
import { S as SchemaDeclaration } from '../plugin-CmPOFVmn.js';
|
|
2
3
|
|
|
3
4
|
type Row = Record<string, unknown>;
|
|
4
5
|
/**
|
|
@@ -35,4 +36,46 @@ type PostgresAdapterOptions = {
|
|
|
35
36
|
*/
|
|
36
37
|
declare function postgresAdapter(query: SqlQuery, options?: PostgresAdapterOptions): DatabaseAdapter;
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The slice of `pg`'s Pool this needs, declared structurally so the library
|
|
41
|
+
* takes no dependency on the driver. `postgres.js` and Kysely have their own
|
|
42
|
+
* transaction helpers and do not need this one.
|
|
43
|
+
*/
|
|
44
|
+
type PgPoolLike = {
|
|
45
|
+
connect(): Promise<{
|
|
46
|
+
query(text: string, params?: readonly unknown[]): Promise<{
|
|
47
|
+
rows: Record<string, unknown>[];
|
|
48
|
+
}>;
|
|
49
|
+
release(): void;
|
|
50
|
+
}>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The `transaction` option for `node-postgres`, so callers stop hand-writing
|
|
54
|
+
* the same sixteen lines of BEGIN/COMMIT/ROLLBACK/release.
|
|
55
|
+
*
|
|
56
|
+
* postgresAdapter(query, { transaction: pgTransaction(pool) })
|
|
57
|
+
*
|
|
58
|
+
* The `finally` is the part worth having in one place: drop the release and
|
|
59
|
+
* every send leaks a connection until the pool is exhausted.
|
|
60
|
+
*/
|
|
61
|
+
declare function pgTransaction(pool: PgPoolLike): <T>(fn: (query: SqlQuery) => Promise<T>) => Promise<T>;
|
|
62
|
+
type CreateTablesOptions = {
|
|
63
|
+
/** Must match the prefix given to the adapter. */
|
|
64
|
+
prefix?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Schemas owned by plugins, exported alongside each plugin — `pushSchema`
|
|
67
|
+
* from `easy-ping/plugins/push`, for instance.
|
|
68
|
+
*/
|
|
69
|
+
plugins?: readonly SchemaDeclaration[];
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Creates the core tables, plus any plugin tables passed, and returns how many
|
|
73
|
+
* statements ran.
|
|
74
|
+
*
|
|
75
|
+
* Every statement is `IF NOT EXISTS`, so this is safe to run on every boot.
|
|
76
|
+
* It is a bootstrap, not a migration: it never alters a table that already
|
|
77
|
+
* exists. Once a database is live, use `planPostgresMigration` instead.
|
|
78
|
+
*/
|
|
79
|
+
declare function createPostgresTables(query: SqlQuery, options?: CreateTablesOptions): Promise<number>;
|
|
80
|
+
|
|
81
|
+
export { type CreateTablesOptions, type PgPoolLike, type PostgresAdapterOptions, type SqlQuery, createPostgresTables, pgTransaction, postgresAdapter };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isOperator } from '../chunk-OZSOIX23.js';
|
|
2
2
|
import { decodeBase64Url, encodeBase64Url } from '../chunk-PTGHMCCG.js';
|
|
3
|
-
import {
|
|
3
|
+
import { renderPostgresDdl } from '../chunk-RBDW4S6F.js';
|
|
4
|
+
import { toSnakeCase, coreSchema } from '../chunk-NYDVNMKG.js';
|
|
4
5
|
|
|
5
6
|
// src/adapters/postgres/statement.ts
|
|
6
7
|
var Statement = class {
|
|
@@ -206,7 +207,7 @@ function postgresAdapter(query, options = {}) {
|
|
|
206
207
|
if (releases.length === 0) return;
|
|
207
208
|
const now = /* @__PURE__ */ new Date();
|
|
208
209
|
const statement = new Statement().raw(
|
|
209
|
-
`UPDATE ${DELIVERY} d SET status = CASE WHEN v.result = 'sent' THEN 'sent' WHEN v.retryable AND d.attempts + 1 < d.max_attempts THEN 'pending' ELSE 'failed' END, attempts = CASE WHEN v.result
|
|
210
|
+
`UPDATE ${DELIVERY} d SET status = CASE WHEN v.result = 'sent' THEN 'sent' WHEN v.result = 'skipped' THEN 'skipped' WHEN v.retryable AND d.attempts + 1 < d.max_attempts THEN 'pending' ELSE 'failed' END, attempts = CASE WHEN v.result IN ('sent', 'skipped') THEN d.attempts ELSE d.attempts + 1 END, last_error = v.error, not_before = COALESCE(v.not_before, d.not_before), claimed_at = NULL, claimed_by = NULL, updated_at = `
|
|
210
211
|
);
|
|
211
212
|
statement.value(now).raw("::timestamptz FROM (VALUES ");
|
|
212
213
|
releases.forEach(({ id, outcome, nextAttemptAt }, index) => {
|
|
@@ -215,7 +216,10 @@ function postgresAdapter(query, options = {}) {
|
|
|
215
216
|
[id, "text"],
|
|
216
217
|
[outcome.result, "text"],
|
|
217
218
|
[outcome.result === "failed" && outcome.retryable, "boolean"],
|
|
218
|
-
[
|
|
219
|
+
[
|
|
220
|
+
outcome.result === "failed" ? outcome.error.slice(0, 2e3) : outcome.result === "skipped" ? outcome.reason.slice(0, 2e3) : null,
|
|
221
|
+
"text"
|
|
222
|
+
],
|
|
219
223
|
[nextAttemptAt ?? null, "timestamptz"]
|
|
220
224
|
]);
|
|
221
225
|
});
|
|
@@ -322,6 +326,35 @@ function postgresAdapter(query, options = {}) {
|
|
|
322
326
|
};
|
|
323
327
|
}
|
|
324
328
|
|
|
325
|
-
|
|
329
|
+
// src/adapters/postgres/helpers.ts
|
|
330
|
+
function pgTransaction(pool) {
|
|
331
|
+
return async (fn) => {
|
|
332
|
+
const client = await pool.connect();
|
|
333
|
+
try {
|
|
334
|
+
await client.query("BEGIN");
|
|
335
|
+
const result = await fn(async (text, params) => (await client.query(text, params)).rows);
|
|
336
|
+
await client.query("COMMIT");
|
|
337
|
+
return result;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
await client.query("ROLLBACK");
|
|
340
|
+
throw error;
|
|
341
|
+
} finally {
|
|
342
|
+
client.release();
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
async function createPostgresTables(query, options = {}) {
|
|
347
|
+
const schemas = [coreSchema, ...options.plugins ?? []];
|
|
348
|
+
let count = 0;
|
|
349
|
+
for (const schema of schemas) {
|
|
350
|
+
for (const statement of renderPostgresDdl(schema, options.prefix)) {
|
|
351
|
+
await query(statement, []);
|
|
352
|
+
count += 1;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return count;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export { createPostgresTables, pgTransaction, postgresAdapter };
|
|
326
359
|
//# sourceMappingURL=postgres.js.map
|
|
327
360
|
//# sourceMappingURL=postgres.js.map
|