easy-ping 0.3.0 → 0.4.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 (79) hide show
  1. package/README.md +15 -4
  2. package/dist/adapters/drizzle.cjs +3 -2
  3. package/dist/adapters/drizzle.cjs.map +1 -1
  4. package/dist/adapters/drizzle.d.cts +34 -0
  5. package/dist/adapters/drizzle.d.ts +34 -0
  6. package/dist/adapters/drizzle.js +4 -3
  7. package/dist/adapters/drizzle.js.map +1 -1
  8. package/dist/adapters/mongodb.cjs +6 -2
  9. package/dist/adapters/mongodb.cjs.map +1 -1
  10. package/dist/adapters/mongodb.d.cts +1 -1
  11. package/dist/adapters/mongodb.d.ts +1 -1
  12. package/dist/adapters/mongodb.js +5 -2
  13. package/dist/adapters/mongodb.js.map +1 -1
  14. package/dist/adapters/postgres.cjs +3 -2
  15. package/dist/adapters/postgres.cjs.map +1 -1
  16. package/dist/adapters/postgres.d.cts +1 -1
  17. package/dist/adapters/postgres.d.ts +1 -1
  18. package/dist/adapters/postgres.js +3 -3
  19. package/dist/{chunk-OZSOIX23.js → chunk-3Y2MBUAW.js} +53 -12
  20. package/dist/chunk-3Y2MBUAW.js.map +1 -0
  21. package/dist/chunk-CYKAM7LE.js +226 -0
  22. package/dist/chunk-CYKAM7LE.js.map +1 -0
  23. package/dist/chunk-DG3ZL5NP.js +315 -0
  24. package/dist/chunk-DG3ZL5NP.js.map +1 -0
  25. package/dist/{chunk-NYDVNMKG.js → chunk-PSJHXZPR.js} +4 -3
  26. package/dist/chunk-PSJHXZPR.js.map +1 -0
  27. package/dist/{chunk-BV3U542P.js → chunk-W6I3B2CI.js} +2 -2
  28. package/dist/chunk-W6I3B2CI.js.map +1 -0
  29. package/dist/{chunk-RBDW4S6F.js → chunk-YEMRRMJQ.js} +3 -3
  30. package/dist/{chunk-RBDW4S6F.js.map → chunk-YEMRRMJQ.js.map} +1 -1
  31. package/dist/index.cjs +360 -62
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +84 -20
  34. package/dist/index.d.ts +84 -20
  35. package/dist/index.js +119 -357
  36. package/dist/index.js.map +1 -1
  37. package/dist/node.cjs +27 -6
  38. package/dist/node.cjs.map +1 -1
  39. package/dist/node.d.cts +8 -2
  40. package/dist/node.d.ts +8 -2
  41. package/dist/node.js +26 -6
  42. package/dist/node.js.map +1 -1
  43. package/dist/{plugin-CmPOFVmn.d.ts → plugin-C9Hoxus2.d.ts} +37 -9
  44. package/dist/{plugin-f5KaFWSN.d.cts → plugin-CiMsGlQe.d.cts} +37 -9
  45. package/dist/plugins/digests.d.cts +1 -1
  46. package/dist/plugins/digests.d.ts +1 -1
  47. package/dist/plugins/preferences-client.cjs.map +1 -1
  48. package/dist/plugins/preferences-client.d.cts +1 -1
  49. package/dist/plugins/preferences-client.d.ts +1 -1
  50. package/dist/plugins/preferences-client.js +1 -1
  51. package/dist/plugins/preferences.cjs +100 -15
  52. package/dist/plugins/preferences.cjs.map +1 -1
  53. package/dist/plugins/preferences.d.cts +3 -1
  54. package/dist/plugins/preferences.d.ts +3 -1
  55. package/dist/plugins/preferences.js +35 -7
  56. package/dist/plugins/preferences.js.map +1 -1
  57. package/dist/plugins/push.cjs +197 -38
  58. package/dist/plugins/push.cjs.map +1 -1
  59. package/dist/plugins/push.d.cts +32 -10
  60. package/dist/plugins/push.d.ts +32 -10
  61. package/dist/plugins/push.js +140 -39
  62. package/dist/plugins/push.js.map +1 -1
  63. package/dist/providers/web-push.cjs +37 -11
  64. package/dist/providers/web-push.cjs.map +1 -1
  65. package/dist/providers/web-push.d.cts +8 -2
  66. package/dist/providers/web-push.d.ts +8 -2
  67. package/dist/providers/web-push.js +37 -12
  68. package/dist/providers/web-push.js.map +1 -1
  69. package/dist/schema.cjs +2 -1
  70. package/dist/schema.cjs.map +1 -1
  71. package/dist/schema.d.cts +6 -1
  72. package/dist/schema.d.ts +6 -1
  73. package/dist/schema.js +2 -2
  74. package/package.json +1 -1
  75. package/dist/chunk-BV3U542P.js.map +0 -1
  76. package/dist/chunk-NYDVNMKG.js.map +0 -1
  77. package/dist/chunk-OZSOIX23.js.map +0 -1
  78. package/dist/chunk-W3BMEPPQ.js +0 -50
  79. package/dist/chunk-W3BMEPPQ.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/web-push/crypto.ts","../../src/providers/web-push/index.ts"],"names":[],"mappings":"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","file":"web-push.js","sourcesContent":["import { decodeBase64UrlBytes, encodeBase64Url } from \"../../core/base64url\";\n\n/**\n * VAPID (RFC 8292) and aes128gcm payload encryption (RFC 8291 over RFC 8188),\n * on Web Crypto.\n *\n * The `web-push` package is node:crypto only, so it cannot run on Cloudflare\n * Workers or Vercel Edge — the runtimes this library targets. Everything here\n * uses crypto.subtle and therefore runs anywhere the rest of the package does.\n */\n\nconst utf8 = (value: string) => new TextEncoder().encode(value);\n\nfunction concat(...parts: Uint8Array[]): Uint8Array<ArrayBuffer> {\n const total = parts.reduce((sum, part) => sum + part.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.length;\n }\n return out;\n}\n\nfunction requireBytes(value: string, label: string, length?: number): Uint8Array<ArrayBuffer> {\n const bytes = decodeBase64UrlBytes(value);\n if (!bytes) throw new Error(`${label} is not valid base64url`);\n if (length !== undefined && bytes.length !== length) {\n throw new Error(`${label} must be ${length} bytes, got ${bytes.length}`);\n }\n return bytes;\n}\n\n/** Web Crypto's HKDF does extract and expand in one call. */\nasync function hkdf(\n // Uint8Array<ArrayBuffer>, not bare Uint8Array: the latter widens to\n // ArrayBufferLike, which Web Crypto will not accept as a BufferSource.\n salt: Uint8Array<ArrayBuffer>,\n ikm: Uint8Array<ArrayBuffer>,\n info: Uint8Array<ArrayBuffer>,\n length: number,\n): Promise<Uint8Array<ArrayBuffer>> {\n const key = await crypto.subtle.importKey(\"raw\", ikm, \"HKDF\", false, [\"deriveBits\"]);\n const bits = await crypto.subtle.deriveBits(\n { name: \"HKDF\", hash: \"SHA-256\", salt, info },\n key,\n length * 8,\n );\n return new Uint8Array(bits);\n}\n\nexport const RECORD_SIZE = 4096;\n/** One record: record size less the padding delimiter and the GCM tag. */\nexport const MAX_PAYLOAD_BYTES = RECORD_SIZE - 17;\n\nexport type EncryptedPush = {\n body: Uint8Array<ArrayBuffer>;\n salt: Uint8Array<ArrayBuffer>;\n serverPublicKey: Uint8Array<ArrayBuffer>;\n};\n\n/**\n * RFC 8291 §3.4 then RFC 8188 §2.\n *\n * `salt` and `serverKeys` are injectable so tests can pin them; production\n * always generates fresh ones, and reusing a salt with the same key would be\n * catastrophic for GCM.\n */\nexport async function encryptPayload(\n plaintext: Uint8Array<ArrayBuffer>,\n userAgentPublicKey: string,\n authSecret: string,\n overrides?: { salt?: Uint8Array<ArrayBuffer>; serverKeys?: CryptoKeyPair },\n): Promise<EncryptedPush> {\n if (plaintext.length > MAX_PAYLOAD_BYTES) {\n throw new Error(`push payload is ${plaintext.length} bytes; the limit is ${MAX_PAYLOAD_BYTES}`);\n }\n\n const uaPublic = requireBytes(userAgentPublicKey, \"p256dh\", 65);\n const auth = requireBytes(authSecret, \"auth\", 16);\n\n const salt = overrides?.salt\n ? new Uint8Array(overrides.salt)\n : crypto.getRandomValues(new Uint8Array(16));\n\n const serverKeys =\n overrides?.serverKeys ??\n (await crypto.subtle.generateKey({ name: \"ECDH\", namedCurve: \"P-256\" }, true, [\"deriveBits\"]));\n\n const serverPublic = new Uint8Array(\n await crypto.subtle.exportKey(\"raw\", serverKeys.publicKey),\n ) as Uint8Array<ArrayBuffer>;\n\n const uaKey = await crypto.subtle.importKey(\n \"raw\",\n uaPublic,\n { name: \"ECDH\", namedCurve: \"P-256\" },\n false,\n [],\n );\n\n const shared = new Uint8Array(\n await crypto.subtle.deriveBits({ name: \"ECDH\", public: uaKey }, serverKeys.privateKey, 256),\n );\n\n // The receiver's key comes first; swapping the order yields a key the\n // browser cannot derive, and the push simply never decrypts.\n const keyInfo = concat(utf8(\"WebPush: info\"), new Uint8Array([0]), uaPublic, serverPublic);\n const ikm = await hkdf(auth, shared, keyInfo, 32);\n\n const cek = await hkdf(\n salt,\n ikm,\n concat(utf8(\"Content-Encoding: aes128gcm\"), new Uint8Array([0])),\n 16,\n );\n const nonce = await hkdf(\n salt,\n ikm,\n concat(utf8(\"Content-Encoding: nonce\"), new Uint8Array([0])),\n 12,\n );\n\n const aesKey = await crypto.subtle.importKey(\"raw\", cek, \"AES-GCM\", false, [\"encrypt\"]);\n\n // 0x02 marks the final record. 0x01 would mean \"more records follow\" and the\n // receiver would wait for one that never arrives.\n const padded = concat(plaintext, new Uint8Array([2]));\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt({ name: \"AES-GCM\", iv: nonce }, aesKey, padded),\n );\n\n // RFC 8188 header: salt(16) | rs(4, big endian) | idlen(1) | keyid\n const header = new Uint8Array(21 + serverPublic.length);\n header.set(salt, 0);\n new DataView(header.buffer).setUint32(16, RECORD_SIZE, false);\n header[20] = serverPublic.length;\n header.set(serverPublic, 21);\n\n return { body: concat(header, ciphertext), salt, serverPublicKey: serverPublic };\n}\n\nexport type VapidKeys = { publicKey: string; privateKey: string };\n\n/** A VAPID keypair in the base64url form every push library uses. */\nexport async function generateVapidKeys(): Promise<VapidKeys> {\n const keys = await crypto.subtle.generateKey({ name: \"ECDSA\", namedCurve: \"P-256\" }, true, [\n \"sign\",\n \"verify\",\n ]);\n\n const publicKey = new Uint8Array(await crypto.subtle.exportKey(\"raw\", keys.publicKey));\n const jwk = await crypto.subtle.exportKey(\"jwk\", keys.privateKey);\n\n if (!jwk.d) throw new Error(\"generated key has no private component\");\n return { publicKey: encodeBase64Url(publicKey), privateKey: jwk.d };\n}\n\n/**\n * VAPID keys are distributed as a raw public point plus a bare scalar, but Web\n * Crypto will only import a private EC key as JWK — which needs x and y. They\n * are recovered by slicing the uncompressed public point.\n */\nasync function importVapidKey(keys: VapidKeys): Promise<CryptoKey> {\n const publicKey = requireBytes(keys.publicKey, \"vapid publicKey\", 65);\n if (publicKey[0] !== 0x04) throw new Error(\"vapid publicKey must be an uncompressed EC point\");\n\n return crypto.subtle.importKey(\n \"jwk\",\n {\n kty: \"EC\",\n crv: \"P-256\",\n x: encodeBase64Url(publicKey.slice(1, 33)),\n y: encodeBase64Url(publicKey.slice(33, 65)),\n d: keys.privateKey,\n ext: true,\n },\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n false,\n [\"sign\"],\n );\n}\n\nexport type VapidOptions = {\n subject: string;\n keys: VapidKeys;\n /** Injectable for tests; VAPID tokens are short-lived. */\n now?: Date;\n ttlSeconds?: number;\n};\n\n/** RFC 8292 §2: an ES256 JWT scoped to the push service origin. */\nexport async function vapidAuthorization(endpoint: string, options: VapidOptions): Promise<string> {\n if (!/^(mailto:|https:)/.test(options.subject)) {\n throw new Error('vapid subject must be a \"mailto:\" or \"https:\" URL');\n }\n\n const now = options.now ?? new Date();\n const header = { typ: \"JWT\", alg: \"ES256\" };\n const claims = {\n // Origin only: a token scoped to the full endpoint is rejected.\n aud: new URL(endpoint).origin,\n exp: Math.floor(now.getTime() / 1000) + (options.ttlSeconds ?? 12 * 3600),\n sub: options.subject,\n };\n\n const signingInput = `${encodeBase64Url(JSON.stringify(header))}.${encodeBase64Url(\n JSON.stringify(claims),\n )}`;\n\n const signature = await crypto.subtle.sign(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n await importVapidKey(options.keys),\n utf8(signingInput),\n );\n\n // Web Crypto emits raw r||s, which is what JWS ES256 wants — no DER unwrap.\n const jwt = `${signingInput}.${encodeBase64Url(new Uint8Array(signature))}`;\n return `vapid t=${jwt}, k=${options.keys.publicKey}`;\n}\n","import type { PushMessage, PushProvider, PushSendResult } from \"../../plugins/push\";\nimport { encryptPayload, MAX_PAYLOAD_BYTES, type VapidKeys, vapidAuthorization } from \"./crypto\";\n\nexport type WebPushOptions = {\n /** \"mailto:you@example.com\" or an https URL. Push services require it. */\n subject: string;\n vapid: VapidKeys;\n /** How long the service should hold an undelivered message. */\n ttlSeconds?: number;\n urgency?: \"very-low\" | \"low\" | \"normal\" | \"high\";\n fetch?: typeof globalThis.fetch;\n};\n\n/**\n * Web push over Web Crypto, so it runs on Workers and Edge as well as Node.\n *\n * Endpoints that report themselves gone are surfaced as `expired`, which the\n * push plugin turns into an immediate row deletion. Everything transient is\n * `retryable`; anything the push service will reject identically forever is\n * neither, so it fails on the first attempt.\n */\nexport function webPush(options: WebPushOptions): PushProvider {\n const doFetch = options.fetch ?? globalThis.fetch;\n const ttl = options.ttlSeconds ?? 12 * 3600;\n\n return {\n name: \"web-push\",\n\n async send(message: PushMessage): Promise<PushSendResult> {\n const payload = new TextEncoder().encode(\n JSON.stringify({\n title: message.title,\n body: message.body,\n data: message.data ?? {},\n }),\n );\n\n if (payload.length > MAX_PAYLOAD_BYTES) {\n // Trimming silently would deliver a truncated notification; a bigger\n // payload will never fit, so retrying cannot help either.\n throw new Error(\n `push payload is ${payload.length} bytes, over the ${MAX_PAYLOAD_BYTES} byte limit`,\n );\n }\n\n const encrypted = await encryptPayload(\n payload,\n message.subscription.keys.p256dh,\n message.subscription.keys.auth,\n );\n\n const authorization = await vapidAuthorization(message.subscription.endpoint, {\n subject: options.subject,\n keys: options.vapid,\n });\n\n let response: Response;\n try {\n response = await doFetch(message.subscription.endpoint, {\n method: \"POST\",\n headers: {\n Authorization: authorization,\n \"Content-Encoding\": \"aes128gcm\",\n \"Content-Type\": \"application/octet-stream\",\n TTL: String(ttl),\n ...(options.urgency ? { Urgency: options.urgency } : {}),\n },\n body: encrypted.body,\n ...(message.signal ? { signal: message.signal } : {}),\n });\n } catch (error) {\n // Never reached the service: DNS, socket, timeout.\n throw new Error(error instanceof Error ? error.message : String(error));\n }\n\n // 201 is the documented success; 200/202 appear in the wild.\n if (response.ok) return {};\n\n // The subscription is permanently gone and must be pruned.\n if (response.status === 404 || response.status === 410) return { expired: true };\n\n if (response.status === 429 || response.status === 408 || response.status >= 500) {\n return { retryable: true };\n }\n\n const detail = await response.text().catch(() => \"\");\n throw new Error(\n `push service responded ${response.status}${detail ? `: ${detail.slice(0, 300)}` : \"\"}`,\n );\n },\n };\n}\n\nexport type { VapidKeys } from \"./crypto\";\nexport { generateVapidKeys, MAX_PAYLOAD_BYTES } from \"./crypto\";\n"]}
