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.
Files changed (63) hide show
  1. package/dist/{adapter-f0OR2DhY.d.cts → adapter-BfLe2J0t.d.cts} +10 -0
  2. package/dist/{adapter-f0OR2DhY.d.ts → adapter-BfLe2J0t.d.ts} +10 -0
  3. package/dist/adapters/drizzle.cjs +6 -2
  4. package/dist/adapters/drizzle.cjs.map +1 -1
  5. package/dist/adapters/drizzle.d.cts +1 -1
  6. package/dist/adapters/drizzle.d.ts +1 -1
  7. package/dist/adapters/drizzle.js +6 -2
  8. package/dist/adapters/drizzle.js.map +1 -1
  9. package/dist/adapters/mongodb.cjs +3 -2
  10. package/dist/adapters/mongodb.cjs.map +1 -1
  11. package/dist/adapters/mongodb.d.cts +2 -2
  12. package/dist/adapters/mongodb.d.ts +2 -2
  13. package/dist/adapters/mongodb.js +3 -2
  14. package/dist/adapters/mongodb.js.map +1 -1
  15. package/dist/adapters/postgres.cjs +134 -2
  16. package/dist/adapters/postgres.cjs.map +1 -1
  17. package/dist/adapters/postgres.d.cts +45 -2
  18. package/dist/adapters/postgres.d.ts +45 -2
  19. package/dist/adapters/postgres.js +37 -4
  20. package/dist/adapters/postgres.js.map +1 -1
  21. package/dist/chunk-RBDW4S6F.js +199 -0
  22. package/dist/chunk-RBDW4S6F.js.map +1 -0
  23. package/dist/client.cjs +3 -5
  24. package/dist/client.cjs.map +1 -1
  25. package/dist/client.js +3 -5
  26. package/dist/client.js.map +1 -1
  27. package/dist/index.cjs +17 -11
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +6 -4
  30. package/dist/index.d.ts +6 -4
  31. package/dist/index.js +17 -11
  32. package/dist/index.js.map +1 -1
  33. package/dist/{plugin-bTJKmnlq.d.ts → plugin-CmPOFVmn.d.ts} +1 -1
  34. package/dist/{plugin-Bqj9xAOS.d.cts → plugin-f5KaFWSN.d.cts} +1 -1
  35. package/dist/plugins/digests.d.cts +2 -2
  36. package/dist/plugins/digests.d.ts +2 -2
  37. package/dist/plugins/preferences-client.d.cts +2 -2
  38. package/dist/plugins/preferences-client.d.ts +2 -2
  39. package/dist/plugins/preferences.d.cts +2 -2
  40. package/dist/plugins/preferences.d.ts +2 -2
  41. package/dist/plugins/push.cjs +25 -23
  42. package/dist/plugins/push.cjs.map +1 -1
  43. package/dist/plugins/push.d.cts +59 -3
  44. package/dist/plugins/push.d.ts +59 -3
  45. package/dist/plugins/push.js +25 -24
  46. package/dist/plugins/push.js.map +1 -1
  47. package/dist/providers/web-push.d.cts +2 -2
  48. package/dist/providers/web-push.d.ts +2 -2
  49. package/dist/react.cjs +3 -5
  50. package/dist/react.cjs.map +1 -1
  51. package/dist/react.js +3 -5
  52. package/dist/react.js.map +1 -1
  53. package/dist/schema.d.cts +2 -2
  54. package/dist/schema.d.ts +2 -2
  55. package/dist/schema.js +1 -197
  56. package/dist/schema.js.map +1 -1
  57. package/dist/testing.cjs +29 -0
  58. package/dist/testing.cjs.map +1 -1
  59. package/dist/testing.d.cts +1 -1
  60. package/dist/testing.d.ts +1 -1
  61. package/dist/testing.js +29 -0
  62. package/dist/testing.js.map +1 -1
  63. package/package.json +1 -1
@@ -137,6 +137,16 @@ type DeliveryOutcome = {
137
137
  result: "failed";
138
138
  error: string;
139
139
  retryable: boolean;
140
+ }
141
+ /**
142
+ * Nothing to do, and nothing wrong. A channel whose preconditions are
143
+ * simply absent — push with no registered device, say — must not land in
144
+ * `getFailedDeliveries`, or the one view meant to surface real breakage
145
+ * fills up with people who never opted in. Terminal: never retried.
146
+ */
147
+ | {
148
+ result: "skipped";
149
+ reason: string;
140
150
  };
