@growth-labs/push 0.1.3

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 (87) hide show
  1. package/README.md +106 -0
  2. package/dist/client/push-optin.d.ts +24 -0
  3. package/dist/client/push-optin.d.ts.map +1 -0
  4. package/dist/client/push-optin.js +122 -0
  5. package/dist/client/push-optin.js.map +1 -0
  6. package/dist/client/push-sw.d.ts +3 -0
  7. package/dist/client/push-sw.d.ts.map +1 -0
  8. package/dist/client/push-sw.js +36 -0
  9. package/dist/client/push-sw.js.map +1 -0
  10. package/dist/db/schema.d.ts +178 -0
  11. package/dist/db/schema.d.ts.map +1 -0
  12. package/dist/db/schema.js +14 -0
  13. package/dist/db/schema.js.map +1 -0
  14. package/dist/index.d.ts +4 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +56 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/middleware/push.d.ts +2 -0
  19. package/dist/middleware/push.d.ts.map +1 -0
  20. package/dist/middleware/push.js +10 -0
  21. package/dist/middleware/push.js.map +1 -0
  22. package/dist/options.d.ts +108 -0
  23. package/dist/options.d.ts.map +1 -0
  24. package/dist/options.js +36 -0
  25. package/dist/options.js.map +1 -0
  26. package/dist/routes/subscribe.d.ts +3 -0
  27. package/dist/routes/subscribe.d.ts.map +1 -0
  28. package/dist/routes/subscribe.js +68 -0
  29. package/dist/routes/subscribe.js.map +1 -0
  30. package/dist/routes/unsubscribe.d.ts +3 -0
  31. package/dist/routes/unsubscribe.d.ts.map +1 -0
  32. package/dist/routes/unsubscribe.js +24 -0
  33. package/dist/routes/unsubscribe.js.map +1 -0
  34. package/dist/types.d.ts +90 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/utils/analytics.d.ts +47 -0
  39. package/dist/utils/analytics.d.ts.map +1 -0
  40. package/dist/utils/analytics.js +78 -0
  41. package/dist/utils/analytics.js.map +1 -0
  42. package/dist/utils/cleanup.d.ts +12 -0
  43. package/dist/utils/cleanup.d.ts.map +1 -0
  44. package/dist/utils/cleanup.js +18 -0
  45. package/dist/utils/cleanup.js.map +1 -0
  46. package/dist/utils/composer.d.ts +95 -0
  47. package/dist/utils/composer.d.ts.map +1 -0
  48. package/dist/utils/composer.js +98 -0
  49. package/dist/utils/composer.js.map +1 -0
  50. package/dist/utils/index.d.ts +10 -0
  51. package/dist/utils/index.d.ts.map +1 -0
  52. package/dist/utils/index.js +10 -0
  53. package/dist/utils/index.js.map +1 -0
  54. package/dist/utils/queue.d.ts +40 -0
  55. package/dist/utils/queue.d.ts.map +1 -0
  56. package/dist/utils/queue.js +69 -0
  57. package/dist/utils/queue.js.map +1 -0
  58. package/dist/utils/scheduled.d.ts +105 -0
  59. package/dist/utils/scheduled.d.ts.map +1 -0
  60. package/dist/utils/scheduled.js +75 -0
  61. package/dist/utils/scheduled.js.map +1 -0
  62. package/dist/utils/send.d.ts +23 -0
  63. package/dist/utils/send.d.ts.map +1 -0
  64. package/dist/utils/send.js +99 -0
  65. package/dist/utils/send.js.map +1 -0
  66. package/dist/utils/subscriptions.d.ts +21 -0
  67. package/dist/utils/subscriptions.d.ts.map +1 -0
  68. package/dist/utils/subscriptions.js +84 -0
  69. package/dist/utils/subscriptions.js.map +1 -0
  70. package/dist/utils/turnstile.d.ts +5 -0
  71. package/dist/utils/turnstile.d.ts.map +1 -0
  72. package/dist/utils/turnstile.js +22 -0
  73. package/dist/utils/turnstile.js.map +1 -0
  74. package/dist/utils/vapid.d.ts +9 -0
  75. package/dist/utils/vapid.d.ts.map +1 -0
  76. package/dist/utils/vapid.js +23 -0
  77. package/dist/utils/vapid.js.map +1 -0
  78. package/package.json +77 -0
  79. package/src/client/push-optin.ts +189 -0
  80. package/src/client/push-sw.d.ts +3 -0
  81. package/src/client/push-sw.d.ts.map +1 -0
  82. package/src/client/push-sw.js +25 -0
  83. package/src/client/push-sw.js.map +1 -0
  84. package/src/client/push-sw.ts +45 -0
  85. package/src/components/PushOptIn.astro +53 -0
  86. package/src/components/index.ts +1 -0
  87. package/src/virtual.d.ts +5 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Replace {{key}} placeholders in a string with values from the variables map.