1
+ {"version":3,"sources":["../../src/providers/web-push/crypto.ts","../../src/providers/web-push/index.ts"],"names":[],"mappings":"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","file":"web-push.js","sourcesContent":["import { decodeBase64UrlBytes, encodeBase64Url } from \"../../core/base64url\";\n\n/**\n * VAPID (RFC 8292) and aes128gcm payload encryption (RFC 8291 over RFC 8188),\n * on Web Crypto.\n *\n * The `web-push` package is node:crypto only, so it cannot run on Cloudflare\n * Workers or Vercel Edge — the runtimes this library targets. Everything here\n * uses crypto.subtle and therefore runs anywhere the rest of the package does.\n */\n\nconst utf8 = (value: string) => new TextEncoder().encode(value);\n\nfunction concat(...parts: Uint8Array[]): Uint8Array<ArrayBuffer> {\n const total = parts.reduce((sum, part) => sum + part.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const part of parts) {\n out.set(part, offset);\n offset += part.length;\n }\n return out;\n}\n\n/** The subscription itself is unusable. Distinct from a transport failure so the plugin prunes rather than retries. */\nexport class InvalidSubscriptionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidSubscriptionError\";\n }\n}\n\nfunction requireBytes(\n value: string,\n label: string,\n length?: number,\n Failure: new (message: string) => Error = Error,\n): Uint8Array<ArrayBuffer> {\n const bytes = decodeBase64UrlBytes(value);\n if (!bytes) throw new Failure(`${label} is not valid base64url`);\n if (length !== undefined && bytes.length !== length) {\n throw new Failure(`${label} must be ${length} bytes, got ${bytes.length}`);\n }\n return bytes;\n}\n\n/** Web Crypto's HKDF does extract and expand in one call. */\nasync function hkdf(\n // Uint8Array<ArrayBuffer>, not bare Uint8Array: the latter widens to\n // ArrayBufferLike, which Web Crypto will not accept as a BufferSource.\n salt: Uint8Array<ArrayBuffer>,\n ikm: Uint8Array<ArrayBuffer>,\n info: Uint8Array<ArrayBuffer>,\n length: number,\n): Promise<Uint8Array<ArrayBuffer>> {\n const key = await crypto.subtle.importKey(\"raw\", ikm, \"HKDF\", false, [\"deriveBits\"]);\n const bits = await crypto.subtle.deriveBits(\n { name: \"HKDF\", hash: \"SHA-256\", salt, info },\n key,\n length * 8,\n );\n return new Uint8Array(bits);\n}\n\nexport const RECORD_SIZE = 4096;\n/** One record: record size less the padding delimiter and the GCM tag. */\nexport const MAX_PAYLOAD_BYTES = RECORD_SIZE - 17;\n\nexport type EncryptedPush = {\n body: Uint8Array<ArrayBuffer>;\n salt: Uint8Array<ArrayBuffer>;\n serverPublicKey: Uint8Array<ArrayBuffer>;\n};\n\n/**\n * RFC 8291 §3.4 then RFC 8188 §2.\n *\n * `salt` and `serverKeys` are injectable so tests can pin them; production\n * always generates fresh ones, and reusing a salt with the same key would be\n * catastrophic for GCM.\n */\nexport async function encryptPayload(\n plaintext: Uint8Array<ArrayBuffer>,\n userAgentPublicKey: string,\n authSecret: string,\n overrides?: { salt?: Uint8Array<ArrayBuffer>; serverKeys?: CryptoKeyPair },\n): Promise<EncryptedPush> {\n if (plaintext.length > MAX_PAYLOAD_BYTES) {\n throw new Error(`push payload is ${plaintext.length} bytes; the limit is ${MAX_PAYLOAD_BYTES}`);\n }\n\n const uaPublic = requireBytes(userAgentPublicKey, \"p256dh\", 65, InvalidSubscriptionError);\n const auth = requireBytes(authSecret, \"auth\", 16, InvalidSubscriptionError);\n if (uaPublic[0] !== 0x04) {\n throw new InvalidSubscriptionError(\"p256dh must be an uncompressed EC point\");\n }\n\n const salt = overrides?.salt\n ? new Uint8Array(overrides.salt)\n : crypto.getRandomValues(new Uint8Array(16));\n\n const serverKeys =\n overrides?.serverKeys ??\n (await crypto.subtle.generateKey({ name: \"ECDH\", namedCurve: \"P-256\" }, true, [\"deriveBits\"]));\n\n const serverPublic = new Uint8Array(\n await crypto.subtle.exportKey(\"raw\", serverKeys.publicKey),\n ) as Uint8Array<ArrayBuffer>;\n\n const uaKey = await crypto.subtle.importKey(\n \"raw\",\n uaPublic,\n { name: \"ECDH\", namedCurve: \"P-256\" },\n false,\n [],\n );\n\n const shared = new Uint8Array(\n await crypto.subtle.deriveBits({ name: \"ECDH\", public: uaKey }, serverKeys.privateKey, 256),\n );\n\n // The receiver's key comes first; swapping the order yields a key the\n // browser cannot derive, and the push simply never decrypts.\n const keyInfo = concat(utf8(\"WebPush: info\"), new Uint8Array([0]), uaPublic, serverPublic);\n const ikm = await hkdf(auth, shared, keyInfo, 32);\n\n const cek = await hkdf(\n salt,\n ikm,\n concat(utf8(\"Content-Encoding: aes128gcm\"), new Uint8Array([0])),\n 16,\n );\n const nonce = await hkdf(\n salt,\n ikm,\n concat(utf8(\"Content-Encoding: nonce\"), new Uint8Array([0])),\n 12,\n );\n\n const aesKey = await crypto.subtle.importKey(\"raw\", cek, \"AES-GCM\", false, [\"encrypt\"]);\n\n // 0x02 marks the final record. 0x01 would mean \"more records follow\" and the\n // receiver would wait for one that never arrives.\n const padded = concat(plaintext, new Uint8Array([2]));\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt({ name: \"AES-GCM\", iv: nonce }, aesKey, padded),\n );\n\n // RFC 8188 header: salt(16) | rs(4, big endian) | idlen(1) | keyid\n const header = new Uint8Array(21 + serverPublic.length);\n header.set(salt, 0);\n new DataView(header.buffer).setUint32(16, RECORD_SIZE, false);\n header[20] = serverPublic.length;\n header.set(serverPublic, 21);\n\n return { body: concat(header, ciphertext), salt, serverPublicKey: serverPublic };\n}\n\nexport type VapidKeys = { publicKey: string; privateKey: string };\n\n/** A VAPID keypair in the base64url form every push library uses. */\nexport async function generateVapidKeys(): Promise<VapidKeys> {\n const keys = await crypto.subtle.generateKey({ name: \"ECDSA\", namedCurve: \"P-256\" }, true, [\n \"sign\",\n \"verify\",\n ]);\n\n const publicKey = new Uint8Array(await crypto.subtle.exportKey(\"raw\", keys.publicKey));\n const jwk = await crypto.subtle.exportKey(\"jwk\", keys.privateKey);\n\n if (!jwk.d) throw new Error(\"generated key has no private component\");\n return { publicKey: encodeBase64Url(publicKey), privateKey: jwk.d };\n}\n\n/**\n * VAPID keys are distributed as a raw public point plus a bare scalar, but Web\n * Crypto will only import a private EC key as JWK — which needs x and y. They\n * are recovered by slicing the uncompressed public point.\n */\nasync function importVapidKey(keys: VapidKeys): Promise<CryptoKey> {\n const publicKey = requireBytes(keys.publicKey, \"vapid publicKey\", 65);\n if (publicKey[0] !== 0x04) throw new Error(\"vapid publicKey must be an uncompressed EC point\");\n\n return crypto.subtle.importKey(\n \"jwk\",\n {\n kty: \"EC\",\n crv: \"P-256\",\n x: encodeBase64Url(publicKey.slice(1, 33)),\n y: encodeBase64Url(publicKey.slice(33, 65)),\n d: keys.privateKey,\n ext: true,\n },\n { name: \"ECDSA\", namedCurve: \"P-256\" },\n false,\n [\"sign\"],\n );\n}\n\nexport type VapidOptions = {\n subject: string;\n keys: VapidKeys;\n /** Injectable for tests; VAPID tokens are short-lived. */\n now?: Date;\n ttlSeconds?: number;\n};\n\n/** RFC 8292 §2: an ES256 JWT scoped to the push service origin. */\nexport async function vapidAuthorization(endpoint: string, options: VapidOptions): Promise<string> {\n if (!/^(mailto:|https:)/.test(options.subject)) {\n throw new Error('vapid subject must be a \"mailto:\" or \"https:\" URL');\n }\n\n const now = options.now ?? new Date();\n const header = { typ: \"JWT\", alg: \"ES256\" };\n const claims = {\n // Origin only: a token scoped to the full endpoint is rejected.\n aud: new URL(endpoint).origin,\n exp: Math.floor(now.getTime() / 1000) + (options.ttlSeconds ?? 12 * 3600),\n sub: options.subject,\n };\n\n const signingInput = `${encodeBase64Url(JSON.stringify(header))}.${encodeBase64Url(\n JSON.stringify(claims),\n )}`;\n\n const signature = await crypto.subtle.sign(\n { name: \"ECDSA\", hash: \"SHA-256\" },\n await importVapidKey(options.keys),\n utf8(signingInput),\n );\n\n // Web Crypto emits raw r||s, which is what JWS ES256 wants — no DER unwrap.\n const jwt = `${signingInput}.${encodeBase64Url(new Uint8Array(signature))}`;\n return `vapid t=${jwt}, k=${options.keys.publicKey}`;\n}\n","import type { PushMessage, PushProvider, PushSendResult } from \"../../plugins/push\";\nimport {\n encryptPayload,\n InvalidSubscriptionError,\n MAX_PAYLOAD_BYTES,\n type VapidKeys,\n vapidAuthorization,\n} from \"./crypto\";\n\nexport type WebPushOptions = {\n /** \"mailto:you@example.com\" or an https URL. Push services require it. */\n subject: string;\n vapid: VapidKeys;\n /** How long the service should hold an undelivered message. */\n ttlSeconds?: number;\n urgency?: \"very-low\" | \"low\" | \"normal\" | \"high\";\n fetch?: typeof globalThis.fetch;\n /** Lets an http endpoint through. For a local fake push service only; never in production. */\n allowInsecureEndpoints?: boolean;\n};\n\n/**\n * Web push over Web Crypto, so it runs on Workers and Edge as well as Node.\n *\n * Endpoints that report themselves gone are surfaced as `expired`, which the\n * push plugin turns into an immediate row deletion. Everything transient is\n * `retryable`; anything the push service will reject identically forever is\n * neither, so it fails on the first attempt.\n */\nexport function webPush(options: WebPushOptions): PushProvider {\n const doFetch = options.fetch ?? globalThis.fetch;\n const ttl = options.ttlSeconds ?? 12 * 3600;\n\n return {\n name: \"web-push\",\n\n async send(message: PushMessage): Promise<PushSendResult> {\n const payload = new TextEncoder().encode(\n JSON.stringify({\n title: message.title,\n body: message.body,\n data: message.data ?? {},\n }),\n );\n\n if (payload.length > MAX_PAYLOAD_BYTES) {\n // Trimming silently would deliver a truncated notification; a bigger\n // payload will never fit, so retrying cannot help either.\n throw new Error(\n `push payload is ${payload.length} bytes, over the ${MAX_PAYLOAD_BYTES} byte limit`,\n );\n }\n\n // A subscription that cannot be encrypted for, or whose endpoint is not\n // an https URL, will never deliver. Report it so the row is pruned\n // instead of retried five times.\n let endpoint: URL;\n try {\n endpoint = new URL(message.subscription.endpoint);\n } catch {\n return { invalid: true };\n }\n const secure = endpoint.protocol === \"https:\";\n if (!secure && !(options.allowInsecureEndpoints && endpoint.protocol === \"http:\")) {\n return { invalid: true };\n }\n\n let encrypted: Awaited<ReturnType<typeof encryptPayload>>;\n try {\n encrypted = await encryptPayload(\n payload,\n message.subscription.keys.p256dh,\n message.subscription.keys.auth,\n );\n } catch (error) {\n if (error instanceof InvalidSubscriptionError) return { invalid: true };\n throw error;\n }\n\n const authorization = await vapidAuthorization(endpoint.href, {\n subject: options.subject,\n keys: options.vapid,\n });\n\n let response: Response;\n try {\n response = await doFetch(message.subscription.endpoint, {\n method: \"POST\",\n headers: {\n Authorization: authorization,\n \"Content-Encoding\": \"aes128gcm\",\n \"Content-Type\": \"application/octet-stream\",\n TTL: String(ttl),\n ...(options.urgency ? { Urgency: options.urgency } : {}),\n },\n body: encrypted.body,\n ...(message.signal ? { signal: message.signal } : {}),\n });\n } catch (error) {\n // Never reached the service: DNS, socket, timeout.\n throw new Error(error instanceof Error ? error.message : String(error));\n }\n\n // 201 is the documented success; 200/202 appear in the wild.\n if (response.ok) return {};\n\n // The subscription is permanently gone and must be pruned.\n if (response.status === 404 || response.status === 410) return { expired: true };\n\n if (response.status === 429 || response.status === 408 || response.status >= 500) {\n return { retryable: true };\n }\n\n const detail = await response.text().catch(() => \"\");\n throw new Error(\n `push service responded ${response.status}${detail ? `: ${detail.slice(0, 300)}` : \"\"}`,\n );\n },\n };\n}\n\nexport type { VapidKeys } from \"./crypto\";\nexport { generateVapidKeys, InvalidSubscriptionError, MAX_PAYLOAD_BYTES } from \"./crypto\";\n"]}
package/dist/schema.cjs CHANGED
@@ -53,7 +53,8 @@ var coreSchema = {
53
53
  type: { type: "string", required: true },
54
54
  channel: { type: "string", required: true },
55
55
  enabled: { type: "boolean", required: true, default: true },
56
- frequency: { type: "string", required: true, default: "instant" }
56
+ frequency: { type: "string", required: true, default: "instant" },
57
+ updatedAt: { type: "date", required: true, defaultNow: true }
57
58
  },