141
151
  type DeliveryRelease = {
142
152
  id: string;
@@ -137,6 +137,16 @@ type DeliveryOutcome = {
137
137
  result: "failed";
138
138
  error: string;
139
139
  retryable: boolean;
140
+ }
141
+ /**
142
+ * Nothing to do, and nothing wrong. A channel whose preconditions are
143
+ * simply absent — push with no registered device, say — must not land in
144
+ * `getFailedDeliveries`, or the one view meant to surface real breakage
145
+ * fills up with people who never opted in. Terminal: never retried.
146
+ */
147
+ | {
148
+ result: "skipped";
149
+ reason: string;
140
150
  };
141
151
  type DeliveryRelease = {
142
152
  id: string;
@@ -226,7 +226,7 @@ function drizzleAdapter(client, options = {}) {
226
226
  const now = /* @__PURE__ */ new Date();
227
227
  const values = releases.map(({ id, outcome, nextAttemptAt }) => {
228
228
  const retryable = outcome.result === "failed" && outcome.retryable;
229
- const error = outcome.result === "failed" ? outcome.error.slice(0, 2e3) : null;
229
+ const error = outcome.result === "failed" ? outcome.error.slice(0, 2e3) : outcome.result === "skipped" ? outcome.reason.slice(0, 2e3) : null;
230
230
  return drizzleOrm.sql`(
231
231
  ${id}::text,
232
232
  ${outcome.result}::text,
@@ -239,10 +239,14 @@ function drizzleAdapter(client, options = {}) {
239
239
  UPDATE ${DELIVERY} d
240
240
  SET status = CASE
241
241
  WHEN v.result = 'sent' THEN 'sent'
242
+ WHEN v.result = 'skipped' THEN 'skipped'
242
243
  WHEN v.retryable AND d.attempts + 1 < d.max_attempts THEN 'pending'
243
244
  ELSE 'failed'
244
245
  END,
245
- attempts = CASE WHEN v.result = 'sent' THEN d.attempts ELSE d.attempts + 1 END,
246
+ attempts = CASE
247
+ WHEN v.result IN ('sent', 'skipped') THEN d.attempts
248
+ ELSE d.attempts + 1
249
+ END,
246
250
  last_error = v.error,
247
251
  not_before = COALESCE(v.not_before, d.not_before),
248
252
  claimed_at = NULL,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/base64url.ts","../../src/schema/declaration.ts","../../src/core/store.ts","../../src/adapters/drizzle/adapter.ts","../../src/adapters/drizzle/schema.ts"],"names":["sql","options","pgTable","text","jsonb","timestamp","index","uniqueIndex","integer","boolean","primaryKey"],"mappings":"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","file":"drizzle.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","import { type SQL, sql } from \"drizzle-orm\";\nimport 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\";\n\ntype Row = Record<string, unknown>;\n\n/**\n * Minimal structural view of a Drizzle client. Deliberately not the real\n * inferred type — introspecting Drizzle's generics at a boundary costs\n * thousands of type instantiations for nothing. See plan §16.\n */\ntype ExecutableDb = {\n execute: (query: SQL) => Promise<unknown>;\n transaction: <T>(fn: (tx: ExecutableDb) => Promise<T>) => Promise<T>;\n};\n\nexport type DrizzleAdapterOptions = {\n prefix?: string;\n};\n\n/** postgres-js returns rows directly; node-postgres wraps them in { rows }. */\nfunction toRows(result: unknown): Row[] {\n if (Array.isArray(result)) return result as Row[];\n if (result && typeof result === \"object\" && \"rows\" in result) {\n const { rows } = result as { rows: unknown };\n if (Array.isArray(rows)) return rows as Row[];\n }\n return [];\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\n/**\n * postgres-js rejects a Date bound to an explicitly-typed ::timestamptz param.\n * ISO strings bind cleanly on every driver, and the cast does the conversion.\n */\nconst ts = (value: Date) => value.toISOString();\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: 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/** Dates bind as ISO strings; see the note on `ts` above. */\nconst bind = (value: unknown): unknown => (value instanceof Date ? value.toISOString() : value);\n\n/**\n * Identifiers here are already validated by the PluginStore against the\n * plugin's own schema, and still go through sql.identifier rather than\n * interpolation. Values are always bound.\n */\nfunction whereSql(where: WhereClause): SQL {\n const entries = Object.entries(where);\n if (entries.length === 0) return sql``;\n\n const conditions = entries.map(([field, condition]) => {\n const column = sql.identifier(toSnakeCase(field));\n\n if (condition === null) return sql`${column} IS NULL`;\n if (!isOperator(condition)) return sql`${column} = ${bind(condition)}`;\n\n const operator = condition as Record<string, unknown>;\n if (\"in\" in operator) {\n const list = operator.in as readonly (string | number)[];\n if (list.length === 0) return sql`FALSE`;\n return sql`${column} IN (${sql.join(\n list.map((item) => sql`${item}`),\n sql`, `,\n )})`;\n }\n if (\"lt\" in operator) return sql`${column} < ${bind(operator.lt)}`;\n if (\"lte\" in operator) return sql`${column} <= ${bind(operator.lte)}`;\n if (\"gt\" in operator) return sql`${column} > ${bind(operator.gt)}`;\n if (\"gte\" in operator) return sql`${column} >= ${bind(operator.gte)}`;\n if (\"not\" in operator) {\n return operator.not === null\n ? sql`${column} IS NOT NULL`\n : sql`${column} IS DISTINCT FROM ${bind(operator.not)}`;\n }\n return sql`TRUE`;\n });\n\n return sql` WHERE ${sql.join(conditions, sql` AND `)}`;\n}\n\nexport function drizzleAdapter(\n client: unknown,\n options: DrizzleAdapterOptions = {},\n): DatabaseAdapter {\n const db = client as ExecutableDb;\n const prefix = options.prefix ?? \"\";\n\n const NOTIFICATION = sql.identifier(`${prefix}notification`);\n const DELIVERY = sql.identifier(`${prefix}notification_delivery`);\n\n const run = async (query: SQL, on: ExecutableDb = db) => toRows(await on.execute(query));\n\n return {\n name: \"drizzle-pg\",\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/updated_at have a DEFAULT now() for hand-written\n // SQL, but now() is the *database* clock while every cutoff the library\n // compares it against (markSeen, getFailedDeliveries) comes from the app.\n // App and database are different hosts in production, and NTP skew of a\n // few hundred ms is routine, so the newest rows fell outside markSeen and\n // the unseen badge never cleared. The mongo adapter always wrote these in\n // the app; this makes both agree.\n const now = new Date();\n\n return db.transaction(async (tx) => {\n const values = input.map(\n (row) => sql`(\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 ${ts(now)}::timestamptz\n )`,\n );\n\n // Rows with a NULL dedupe_key never conflict — Postgres treats NULLs\n // as distinct — so unlimited undeduped notifications coexist.\n const inserted = await run(\n sql`\n INSERT INTO ${NOTIFICATION} (id, user_id, type, payload, actor_id, group_key, dedupe_key, created_at)\n VALUES ${sql.join(values, sql`, `)}\n ON CONFLICT (user_id, dedupe_key) DO NOTHING\n RETURNING id\n `,\n tx,\n );\n\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) =>\n row.deliveries.map(\n (delivery) => sql`(\n ${delivery.id}::text,\n ${row.id}::text,\n ${delivery.channel}::text,\n ${delivery.maxAttempts}::integer,\n ${ts(delivery.notBefore)}::timestamptz,\n ${ts(now)}::timestamptz\n )`,\n ),\n );\n\n if (deliveries.length > 0) {\n await tx.execute(sql`\n INSERT INTO ${DELIVERY} (id, notification_id, channel, max_attempts, not_before, updated_at)\n VALUES ${sql.join(deliveries, sql`, `)}\n `);\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 const channelFilter =\n args.channels && args.channels.length > 0\n ? sql`AND channel IN (${sql.join(\n args.channels.map((channel) => sql`${channel}`),\n sql`, `,\n )})`\n : sql``;\n\n const idFilter =\n args.ids && args.ids.length > 0\n ? sql`AND id IN (${sql.join(\n args.ids.map((id) => sql`${id}`),\n sql`, `,\n )})`\n : sql``;\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 rows = await run(sql`\n WITH claimed AS (\n UPDATE ${DELIVERY}\n SET status = 'claimed', claimed_at = ${ts(now)}::timestamptz, claimed_by = ${args.claimToken}::text\n WHERE id IN (\n SELECT id FROM ${DELIVERY}\n WHERE (status = 'pending' OR (status = 'claimed' AND claimed_at < ${ts(staleBefore)}::timestamptz))\n AND not_before <= ${ts(now)}::timestamptz\n AND attempts < max_attempts\n ${channelFilter}\n ${idFilter}\n ORDER BY not_before ASC, id ASC\n LIMIT ${args.limit}\n FOR UPDATE SKIP LOCKED\n )\n RETURNING id, notification_id, channel, attempts, max_attempts\n )\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\n FROM claimed c\n JOIN ${NOTIFICATION} n ON n.id = c.notification_id\n `);\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: 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 const values = releases.map(({ id, outcome, nextAttemptAt }) => {\n const retryable = outcome.result === \"failed\" && outcome.retryable;\n const error = outcome.result === \"failed\" ? outcome.error.slice(0, 2000) : null;\n\n return sql`(\n ${id}::text,\n ${outcome.result}::text,\n ${retryable}::boolean,\n ${error}::text,\n ${nextAttemptAt ? ts(nextAttemptAt) : null}::timestamptz\n )`;\n });\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 await db.execute(sql`\n UPDATE ${DELIVERY} d\n 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'\n 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,\n claimed_by = NULL,\n updated_at = ${ts(now)}::timestamptz\n FROM (VALUES ${sql.join(values, sql`, `)}) AS v(id, result, retryable, error, not_before)\n WHERE d.id = v.id\n `);\n },\n\n async listNotifications(query: FeedQuery): Promise<FeedPage> {\n const cursor = query.cursor ? decodeCursor(query.cursor) : null;\n\n const rows = await run(sql`\n SELECT * FROM ${NOTIFICATION}\n WHERE user_id = ${query.userId}::text\n AND archived_at IS NULL\n ${query.unreadOnly ? sql`AND read_at IS NULL` : sql``}\n ${\n cursor\n ? sql`AND (created_at, id) < (${ts(cursor.createdAt)}::timestamptz, ${cursor.id}::text)`\n : sql``\n }\n ORDER BY created_at DESC, id DESC\n LIMIT ${query.limit + 1}\n `);\n\n const page = rows.slice(0, query.limit).map(toNotification);\n const last = page.at(-1);\n\n return {\n notifications: page,\n nextCursor:\n rows.length > query.limit && last ? encodeCursor(last.createdAt, last.id) : null,\n };\n },\n\n async countUnseen(userId: string) {\n const rows = await run(sql`\n SELECT count(*)::int AS count FROM ${NOTIFICATION}\n WHERE user_id = ${userId}::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 db.execute(sql`\n UPDATE ${NOTIFICATION} SET seen_at = ${ts(new Date())}::timestamptz\n WHERE user_id = ${userId}::text AND seen_at IS NULL AND created_at <= ${ts(before)}::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\n // 0 for an already-read row made the route 404, which made the client\n // roll its optimistic update back and show the item as unread again.\n // COALESCE preserves the original timestamp.\n const rows = await run(sql`\n UPDATE ${NOTIFICATION}\n SET read_at = COALESCE(read_at, ${ts(new Date())}::timestamptz)\n WHERE user_id = ${userId}::text\n AND id IN (${sql.join(\n notificationIds.map((id) => sql`${id}`),\n sql`, `,\n )})\n RETURNING id\n `);\n return rows.length;\n },\n\n async markAllRead(userId: string) {\n const rows = await run(sql`\n UPDATE ${NOTIFICATION} SET read_at = ${ts(new Date())}::timestamptz\n WHERE user_id = ${userId}::text AND read_at IS NULL\n RETURNING id\n `);\n return rows.length;\n },\n\n async queryTable(table: string, where: WhereClause, options: QueryOptions) {\n const order = options.orderBy\n ? sql` ORDER BY ${sql.identifier(toSnakeCase(options.orderBy.field))} ${\n options.orderBy.direction === \"desc\" ? sql`DESC` : sql`ASC`\n }`\n : sql``;\n const limit = options.limit ? sql` LIMIT ${options.limit}` : sql``;\n\n return run(\n sql`SELECT * FROM ${sql.identifier(table)}${whereSql(where)}${order}${limit}`,\n ) as Promise<Record<string, unknown>[]>;\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 values = rows.map(\n (row) =>\n sql`(${sql.join(\n columns.map((column) => sql`${bind(row[column])}`),\n sql`, `,\n )})`,\n );\n\n const conflict =\n onConflict && onConflict.length > 0\n ? sql` ON CONFLICT (${sql.join(\n onConflict.map((field) => sql.identifier(toSnakeCase(field))),\n sql`, `,\n )}) DO UPDATE SET ${sql.join(\n columns\n .filter((column) => !onConflict.includes(column))\n .map(\n (column) =>\n sql`${sql.identifier(toSnakeCase(column))} = EXCLUDED.${sql.identifier(\n toSnakeCase(column),\n )}`,\n ),\n sql`, `,\n )}`\n : sql``;\n\n const inserted = await run(sql`\n INSERT INTO ${sql.identifier(table)} (${sql.join(\n columns.map((column) => sql.identifier(toSnakeCase(column))),\n sql`, `,\n )})\n VALUES ${sql.join(values, sql`, `)}${conflict}\n RETURNING 1 AS ok\n `);\n return inserted.length;\n },\n\n async updateRows(table: string, where: WhereClause, set: Record<string, unknown>) {\n const assignments = Object.entries(set).map(\n ([field, value]) => sql`${sql.identifier(toSnakeCase(field))} = ${bind(value)}`,\n );\n if (assignments.length === 0) return 0;\n\n const rows = await run(sql`\n UPDATE ${sql.identifier(table)}\n SET ${sql.join(assignments, sql`, `)}${whereSql(where)}\n RETURNING 1 AS ok\n `);\n return rows.length;\n },\n\n async deleteRows(table: string, where: WhereClause) {\n const rows = await run(\n sql`DELETE FROM ${sql.identifier(table)}${whereSql(where)} RETURNING 1 AS ok`,\n );\n return rows.length;\n },\n\n async getFailedDeliveries(args: { since: Date; limit: number }) {\n const rows = await run(sql`\n SELECT * FROM ${DELIVERY}\n WHERE status = 'failed' AND updated_at >= ${ts(args.since)}::timestamptz\n ORDER BY updated_at DESC\n LIMIT ${args.limit}\n `);\n return rows.map(toDelivery);\n },\n };\n}\n","import { sql } from \"drizzle-orm\";\nimport {\n boolean,\n index,\n integer,\n jsonb,\n pgTable,\n primaryKey,\n text,\n timestamp,\n uniqueIndex,\n} from \"drizzle-orm/pg-core\";\nimport type { Channel, DeliveryStatus, Frequency } from \"../../core/types\";\n\nexport function createSchema(prefix = \"\") {\n const name = (base: string) => `${prefix}${base}`;\n\n const notification = pgTable(\n name(\"notification\"),\n {\n id: text(\"id\").primaryKey(),\n userId: text(\"user_id\").notNull(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n actorId: text(\"actor_id\"),\n groupKey: text(\"group_key\"),\n dedupeKey: text(\"dedupe_key\"),\n seenAt: timestamp(\"seen_at\", { withTimezone: true }),\n readAt: timestamp(\"read_at\", { withTimezone: true }),\n archivedAt: timestamp(\"archived_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index(name(\"notification_feed_idx\")).on(table.userId, table.createdAt.desc()),\n index(name(\"notification_unseen_idx\")).on(table.userId).where(sql`${table.seenAt} is null`),\n index(name(\"notification_group_idx\")).on(table.userId, table.groupKey),\n // Postgres treats NULLs as distinct here, so unlimited rows may carry no\n // dedupe key while a present one is enforced unique per user.\n uniqueIndex(name(\"notification_dedupe_idx\")).on(table.userId, table.dedupeKey),\n ],\n );\n\n const notificationDelivery = pgTable(\n name(\"notification_delivery\"),\n {\n id: text(\"id\").primaryKey(),\n notificationId: text(\"notification_id\")\n .notNull()\n .references(() => notification.id, { onDelete: \"cascade\" }),\n channel: text(\"channel\").$type<Channel>().notNull(),\n status: text(\"status\").$type<DeliveryStatus>().notNull().default(\"pending\"),\n attempts: integer(\"attempts\").notNull().default(0),\n maxAttempts: integer(\"max_attempts\").notNull().default(5),\n notBefore: timestamp(\"not_before\", { withTimezone: true }).notNull().defaultNow(),\n claimedAt: timestamp(\"claimed_at\", { withTimezone: true }),\n claimedBy: text(\"claimed_by\"),\n lastError: text(\"last_error\"),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index(name(\"delivery_claim_idx\")).on(table.status, table.notBefore),\n index(name(\"delivery_notification_idx\")).on(table.notificationId),\n ],\n );\n\n const notificationPreference = pgTable(\n name(\"notification_preference\"),\n {\n userId: text(\"user_id\").notNull(),\n type: text(\"type\").notNull(),\n channel: text(\"channel\").$type<Channel>().notNull(),\n enabled: boolean(\"enabled\").notNull().default(true),\n frequency: text(\"frequency\").$type<Frequency>().notNull().default(\"instant\"),\n },\n (table) => [primaryKey({ columns: [table.userId, table.type, table.channel] })],\n );\n\n return { notification, notificationDelivery, notificationPreference };\n}\n\nexport type EasyPingSchema = ReturnType<typeof createSchema>;\n\nexport const schema = createSchema();\n"]}
1
+ {"version":3,"sources":["../../src/core/base64url.ts","../../src/schema/declaration.ts","../../src/core/store.ts","../../src/adapters/drizzle/adapter.ts","../../src/adapters/drizzle/schema.ts"],"names":["sql","options","pgTable","text","jsonb","timestamp","index","uniqueIndex","integer","boolean","primaryKey"],"mappings":"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","file":"drizzle.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","import { type SQL, sql } from \"drizzle-orm\";\nimport 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\";\n\ntype Row = Record<string, unknown>;\n\n/**\n * Minimal structural view of a Drizzle client. Deliberately not the real\n * inferred type — introspecting Drizzle's generics at a boundary costs\n * thousands of type instantiations for nothing. See plan §16.\n */\ntype ExecutableDb = {\n execute: (query: SQL) => Promise<unknown>;\n transaction: <T>(fn: (tx: ExecutableDb) => Promise<T>) => Promise<T>;\n};\n\nexport type DrizzleAdapterOptions = {\n prefix?: string;\n};\n\n/** postgres-js returns rows directly; node-postgres wraps them in { rows }. */\nfunction toRows(result: unknown): Row[] {\n if (Array.isArray(result)) return result as Row[];\n if (result && typeof result === \"object\" && \"rows\" in result) {\n const { rows } = result as { rows: unknown };\n if (Array.isArray(rows)) return rows as Row[];\n }\n return [];\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\n/**\n * postgres-js rejects a Date bound to an explicitly-typed ::timestamptz param.\n * ISO strings bind cleanly on every driver, and the cast does the conversion.\n */\nconst ts = (value: Date) => value.toISOString();\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: 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/** Dates bind as ISO strings; see the note on `ts` above. */\nconst bind = (value: unknown): unknown => (value instanceof Date ? value.toISOString() : value);\n\n/**\n * Identifiers here are already validated by the PluginStore against the\n * plugin's own schema, and still go through sql.identifier rather than\n * interpolation. Values are always bound.\n */\nfunction whereSql(where: WhereClause): SQL {\n const entries = Object.entries(where);\n if (entries.length === 0) return sql``;\n\n const conditions = entries.map(([field, condition]) => {\n const column = sql.identifier(toSnakeCase(field));\n\n if (condition === null) return sql`${column} IS NULL`;\n if (!isOperator(condition)) return sql`${column} = ${bind(condition)}`;\n\n const operator = condition as Record<string, unknown>;\n if (\"in\" in operator) {\n const list = operator.in as readonly (string | number)[];\n if (list.length === 0) return sql`FALSE`;\n return sql`${column} IN (${sql.join(\n list.map((item) => sql`${item}`),\n sql`, `,\n )})`;\n }\n if (\"lt\" in operator) return sql`${column} < ${bind(operator.lt)}`;\n if (\"lte\" in operator) return sql`${column} <= ${bind(operator.lte)}`;\n if (\"gt\" in operator) return sql`${column} > ${bind(operator.gt)}`;\n if (\"gte\" in operator) return sql`${column} >= ${bind(operator.gte)}`;\n if (\"not\" in operator) {\n return operator.not === null\n ? sql`${column} IS NOT NULL`\n : sql`${column} IS DISTINCT FROM ${bind(operator.not)}`;\n }\n return sql`TRUE`;\n });\n\n return sql` WHERE ${sql.join(conditions, sql` AND `)}`;\n}\n\nexport function drizzleAdapter(\n client: unknown,\n options: DrizzleAdapterOptions = {},\n): DatabaseAdapter {\n const db = client as ExecutableDb;\n const prefix = options.prefix ?? \"\";\n\n const NOTIFICATION = sql.identifier(`${prefix}notification`);\n const DELIVERY = sql.identifier(`${prefix}notification_delivery`);\n\n const run = async (query: SQL, on: ExecutableDb = db) => toRows(await on.execute(query));\n\n return {\n name: \"drizzle-pg\",\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/updated_at have a DEFAULT now() for hand-written\n // SQL, but now() is the *database* clock while every cutoff the library\n // compares it against (markSeen, getFailedDeliveries) comes from the app.\n // App and database are different hosts in production, and NTP skew of a\n // few hundred ms is routine, so the newest rows fell outside markSeen and\n // the unseen badge never cleared. The mongo adapter always wrote these in\n // the app; this makes both agree.\n const now = new Date();\n\n return db.transaction(async (tx) => {\n const values = input.map(\n (row) => sql`(\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 ${ts(now)}::timestamptz\n )`,\n );\n\n // Rows with a NULL dedupe_key never conflict — Postgres treats NULLs\n // as distinct — so unlimited undeduped notifications coexist.\n const inserted = await run(\n sql`\n INSERT INTO ${NOTIFICATION} (id, user_id, type, payload, actor_id, group_key, dedupe_key, created_at)\n VALUES ${sql.join(values, sql`, `)}\n ON CONFLICT (user_id, dedupe_key) DO NOTHING\n RETURNING id\n `,\n tx,\n );\n\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) =>\n row.deliveries.map(\n (delivery) => sql`(\n ${delivery.id}::text,\n ${row.id}::text,\n ${delivery.channel}::text,\n ${delivery.maxAttempts}::integer,\n ${ts(delivery.notBefore)}::timestamptz,\n ${ts(now)}::timestamptz\n )`,\n ),\n );\n\n if (deliveries.length > 0) {\n await tx.execute(sql`\n INSERT INTO ${DELIVERY} (id, notification_id, channel, max_attempts, not_before, updated_at)\n VALUES ${sql.join(deliveries, sql`, `)}\n `);\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 const channelFilter =\n args.channels && args.channels.length > 0\n ? sql`AND channel IN (${sql.join(\n args.channels.map((channel) => sql`${channel}`),\n sql`, `,\n )})`\n : sql``;\n\n const idFilter =\n args.ids && args.ids.length > 0\n ? sql`AND id IN (${sql.join(\n args.ids.map((id) => sql`${id}`),\n sql`, `,\n )})`\n : sql``;\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 rows = await run(sql`\n WITH claimed AS (\n UPDATE ${DELIVERY}\n SET status = 'claimed', claimed_at = ${ts(now)}::timestamptz, claimed_by = ${args.claimToken}::text\n WHERE id IN (\n SELECT id FROM ${DELIVERY}\n WHERE (status = 'pending' OR (status = 'claimed' AND claimed_at < ${ts(staleBefore)}::timestamptz))\n AND not_before <= ${ts(now)}::timestamptz\n AND attempts < max_attempts\n ${channelFilter}\n ${idFilter}\n ORDER BY not_before ASC, id ASC\n LIMIT ${args.limit}\n FOR UPDATE SKIP LOCKED\n )\n RETURNING id, notification_id, channel, attempts, max_attempts\n )\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\n FROM claimed c\n JOIN ${NOTIFICATION} n ON n.id = c.notification_id\n `);\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: 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 const values = releases.map(({ id, outcome, nextAttemptAt }) => {\n const retryable = outcome.result === \"failed\" && outcome.retryable;\n const error =\n outcome.result === \"failed\"\n ? outcome.error.slice(0, 2000)\n : outcome.result === \"skipped\"\n ? outcome.reason.slice(0, 2000)\n : null;\n\n return sql`(\n ${id}::text,\n ${outcome.result}::text,\n ${retryable}::boolean,\n ${error}::text,\n ${nextAttemptAt ? ts(nextAttemptAt) : null}::timestamptz\n )`;\n });\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 await db.execute(sql`\n UPDATE ${DELIVERY} d\n 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'\n END,\n attempts = CASE\n WHEN v.result IN ('sent', 'skipped') THEN d.attempts\n ELSE d.attempts + 1\n END,\n last_error = v.error,\n not_before = COALESCE(v.not_before, d.not_before),\n claimed_at = NULL,\n claimed_by = NULL,\n updated_at = ${ts(now)}::timestamptz\n FROM (VALUES ${sql.join(values, sql`, `)}) AS v(id, result, retryable, error, not_before)\n WHERE d.id = v.id\n `);\n },\n\n async listNotifications(query: FeedQuery): Promise<FeedPage> {\n const cursor = query.cursor ? decodeCursor(query.cursor) : null;\n\n const rows = await run(sql`\n SELECT * FROM ${NOTIFICATION}\n WHERE user_id = ${query.userId}::text\n AND archived_at IS NULL\n ${query.unreadOnly ? sql`AND read_at IS NULL` : sql``}\n ${\n cursor\n ? sql`AND (created_at, id) < (${ts(cursor.createdAt)}::timestamptz, ${cursor.id}::text)`\n : sql``\n }\n ORDER BY created_at DESC, id DESC\n LIMIT ${query.limit + 1}\n `);\n\n const page = rows.slice(0, query.limit).map(toNotification);\n const last = page.at(-1);\n\n return {\n notifications: page,\n nextCursor:\n rows.length > query.limit && last ? encodeCursor(last.createdAt, last.id) : null,\n };\n },\n\n async countUnseen(userId: string) {\n const rows = await run(sql`\n SELECT count(*)::int AS count FROM ${NOTIFICATION}\n WHERE user_id = ${userId}::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 db.execute(sql`\n UPDATE ${NOTIFICATION} SET seen_at = ${ts(new Date())}::timestamptz\n WHERE user_id = ${userId}::text AND seen_at IS NULL AND created_at <= ${ts(before)}::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\n // 0 for an already-read row made the route 404, which made the client\n // roll its optimistic update back and show the item as unread again.\n // COALESCE preserves the original timestamp.\n const rows = await run(sql`\n UPDATE ${NOTIFICATION}\n SET read_at = COALESCE(read_at, ${ts(new Date())}::timestamptz)\n WHERE user_id = ${userId}::text\n AND id IN (${sql.join(\n notificationIds.map((id) => sql`${id}`),\n sql`, `,\n )})\n RETURNING id\n `);\n return rows.length;\n },\n\n async markAllRead(userId: string) {\n const rows = await run(sql`\n UPDATE ${NOTIFICATION} SET read_at = ${ts(new Date())}::timestamptz\n WHERE user_id = ${userId}::text AND read_at IS NULL\n RETURNING id\n `);\n return rows.length;\n },\n\n async queryTable(table: string, where: WhereClause, options: QueryOptions) {\n const order = options.orderBy\n ? sql` ORDER BY ${sql.identifier(toSnakeCase(options.orderBy.field))} ${\n options.orderBy.direction === \"desc\" ? sql`DESC` : sql`ASC`\n }`\n : sql``;\n const limit = options.limit ? sql` LIMIT ${options.limit}` : sql``;\n\n return run(\n sql`SELECT * FROM ${sql.identifier(table)}${whereSql(where)}${order}${limit}`,\n ) as Promise<Record<string, unknown>[]>;\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 values = rows.map(\n (row) =>\n sql`(${sql.join(\n columns.map((column) => sql`${bind(row[column])}`),\n sql`, `,\n )})`,\n );\n\n const conflict =\n onConflict && onConflict.length > 0\n ? sql` ON CONFLICT (${sql.join(\n onConflict.map((field) => sql.identifier(toSnakeCase(field))),\n sql`, `,\n )}) DO UPDATE SET ${sql.join(\n columns\n .filter((column) => !onConflict.includes(column))\n .map(\n (column) =>\n sql`${sql.identifier(toSnakeCase(column))} = EXCLUDED.${sql.identifier(\n toSnakeCase(column),\n )}`,\n ),\n sql`, `,\n )}`\n : sql``;\n\n const inserted = await run(sql`\n INSERT INTO ${sql.identifier(table)} (${sql.join(\n columns.map((column) => sql.identifier(toSnakeCase(column))),\n sql`, `,\n )})\n VALUES ${sql.join(values, sql`, `)}${conflict}\n RETURNING 1 AS ok\n `);\n return inserted.length;\n },\n\n async updateRows(table: string, where: WhereClause, set: Record<string, unknown>) {\n const assignments = Object.entries(set).map(\n ([field, value]) => sql`${sql.identifier(toSnakeCase(field))} = ${bind(value)}`,\n );\n if (assignments.length === 0) return 0;\n\n const rows = await run(sql`\n UPDATE ${sql.identifier(table)}\n SET ${sql.join(assignments, sql`, `)}${whereSql(where)}\n RETURNING 1 AS ok\n `);\n return rows.length;\n },\n\n async deleteRows(table: string, where: WhereClause) {\n const rows = await run(\n sql`DELETE FROM ${sql.identifier(table)}${whereSql(where)} RETURNING 1 AS ok`,\n );\n return rows.length;\n },\n\n async getFailedDeliveries(args: { since: Date; limit: number }) {\n const rows = await run(sql`\n SELECT * FROM ${DELIVERY}\n WHERE status = 'failed' AND updated_at >= ${ts(args.since)}::timestamptz\n ORDER BY updated_at DESC\n LIMIT ${args.limit}\n `);\n return rows.map(toDelivery);\n },\n };\n}\n","import { sql } from \"drizzle-orm\";\nimport {\n boolean,\n index,\n integer,\n jsonb,\n pgTable,\n primaryKey,\n text,\n timestamp,\n uniqueIndex,\n} from \"drizzle-orm/pg-core\";\nimport type { Channel, DeliveryStatus, Frequency } from \"../../core/types\";\n\nexport function createSchema(prefix = \"\") {\n const name = (base: string) => `${prefix}${base}`;\n\n const notification = pgTable(\n name(\"notification\"),\n {\n id: text(\"id\").primaryKey(),\n userId: text(\"user_id\").notNull(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n actorId: text(\"actor_id\"),\n groupKey: text(\"group_key\"),\n dedupeKey: text(\"dedupe_key\"),\n seenAt: timestamp(\"seen_at\", { withTimezone: true }),\n readAt: timestamp(\"read_at\", { withTimezone: true }),\n archivedAt: timestamp(\"archived_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index(name(\"notification_feed_idx\")).on(table.userId, table.createdAt.desc()),\n index(name(\"notification_unseen_idx\")).on(table.userId).where(sql`${table.seenAt} is null`),\n index(name(\"notification_group_idx\")).on(table.userId, table.groupKey),\n // Postgres treats NULLs as distinct here, so unlimited rows may carry no\n // dedupe key while a present one is enforced unique per user.\n uniqueIndex(name(\"notification_dedupe_idx\")).on(table.userId, table.dedupeKey),\n ],\n );\n\n const notificationDelivery = pgTable(\n name(\"notification_delivery\"),\n {\n id: text(\"id\").primaryKey(),\n notificationId: text(\"notification_id\")\n .notNull()\n .references(() => notification.id, { onDelete: \"cascade\" }),\n channel: text(\"channel\").$type<Channel>().notNull(),\n status: text(\"status\").$type<DeliveryStatus>().notNull().default(\"pending\"),\n attempts: integer(\"attempts\").notNull().default(0),\n maxAttempts: integer(\"max_attempts\").notNull().default(5),\n notBefore: timestamp(\"not_before\", { withTimezone: true }).notNull().defaultNow(),\n claimedAt: timestamp(\"claimed_at\", { withTimezone: true }),\n claimedBy: text(\"claimed_by\"),\n lastError: text(\"last_error\"),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index(name(\"delivery_claim_idx\")).on(table.status, table.notBefore),\n index(name(\"delivery_notification_idx\")).on(table.notificationId),\n ],\n );\n\n const notificationPreference = pgTable(\n name(\"notification_preference\"),\n {\n userId: text(\"user_id\").notNull(),\n type: text(\"type\").notNull(),\n channel: text(\"channel\").$type<Channel>().notNull(),\n enabled: boolean(\"enabled\").notNull().default(true),\n frequency: text(\"frequency\").$type<Frequency>().notNull().default(\"instant\"),\n },\n (table) => [primaryKey({ columns: [table.userId, table.type, table.channel] })],\n );\n\n return { notification, notificationDelivery, notificationPreference };\n}\n\nexport type EasyPingSchema = ReturnType<typeof createSchema>;\n\nexport const schema = createSchema();\n"]}
@@ -1,4 +1,4 @@
1
- import { D as DatabaseAdapter, C as Channel, a as DeliveryStatus, F as Frequency } from '../adapter-f0OR2DhY.cjs';
1
+ import { D as DatabaseAdapter, C as Channel, a as DeliveryStatus, F as Frequency } from '../adapter-BfLe2J0t.cjs';
2
2
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
3
3
 
4
4
  type DrizzleAdapterOptions = {
@@ -1,4 +1,4 @@
1
- import { D as DatabaseAdapter, C as Channel, a as DeliveryStatus, F as Frequency } from '../adapter-f0OR2DhY.js';
1
+ import { D as DatabaseAdapter, C as Channel, a as DeliveryStatus, F as Frequency } from '../adapter-BfLe2J0t.js';
2
2
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
3
3
 
4
4
  type DrizzleAdapterOptions = {
@@ -197,7 +197,7 @@ function drizzleAdapter(client, options = {}) {
197
197
  const now = /* @__PURE__ */ new Date();
198
198
  const values = releases.map(({ id, outcome, nextAttemptAt }) => {
199
199
  const retryable = outcome.result === "failed" && outcome.retryable;
200
- const error = outcome.result === "failed" ? outcome.error.slice(0, 2e3) : null;
200
+ const error = outcome.result === "failed" ? outcome.error.slice(0, 2e3) : outcome.result === "skipped" ? outcome.reason.slice(0, 2e3) : null;
201
201
  return sql`(
202
202
  ${id}::text,
203
203
  ${outcome.result}::text,
@@ -210,10 +210,14 @@ function drizzleAdapter(client, options = {}) {
210
210
  UPDATE ${DELIVERY} d
211
211
  SET status = CASE
212
212
  WHEN v.result = 'sent' THEN 'sent'
213
+ WHEN v.result = 'skipped' THEN 'skipped'
213
214
  WHEN v.retryable AND d.attempts + 1 < d.max_attempts THEN 'pending'
214
215
  ELSE 'failed'
215
216
  END,
216
- attempts = CASE WHEN v.result = 'sent' THEN d.attempts ELSE d.attempts + 1 END,
217
+ attempts = CASE
218
+ WHEN v.result IN ('sent', 'skipped') THEN d.attempts
219
+ ELSE d.attempts + 1
220
+ END,
217
221
  last_error = v.error,
218
222
  not_before = COALESCE(v.not_before, d.not_before),
219
223
  claimed_at = NULL,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/adapters/drizzle/adapter.ts","../../src/adapters/drizzle/schema.ts"],"names":["options","sql"],"mappings":"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","file":"drizzle.js","sourcesContent":["import { type SQL, sql } from \"drizzle-orm\";\nimport 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\";\n\ntype Row = Record<string, unknown>;\n\n/**\n * Minimal structural view of a Drizzle client. Deliberately not the real\n * inferred type — introspecting Drizzle's generics at a boundary costs\n * thousands of type instantiations for nothing. See plan §16.\n */\ntype ExecutableDb = {\n execute: (query: SQL) => Promise<unknown>;\n transaction: <T>(fn: (tx: ExecutableDb) => Promise<T>) => Promise<T>;\n};\n\nexport type DrizzleAdapterOptions = {\n prefix?: string;\n};\n\n/** postgres-js returns rows directly; node-postgres wraps them in { rows }. */\nfunction toRows(result: unknown): Row[] {\n if (Array.isArray(result)) return result as Row[];\n if (result && typeof result === \"object\" && \"rows\" in result) {\n const { rows } = result as { rows: unknown };\n if (Array.isArray(rows)) return rows as Row[];\n }\n return [];\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\n/**\n * postgres-js rejects a Date bound to an explicitly-typed ::timestamptz param.\n * ISO strings bind cleanly on every driver, and the cast does the conversion.\n */\nconst ts = (value: Date) => value.toISOString();\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: 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/** Dates bind as ISO strings; see the note on `ts` above. */\nconst bind = (value: unknown): unknown => (value instanceof Date ? value.toISOString() : value);\n\n/**\n * Identifiers here are already validated by the PluginStore against the\n * plugin's own schema, and still go through sql.identifier rather than\n * interpolation. Values are always bound.\n */\nfunction whereSql(where: WhereClause): SQL {\n const entries = Object.entries(where);\n if (entries.length === 0) return sql``;\n\n const conditions = entries.map(([field, condition]) => {\n const column = sql.identifier(toSnakeCase(field));\n\n if (condition === null) return sql`${column} IS NULL`;\n if (!isOperator(condition)) return sql`${column} = ${bind(condition)}`;\n\n const operator = condition as Record<string, unknown>;\n if (\"in\" in operator) {\n const list = operator.in as readonly (string | number)[];\n if (list.length === 0) return sql`FALSE`;\n return sql`${column} IN (${sql.join(\n list.map((item) => sql`${item}`),\n sql`, `,\n )})`;\n }\n if (\"lt\" in operator) return sql`${column} < ${bind(operator.lt)}`;\n if (\"lte\" in operator) return sql`${column} <= ${bind(operator.lte)}`;\n if (\"gt\" in operator) return sql`${column} > ${bind(operator.gt)}`;\n if (\"gte\" in operator) return sql`${column} >= ${bind(operator.gte)}`;\n if (\"not\" in operator) {\n return operator.not === null\n ? sql`${column} IS NOT NULL`\n : sql`${column} IS DISTINCT FROM ${bind(operator.not)}`;\n }\n return sql`TRUE`;\n });\n\n return sql` WHERE ${sql.join(conditions, sql` AND `)}`;\n}\n\nexport function drizzleAdapter(\n client: unknown,\n options: DrizzleAdapterOptions = {},\n): DatabaseAdapter {\n const db = client as ExecutableDb;\n const prefix = options.prefix ?? \"\";\n\n const NOTIFICATION = sql.identifier(`${prefix}notification`);\n const DELIVERY = sql.identifier(`${prefix}notification_delivery`);\n\n const run = async (query: SQL, on: ExecutableDb = db) => toRows(await on.execute(query));\n\n return {\n name: \"drizzle-pg\",\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/updated_at have a DEFAULT now() for hand-written\n // SQL, but now() is the *database* clock while every cutoff the library\n // compares it against (markSeen, getFailedDeliveries) comes from the app.\n // App and database are different hosts in production, and NTP skew of a\n // few hundred ms is routine, so the newest rows fell outside markSeen and\n // the unseen badge never cleared. The mongo adapter always wrote these in\n // the app; this makes both agree.\n const now = new Date();\n\n return db.transaction(async (tx) => {\n const values = input.map(\n (row) => sql`(\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 ${ts(now)}::timestamptz\n )`,\n );\n\n // Rows with a NULL dedupe_key never conflict — Postgres treats NULLs\n // as distinct — so unlimited undeduped notifications coexist.\n const inserted = await run(\n sql`\n INSERT INTO ${NOTIFICATION} (id, user_id, type, payload, actor_id, group_key, dedupe_key, created_at)\n VALUES ${sql.join(values, sql`, `)}\n ON CONFLICT (user_id, dedupe_key) DO NOTHING\n RETURNING id\n `,\n tx,\n );\n\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) =>\n row.deliveries.map(\n (delivery) => sql`(\n ${delivery.id}::text,\n ${row.id}::text,\n ${delivery.channel}::text,\n ${delivery.maxAttempts}::integer,\n ${ts(delivery.notBefore)}::timestamptz,\n ${ts(now)}::timestamptz\n )`,\n ),\n );\n\n if (deliveries.length > 0) {\n await tx.execute(sql`\n INSERT INTO ${DELIVERY} (id, notification_id, channel, max_attempts, not_before, updated_at)\n VALUES ${sql.join(deliveries, sql`, `)}\n `);\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 const channelFilter =\n args.channels && args.channels.length > 0\n ? sql`AND channel IN (${sql.join(\n args.channels.map((channel) => sql`${channel}`),\n sql`, `,\n )})`\n : sql``;\n\n const idFilter =\n args.ids && args.ids.length > 0\n ? sql`AND id IN (${sql.join(\n args.ids.map((id) => sql`${id}`),\n sql`, `,\n )})`\n : sql``;\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 rows = await run(sql`\n WITH claimed AS (\n UPDATE ${DELIVERY}\n SET status = 'claimed', claimed_at = ${ts(now)}::timestamptz, claimed_by = ${args.claimToken}::text\n WHERE id IN (\n SELECT id FROM ${DELIVERY}\n WHERE (status = 'pending' OR (status = 'claimed' AND claimed_at < ${ts(staleBefore)}::timestamptz))\n AND not_before <= ${ts(now)}::timestamptz\n AND attempts < max_attempts\n ${channelFilter}\n ${idFilter}\n ORDER BY not_before ASC, id ASC\n LIMIT ${args.limit}\n FOR UPDATE SKIP LOCKED\n )\n RETURNING id, notification_id, channel, attempts, max_attempts\n )\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\n FROM claimed c\n JOIN ${NOTIFICATION} n ON n.id = c.notification_id\n `);\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: 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 const values = releases.map(({ id, outcome, nextAttemptAt }) => {\n const retryable = outcome.result === \"failed\" && outcome.retryable;\n const error = outcome.result === \"failed\" ? outcome.error.slice(0, 2000) : null;\n\n return sql`(\n ${id}::text,\n ${outcome.result}::text,\n ${retryable}::boolean,\n ${error}::text,\n ${nextAttemptAt ? ts(nextAttemptAt) : null}::timestamptz\n )`;\n });\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 await db.execute(sql`\n UPDATE ${DELIVERY} d\n 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'\n 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,\n claimed_by = NULL,\n updated_at = ${ts(now)}::timestamptz\n FROM (VALUES ${sql.join(values, sql`, `)}) AS v(id, result, retryable, error, not_before)\n WHERE d.id = v.id\n `);\n },\n\n async listNotifications(query: FeedQuery): Promise<FeedPage> {\n const cursor = query.cursor ? decodeCursor(query.cursor) : null;\n\n const rows = await run(sql`\n SELECT * FROM ${NOTIFICATION}\n WHERE user_id = ${query.userId}::text\n AND archived_at IS NULL\n ${query.unreadOnly ? sql`AND read_at IS NULL` : sql``}\n ${\n cursor\n ? sql`AND (created_at, id) < (${ts(cursor.createdAt)}::timestamptz, ${cursor.id}::text)`\n : sql``\n }\n ORDER BY created_at DESC, id DESC\n LIMIT ${query.limit + 1}\n `);\n\n const page = rows.slice(0, query.limit).map(toNotification);\n const last = page.at(-1);\n\n return {\n notifications: page,\n nextCursor:\n rows.length > query.limit && last ? encodeCursor(last.createdAt, last.id) : null,\n };\n },\n\n async countUnseen(userId: string) {\n const rows = await run(sql`\n SELECT count(*)::int AS count FROM ${NOTIFICATION}\n WHERE user_id = ${userId}::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 db.execute(sql`\n UPDATE ${NOTIFICATION} SET seen_at = ${ts(new Date())}::timestamptz\n WHERE user_id = ${userId}::text AND seen_at IS NULL AND created_at <= ${ts(before)}::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\n // 0 for an already-read row made the route 404, which made the client\n // roll its optimistic update back and show the item as unread again.\n // COALESCE preserves the original timestamp.\n const rows = await run(sql`\n UPDATE ${NOTIFICATION}\n SET read_at = COALESCE(read_at, ${ts(new Date())}::timestamptz)\n WHERE user_id = ${userId}::text\n AND id IN (${sql.join(\n notificationIds.map((id) => sql`${id}`),\n sql`, `,\n )})\n RETURNING id\n `);\n return rows.length;\n },\n\n async markAllRead(userId: string) {\n const rows = await run(sql`\n UPDATE ${NOTIFICATION} SET read_at = ${ts(new Date())}::timestamptz\n WHERE user_id = ${userId}::text AND read_at IS NULL\n RETURNING id\n `);\n return rows.length;\n },\n\n async queryTable(table: string, where: WhereClause, options: QueryOptions) {\n const order = options.orderBy\n ? sql` ORDER BY ${sql.identifier(toSnakeCase(options.orderBy.field))} ${\n options.orderBy.direction === \"desc\" ? sql`DESC` : sql`ASC`\n }`\n : sql``;\n const limit = options.limit ? sql` LIMIT ${options.limit}` : sql``;\n\n return run(\n sql`SELECT * FROM ${sql.identifier(table)}${whereSql(where)}${order}${limit}`,\n ) as Promise<Record<string, unknown>[]>;\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 values = rows.map(\n (row) =>\n sql`(${sql.join(\n columns.map((column) => sql`${bind(row[column])}`),\n sql`, `,\n )})`,\n );\n\n const conflict =\n onConflict && onConflict.length > 0\n ? sql` ON CONFLICT (${sql.join(\n onConflict.map((field) => sql.identifier(toSnakeCase(field))),\n sql`, `,\n )}) DO UPDATE SET ${sql.join(\n columns\n .filter((column) => !onConflict.includes(column))\n .map(\n (column) =>\n sql`${sql.identifier(toSnakeCase(column))} = EXCLUDED.${sql.identifier(\n toSnakeCase(column),\n )}`,\n ),\n sql`, `,\n )}`\n : sql``;\n\n const inserted = await run(sql`\n INSERT INTO ${sql.identifier(table)} (${sql.join(\n columns.map((column) => sql.identifier(toSnakeCase(column))),\n sql`, `,\n )})\n VALUES ${sql.join(values, sql`, `)}${conflict}\n RETURNING 1 AS ok\n `);\n return inserted.length;\n },\n\n async updateRows(table: string, where: WhereClause, set: Record<string, unknown>) {\n const assignments = Object.entries(set).map(\n ([field, value]) => sql`${sql.identifier(toSnakeCase(field))} = ${bind(value)}`,\n );\n if (assignments.length === 0) return 0;\n\n const rows = await run(sql`\n UPDATE ${sql.identifier(table)}\n SET ${sql.join(assignments, sql`, `)}${whereSql(where)}\n RETURNING 1 AS ok\n `);\n return rows.length;\n },\n\n async deleteRows(table: string, where: WhereClause) {\n const rows = await run(\n sql`DELETE FROM ${sql.identifier(table)}${whereSql(where)} RETURNING 1 AS ok`,\n );\n return rows.length;\n },\n\n async getFailedDeliveries(args: { since: Date; limit: number }) {\n const rows = await run(sql`\n SELECT * FROM ${DELIVERY}\n WHERE status = 'failed' AND updated_at >= ${ts(args.since)}::timestamptz\n ORDER BY updated_at DESC\n LIMIT ${args.limit}\n `);\n return rows.map(toDelivery);\n },\n };\n}\n","import { sql } from \"drizzle-orm\";\nimport {\n boolean,\n index,\n integer,\n jsonb,\n pgTable,\n primaryKey,\n text,\n timestamp,\n uniqueIndex,\n} from \"drizzle-orm/pg-core\";\nimport type { Channel, DeliveryStatus, Frequency } from \"../../core/types\";\n\nexport function createSchema(prefix = \"\") {\n const name = (base: string) => `${prefix}${base}`;\n\n const notification = pgTable(\n name(\"notification\"),\n {\n id: text(\"id\").primaryKey(),\n userId: text(\"user_id\").notNull(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n actorId: text(\"actor_id\"),\n groupKey: text(\"group_key\"),\n dedupeKey: text(\"dedupe_key\"),\n seenAt: timestamp(\"seen_at\", { withTimezone: true }),\n readAt: timestamp(\"read_at\", { withTimezone: true }),\n archivedAt: timestamp(\"archived_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index(name(\"notification_feed_idx\")).on(table.userId, table.createdAt.desc()),\n index(name(\"notification_unseen_idx\")).on(table.userId).where(sql`${table.seenAt} is null`),\n index(name(\"notification_group_idx\")).on(table.userId, table.groupKey),\n // Postgres treats NULLs as distinct here, so unlimited rows may carry no\n // dedupe key while a present one is enforced unique per user.\n uniqueIndex(name(\"notification_dedupe_idx\")).on(table.userId, table.dedupeKey),\n ],\n );\n\n const notificationDelivery = pgTable(\n name(\"notification_delivery\"),\n {\n id: text(\"id\").primaryKey(),\n notificationId: text(\"notification_id\")\n .notNull()\n .references(() => notification.id, { onDelete: \"cascade\" }),\n channel: text(\"channel\").$type<Channel>().notNull(),\n status: text(\"status\").$type<DeliveryStatus>().notNull().default(\"pending\"),\n attempts: integer(\"attempts\").notNull().default(0),\n maxAttempts: integer(\"max_attempts\").notNull().default(5),\n notBefore: timestamp(\"not_before\", { withTimezone: true }).notNull().defaultNow(),\n claimedAt: timestamp(\"claimed_at\", { withTimezone: true }),\n claimedBy: text(\"claimed_by\"),\n lastError: text(\"last_error\"),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index(name(\"delivery_claim_idx\")).on(table.status, table.notBefore),\n index(name(\"delivery_notification_idx\")).on(table.notificationId),\n ],\n );\n\n const notificationPreference = pgTable(\n name(\"notification_preference\"),\n {\n userId: text(\"user_id\").notNull(),\n type: text(\"type\").notNull(),\n channel: text(\"channel\").$type<Channel>().notNull(),\n enabled: boolean(\"enabled\").notNull().default(true),\n frequency: text(\"frequency\").$type<Frequency>().notNull().default(\"instant\"),\n },\n (table) => [primaryKey({ columns: [table.userId, table.type, table.channel] })],\n );\n\n return { notification, notificationDelivery, notificationPreference };\n}\n\nexport type EasyPingSchema = ReturnType<typeof createSchema>;\n\nexport const schema = createSchema();\n"]}
1
+ {"version":3,"sources":["../../src/adapters/drizzle/adapter.ts","../../src/adapters/drizzle/schema.ts"],"names":["options","sql"],"mappings":"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","file":"drizzle.js","sourcesContent":["import { type SQL, sql } from \"drizzle-orm\";\nimport 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\";\n\ntype Row = Record<string, unknown>;\n\n/**\n * Minimal structural view of a Drizzle client. Deliberately not the real\n * inferred type — introspecting Drizzle's generics at a boundary costs\n * thousands of type instantiations for nothing. See plan §16.\n */\ntype ExecutableDb = {\n execute: (query: SQL) => Promise<unknown>;\n transaction: <T>(fn: (tx: ExecutableDb) => Promise<T>) => Promise<T>;\n};\n\nexport type DrizzleAdapterOptions = {\n prefix?: string;\n};\n\n/** postgres-js returns rows directly; node-postgres wraps them in { rows }. */\nfunction toRows(result: unknown): Row[] {\n if (Array.isArray(result)) return result as Row[];\n if (result && typeof result === \"object\" && \"rows\" in result) {\n const { rows } = result as { rows: unknown };\n if (Array.isArray(rows)) return rows as Row[];\n }\n return [];\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\n/**\n * postgres-js rejects a Date bound to an explicitly-typed ::timestamptz param.\n * ISO strings bind cleanly on every driver, and the cast does the conversion.\n */\nconst ts = (value: Date) => value.toISOString();\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: 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/** Dates bind as ISO strings; see the note on `ts` above. */\nconst bind = (value: unknown): unknown => (value instanceof Date ? value.toISOString() : value);\n\n/**\n * Identifiers here are already validated by the PluginStore against the\n * plugin's own schema, and still go through sql.identifier rather than\n * interpolation. Values are always bound.\n */\nfunction whereSql(where: WhereClause): SQL {\n const entries = Object.entries(where);\n if (entries.length === 0) return sql``;\n\n const conditions = entries.map(([field, condition]) => {\n const column = sql.identifier(toSnakeCase(field));\n\n if (condition === null) return sql`${column} IS NULL`;\n if (!isOperator(condition)) return sql`${column} = ${bind(condition)}`;\n\n const operator = condition as Record<string, unknown>;\n if (\"in\" in operator) {\n const list = operator.in as readonly (string | number)[];\n if (list.length === 0) return sql`FALSE`;\n return sql`${column} IN (${sql.join(\n list.map((item) => sql`${item}`),\n sql`, `,\n )})`;\n }\n if (\"lt\" in operator) return sql`${column} < ${bind(operator.lt)}`;\n if (\"lte\" in operator) return sql`${column} <= ${bind(operator.lte)}`;\n if (\"gt\" in operator) return sql`${column} > ${bind(operator.gt)}`;\n if (\"gte\" in operator) return sql`${column} >= ${bind(operator.gte)}`;\n if (\"not\" in operator) {\n return operator.not === null\n ? sql`${column} IS NOT NULL`\n : sql`${column} IS DISTINCT FROM ${bind(operator.not)}`;\n }\n return sql`TRUE`;\n });\n\n return sql` WHERE ${sql.join(conditions, sql` AND `)}`;\n}\n\nexport function drizzleAdapter(\n client: unknown,\n options: DrizzleAdapterOptions = {},\n): DatabaseAdapter {\n const db = client as ExecutableDb;\n const prefix = options.prefix ?? \"\";\n\n const NOTIFICATION = sql.identifier(`${prefix}notification`);\n const DELIVERY = sql.identifier(`${prefix}notification_delivery`);\n\n const run = async (query: SQL, on: ExecutableDb = db) => toRows(await on.execute(query));\n\n return {\n name: \"drizzle-pg\",\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/updated_at have a DEFAULT now() for hand-written\n // SQL, but now() is the *database* clock while every cutoff the library\n // compares it against (markSeen, getFailedDeliveries) comes from the app.\n // App and database are different hosts in production, and NTP skew of a\n // few hundred ms is routine, so the newest rows fell outside markSeen and\n // the unseen badge never cleared. The mongo adapter always wrote these in\n // the app; this makes both agree.\n const now = new Date();\n\n return db.transaction(async (tx) => {\n const values = input.map(\n (row) => sql`(\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 ${ts(now)}::timestamptz\n )`,\n );\n\n // Rows with a NULL dedupe_key never conflict — Postgres treats NULLs\n // as distinct — so unlimited undeduped notifications coexist.\n const inserted = await run(\n sql`\n INSERT INTO ${NOTIFICATION} (id, user_id, type, payload, actor_id, group_key, dedupe_key, created_at)\n VALUES ${sql.join(values, sql`, `)}\n ON CONFLICT (user_id, dedupe_key) DO NOTHING\n RETURNING id\n `,\n tx,\n );\n\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) =>\n row.deliveries.map(\n (delivery) => sql`(\n ${delivery.id}::text,\n ${row.id}::text,\n ${delivery.channel}::text,\n ${delivery.maxAttempts}::integer,\n ${ts(delivery.notBefore)}::timestamptz,\n ${ts(now)}::timestamptz\n )`,\n ),\n );\n\n if (deliveries.length > 0) {\n await tx.execute(sql`\n INSERT INTO ${DELIVERY} (id, notification_id, channel, max_attempts, not_before, updated_at)\n VALUES ${sql.join(deliveries, sql`, `)}\n `);\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 const channelFilter =\n args.channels && args.channels.length > 0\n ? sql`AND channel IN (${sql.join(\n args.channels.map((channel) => sql`${channel}`),\n sql`, `,\n )})`\n : sql``;\n\n const idFilter =\n args.ids && args.ids.length > 0\n ? sql`AND id IN (${sql.join(\n args.ids.map((id) => sql`${id}`),\n sql`, `,\n )})`\n : sql``;\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 rows = await run(sql`\n WITH claimed AS (\n UPDATE ${DELIVERY}\n SET status = 'claimed', claimed_at = ${ts(now)}::timestamptz, claimed_by = ${args.claimToken}::text\n WHERE id IN (\n SELECT id FROM ${DELIVERY}\n WHERE (status = 'pending' OR (status = 'claimed' AND claimed_at < ${ts(staleBefore)}::timestamptz))\n AND not_before <= ${ts(now)}::timestamptz\n AND attempts < max_attempts\n ${channelFilter}\n ${idFilter}\n ORDER BY not_before ASC, id ASC\n LIMIT ${args.limit}\n FOR UPDATE SKIP LOCKED\n )\n RETURNING id, notification_id, channel, attempts, max_attempts\n )\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\n FROM claimed c\n JOIN ${NOTIFICATION} n ON n.id = c.notification_id\n `);\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: 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 const values = releases.map(({ id, outcome, nextAttemptAt }) => {\n const retryable = outcome.result === \"failed\" && outcome.retryable;\n const error =\n outcome.result === \"failed\"\n ? outcome.error.slice(0, 2000)\n : outcome.result === \"skipped\"\n ? outcome.reason.slice(0, 2000)\n : null;\n\n return sql`(\n ${id}::text,\n ${outcome.result}::text,\n ${retryable}::boolean,\n ${error}::text,\n ${nextAttemptAt ? ts(nextAttemptAt) : null}::timestamptz\n )`;\n });\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 await db.execute(sql`\n UPDATE ${DELIVERY} d\n 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'\n END,\n attempts = CASE\n WHEN v.result IN ('sent', 'skipped') THEN d.attempts\n ELSE d.attempts + 1\n END,\n last_error = v.error,\n not_before = COALESCE(v.not_before, d.not_before),\n claimed_at = NULL,\n claimed_by = NULL,\n updated_at = ${ts(now)}::timestamptz\n FROM (VALUES ${sql.join(values, sql`, `)}) AS v(id, result, retryable, error, not_before)\n WHERE d.id = v.id\n `);\n },\n\n async listNotifications(query: FeedQuery): Promise<FeedPage> {\n const cursor = query.cursor ? decodeCursor(query.cursor) : null;\n\n const rows = await run(sql`\n SELECT * FROM ${NOTIFICATION}\n WHERE user_id = ${query.userId}::text\n AND archived_at IS NULL\n ${query.unreadOnly ? sql`AND read_at IS NULL` : sql``}\n ${\n cursor\n ? sql`AND (created_at, id) < (${ts(cursor.createdAt)}::timestamptz, ${cursor.id}::text)`\n : sql``\n }\n ORDER BY created_at DESC, id DESC\n LIMIT ${query.limit + 1}\n `);\n\n const page = rows.slice(0, query.limit).map(toNotification);\n const last = page.at(-1);\n\n return {\n notifications: page,\n nextCursor:\n rows.length > query.limit && last ? encodeCursor(last.createdAt, last.id) : null,\n };\n },\n\n async countUnseen(userId: string) {\n const rows = await run(sql`\n SELECT count(*)::int AS count FROM ${NOTIFICATION}\n WHERE user_id = ${userId}::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 db.execute(sql`\n UPDATE ${NOTIFICATION} SET seen_at = ${ts(new Date())}::timestamptz\n WHERE user_id = ${userId}::text AND seen_at IS NULL AND created_at <= ${ts(before)}::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\n // 0 for an already-read row made the route 404, which made the client\n // roll its optimistic update back and show the item as unread again.\n // COALESCE preserves the original timestamp.\n const rows = await run(sql`\n UPDATE ${NOTIFICATION}\n SET read_at = COALESCE(read_at, ${ts(new Date())}::timestamptz)\n WHERE user_id = ${userId}::text\n AND id IN (${sql.join(\n notificationIds.map((id) => sql`${id}`),\n sql`, `,\n )})\n RETURNING id\n `);\n return rows.length;\n },\n\n async markAllRead(userId: string) {\n const rows = await run(sql`\n UPDATE ${NOTIFICATION} SET read_at = ${ts(new Date())}::timestamptz\n WHERE user_id = ${userId}::text AND read_at IS NULL\n RETURNING id\n `);\n return rows.length;\n },\n\n async queryTable(table: string, where: WhereClause, options: QueryOptions) {\n const order = options.orderBy\n ? sql` ORDER BY ${sql.identifier(toSnakeCase(options.orderBy.field))} ${\n options.orderBy.direction === \"desc\" ? sql`DESC` : sql`ASC`\n }`\n : sql``;\n const limit = options.limit ? sql` LIMIT ${options.limit}` : sql``;\n\n return run(\n sql`SELECT * FROM ${sql.identifier(table)}${whereSql(where)}${order}${limit}`,\n ) as Promise<Record<string, unknown>[]>;\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 values = rows.map(\n (row) =>\n sql`(${sql.join(\n columns.map((column) => sql`${bind(row[column])}`),\n sql`, `,\n )})`,\n );\n\n const conflict =\n onConflict && onConflict.length > 0\n ? sql` ON CONFLICT (${sql.join(\n onConflict.map((field) => sql.identifier(toSnakeCase(field))),\n sql`, `,\n )}) DO UPDATE SET ${sql.join(\n columns\n .filter((column) => !onConflict.includes(column))\n .map(\n (column) =>\n sql`${sql.identifier(toSnakeCase(column))} = EXCLUDED.${sql.identifier(\n toSnakeCase(column),\n )}`,\n ),\n sql`, `,\n )}`\n : sql``;\n\n const inserted = await run(sql`\n INSERT INTO ${sql.identifier(table)} (${sql.join(\n columns.map((column) => sql.identifier(toSnakeCase(column))),\n sql`, `,\n )})\n VALUES ${sql.join(values, sql`, `)}${conflict}\n RETURNING 1 AS ok\n `);\n return inserted.length;\n },\n\n async updateRows(table: string, where: WhereClause, set: Record<string, unknown>) {\n const assignments = Object.entries(set).map(\n ([field, value]) => sql`${sql.identifier(toSnakeCase(field))} = ${bind(value)}`,\n );\n if (assignments.length === 0) return 0;\n\n const rows = await run(sql`\n UPDATE ${sql.identifier(table)}\n SET ${sql.join(assignments, sql`, `)}${whereSql(where)}\n RETURNING 1 AS ok\n `);\n return rows.length;\n },\n\n async deleteRows(table: string, where: WhereClause) {\n const rows = await run(\n sql`DELETE FROM ${sql.identifier(table)}${whereSql(where)} RETURNING 1 AS ok`,\n );\n return rows.length;\n },\n\n async getFailedDeliveries(args: { since: Date; limit: number }) {\n const rows = await run(sql`\n SELECT * FROM ${DELIVERY}\n WHERE status = 'failed' AND updated_at >= ${ts(args.since)}::timestamptz\n ORDER BY updated_at DESC\n LIMIT ${args.limit}\n `);\n return rows.map(toDelivery);\n },\n };\n}\n","import { sql } from \"drizzle-orm\";\nimport {\n boolean,\n index,\n integer,\n jsonb,\n pgTable,\n primaryKey,\n text,\n timestamp,\n uniqueIndex,\n} from \"drizzle-orm/pg-core\";\nimport type { Channel, DeliveryStatus, Frequency } from \"../../core/types\";\n\nexport function createSchema(prefix = \"\") {\n const name = (base: string) => `${prefix}${base}`;\n\n const notification = pgTable(\n name(\"notification\"),\n {\n id: text(\"id\").primaryKey(),\n userId: text(\"user_id\").notNull(),\n type: text(\"type\").notNull(),\n payload: jsonb(\"payload\").notNull(),\n actorId: text(\"actor_id\"),\n groupKey: text(\"group_key\"),\n dedupeKey: text(\"dedupe_key\"),\n seenAt: timestamp(\"seen_at\", { withTimezone: true }),\n readAt: timestamp(\"read_at\", { withTimezone: true }),\n archivedAt: timestamp(\"archived_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index(name(\"notification_feed_idx\")).on(table.userId, table.createdAt.desc()),\n index(name(\"notification_unseen_idx\")).on(table.userId).where(sql`${table.seenAt} is null`),\n index(name(\"notification_group_idx\")).on(table.userId, table.groupKey),\n // Postgres treats NULLs as distinct here, so unlimited rows may carry no\n // dedupe key while a present one is enforced unique per user.\n uniqueIndex(name(\"notification_dedupe_idx\")).on(table.userId, table.dedupeKey),\n ],\n );\n\n const notificationDelivery = pgTable(\n name(\"notification_delivery\"),\n {\n id: text(\"id\").primaryKey(),\n notificationId: text(\"notification_id\")\n .notNull()\n .references(() => notification.id, { onDelete: \"cascade\" }),\n channel: text(\"channel\").$type<Channel>().notNull(),\n status: text(\"status\").$type<DeliveryStatus>().notNull().default(\"pending\"),\n attempts: integer(\"attempts\").notNull().default(0),\n maxAttempts: integer(\"max_attempts\").notNull().default(5),\n notBefore: timestamp(\"not_before\", { withTimezone: true }).notNull().defaultNow(),\n claimedAt: timestamp(\"claimed_at\", { withTimezone: true }),\n claimedBy: text(\"claimed_by\"),\n lastError: text(\"last_error\"),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index(name(\"delivery_claim_idx\")).on(table.status, table.notBefore),\n index(name(\"delivery_notification_idx\")).on(table.notificationId),\n ],\n );\n\n const notificationPreference = pgTable(\n name(\"notification_preference\"),\n {\n userId: text(\"user_id\").notNull(),\n type: text(\"type\").notNull(),\n channel: text(\"channel\").$type<Channel>().notNull(),\n enabled: boolean(\"enabled\").notNull().default(true),\n frequency: text(\"frequency\").$type<Frequency>().notNull().default(\"instant\"),\n },\n (table) => [primaryKey({ columns: [table.userId, table.type, table.channel] })],\n );\n\n return { notification, notificationDelivery, notificationPreference };\n}\n\nexport type EasyPingSchema = ReturnType<typeof createSchema>;\n\nexport const schema = createSchema();\n"]}
@@ -286,6 +286,7 @@ function mongoAdapter(db, options = {}) {
286
286
  const now = /* @__PURE__ */ new Date();
287
287
  for (const { id, outcome, nextAttemptAt } of releases) {
288
288
  const failed = outcome.result === "failed";
289
+ const skipped = outcome.result === "skipped";
289
290
  const retryable = failed && outcome.retryable;
290
291
  await deliveries().updateOne({ _id: id }, [
291
292
  {
@@ -297,10 +298,10 @@ function mongoAdapter(db, options = {}) {
297
298
  "pending",
298
299
  "failed"
299
300
  ]
300
- } : "sent",
301
+ } : skipped ? "skipped" : "sent",
301
302
  // $literal: an error message starting with "$" would otherwise
302
303
  // be read as a field path.
303
- lastError: failed ? { $literal: outcome.error.slice(0, 2e3) } : null,
304
+ lastError: failed ? { $literal: outcome.error.slice(0, 2e3) } : skipped ? { $literal: outcome.reason.slice(0, 2e3) } : null,
304
305
  notBefore: nextAttemptAt ?? "$notBefore",
305
306
  // Written unconditionally: if the lease expired and another
306
307
  // worker re-sent, the duplicate already happened and the true