3
+ * Unknown placeholders are left as-is.
4
+ */
5
+ function interpolate(template, variables) {
6
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
7
+ return key in variables ? variables[key] : match;
8
+ });
9
+ }
10
+ /**
11
+ * Compose a PushPayload from a template and variables.
12
+ *
13
+ * Templates define the structure with {{placeholder}} tokens.
14
+ * Variables fill in the dynamic parts at send time.
15
+ *
16
+ * ```typescript
17
+ * const template: PushTemplate = {
18
+ * name: 'new-article',
19
+ * title: 'New on {{siteName}}',
20
+ * body: '{{articleTitle}}',
21
+ * url: 'https://{{domain}}/articles/{{slug}}',
22
+ * icon: '/icons/notification-192.png',
23
+ * tag: 'article-{{slug}}',
24
+ * action: {
25
+ * title: 'Read article',
26
+ * url: 'https://{{domain}}/articles/{{slug}}',
27
+ * },
28
+ * }
29
+ *
30
+ * const payload = composePushPayload(template, {
31
+ * siteName: 'FEDweek',
32
+ * domain: 'fedweek.com',
33
+ * articleTitle: 'Federal Pay Freeze Announced',
34
+ * slug: 'pay-freeze-2026',
35
+ * })
36
+ * ```
37
+ */
38
+ export function composePushPayload(template, variables = {}, overrides = {}) {
39
+ const payload = {
40
+ title: interpolate(template.title, variables),
41
+ body: interpolate(template.body, variables),
42
+ url: interpolate(template.url, variables),
43
+ };
44
+ if (template.icon) {
45
+ payload.icon = interpolate(template.icon, variables);
46
+ }
47
+ if (template.image) {
48
+ payload.image = interpolate(template.image, variables);
49
+ }
50
+ if (template.tag) {
51
+ payload.tag = interpolate(template.tag, variables);
52
+ }
53
+ if (template.action) {
54
+ payload.action = {
55
+ title: interpolate(template.action.title, variables),
56
+ url: interpolate(template.action.url, variables),
57
+ };
58
+ }
59
+ return { ...payload, ...overrides };
60
+ }
61
+ /**
62
+ * Built-in templates for common notification patterns.
63
+ * Consumers can use these directly or define their own.
64
+ */
65
+ export const templates = {
66
+ /** New content published notification */
67
+ newContent: {
68
+ name: 'new-content',
69
+ title: 'New on {{siteName}}',
70
+ body: '{{contentTitle}}',
71
+ url: '{{contentUrl}}',
72
+ tag: 'content-{{contentId}}',
73
+ action: {
74
+ title: 'Read now',
75
+ url: '{{contentUrl}}',
76
+ },
77
+ },
78
+ /** Breaking news / high-urgency notification */
79
+ breaking: {
80
+ name: 'breaking',
81
+ title: 'Breaking: {{headline}}',
82
+ body: '{{summary}}',
83
+ url: '{{contentUrl}}',
84
+ tag: 'breaking-{{contentId}}',
85
+ action: {
86
+ title: 'Read more',
87
+ url: '{{contentUrl}}',
88
+ },
89
+ },
90
+ /** Generic notification with just title and body */
91
+ generic: {
92
+ name: 'generic',
93
+ title: '{{title}}',
94
+ body: '{{body}}',
95
+ url: '{{url}}',
96
+ },
97
+ };
98
+ //# sourceMappingURL=composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer.js","sourceRoot":"","sources":["../../src/utils/composer.ts"],"names":[],"mappings":"AA+BA;;;GAGG;AACH,SAAS,WAAW,CAAC,QAAgB,EAAE,SAA4B;IAClE,OAAO,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE;QAChE,OAAO,GAAG,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACjD,CAAC,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,kBAAkB,CACjC,QAAsB,EACtB,YAA+B,EAAE,EACjC,YAAkC,EAAE;IAEpC,MAAM,OAAO,GAAgB;QAC5B,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;QAC7C,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;QAC3C,GAAG,EAAE,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;KACzC,CAAA;IAED,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACrD,CAAC;IAED,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,GAAG,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IACvD,CAAC;IAED,IAAI,QAAQ,CAAC,GAAG,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,GAAG,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;IACnD,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrB,OAAO,CAAC,MAAM,GAAG;YAChB,KAAK,EAAE,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC;YACpD,GAAG,EAAE,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC;SAChD,CAAA;IACF,CAAC;IAED,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,EAAE,CAAA;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACxB,yCAAyC;IACzC,UAAU,EAAE;QACX,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,qBAAqB;QAC5B,IAAI,EAAE,kBAAkB;QACxB,GAAG,EAAE,gBAAgB;QACrB,GAAG,EAAE,uBAAuB;QAC5B,MAAM,EAAE;YACP,KAAK,EAAE,UAAU;YACjB,GAAG,EAAE,gBAAgB;SACrB;KACD;IAED,gDAAgD;IAChD,QAAQ,EAAE;QACT,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,wBAAwB;QAC/B,IAAI,EAAE,aAAa;QACnB,GAAG,EAAE,gBAAgB;QACrB,GAAG,EAAE,wBAAwB;QAC7B,MAAM,EAAE;YACP,KAAK,EAAE,WAAW;YAClB,GAAG,EAAE,gBAAgB;SACrB;KACD;IAED,oDAAoD;IACpD,OAAO,EAAE;QACR,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,WAAW;QAClB,IAAI,EAAE,UAAU;QAChB,GAAG,EAAE,SAAS;KACd;CAC+C,CAAA"}
@@ -0,0 +1,10 @@
1
+ export { emitPushAnalyticsEvent, trackPushCleanup, trackPushEvent, trackPushSend, trackPushSubscription, } from './analytics.js';
2
+ export { cleanupStaleSubscriptions } from './cleanup.js';
3
+ export { composePushPayload, type PushTemplate, type TemplateVariables, templates, } from './composer.js';
4
+ export { enqueuePushBatch, enqueuePushBatchAll, handlePushQueue, } from './queue.js';
5
+ export { handleScheduledPush, type ScheduledContentProvider, type ScheduledPushContext, type ScheduledPushResult, } from './scheduled.js';
6
+ export { type SendContext, sendPushBatch, sendPushNotification, } from './send.js';
7
+ export { countSubscriptions, deleteSubscriptionByEndpoint, deleteSubscriptionById, findSubscriptionByEndpoint, getAllSubscriptions, incrementFailureCount, markDelivered, saveSubscription, } from './subscriptions.js';
8
+ export { verifyTurnstile } from './turnstile.js';
9
+ export { generateVapidKeys } from './vapid.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,qBAAqB,GACrB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EACN,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,SAAS,GACT,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,GACf,MAAM,YAAY,CAAA;AACnB,OAAO,EACN,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACN,KAAK,WAAW,EAChB,aAAa,EACb,oBAAoB,GACpB,MAAM,WAAW,CAAA;AAClB,OAAO,EACN,kBAAkB,EAClB,4BAA4B,EAC5B,sBAAsB,EACtB,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,GAChB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,10 @@
1
+ export { emitPushAnalyticsEvent, trackPushCleanup, trackPushEvent, trackPushSend, trackPushSubscription, } from './analytics.js';
2
+ export { cleanupStaleSubscriptions } from './cleanup.js';
3
+ export { composePushPayload, templates, } from './composer.js';
4
+ export { enqueuePushBatch, enqueuePushBatchAll, handlePushQueue, } from './queue.js';
5
+ export { handleScheduledPush, } from './scheduled.js';
6
+ export { sendPushBatch, sendPushNotification, } from './send.js';
7
+ export { countSubscriptions, deleteSubscriptionByEndpoint, deleteSubscriptionById, findSubscriptionByEndpoint, getAllSubscriptions, incrementFailureCount, markDelivered, saveSubscription, } from './subscriptions.js';
8
+ export { verifyTurnstile } from './turnstile.js';
9
+ export { generateVapidKeys } from './vapid.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,qBAAqB,GACrB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EACN,kBAAkB,EAGlB,SAAS,GACT,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,GACf,MAAM,YAAY,CAAA;AACnB,OAAO,EACN,mBAAmB,GAInB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAEN,aAAa,EACb,oBAAoB,GACpB,MAAM,WAAW,CAAA;AAClB,OAAO,EACN,kBAAkB,EAClB,4BAA4B,EAC5B,sBAAsB,EACtB,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,EACb,gBAAgB,GAChB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,40 @@
1
+ import type { PushPayload, PushQueueMessage, PushSendOptions, PushSubscriptionRecord } from '../types.js';
2
+ interface EnqueueContext {
3
+ queue: Queue;
4
+ batchSize?: number;
5
+ }
6
+ /**
7
+ * Split a known set of subscriptions into batches and enqueue each batch.
8
+ */
9
+ export declare function enqueuePushBatch(subscriptions: PushSubscriptionRecord[], payload: PushPayload, context: EnqueueContext, options?: PushSendOptions): Promise<{
10
+ batches: number;
11
+ totalSubscriptions: number;
12
+ }>;
13
+ /**
14
+ * Page through all subscriptions in D1 and enqueue batches as it goes.
15
+ * Never holds more than one page of subscriptions in memory.
16
+ */
17
+ export declare function enqueuePushBatchAll(db: D1Database, payload: PushPayload, context: EnqueueContext, options?: PushSendOptions): Promise<{
18
+ batches: number;
19
+ totalSubscriptions: number;
20
+ }>;
21
+ interface QueueConsumerContext {
22
+ db: D1Database;
23
+ vapid: {
24
+ subject: string;
25
+ publicKey: string;
26
+ privateKey: string;
27
+ };
28
+ defaults: {
29
+ icon?: string;
30
+ ttl: number;
31
+ urgency: 'very-low' | 'low' | 'normal' | 'high';
32
+ };
33
+ }
34
+ /**
35
+ * Queue consumer handler. Process a batch of push notification sends.
36
+ * VAPID credentials come from env — they are NOT in the Queue message.
37
+ */
38
+ export declare function handlePushQueue(batch: MessageBatch<PushQueueMessage>, context: QueueConsumerContext): Promise<void>;
39
+ export {};
40
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/utils/queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,MAAM,aAAa,CAAA;AASpB,UAAU,cAAc;IACvB,KAAK,EAAE,KAAK,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACrC,aAAa,EAAE,sBAAsB,EAAE,EACvC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAAC,CAe1D;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACxC,EAAE,EAAE,UAAU,EACd,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB1D;AAED,UAAU,oBAAoB;IAC7B,EAAE,EAAE,UAAU,CAAA;IACd,KAAK,EAAE;QACN,OAAO,EAAE,MAAM,CAAA;QACf,SAAS,EAAE,MAAM,CAAA;QACjB,UAAU,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,QAAQ,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,GAAG,EAAE,MAAM,CAAA;QACX,OAAO,EAAE,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;KAC/C,CAAA;CACD;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACpC,KAAK,EAAE,YAAY,CAAC,gBAAgB,CAAC,EACrC,OAAO,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -0,0 +1,69 @@
1
+ import { sendPushBatch } from './send.js';
2
+ import { deleteSubscriptionById, getAllSubscriptions, incrementFailureCount, markDelivered, } from './subscriptions.js';
3
+ /**
4
+ * Split a known set of subscriptions into batches and enqueue each batch.
5
+ */
6
+ export async function enqueuePushBatch(subscriptions, payload, context, options = {}) {
7
+ const batchSize = context.batchSize ?? 100;
8
+ let batches = 0;
9
+ for (let i = 0; i < subscriptions.length; i += batchSize) {
10
+ const batch = subscriptions.slice(i, i + batchSize);
11
+ const message = { subscriptions: batch, payload, options };
12
+ await context.queue.send(message);
13
+ batches++;
14
+ }
15
+ return {
16
+ batches,
17
+ totalSubscriptions: subscriptions.length,
18
+ };
19
+ }
20
+ /**
21
+ * Page through all subscriptions in D1 and enqueue batches as it goes.
22
+ * Never holds more than one page of subscriptions in memory.
23
+ */
24
+ export async function enqueuePushBatchAll(db, payload, context, options = {}) {
25
+ const batchSize = context.batchSize ?? 100;
26
+ let offset = 0;
27
+ let batches = 0;
28
+ let total = 0;
29
+ while (true) {
30
+ const subs = await getAllSubscriptions(db, { limit: batchSize, offset });
31
+ if (subs.length === 0)
32
+ break;
33
+ const message = { subscriptions: subs, payload, options };
34
+ await context.queue.send(message);
35
+ batches++;
36
+ total += subs.length;
37
+ offset += batchSize;
38
+ }
39
+ return { batches, totalSubscriptions: total };
40
+ }
41
+ /**
42
+ * Queue consumer handler. Process a batch of push notification sends.
43
+ * VAPID credentials come from env — they are NOT in the Queue message.
44
+ */
45
+ export async function handlePushQueue(batch, context) {
46
+ const sendContext = {
47
+ vapidSubject: context.vapid.subject,
48
+ vapidPublicKey: context.vapid.publicKey,
49
+ vapidPrivateKey: context.vapid.privateKey,
50
+ defaults: context.defaults,
51
+ };
52
+ for (const message of batch.messages) {
53
+ const { subscriptions, payload, options } = message.body;
54
+ const result = await sendPushBatch(subscriptions, payload, sendContext, options);
55
+ for (const sendResult of result.results) {
56
+ if (sendResult.success) {
57
+ await markDelivered(context.db, sendResult.subscriptionId);
58
+ }
59
+ else if (sendResult.expired) {
60
+ await deleteSubscriptionById(context.db, sendResult.subscriptionId);
61
+ }
62
+ else {
63
+ await incrementFailureCount(context.db, sendResult.subscriptionId);
64
+ }
65
+ }
66
+ message.ack();
67
+ }
68
+ }
69
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/utils/queue.ts"],"names":[],"mappings":"AAMA,OAAO,EAAoB,aAAa,EAAE,MAAM,WAAW,CAAA;AAC3D,OAAO,EACN,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,aAAa,GACb,MAAM,oBAAoB,CAAA;AAO3B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,aAAuC,EACvC,OAAoB,EACpB,OAAuB,EACvB,UAA2B,EAAE;IAE7B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,GAAG,CAAA;IAC1C,IAAI,OAAO,GAAG,CAAC,CAAA;IAEf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAA;QACnD,MAAM,OAAO,GAAqB,EAAE,aAAa,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;QAC5E,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACjC,OAAO,EAAE,CAAA;IACV,CAAC;IAED,OAAO;QACN,OAAO;QACP,kBAAkB,EAAE,aAAa,CAAC,MAAM;KACxC,CAAA;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,EAAc,EACd,OAAoB,EACpB,OAAuB,EACvB,UAA2B,EAAE;IAE7B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,GAAG,CAAA;IAC1C,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,IAAI,KAAK,GAAG,CAAC,CAAA;IAEb,OAAO,IAAI,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAA;QACxE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,MAAK;QAE5B,MAAM,OAAO,GAAqB,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;QAC3E,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAEjC,OAAO,EAAE,CAAA;QACT,KAAK,IAAI,IAAI,CAAC,MAAM,CAAA;QACpB,MAAM,IAAI,SAAS,CAAA;IACpB,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAA;AAC9C,CAAC;AAgBD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,KAAqC,EACrC,OAA6B;IAE7B,MAAM,WAAW,GAAgB;QAChC,YAAY,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;QACnC,cAAc,EAAE,OAAO,CAAC,KAAK,CAAC,SAAS;QACvC,eAAe,EAAE,OAAO,CAAC,KAAK,CAAC,UAAU;QACzC,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC1B,CAAA;IAED,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAA;QAExD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;QAEhF,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACxB,MAAM,aAAa,CAAC,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,cAAc,CAAC,CAAA;YAC3D,CAAC;iBAAM,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBAC/B,MAAM,sBAAsB,CAAC,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,cAAc,CAAC,CAAA;YACpE,CAAC;iBAAM,CAAC;gBACP,MAAM,qBAAqB,CAAC,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,cAAc,CAAC,CAAA;YACnE,CAAC;QACF,CAAC;QAED,OAAO,CAAC,GAAG,EAAE,CAAA;IACd,CAAC;AACF,CAAC"}
@@ -0,0 +1,105 @@
1
+ import type { PushPayload, PushSendOptions } from '../types.js';
2
+ /**
3
+ * Context required by the scheduled push handler.
4
+ * The consumer provides this from their cron trigger's env bindings.
5
+ */
6
+ export interface ScheduledPushContext {
7
+ db: D1Database;
8
+ queue: Queue;
9
+ batchSize?: number;
10
+ cleanup: {
11
+ staleAfterDays: number;
12
+ };
13
+ }
14
+ /**
15
+ * Result of a scheduled push run.
16
+ */
17
+ export interface ScheduledPushResult {
18
+ notification: {
19
+ batches: number;
20
+ totalSubscriptions: number;
21
+ } | null;
22
+ cleanup: {
23
+ removed: number;
24
+ };
25
+ }
26
+ /**
27
+ * A content provider function that returns a push payload if there is
28
+ * something to send, or null if there is nothing to notify about.
29
+ *
30
+ * The consumer implements this to check for new content, scheduled
31
+ * campaigns, or other triggers that should produce a notification.
32
+ *
33
+ * ```typescript
34
+ * const provider: ScheduledContentProvider = async (context) => {
35
+ * const latest = await context.db
36
+ * .prepare('SELECT * FROM articles WHERE published_at > ? AND push_sent = 0 LIMIT 1')
37
+ * .bind(Date.now() / 1000 - 3600)
38
+ * .first()
39
+ *
40
+ * if (!latest) return null
41
+ *
42
+ * await context.db
43
+ * .prepare('UPDATE articles SET push_sent = 1 WHERE id = ?')
44
+ * .bind(latest.id)
45
+ * .run()
46
+ *
47
+ * return {
48
+ * payload: {
49
+ * title: 'New article',
50
+ * body: latest.title as string,
51
+ * url: `https://example.com/articles/${latest.slug}`,
52
+ * },
53
+ * }
54
+ * }
55
+ * ```
56
+ */
57
+ export type ScheduledContentProvider = (context: {
58
+ db: D1Database;
59
+ }) => Promise<{
60
+ payload: PushPayload;
61
+ options?: PushSendOptions;
62
+ } | null>;
63
+ /**
64
+ * Cron-triggered push handler. Wires together content checking, notification
65
+ * enqueuing, and subscription cleanup into a single scheduled invocation.
66
+ *
67
+ * The consumer wires this into their Cloudflare Worker's scheduled handler:
68
+ *
69
+ * ```typescript
70
+ * import { handleScheduledPush } from '@growth-labs/push/utils'
71
+ *
72
+ * export default {
73
+ * async scheduled(event, env) {
74
+ * await handleScheduledPush(
75
+ * {
76
+ * db: env.SITE_DB,
77
+ * queue: env.PUSH_QUEUE,
78
+ * cleanup: { staleAfterDays: 90 },
79
+ * },
80
+ * async ({ db }) => {
81
+ * // Check for unsent content
82
+ * const article = await db
83
+ * .prepare('SELECT * FROM articles WHERE push_sent = 0 LIMIT 1')
84
+ * .first()
85
+ * if (!article) return null
86
+ * return {
87
+ * payload: {
88
+ * title: 'New article',
89
+ * body: article.title as string,
90
+ * url: `https://example.com/${article.slug}`,
91
+ * },
92
+ * }
93
+ * },
94
+ * )
95
+ * },
96
+ * }
97
+ * ```
98
+ *
99
+ * On each invocation:
100
+ * 1. Calls the content provider to check if there's something to send
101
+ * 2. If yes, enqueues push batches for all subscribers via the Queue
102
+ * 3. Always runs subscription cleanup to remove stale/dead subscriptions
103
+ */
104
+ export declare function handleScheduledPush(context: ScheduledPushContext, contentProvider: ScheduledContentProvider): Promise<ScheduledPushResult>;
105
+ //# sourceMappingURL=scheduled.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled.d.ts","sourceRoot":"","sources":["../../src/utils/scheduled.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAK/D;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,UAAU,CAAA;IACd,KAAK,EAAE,KAAK,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE;QACR,cAAc,EAAE,MAAM,CAAA;KACtB,CAAA;CACD;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,YAAY,EAAE;QACb,OAAO,EAAE,MAAM,CAAA;QACf,kBAAkB,EAAE,MAAM,CAAA;KAC1B,GAAG,IAAI,CAAA;IACR,OAAO,EAAE;QACR,OAAO,EAAE,MAAM,CAAA;KACf,CAAA;CACD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE;IAChD,EAAE,EAAE,UAAU,CAAA;CACd,KAAK,OAAO,CAAC;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,eAAe,CAAA;CAAE,GAAG,IAAI,CAAC,CAAA;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,oBAAoB,EAC7B,eAAe,EAAE,wBAAwB,GACvC,OAAO,CAAC,mBAAmB,CAAC,CAwC9B"}
@@ -0,0 +1,75 @@
1
+ import { trackPushSend } from './analytics.js';
2
+ import { cleanupStaleSubscriptions } from './cleanup.js';
3
+ import { enqueuePushBatchAll } from './queue.js';
4
+ /**
5
+ * Cron-triggered push handler. Wires together content checking, notification
6
+ * enqueuing, and subscription cleanup into a single scheduled invocation.
7
+ *
8
+ * The consumer wires this into their Cloudflare Worker's scheduled handler:
9
+ *
10
+ * ```typescript
11
+ * import { handleScheduledPush } from '@growth-labs/push/utils'
12
+ *
13
+ * export default {
14
+ * async scheduled(event, env) {
15
+ * await handleScheduledPush(
16
+ * {
17
+ * db: env.SITE_DB,
18
+ * queue: env.PUSH_QUEUE,
19
+ * cleanup: { staleAfterDays: 90 },
20
+ * },
21
+ * async ({ db }) => {
22
+ * // Check for unsent content
23
+ * const article = await db
24
+ * .prepare('SELECT * FROM articles WHERE push_sent = 0 LIMIT 1')
25
+ * .first()
26
+ * if (!article) return null
27
+ * return {
28
+ * payload: {
29
+ * title: 'New article',
30
+ * body: article.title as string,
31
+ * url: `https://example.com/${article.slug}`,
32
+ * },
33
+ * }
34
+ * },
35
+ * )
36
+ * },
37
+ * }
38
+ * ```
39
+ *
40
+ * On each invocation:
41
+ * 1. Calls the content provider to check if there's something to send
42
+ * 2. If yes, enqueues push batches for all subscribers via the Queue
43
+ * 3. Always runs subscription cleanup to remove stale/dead subscriptions
44
+ */
45
+ export async function handleScheduledPush(context, contentProvider) {
46
+ let notification = null;
47
+ // Step 1: Check if there's content to push
48
+ const content = await contentProvider({ db: context.db });
49
+ // Step 2: Enqueue notifications if content is available
50
+ if (content) {
51
+ const enqueueResult = await enqueuePushBatchAll(context.db, content.payload, { queue: context.queue, batchSize: context.batchSize }, content.options ?? {});
52
+ notification = {
53
+ batches: enqueueResult.batches,
54
+ totalSubscriptions: enqueueResult.totalSubscriptions,
55
+ };
56
+ // Emit server-side analytics event if @growth-labs/analytics is available
57
+ await trackPushSend(content.payload, {
58
+ sent: enqueueResult.totalSubscriptions,
59
+ failed: 0,
60
+ expired: 0,
61
+ results: [],
62
+ });
63
+ }
64
+ // Step 3: Always clean up stale subscriptions
65
+ const cleanupResult = await cleanupStaleSubscriptions(context.db, {
66
+ staleAfterDays: context.cleanup.staleAfterDays,
67
+ });
68
+ return {
69
+ notification,
70
+ cleanup: {
71
+ removed: cleanupResult.removed,
72
+ },
73
+ };
74
+ }
75
+ //# sourceMappingURL=scheduled.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled.js","sourceRoot":"","sources":["../../src/utils/scheduled.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AA+DhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACxC,OAA6B,EAC7B,eAAyC;IAEzC,IAAI,YAAY,GAAwC,IAAI,CAAA;IAE5D,2CAA2C;IAC3C,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAA;IAEzD,wDAAwD;IACxD,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,aAAa,GAAG,MAAM,mBAAmB,CAC9C,OAAO,CAAC,EAAE,EACV,OAAO,CAAC,OAAO,EACf,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,EACtD,OAAO,CAAC,OAAO,IAAI,EAAE,CACrB,CAAA;QAED,YAAY,GAAG;YACd,OAAO,EAAE,aAAa,CAAC,OAAO;YAC9B,kBAAkB,EAAE,aAAa,CAAC,kBAAkB;SACpD,CAAA;QAED,0EAA0E;QAC1E,MAAM,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE;YACpC,IAAI,EAAE,aAAa,CAAC,kBAAkB;YACtC,MAAM,EAAE,CAAC;YACT,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,EAAE;SACX,CAAC,CAAA;IACH,CAAC;IAED,8CAA8C;IAC9C,MAAM,aAAa,GAAG,MAAM,yBAAyB,CAAC,OAAO,CAAC,EAAE,EAAE;QACjE,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,cAAc;KAC9C,CAAC,CAAA;IAEF,OAAO;QACN,YAAY;QACZ,OAAO,EAAE;YACR,OAAO,EAAE,aAAa,CAAC,OAAO;SAC9B;KACD,CAAA;AACF,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { PushBatchResult, PushPayload, PushSendOptions, PushSendResult, PushSubscriptionRecord } from '../types.js';
2
+ export interface SendContext {
3
+ vapidSubject: string;
4
+ vapidPublicKey: string;
5
+ vapidPrivateKey: string;
6
+ defaults: {
7
+ icon?: string;
8
+ ttl: number;
9
+ urgency: 'very-low' | 'low' | 'normal' | 'high';
10
+ };
11
+ }
12
+ /**
13
+ * Send a push notification to a single subscription.
14
+ * Uses Web Crypto API for VAPID signing and RFC 8291 payload encryption.
15
+ */
16
+ export declare function sendPushNotification(subscription: PushSubscriptionRecord, payload: PushPayload, context: SendContext, options?: PushSendOptions): Promise<PushSendResult>;
17
+ /**
18
+ * Send a push notification to multiple subscriptions sequentially.
19
+ * For small batches (< 100 subscriptions) called directly.
20
+ * For large subscriber counts, use enqueuePushBatchAll() instead.
21
+ */
22
+ export declare function sendPushBatch(subscriptions: PushSubscriptionRecord[], payload: PushPayload, context: SendContext, options?: PushSendOptions): Promise<PushBatchResult>;
23
+ //# sourceMappingURL=send.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,eAAe,EACf,WAAW,EACX,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,WAAW;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,GAAG,EAAE,MAAM,CAAA;QACX,OAAO,EAAE,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAA;KAC/C,CAAA;CACD;AAOD;;;GAGG;AACH,wBAAsB,oBAAoB,CACzC,YAAY,EAAE,sBAAsB,EACpC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC,cAAc,CAAC,CAyEzB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAClC,aAAa,EAAE,sBAAsB,EAAE,EACvC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC,eAAe,CAAC,CAoB1B"}
@@ -0,0 +1,99 @@
1
+ import { buildPushPayload } from '@block65/webcrypto-web-push';
2
+ /** Map our urgency values to what the library supports */
3
+ function mapUrgency(urgency) {
4
+ return urgency === 'very-low' ? 'low' : urgency;
5
+ }
6
+ /**
7
+ * Send a push notification to a single subscription.
8
+ * Uses Web Crypto API for VAPID signing and RFC 8291 payload encryption.
9
+ */
10
+ export async function sendPushNotification(subscription, payload, context, options = {}) {
11
+ const ttl = options.ttl ?? context.defaults.ttl;
12
+ const urgency = options.urgency ?? context.defaults.urgency;
13
+ const resolvedPayload = {
14
+ ...payload,
15
+ icon: payload.icon ?? context.defaults.icon,
16
+ };
17
+ try {
18
+ // buildPushPayload(message, subscription, vapid) per @block65/webcrypto-web-push API
19
+ const pushRequest = await buildPushPayload({
20
+ data: JSON.parse(JSON.stringify(resolvedPayload)),
21
+ options: { ttl, urgency: mapUrgency(urgency) },
22
+ }, {
23
+ endpoint: subscription.endpoint,
24
+ expirationTime: null,
25
+ keys: {
26
+ p256dh: subscription.keyP256dh,
27
+ auth: subscription.keyAuth,
28
+ },
29
+ }, {
30
+ subject: context.vapidSubject,
31
+ publicKey: context.vapidPublicKey,
32
+ privateKey: context.vapidPrivateKey,
33
+ });
34
+ const response = await fetch(subscription.endpoint, {
35
+ method: pushRequest.method,
36
+ headers: pushRequest.headers,
37
+ body: pushRequest.body,
38
+ });
39
+ if (response.status === 201) {
40
+ return { subscriptionId: subscription.id, success: true, statusCode: 201 };
41
+ }
42
+ if (response.status === 404 || response.status === 410) {
43
+ return {
44
+ subscriptionId: subscription.id,
45
+ success: false,
46
+ statusCode: response.status,
47
+ expired: true,
48
+ error: 'Subscription expired or unsubscribed',
49
+ };
50
+ }
51
+ if (response.status === 429) {
52
+ return {
53
+ subscriptionId: subscription.id,
54
+ success: false,
55
+ statusCode: 429,
56
+ error: 'Rate limited by push service',
57
+ };
58
+ }
59
+ return {
60
+ subscriptionId: subscription.id,
61
+ success: false,
62
+ statusCode: response.status,
63
+ error: `Push service returned ${response.status}`,
64
+ };
65
+ }
66
+ catch (err) {
67
+ return {
68
+ subscriptionId: subscription.id,
69
+ success: false,
70
+ error: err instanceof Error ? err.message : 'Unknown error',
71
+ };
72
+ }
73
+ }
74
+ /**
75
+ * Send a push notification to multiple subscriptions sequentially.
76
+ * For small batches (< 100 subscriptions) called directly.
77
+ * For large subscriber counts, use enqueuePushBatchAll() instead.
78
+ */
79
+ export async function sendPushBatch(subscriptions, payload, context, options = {}) {
80
+ const results = [];
81
+ let sent = 0;
82
+ let failed = 0;
83
+ let expired = 0;
84
+ for (const sub of subscriptions) {
85
+ const result = await sendPushNotification(sub, payload, context, options);
86
+ results.push(result);
87
+ if (result.success) {
88
+ sent++;
89
+ }
90
+ else if (result.expired) {
91
+ expired++;
92
+ }
93
+ else {
94
+ failed++;
95
+ }
96
+ }
97
+ return { sent, failed, expired, results };
98
+ }
99
+ //# sourceMappingURL=send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/utils/send.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { PushSubscriptionRecord } from '../types.js';
2
+ /** Insert or update a subscription */
3
+ export declare function saveSubscription(db: D1Database, sub: PushSubscriptionRecord): Promise<void>;
4
+ /** Find a subscription by its endpoint URL */
5
+ export declare function findSubscriptionByEndpoint(db: D1Database, endpoint: string): Promise<PushSubscriptionRecord | null>;
6
+ /** Delete a subscription by endpoint. Returns true if a row was deleted. */
7
+ export declare function deleteSubscriptionByEndpoint(db: D1Database, endpoint: string): Promise<boolean>;
8
+ /** Delete a subscription by ID */
9
+ export declare function deleteSubscriptionById(db: D1Database, id: string): Promise<void>;
10
+ /** Get subscriptions with offset-based pagination. Used by enqueuePushBatchAll. */
11
+ export declare function getAllSubscriptions(db: D1Database, options?: {
12
+ limit?: number;
13
+ offset?: number;
14
+ }): Promise<PushSubscriptionRecord[]>;
15
+ /** Count total subscriptions */
16
+ export declare function countSubscriptions(db: D1Database): Promise<number>;
17
+ /** Mark a subscription as successfully delivered */
18
+ export declare function markDelivered(db: D1Database, id: string): Promise<void>;
19
+ /** Increment failure count for a subscription */
20
+ export declare function incrementFailureCount(db: D1Database, id: string): Promise<void>;
21
+ //# sourceMappingURL=subscriptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../src/utils/subscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEzD,sCAAsC;AACtC,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBjG;AAED,8CAA8C;AAC9C,wBAAsB,0BAA0B,CAC/C,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAOxC;AAED,4EAA4E;AAC5E,wBAAsB,4BAA4B,CACjD,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED,kCAAkC;AAClC,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtF;AAED,mFAAmF;AACnF,wBAAsB,mBAAmB,CACxC,EAAE,EAAE,UAAU,EACd,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/C,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAUnC;AAED,gCAAgC;AAChC,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAMxE;AAED,oDAAoD;AACpD,wBAAsB,aAAa,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS7E;AAED,iDAAiD;AACjD,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASrF"}