58
59
  primaryKey: ["userId", "type", "channel"]
59
60
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schema/declaration.ts","../src/schema/render-sql.ts","../src/schema/migrate-sql.ts","../src/schema/render-drizzle.ts"],"names":["indexName","columns","SQL_TYPE","quote","defaultClause"],"mappings":"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","file":"schema.cjs","sourcesContent":["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 type { FieldDeclaration, SchemaDeclaration } from \"../core/plugin\";\nimport { toSnakeCase } from \"./declaration\";\n\nconst SQL_TYPE: Record<FieldDeclaration[\"type\"], string> = {\n string: \"text\",\n number: \"integer\",\n boolean: \"boolean\",\n date: \"timestamptz\",\n json: \"jsonb\",\n};\n\nconst quote = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n\nconst defaultClause = (field: FieldDeclaration): string => {\n if (field.defaultNow) return \" DEFAULT now()\";\n if (field.default === undefined) return \"\";\n if (typeof field.default === \"string\") return ` DEFAULT '${field.default.replace(/'/g, \"''\")}'`;\n return ` DEFAULT ${field.default}`;\n};\n\n/** SchemaDeclaration to Postgres DDL, from the same source as the Drizzle renderer. */\nexport function renderPostgresDdl(schema: SchemaDeclaration, prefix = \"\"): string[] {\n const statements: string[] = [];\n\n for (const table of Object.values(schema)) {\n const name = quote(prefix + table.tableName);\n\n const columns = Object.entries(table.fields).map(([field, spec]) => {\n const nullability = spec.required ? \" NOT NULL\" : \"\";\n return ` ${quote(toSnakeCase(field))} ${SQL_TYPE[spec.type]}${nullability}${defaultClause(spec)}`;\n });\n\n if (table.primaryKey?.length) {\n const key = table.primaryKey.map((field) => quote(toSnakeCase(field))).join(\", \");\n columns.push(` PRIMARY KEY (${key})`);\n }\n\n statements.push(`CREATE TABLE IF NOT EXISTS ${name} (\\n${columns.join(\",\\n\")}\\n)`);\n\n for (const declaration of table.indexes ?? []) {\n const indexName = quote(\n prefix + (declaration.name ?? `${table.tableName}_${declaration.on.join(\"_\")}_idx`),\n );\n const columns = declaration.on.map((field) => quote(toSnakeCase(field))).join(\", \");\n const unique = declaration.unique ? \"UNIQUE \" : \"\";\n statements.push(`CREATE ${unique}INDEX IF NOT EXISTS ${indexName} ON ${name} (${columns})`);\n }\n }\n\n return statements;\n}\n","import type { FieldDeclaration, SchemaDeclaration, TableDeclaration } from \"../core/plugin\";\nimport { toSnakeCase } from \"./declaration\";\nimport { renderPostgresDdl } from \"./render-sql\";\n\n/**\n * Introspects a live Postgres schema and emits only what is missing.\n *\n * renderPostgresDdl emits CREATE TABLE IF NOT EXISTS, which is correct exactly\n * once. On an existing database it is a silent no-op, so an adopter who\n * bootstrapped with raw SQL had no way to pick up a column a later version\n * added — their app broke at runtime with \"column does not exist\" instead.\n *\n * Additive only, on purpose. Dropping a column or changing a type is\n * destructive and context-dependent, so those are reported in `unsupported`\n * rather than guessed at.\n */\n\nconst SQL_TYPE: Record<FieldDeclaration[\"type\"], string> = {\n string: \"text\",\n number: \"integer\",\n boolean: \"boolean\",\n date: \"timestamptz\",\n json: \"jsonb\",\n};\n\n/** What information_schema reports for each of our declared types. */\nconst LIVE_TYPE: Record<FieldDeclaration[\"type\"], readonly string[]> = {\n string: [\"text\", \"character varying\", \"character\"],\n number: [\"integer\", \"bigint\", \"smallint\"],\n boolean: [\"boolean\"],\n date: [\"timestamp with time zone\", \"timestamp without time zone\"],\n json: [\"jsonb\", \"json\"],\n};\n\nconst quote = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n\nconst defaultClause = (field: FieldDeclaration): string => {\n if (field.defaultNow) return \" DEFAULT now()\";\n if (field.default === undefined) return \"\";\n if (typeof field.default === \"string\") return ` DEFAULT '${field.default.replace(/'/g, \"''\")}'`;\n return ` DEFAULT ${field.default}`;\n};\n\nconst indexName = (\n table: TableDeclaration,\n declaration: { on: readonly string[]; name?: string },\n) => declaration.name ?? `${table.tableName}_${declaration.on.join(\"_\")}_idx`;\n\nexport type LiveColumn = { table: string; column: string; type: string; nullable: boolean };\n\nexport type MigrationPlan = {\n /** Run in order. Empty means the live schema already matches. */\n statements: readonly string[];\n /** Differences this cannot express safely. Read them; they need a human. */\n unsupported: readonly string[];\n /** Tables that do not exist yet — these get a full CREATE TABLE. */\n createdTables: readonly string[];\n};\n\n/**\n * Reads the live column list. Pass a function that runs the query — this\n * module stays driver-agnostic, as the rest of the schema tooling does.\n */\nexport type Introspector = () => Promise<readonly LiveColumn[]>;\n\n/** The query an Introspector should run. Exported so callers need not retype it. */\nexport const INTROSPECT_SQL = `\n SELECT table_name, column_name, data_type, is_nullable\n FROM information_schema.columns\n WHERE table_schema = current_schema()\n`;\n\nexport async function planPostgresMigration(\n introspect: Introspector,\n schema: SchemaDeclaration,\n prefix = \"\",\n): Promise<MigrationPlan> {\n const live = await introspect();\n\n const byTable = new Map<string, Map<string, LiveColumn>>();\n for (const column of live) {\n const columns = byTable.get(column.table) ?? new Map<string, LiveColumn>();\n columns.set(column.column, column);\n byTable.set(column.table, columns);\n }\n\n const statements: string[] = [];\n const unsupported: string[] = [];\n const createdTables: string[] = [];\n\n for (const table of Object.values(schema)) {\n const qualified = prefix + table.tableName;\n const existing = byTable.get(qualified);\n\n // Absent entirely: the ordinary bootstrap path still applies.\n if (!existing || existing.size === 0) {\n createdTables.push(qualified);\n statements.push(...renderPostgresDdl({ [table.tableName]: table }, prefix));\n continue;\n }\n\n for (const [field, spec] of Object.entries(table.fields)) {\n const column = toSnakeCase(field);\n const found = existing.get(column);\n\n if (!found) {\n // NOT NULL on a populated table fails without a default, so a required\n // column that cannot fill itself is added nullable and flagged.\n const canFill = spec.defaultNow || spec.default !== undefined;\n const nullability = spec.required && canFill ? \" NOT NULL\" : \"\";\n\n statements.push(\n `ALTER TABLE ${quote(qualified)} ADD COLUMN IF NOT EXISTS ` +\n `${quote(column)} ${SQL_TYPE[spec.type]}${nullability}${defaultClause(spec)}`,\n );\n\n if (spec.required && !canFill) {\n unsupported.push(\n `${qualified}.${column} is declared required but has no default; ` +\n \"it was added nullable. Backfill it, then ALTER ... SET NOT NULL yourself.\",\n );\n }\n continue;\n }\n\n if (!LIVE_TYPE[spec.type].includes(found.type)) {\n unsupported.push(\n `${qualified}.${column} is ${found.type} but ${spec.type} is declared ` +\n `(${SQL_TYPE[spec.type]}). Changing a column type can lose data — do it yourself.`,\n );\n }\n }\n\n // Indexes are cheap and idempotent, so they are re-issued unconditionally.\n for (const declaration of table.indexes ?? []) {\n const name = quote(prefix + indexName(table, declaration));\n const columns = declaration.on.map((field) => quote(toSnakeCase(field))).join(\", \");\n statements.push(\n `CREATE ${declaration.unique ? \"UNIQUE \" : \"\"}INDEX IF NOT EXISTS ${name} ` +\n `ON ${quote(qualified)} (${columns})`,\n );\n }\n\n for (const column of existing.keys()) {\n const declared = Object.keys(table.fields).some((field) => toSnakeCase(field) === column);\n if (!declared) {\n unsupported.push(\n `${qualified}.${column} exists but is not declared. Left alone — ` +\n \"drop it yourself if it is ours and no longer needed.\",\n );\n }\n }\n }\n\n return { statements, unsupported, createdTables };\n}\n","import type { FieldDeclaration, SchemaDeclaration, TableDeclaration } from \"../core/plugin\";\nimport { toSnakeCase } from \"./declaration\";\n\nconst COLUMN_BUILDER: Record<FieldDeclaration[\"type\"], string> = {\n string: \"text\",\n number: \"integer\",\n boolean: \"boolean\",\n date: \"timestamp\",\n json: \"jsonb\",\n};\n\nconst literal = (value: string | number | boolean) =>\n typeof value === \"string\" ? JSON.stringify(value) : String(value);\n\nfunction renderColumn(name: string, field: FieldDeclaration, isSinglePk: boolean): string {\n const builder = COLUMN_BUILDER[field.type];\n const column = toSnakeCase(name);\n\n let expression =\n field.type === \"date\"\n ? `${builder}(${JSON.stringify(column)}, { withTimezone: true })`\n : `${builder}(${JSON.stringify(column)})`;\n\n if (isSinglePk) return ` ${name}: ${expression}.primaryKey(),`;\n\n if (field.required) expression += \".notNull()\";\n if (field.defaultNow) expression += \".defaultNow()\";\n else if (field.default !== undefined) expression += `.default(${literal(field.default)})`;\n\n return ` ${name}: ${expression},`;\n}\n\nfunction renderTable(key: string, table: TableDeclaration, prefix: string): string {\n const compositePk = (table.primaryKey?.length ?? 0) > 1;\n const singlePk = !compositePk ? table.primaryKey?.[0] : undefined;\n\n const columns = Object.entries(table.fields)\n .map(([name, field]) => renderColumn(name, field, name === singlePk))\n .join(\"\\n\");\n\n const extras: string[] = [];\n\n for (const declaration of table.indexes ?? []) {\n const builder = declaration.unique ? \"uniqueIndex\" : \"index\";\n const name = declaration.name ?? `${table.tableName}_${declaration.on.join(\"_\")}_idx`;\n const columns = declaration.on.map((field) => `table.${field}`).join(\", \");\n extras.push(` ${builder}(${JSON.stringify(prefix + name)}).on(${columns}),`);\n }\n\n if (compositePk) {\n const columns = (table.primaryKey ?? []).map((field) => `table.${field}`).join(\", \");\n extras.push(` primaryKey({ columns: [${columns}] }),`);\n }\n\n const third = extras.length ? `,\\n (table) => [\\n${extras.join(\"\\n\")}\\n ]` : \"\";\n\n return `export const ${key} = pgTable(\\n ${JSON.stringify(prefix + table.tableName)},\\n {\\n${columns}\\n }${third},\\n);`;\n}\n\n/** Renders a SchemaDeclaration to Drizzle pg-core source. */\nexport function renderDrizzleSchema(schema: SchemaDeclaration, prefix = \"\"): string {\n const tables = Object.entries(schema);\n\n const used = new Set<string>([\"pgTable\"]);\n for (const [, table] of tables) {\n for (const field of Object.values(table.fields)) used.add(COLUMN_BUILDER[field.type]);\n for (const declaration of table.indexes ?? []) {\n used.add(declaration.unique ? \"uniqueIndex\" : \"index\");\n }\n if ((table.primaryKey?.length ?? 0) > 1) used.add(\"primaryKey\");\n }\n\n const imports = [...used].sort().join(\", \");\n const body = tables.map(([key, table]) => renderTable(key, table, prefix)).join(\"\\n\\n\");\n\n return `// Generated by easy-ping. Do not edit by hand.\\nimport { ${imports} } from \"drizzle-orm/pg-core\";\\n\\n${body}\\n`;\n}\n"]}
1
+ {"version":3,"sources":["../src/schema/declaration.ts","../src/schema/render-sql.ts","../src/schema/migrate-sql.ts","../src/schema/render-drizzle.ts"],"names":["indexName","columns","SQL_TYPE","quote","defaultClause"],"mappings":"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","file":"schema.cjs","sourcesContent":["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 updatedAt: { type: \"date\", required: true, defaultNow: true },\n },\n primaryKey: [\"userId\", \"type\", \"channel\"],\n },\n} satisfies SchemaDeclaration;\n","import type { FieldDeclaration, SchemaDeclaration } from \"../core/plugin\";\nimport { toSnakeCase } from \"./declaration\";\n\nconst SQL_TYPE: Record<FieldDeclaration[\"type\"], string> = {\n string: \"text\",\n number: \"integer\",\n boolean: \"boolean\",\n date: \"timestamptz\",\n json: \"jsonb\",\n};\n\nconst quote = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n\nconst defaultClause = (field: FieldDeclaration): string => {\n if (field.defaultNow) return \" DEFAULT now()\";\n if (field.default === undefined) return \"\";\n if (typeof field.default === \"string\") return ` DEFAULT '${field.default.replace(/'/g, \"''\")}'`;\n return ` DEFAULT ${field.default}`;\n};\n\n/** SchemaDeclaration to Postgres DDL, from the same source as the Drizzle renderer. */\nexport function renderPostgresDdl(schema: SchemaDeclaration, prefix = \"\"): string[] {\n const statements: string[] = [];\n\n for (const table of Object.values(schema)) {\n const name = quote(prefix + table.tableName);\n\n const columns = Object.entries(table.fields).map(([field, spec]) => {\n const nullability = spec.required ? \" NOT NULL\" : \"\";\n return ` ${quote(toSnakeCase(field))} ${SQL_TYPE[spec.type]}${nullability}${defaultClause(spec)}`;\n });\n\n if (table.primaryKey?.length) {\n const key = table.primaryKey.map((field) => quote(toSnakeCase(field))).join(\", \");\n columns.push(` PRIMARY KEY (${key})`);\n }\n\n statements.push(`CREATE TABLE IF NOT EXISTS ${name} (\\n${columns.join(\",\\n\")}\\n)`);\n\n for (const declaration of table.indexes ?? []) {\n const indexName = quote(\n prefix + (declaration.name ?? `${table.tableName}_${declaration.on.join(\"_\")}_idx`),\n );\n const columns = declaration.on.map((field) => quote(toSnakeCase(field))).join(\", \");\n const unique = declaration.unique ? \"UNIQUE \" : \"\";\n statements.push(`CREATE ${unique}INDEX IF NOT EXISTS ${indexName} ON ${name} (${columns})`);\n }\n }\n\n return statements;\n}\n","import type { FieldDeclaration, SchemaDeclaration, TableDeclaration } from \"../core/plugin\";\nimport { toSnakeCase } from \"./declaration\";\nimport { renderPostgresDdl } from \"./render-sql\";\n\n/**\n * Introspects a live Postgres schema and emits only what is missing.\n *\n * renderPostgresDdl emits CREATE TABLE IF NOT EXISTS, which is correct exactly\n * once. On an existing database it is a silent no-op, so an adopter who\n * bootstrapped with raw SQL had no way to pick up a column a later version\n * added — their app broke at runtime with \"column does not exist\" instead.\n *\n * Additive only, on purpose. Dropping a column or changing a type is\n * destructive and context-dependent, so those are reported in `unsupported`\n * rather than guessed at.\n */\n\nconst SQL_TYPE: Record<FieldDeclaration[\"type\"], string> = {\n string: \"text\",\n number: \"integer\",\n boolean: \"boolean\",\n date: \"timestamptz\",\n json: \"jsonb\",\n};\n\n/** What information_schema reports for each of our declared types. */\nconst LIVE_TYPE: Record<FieldDeclaration[\"type\"], readonly string[]> = {\n string: [\"text\", \"character varying\", \"character\"],\n number: [\"integer\", \"bigint\", \"smallint\"],\n boolean: [\"boolean\"],\n date: [\"timestamp with time zone\", \"timestamp without time zone\"],\n json: [\"jsonb\", \"json\"],\n};\n\nconst quote = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n\nconst defaultClause = (field: FieldDeclaration): string => {\n if (field.defaultNow) return \" DEFAULT now()\";\n if (field.default === undefined) return \"\";\n if (typeof field.default === \"string\") return ` DEFAULT '${field.default.replace(/'/g, \"''\")}'`;\n return ` DEFAULT ${field.default}`;\n};\n\nconst indexName = (\n table: TableDeclaration,\n declaration: { on: readonly string[]; name?: string },\n) => declaration.name ?? `${table.tableName}_${declaration.on.join(\"_\")}_idx`;\n\nexport type LiveColumn = { table: string; column: string; type: string; nullable: boolean };\n\nexport type MigrationPlan = {\n /** Run in order. Empty means the live schema already matches. */\n statements: readonly string[];\n /** Differences this cannot express safely. Read them; they need a human. */\n unsupported: readonly string[];\n /** Tables that do not exist yet — these get a full CREATE TABLE. */\n createdTables: readonly string[];\n};\n\n/**\n * Reads the live column list. Pass a function that runs the query — this\n * module stays driver-agnostic, as the rest of the schema tooling does.\n */\nexport type Introspector = () => Promise<readonly LiveColumn[]>;\n\n/** The query an Introspector should run. Exported so callers need not retype it. */\nexport const INTROSPECT_SQL = `\n SELECT table_name, column_name, data_type, is_nullable\n FROM information_schema.columns\n WHERE table_schema = current_schema()\n`;\n\nexport async function planPostgresMigration(\n introspect: Introspector,\n schema: SchemaDeclaration,\n prefix = \"\",\n): Promise<MigrationPlan> {\n const live = await introspect();\n\n const byTable = new Map<string, Map<string, LiveColumn>>();\n for (const column of live) {\n const columns = byTable.get(column.table) ?? new Map<string, LiveColumn>();\n columns.set(column.column, column);\n byTable.set(column.table, columns);\n }\n\n const statements: string[] = [];\n const unsupported: string[] = [];\n const createdTables: string[] = [];\n\n for (const table of Object.values(schema)) {\n const qualified = prefix + table.tableName;\n const existing = byTable.get(qualified);\n\n // Absent entirely: the ordinary bootstrap path still applies.\n if (!existing || existing.size === 0) {\n createdTables.push(qualified);\n statements.push(...renderPostgresDdl({ [table.tableName]: table }, prefix));\n continue;\n }\n\n for (const [field, spec] of Object.entries(table.fields)) {\n const column = toSnakeCase(field);\n const found = existing.get(column);\n\n if (!found) {\n // NOT NULL on a populated table fails without a default, so a required\n // column that cannot fill itself is added nullable and flagged.\n const canFill = spec.defaultNow || spec.default !== undefined;\n const nullability = spec.required && canFill ? \" NOT NULL\" : \"\";\n\n statements.push(\n `ALTER TABLE ${quote(qualified)} ADD COLUMN IF NOT EXISTS ` +\n `${quote(column)} ${SQL_TYPE[spec.type]}${nullability}${defaultClause(spec)}`,\n );\n\n if (spec.required && !canFill) {\n unsupported.push(\n `${qualified}.${column} is declared required but has no default; ` +\n \"it was added nullable. Backfill it, then ALTER ... SET NOT NULL yourself.\",\n );\n }\n continue;\n }\n\n if (!LIVE_TYPE[spec.type].includes(found.type)) {\n unsupported.push(\n `${qualified}.${column} is ${found.type} but ${spec.type} is declared ` +\n `(${SQL_TYPE[spec.type]}). Changing a column type can lose data — do it yourself.`,\n );\n }\n }\n\n // Indexes are cheap and idempotent, so they are re-issued unconditionally.\n for (const declaration of table.indexes ?? []) {\n const name = quote(prefix + indexName(table, declaration));\n const columns = declaration.on.map((field) => quote(toSnakeCase(field))).join(\", \");\n statements.push(\n `CREATE ${declaration.unique ? \"UNIQUE \" : \"\"}INDEX IF NOT EXISTS ${name} ` +\n `ON ${quote(qualified)} (${columns})`,\n );\n }\n\n for (const column of existing.keys()) {\n const declared = Object.keys(table.fields).some((field) => toSnakeCase(field) === column);\n if (!declared) {\n unsupported.push(\n `${qualified}.${column} exists but is not declared. Left alone — ` +\n \"drop it yourself if it is ours and no longer needed.\",\n );\n }\n }\n }\n\n return { statements, unsupported, createdTables };\n}\n","import type { FieldDeclaration, SchemaDeclaration, TableDeclaration } from \"../core/plugin\";\nimport { toSnakeCase } from \"./declaration\";\n\nconst COLUMN_BUILDER: Record<FieldDeclaration[\"type\"], string> = {\n string: \"text\",\n number: \"integer\",\n boolean: \"boolean\",\n date: \"timestamp\",\n json: \"jsonb\",\n};\n\nconst literal = (value: string | number | boolean) =>\n typeof value === \"string\" ? JSON.stringify(value) : String(value);\n\nfunction renderColumn(name: string, field: FieldDeclaration, isSinglePk: boolean): string {\n const builder = COLUMN_BUILDER[field.type];\n const column = toSnakeCase(name);\n\n let expression =\n field.type === \"date\"\n ? `${builder}(${JSON.stringify(column)}, { withTimezone: true })`\n : `${builder}(${JSON.stringify(column)})`;\n\n if (isSinglePk) return ` ${name}: ${expression}.primaryKey(),`;\n\n if (field.required) expression += \".notNull()\";\n if (field.defaultNow) expression += \".defaultNow()\";\n else if (field.default !== undefined) expression += `.default(${literal(field.default)})`;\n\n return ` ${name}: ${expression},`;\n}\n\nfunction renderTable(key: string, table: TableDeclaration, prefix: string): string {\n const compositePk = (table.primaryKey?.length ?? 0) > 1;\n const singlePk = !compositePk ? table.primaryKey?.[0] : undefined;\n\n const columns = Object.entries(table.fields)\n .map(([name, field]) => renderColumn(name, field, name === singlePk))\n .join(\"\\n\");\n\n const extras: string[] = [];\n\n for (const declaration of table.indexes ?? []) {\n const builder = declaration.unique ? \"uniqueIndex\" : \"index\";\n const name = declaration.name ?? `${table.tableName}_${declaration.on.join(\"_\")}_idx`;\n const columns = declaration.on.map((field) => `table.${field}`).join(\", \");\n extras.push(` ${builder}(${JSON.stringify(prefix + name)}).on(${columns}),`);\n }\n\n if (compositePk) {\n const columns = (table.primaryKey ?? []).map((field) => `table.${field}`).join(\", \");\n extras.push(` primaryKey({ columns: [${columns}] }),`);\n }\n\n const third = extras.length ? `,\\n (table) => [\\n${extras.join(\"\\n\")}\\n ]` : \"\";\n\n return `export const ${key} = pgTable(\\n ${JSON.stringify(prefix + table.tableName)},\\n {\\n${columns}\\n }${third},\\n);`;\n}\n\n/** Renders a SchemaDeclaration to Drizzle pg-core source. */\nexport function renderDrizzleSchema(schema: SchemaDeclaration, prefix = \"\"): string {\n const tables = Object.entries(schema);\n\n const used = new Set<string>([\"pgTable\"]);\n for (const [, table] of tables) {\n for (const field of Object.values(table.fields)) used.add(COLUMN_BUILDER[field.type]);\n for (const declaration of table.indexes ?? []) {\n used.add(declaration.unique ? \"uniqueIndex\" : \"index\");\n }\n if ((table.primaryKey?.length ?? 0) > 1) used.add(\"primaryKey\");\n }\n\n const imports = [...used].sort().join(\", \");\n const body = tables.map(([key, table]) => renderTable(key, table, prefix)).join(\"\\n\\n\");\n\n return `// Generated by easy-ping. Do not edit by hand.\\nimport { ${imports} } from \"drizzle-orm/pg-core\";\\n\\n${body}\\n`;\n}\n"]}
package/dist/schema.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as SchemaDeclaration } from './plugin-f5KaFWSN.cjs';
1
+ import { S as SchemaDeclaration } from './plugin-CiMsGlQe.cjs';
2
2
  import './adapter-BfLe2J0t.cjs';
3
3
 
4
4
  declare const toSnakeCase: (value: string) => string;
@@ -143,6 +143,11 @@ declare const coreSchema: {
143
143
  required: true;
144
144
  default: string;
145
145
  };
146
+ updatedAt: {
147
+ type: "date";
148
+ required: true;
149
+ defaultNow: true;
150
+ };
146
151
  };
147
152
  primaryKey: string[];
148
153
  };
package/dist/schema.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as SchemaDeclaration } from './plugin-CmPOFVmn.js';
1
+ import { S as SchemaDeclaration } from './plugin-C9Hoxus2.js';
2
2
  import './adapter-BfLe2J0t.js';
3
3
 
4
4
  declare const toSnakeCase: (value: string) => string;
@@ -143,6 +143,11 @@ declare const coreSchema: {
143
143
  required: true;
144
144
  default: string;
145
145
  };
146
+ updatedAt: {
147
+ type: "date";
148
+ required: true;
149
+ defaultNow: true;
150
+ };
146
151
  };
147
152
  primaryKey: string[];
148
153
  };
package/dist/schema.js CHANGED
@@ -1,4 +1,4 @@
1
- export { INTROSPECT_SQL, planPostgresMigration, renderDrizzleSchema, renderPostgresDdl } from './chunk-RBDW4S6F.js';
2
- export { coreSchema, toSnakeCase } from './chunk-NYDVNMKG.js';
1
+ export { INTROSPECT_SQL, planPostgresMigration, renderDrizzleSchema, renderPostgresDdl } from './chunk-YEMRRMJQ.js';
2
+ export { coreSchema, toSnakeCase } from './chunk-PSJHXZPR.js';
3
3
  //# sourceMappingURL=schema.js.map
4
4
  //# sourceMappingURL=schema.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easy-ping",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Self-hosted, type-safe notifications for TypeScript apps. Your database, your users.",
5
5
  "license": "MIT",
6
6
  "author": "Teklu Moges",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/plugin.ts"],"names":[],"mappings":";AAgLO,SAAS,aACd,MAAA,EAC8B;AAC9B,EAAA,OAAO,MAAA;AACT;AAgBO,SAAS,mBACd,MAAA,EAC6B;AAC7B,EAAA,OAAO,MAAA;AACT","file":"chunk-BV3U542P.js","sourcesContent":["import type { DeliveryOutcome } from \"./adapter\";\nimport type { Logger } from \"./errors\";\nimport type { PluginStore } from \"./store\";\nimport type { Channel, Recipient } from \"./types\";\n\nexport type Promisable<T> = T | Promise<T>;\n\nexport type PrepareContext = {\n type: string;\n recipients: readonly Recipient[];\n};\n\nexport type BeforeSendContext = {\n type: string;\n recipient: Recipient;\n payload: unknown;\n actorId: string | null;\n dedupeKey: string | null;\n /** Whatever this plugin's own prepare() returned for this send. */\n prepared: unknown;\n};\n\nexport type BeforeSendResult =\n | { action: \"continue\"; payload?: unknown; dedupeKey?: string }\n | { action: \"cancel\"; reason: string };\n\nexport type ChannelDecision = {\n channel: Channel;\n action: \"send\" | \"skip\" | \"defer\";\n /** defer only. Written to notification_delivery.not_before. */\n notBefore?: Date | undefined;\n};\n\nexport type ResolveChannelsContext = {\n type: string;\n recipient: Recipient;\n payload: unknown;\n decisions: readonly ChannelDecision[];\n /** Whatever this plugin's own prepare() returned for this send. */\n prepared: unknown;\n};\n\nexport type DeliverContext = {\n channel: Channel;\n deliveryId: string;\n attempt: number;\n recipient: Recipient;\n notification: { id: string; userId: string; type: string; payload: unknown };\n /** Aborted on the runner's timeout. */\n signal: AbortSignal;\n};\n\nexport type AfterSendContext = {\n type: string;\n payload: unknown;\n /** Committed rows. Ids exist only at this point. */\n notifications: readonly { id: string; userId: string }[];\n /** Whatever this plugin's own prepare() returned for this send. */\n prepared: unknown;\n};\n\nexport type AfterDeliverContext = {\n deliveryId: string;\n notificationId: string;\n userId: string;\n channel: Channel;\n outcome: DeliveryOutcome;\n attempt: number;\n isTerminal: boolean;\n};\n\n/** beforeSend and resolveChannels fail closed; afterDeliver fails open. */\nexport type PluginHooks = {\n /** Once per send, for bulk loading — the per-recipient hooks would be an N+1. */\n prepare?: (ctx: PrepareContext) => Promisable<unknown>;\n beforeSend?: (ctx: BeforeSendContext) => Promisable<BeforeSendResult>;\n resolveChannels?: (ctx: ResolveChannelsContext) => Promisable<readonly ChannelDecision[]>;\n /**\n * Once per send, after the rows are committed. The only hook that sees\n * notification ids, which digests needs to bucket an entry against one.\n * Fails open: the notification is already written.\n */\n afterSend?: (ctx: AfterSendContext) => Promisable<void>;\n /**\n * Delivers one of the plugin's declared `channels`. Core carries inApp and\n * email; everything else arrives here, which is how push, sms and slack are\n * added without core learning about them.\n */\n deliver?: (ctx: DeliverContext) => Promisable<DeliveryOutcome>;\n afterDeliver?: (ctx: AfterDeliverContext) => Promisable<void>;\n};\n\nexport type FieldType = \"string\" | \"number\" | \"boolean\" | \"date\" | \"json\";\n\nexport type FieldDeclaration = {\n type: FieldType;\n required?: boolean;\n default?: string | number | boolean;\n /** date fields only. Renders as defaultNow() / CURRENT_TIMESTAMP. */\n defaultNow?: boolean;\n};\n\nexport type IndexDeclaration = {\n on: readonly string[];\n unique?: boolean;\n name?: string;\n};\n\nexport type TableDeclaration = {\n tableName: string;\n fields: Record<string, FieldDeclaration>;\n primaryKey?: readonly string[];\n indexes?: readonly IndexDeclaration[];\n};\n\nexport type SchemaDeclaration = Record<string, TableDeclaration>;\n\nexport type RouteScope =\n | { type: \"user\" }\n | { type: \"machine\" }\n | { type: \"signed\"; purpose: string }\n | { type: \"custom\"; justification: string };\n\nexport type RouteContext = {\n request: Request;\n /** Resolved for user-scoped routes, so a handler cannot forget to scope. */\n userId: string | null;\n /** Verified before dispatch, so a handler cannot forget to check. */\n claims: { uid: string; purpose: string; exp: number; data?: Record<string, string> } | null;\n params: Record<string, string>;\n};\n\nexport type RouteDefinition = {\n path: string;\n method: \"GET\" | \"POST\";\n scope: RouteScope;\n handler: (ctx: RouteContext) => Promise<Response>;\n};\n\n/** Handed to a plugin at startup so it need not be given the database twice. */\nexport type PluginInitContext = {\n /** Scoped to the tables this plugin declares in schema(). */\n store: PluginStore;\n /** The top-level signing secret, for plugins issuing signed links. */\n secret: string;\n logger: Logger;\n /** The app's resolver, for plugins that need timezones or addresses. */\n getRecipients: (userIds: readonly string[]) => Promise<readonly Recipient[]>;\n /**\n * The instance's own send(). A plugin composing a message (a digest, say)\n * routes it back through the pipeline rather than reaching for a provider,\n * so it inherits retry, backoff and idempotency for free.\n */\n send: (\n type: string,\n args: { to: string | readonly string[]; payload: unknown; dedupeKey?: string },\n ) => Promise<unknown>;\n};\n\nexport type EasyPingPlugin<\n TId extends string = string,\n TSchema extends SchemaDeclaration = SchemaDeclaration,\n> = {\n id: TId;\n dependsOn?: readonly string[];\n schema?: TSchema;\n routes?: readonly RouteDefinition[];\n hooks?: PluginHooks;\n /** Channels this plugin delivers. Makes them usable in a notification. */\n channels?: readonly Channel[];\n /** Called once by easyPing() before any hook or route can run. */\n init?: (ctx: PluginInitContext) => void;\n};\n\nexport type AnyPlugin = EasyPingPlugin<string, SchemaDeclaration>;\n\nexport function definePlugin<TId extends string, TSchema extends SchemaDeclaration>(\n plugin: EasyPingPlugin<TId, TSchema>,\n): EasyPingPlugin<TId, TSchema> {\n return plugin;\n}\n\nexport type ClientFetch = (path: string, init?: RequestInit) => Promise<unknown>;\n\nexport type ClientPlugin<\n TId extends string = string,\n TActions extends Record<string, unknown> = Record<string, unknown>,\n> = {\n id: TId;\n /** Phantom. Links client types to the server plugin so a mismatch is a type error. */\n $InferServerPlugin?: AnyPlugin;\n actions: (fetch: ClientFetch) => TActions;\n};\n\nexport type AnyClientPlugin = ClientPlugin<string, Record<string, unknown>>;\n\nexport function defineClientPlugin<TId extends string, TActions extends Record<string, unknown>>(\n plugin: ClientPlugin<TId, TActions>,\n): ClientPlugin<TId, TActions> {\n return plugin;\n}\n\nexport type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (\n k: infer I,\n) => void\n ? I\n : never;\n\n/** Naked type parameter, so the conditional distributes over the union. */\ntype ExtractActions<TPlugin> = TPlugin extends { actions: (fetch: never) => infer TActions }\n ? TActions\n : never;\n\n/** Indexes the tuple rather than recursing: a fold blows the instantiation depth limit. */\nexport type ActionsOf<TPlugins extends readonly AnyClientPlugin[]> = UnionToIntersection<\n ExtractActions<TPlugins[number]>\n>;\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/schema/declaration.ts"],"names":[],"mappings":"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","file":"chunk-NYDVNMKG.js","sourcesContent":["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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/errors.ts","../src/core/store.ts"],"names":[],"mappings":"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","file":"chunk-OZSOIX23.js","sourcesContent":["export class EasyPingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EasyPingError\";\n }\n}\n\n/** Thrown when a payload fails its Standard Schema. Developer error. */\nexport class ValidationError extends EasyPingError {\n readonly issues: readonly { message: string; path?: string }[];\n\n constructor(type: string, issues: readonly { message: string; path?: string }[]) {\n super(`Payload for \"${type}\" failed validation: ${issues.map((i) => i.message).join(\"; \")}`);\n this.name = \"ValidationError\";\n this.issues = issues;\n }\n}\n\n/** Thrown at startup for a malformed config. Never at send time. */\nexport class ConfigError extends EasyPingError {\n constructor(message: string) {\n super(message);\n this.name = \"ConfigError\";\n }\n}\n\nexport type Logger = {\n warn: (message: string, meta?: Record<string, unknown>) => void;\n error: (message: string, meta?: Record<string, unknown>) => void;\n};\n\nexport const consoleLogger: Logger = {\n warn: (message, meta) => console.warn(`[easy-ping] ${message}`, meta ?? \"\"),\n error: (message, meta) => console.error(`[easy-ping] ${message}`, meta ?? \"\"),\n};\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"]}
@@ -1,50 +0,0 @@
1
- import { encodeBase64Url, decodeBase64UrlBytes, decodeBase64Url } from './chunk-PTGHMCCG.js';
2
-
3
- // src/core/tokens.ts
4
- var encoder = new TextEncoder();
5
- async function hmacKey(secret) {
6
- return crypto.subtle.importKey(
7
- "raw",
8
- encoder.encode(secret),
9
- { name: "HMAC", hash: "SHA-256" },
10
- false,
11
- ["sign", "verify"]
12
- );
13
- }
14
- async function signToken(secret, claims) {
15
- const body = encodeBase64Url(JSON.stringify(claims));
16
- const signature = await crypto.subtle.sign("HMAC", await hmacKey(secret), encoder.encode(body));
17
- return `${body}.${encodeBase64Url(new Uint8Array(signature))}`;
18
- }
19
- async function verifyToken(secret, token, purpose, now = /* @__PURE__ */ new Date()) {
20
- const separator = token.lastIndexOf(".");
21
- if (separator <= 0) return null;
22
- const body = token.slice(0, separator);
23
- const signature = decodeBase64UrlBytes(token.slice(separator + 1));
24
- if (!signature) return null;
25
- const valid = await crypto.subtle.verify(
26
- "HMAC",
27
- await hmacKey(secret),
28
- signature,
29
- encoder.encode(body)
30
- );
31
- if (!valid) return null;
32
- const decoded = decodeBase64Url(body);
33
- if (!decoded) return null;
34
- let claims;
35
- try {
36
- claims = JSON.parse(decoded);
37
- } catch {
38
- return null;
39
- }
40
- if (typeof claims?.uid !== "string" || typeof claims.exp !== "number") return null;
41
- if (claims.purpose !== purpose) return null;
42
- if (claims.exp * 1e3 <= now.getTime()) return null;
43
- return claims;
44
- }
45
- var DEFAULT_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
46
- var expiresIn = (seconds, now = /* @__PURE__ */ new Date()) => Math.floor(now.getTime() / 1e3) + seconds;
47
-
48
- export { DEFAULT_TOKEN_TTL_SECONDS, expiresIn, signToken, verifyToken };
49
- //# sourceMappingURL=chunk-W3BMEPPQ.js.map
50
- //# sourceMappingURL=chunk-W3BMEPPQ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/tokens.ts"],"names":[],"mappings":";;;AAaA,IAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAEhC,eAAe,QAAQ,MAAA,EAAoC;AACzD,EAAA,OAAO,OAAO,MAAA,CAAO,SAAA;AAAA,IACnB,KAAA;AAAA,IACA,OAAA,CAAQ,OAAO,MAAM,CAAA;AAAA,IACrB,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAU;AAAA,IAChC,KAAA;AAAA,IACA,CAAC,QAAQ,QAAQ;AAAA,GACnB;AACF;AAEA,eAAsB,SAAA,CAAU,QAAgB,MAAA,EAAsC;AACpF,EAAA,MAAM,IAAA,GAAO,eAAA,CAAgB,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AACnD,EAAA,MAAM,SAAA,GAAY,MAAM,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,MAAM,OAAA,CAAQ,MAAM,CAAA,EAAG,OAAA,CAAQ,MAAA,CAAO,IAAI,CAAC,CAAA;AAC9F,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,eAAA,CAAgB,IAAI,UAAA,CAAW,SAAS,CAAC,CAAC,CAAA,CAAA;AAC9D;AAGA,eAAsB,YACpB,MAAA,EACA,KAAA,EACA,SACA,GAAA,mBAAY,IAAI,MAAK,EACQ;AAC7B,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,WAAA,CAAY,GAAG,CAAA;AACvC,EAAA,IAAI,SAAA,IAAa,GAAG,OAAO,IAAA;AAE3B,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA;AACrC,EAAA,MAAM,YAAY,oBAAA,CAAqB,KAAA,CAAM,KAAA,CAAM,SAAA,GAAY,CAAC,CAAC,CAAA;AACjE,EAAA,IAAI,CAAC,WAAW,OAAO,IAAA;AAGvB,EAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,CAAO,MAAA,CAAO,MAAA;AAAA,IAChC,MAAA;AAAA,IACA,MAAM,QAAQ,MAAM,CAAA;AAAA,IACpB,SAAA;AAAA,IACA,OAAA,CAAQ,OAAO,IAAI;AAAA,GACrB;AACA,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AAEnB,EAAA,MAAM,OAAA,GAAU,gBAAgB,IAAI,CAAA;AACpC,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAO,QAAQ,GAAA,KAAQ,QAAA,IAAY,OAAO,MAAA,CAAO,GAAA,KAAQ,UAAU,OAAO,IAAA;AAE9E,EAAA,IAAI,MAAA,CAAO,OAAA,KAAY,OAAA,EAAS,OAAO,IAAA;AACvC,EAAA,IAAI,OAAO,GAAA,GAAM,GAAA,IAAQ,GAAA,CAAI,OAAA,IAAW,OAAO,IAAA;AAE/C,EAAA,OAAO,MAAA;AACT;AAEO,IAAM,yBAAA,GAA4B,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK;AAEjD,IAAM,SAAA,GAAY,CAAC,OAAA,EAAiB,GAAA,mBAAY,IAAI,IAAA,EAAK,KAC9D,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,OAAA,EAAQ,GAAI,GAAI,CAAA,GAAI","file":"chunk-W3BMEPPQ.js","sourcesContent":["import { decodeBase64Url, decodeBase64UrlBytes, encodeBase64Url } from \"./base64url\";\n\nexport type TokenClaims = {\n /** Subject — the user the token acts for. */\n uid: string;\n /** What the token is allowed to do. Verification requires an exact match. */\n purpose: string;\n /** Expiry, epoch seconds. */\n exp: number;\n /** Free-form, purpose-specific. */\n data?: Record<string, string>;\n};\n\nconst encoder = new TextEncoder();\n\nasync function hmacKey(secret: string): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\", \"verify\"],\n );\n}\n\nexport async function signToken(secret: string, claims: TokenClaims): Promise<string> {\n const body = encodeBase64Url(JSON.stringify(claims));\n const signature = await crypto.subtle.sign(\"HMAC\", await hmacKey(secret), encoder.encode(body));\n return `${body}.${encodeBase64Url(new Uint8Array(signature))}`;\n}\n\n/** null for every failure mode. Distinguishing them tells an attacker which part to fix. */\nexport async function verifyToken(\n secret: string,\n token: string,\n purpose: string,\n now: Date = new Date(),\n): Promise<TokenClaims | null> {\n const separator = token.lastIndexOf(\".\");\n if (separator <= 0) return null;\n\n const body = token.slice(0, separator);\n const signature = decodeBase64UrlBytes(token.slice(separator + 1));\n if (!signature) return null;\n\n // crypto.subtle.verify is constant-time.\n const valid = await crypto.subtle.verify(\n \"HMAC\",\n await hmacKey(secret),\n signature,\n encoder.encode(body),\n );\n if (!valid) return null;\n\n const decoded = decodeBase64Url(body);\n if (!decoded) return null;\n\n let claims: TokenClaims;\n try {\n claims = JSON.parse(decoded) as TokenClaims;\n } catch {\n return null;\n }\n\n if (typeof claims?.uid !== \"string\" || typeof claims.exp !== \"number\") return null;\n // Scoped so an unsubscribe token can never be replayed against another route.\n if (claims.purpose !== purpose) return null;\n if (claims.exp * 1000 <= now.getTime()) return null;\n\n return claims;\n}\n\nexport const DEFAULT_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;\n\nexport const expiresIn = (seconds: number, now: Date = new Date()): number =>\n Math.floor(now.getTime() / 1000) + seconds;\n"]}