easy-ping 0.1.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +374 -0
  3. package/dist/adapter-f0OR2DhY.d.cts +200 -0
  4. package/dist/adapter-f0OR2DhY.d.ts +200 -0
  5. package/dist/adapters/drizzle.cjs +440 -0
  6. package/dist/adapters/drizzle.cjs.map +1 -0
  7. package/dist/adapters/drizzle.d.cts +991 -0
  8. package/dist/adapters/drizzle.d.ts +991 -0
  9. package/dist/adapters/drizzle.js +409 -0
  10. package/dist/adapters/drizzle.js.map +1 -0
  11. package/dist/adapters/mongodb.cjs +458 -0
  12. package/dist/adapters/mongodb.cjs.map +1 -0
  13. package/dist/adapters/mongodb.d.cts +32 -0
  14. package/dist/adapters/mongodb.d.ts +32 -0
  15. package/dist/adapters/mongodb.js +376 -0
  16. package/dist/adapters/mongodb.js.map +1 -0
  17. package/dist/browser.cjs +96 -0
  18. package/dist/browser.cjs.map +1 -0
  19. package/dist/browser.d.cts +45 -0
  20. package/dist/browser.d.ts +45 -0
  21. package/dist/browser.js +89 -0
  22. package/dist/browser.js.map +1 -0
  23. package/dist/chunk-BV3U542P.js +11 -0
  24. package/dist/chunk-BV3U542P.js.map +1 -0
  25. package/dist/chunk-NYDVNMKG.js +62 -0
  26. package/dist/chunk-NYDVNMKG.js.map +1 -0
  27. package/dist/chunk-OZSOIX23.js +120 -0
  28. package/dist/chunk-OZSOIX23.js.map +1 -0
  29. package/dist/chunk-PTGHMCCG.js +23 -0
  30. package/dist/chunk-PTGHMCCG.js.map +1 -0
  31. package/dist/chunk-W3BMEPPQ.js +50 -0
  32. package/dist/chunk-W3BMEPPQ.js.map +1 -0
  33. package/dist/client.cjs +160 -0
  34. package/dist/client.cjs.map +1 -0
  35. package/dist/client.d.cts +42 -0
  36. package/dist/client.d.ts +42 -0
  37. package/dist/client.js +158 -0
  38. package/dist/client.js.map +1 -0
  39. package/dist/index.cjs +920 -0
  40. package/dist/index.cjs.map +1 -0
  41. package/dist/index.d.cts +203 -0
  42. package/dist/index.d.ts +203 -0
  43. package/dist/index.js +723 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/node.cjs +49 -0
  46. package/dist/node.cjs.map +1 -0
  47. package/dist/node.d.cts +8 -0
  48. package/dist/node.d.ts +8 -0
  49. package/dist/node.js +46 -0
  50. package/dist/node.js.map +1 -0
  51. package/dist/plugin-Bqj9xAOS.d.cts +217 -0
  52. package/dist/plugin-bTJKmnlq.d.ts +217 -0
  53. package/dist/plugins/digests.cjs +206 -0
  54. package/dist/plugins/digests.cjs.map +1 -0
  55. package/dist/plugins/digests.d.cts +68 -0
  56. package/dist/plugins/digests.d.ts +68 -0
  57. package/dist/plugins/digests.js +201 -0
  58. package/dist/plugins/digests.js.map +1 -0
  59. package/dist/plugins/preferences-client.cjs +23 -0
  60. package/dist/plugins/preferences-client.cjs.map +1 -0
  61. package/dist/plugins/preferences-client.d.cts +29 -0
  62. package/dist/plugins/preferences-client.d.ts +29 -0
  63. package/dist/plugins/preferences-client.js +18 -0
  64. package/dist/plugins/preferences-client.js.map +1 -0
  65. package/dist/plugins/preferences-react.cjs +125 -0
  66. package/dist/plugins/preferences-react.cjs.map +1 -0
  67. package/dist/plugins/preferences-react.d.cts +40 -0
  68. package/dist/plugins/preferences-react.d.ts +40 -0
  69. package/dist/plugins/preferences-react.js +101 -0
  70. package/dist/plugins/preferences-react.js.map +1 -0
  71. package/dist/plugins/preferences.cjs +147 -0
  72. package/dist/plugins/preferences.cjs.map +1 -0
  73. package/dist/plugins/preferences.d.cts +22 -0
  74. package/dist/plugins/preferences.d.ts +22 -0
  75. package/dist/plugins/preferences.js +119 -0
  76. package/dist/plugins/preferences.js.map +1 -0
  77. package/dist/plugins/push.cjs +146 -0
  78. package/dist/plugins/push.cjs.map +1 -0
  79. package/dist/plugins/push.d.cts +60 -0
  80. package/dist/plugins/push.d.ts +60 -0
  81. package/dist/plugins/push.js +144 -0
  82. package/dist/plugins/push.js.map +1 -0
  83. package/dist/provider-CA45WVqt.d.cts +23 -0
  84. package/dist/provider-CA45WVqt.d.ts +23 -0
  85. package/dist/providers/resend.cjs +69 -0
  86. package/dist/providers/resend.cjs.map +1 -0
  87. package/dist/providers/resend.d.cts +20 -0
  88. package/dist/providers/resend.d.ts +20 -0
  89. package/dist/providers/resend.js +66 -0
  90. package/dist/providers/resend.js.map +1 -0
  91. package/dist/providers/web-push.cjs +211 -0
  92. package/dist/providers/web-push.cjs.map +1 -0
  93. package/dist/providers/web-push.d.cts +33 -0
  94. package/dist/providers/web-push.d.ts +33 -0
  95. package/dist/providers/web-push.js +193 -0
  96. package/dist/providers/web-push.js.map +1 -0
  97. package/dist/react.cjs +218 -0
  98. package/dist/react.cjs.map +1 -0
  99. package/dist/react.d.cts +57 -0
  100. package/dist/react.d.ts +57 -0
  101. package/dist/react.js +193 -0
  102. package/dist/react.js.map +1 -0
  103. package/dist/schema.cjs +263 -0
  104. package/dist/schema.cjs.map +1 -0
  105. package/dist/schema.d.cts +180 -0
  106. package/dist/schema.d.ts +180 -0
  107. package/dist/schema.js +200 -0
  108. package/dist/schema.js.map +1 -0
  109. package/dist/testing.cjs +342 -0
  110. package/dist/testing.cjs.map +1 -0
  111. package/dist/testing.d.cts +38 -0
  112. package/dist/testing.d.ts +38 -0
  113. package/dist/testing.js +340 -0
  114. package/dist/testing.js.map +1 -0
  115. package/package.json +168 -0
@@ -0,0 +1,38 @@
1
+ import { D as DatabaseAdapter } from './adapter-f0OR2DhY.cjs';
2
+
3
+ type ConformanceContext = {
4
+ adapter: DatabaseAdapter;
5
+ /**
6
+ * Forces a delivery's attempt count. Not reachable through the public API —
7
+ * the adapter would have marked the row failed on the way there — so each
8
+ * backend writes it directly.
9
+ */
10
+ setAttempts: (deliveryId: string, attempts: number) => Promise<void>;
11
+ /**
12
+ * Empties the tables. Cases must run against a clean table: claims are
13
+ * ordered by not_before ASC and bounded by limit, so leftover rows from an
14
+ * earlier case can push the row under test out of the claim window.
15
+ */
16
+ reset: () => Promise<void>;
17
+ /**
18
+ * Holds a row lock on a separate connection for the duration of `fn`.
19
+ *
20
+ * Required to test SKIP LOCKED deterministically. Two Promise.all'd claims
21
+ * do not reliably overlap — they complete in single-digit milliseconds and
22
+ * simply queue — so a race-based test passes even with no locking at all.
23
+ *
24
+ * Only meaningful where a claim can block on someone else's lock. A document
25
+ * store whose claim is a single atomic update has nothing to skip, so cases
26
+ * tagged `rowLock` do not apply to it.
27
+ */
28
+ lockRow?: (deliveryId: string, fn: () => Promise<void>) => Promise<void>;
29
+ };
30
+ type ConformanceCase = {
31
+ name: string;
32
+ /** Capability the case needs; a backend without it must filter the case out. */
33
+ requires?: "rowLock";
34
+ run: (ctx: ConformanceContext) => Promise<void>;
35
+ };
36
+ declare const adapterConformanceCases: readonly ConformanceCase[];
37
+
38
+ export { type ConformanceCase, type ConformanceContext, adapterConformanceCases };
@@ -0,0 +1,38 @@
1
+ import { D as DatabaseAdapter } from './adapter-f0OR2DhY.js';
2
+
3
+ type ConformanceContext = {
4
+ adapter: DatabaseAdapter;
5
+ /**
6
+ * Forces a delivery's attempt count. Not reachable through the public API —
7
+ * the adapter would have marked the row failed on the way there — so each
8
+ * backend writes it directly.
9
+ */
10
+ setAttempts: (deliveryId: string, attempts: number) => Promise<void>;
11
+ /**
12
+ * Empties the tables. Cases must run against a clean table: claims are
13
+ * ordered by not_before ASC and bounded by limit, so leftover rows from an
14
+ * earlier case can push the row under test out of the claim window.
15
+ */
16
+ reset: () => Promise<void>;
17
+ /**
18
+ * Holds a row lock on a separate connection for the duration of `fn`.
19
+ *
20
+ * Required to test SKIP LOCKED deterministically. Two Promise.all'd claims
21
+ * do not reliably overlap — they complete in single-digit milliseconds and
22
+ * simply queue — so a race-based test passes even with no locking at all.
23
+ *
24
+ * Only meaningful where a claim can block on someone else's lock. A document
25
+ * store whose claim is a single atomic update has nothing to skip, so cases
26
+ * tagged `rowLock` do not apply to it.
27
+ */
28
+ lockRow?: (deliveryId: string, fn: () => Promise<void>) => Promise<void>;
29
+ };
30
+ type ConformanceCase = {
31
+ name: string;
32
+ /** Capability the case needs; a backend without it must filter the case out. */
33
+ requires?: "rowLock";
34
+ run: (ctx: ConformanceContext) => Promise<void>;
35
+ };
36
+ declare const adapterConformanceCases: readonly ConformanceCase[];
37
+
38
+ export { type ConformanceCase, type ConformanceContext, adapterConformanceCases };
@@ -0,0 +1,340 @@
1
+ // src/testing/conformance.ts
2
+ var ConformanceError = class extends Error {
3
+ };
4
+ function assert(condition, message) {
5
+ if (!condition) throw new ConformanceError(message);
6
+ }
7
+ var equal = (actual, expected, message) => assert(actual === expected, `${message} (got ${String(actual)}, expected ${String(expected)})`);
8
+ var counter = 0;
9
+ function uid(label) {
10
+ counter += 1;
11
+ return `${label}_${Date.now().toString(36)}_${counter}`;
12
+ }
13
+ async function seed(ctx, options = {}) {
14
+ const count = options.count ?? 1;
15
+ const userId = options.userId ?? uid("user");
16
+ const rows = Array.from({ length: count }, () => {
17
+ const id = uid("notif");
18
+ return {
19
+ id,
20
+ userId,
21
+ type: "commentReply",
22
+ payload: { authorName: "Dana" },
23
+ ...options.dedupeKey ? { dedupeKey: options.dedupeKey } : {},
24
+ deliveries: [
25
+ {
26
+ id: uid("delivery"),
27
+ channel: options.channel ?? "email",
28
+ maxAttempts: options.maxAttempts ?? 5,
29
+ notBefore: options.notBefore ?? new Date(Date.now() - 1e3)
30
+ }
31
+ ]
32
+ };
33
+ });
34
+ const result = await ctx.adapter.createNotifications(rows);
35
+ return { userId, rows, result };
36
+ }
37
+ var CLAIM = { limit: 10, leaseMs: 6e4 };
38
+ var adapterConformanceCases = [
39
+ {
40
+ /**
41
+ * The case that actually distinguishes a correct adapter.
42
+ *
43
+ * A row locked by another transaction must be *skipped*, not waited on.
44
+ * Without SKIP LOCKED the claim blocks until the holder commits, which on
45
+ * a busy table serialises every worker behind the slowest one.
46
+ */
47
+ name: "a row locked by another transaction is skipped, not waited on",
48
+ requires: "rowLock",
49
+ run: async (ctx) => {
50
+ const { lockRow } = ctx;
51
+ assert(lockRow, "case requires lockRow");
52
+ const { rows } = await seed(ctx, { count: 2 });
53
+ const locked = rows[0]?.deliveries[0]?.id;
54
+ const free = rows[1]?.deliveries[0]?.id;
55
+ assert(locked && free, "seed produced too few deliveries");
56
+ await lockRow(locked, async () => {
57
+ const claimed = await Promise.race([
58
+ ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("token") }),
59
+ new Promise(
60
+ (_, reject) => setTimeout(
61
+ () => reject(
62
+ new ConformanceError(
63
+ "claim blocked on a row locked by another transaction \u2014 SKIP LOCKED is missing"
64
+ )
65
+ ),
66
+ 2e3
67
+ )
68
+ )
69
+ ]);
70
+ const ids = new Set(claimed.map((row) => row.id));
71
+ assert(!ids.has(locked), "claimed a row locked by another transaction");
72
+ assert(ids.has(free), "skipped an unlocked row alongside the locked one");
73
+ });
74
+ }
75
+ },
76
+ {
77
+ name: "concurrent claims return disjoint sets",
78
+ run: async (ctx) => {
79
+ await seed(ctx, { count: 40 });
80
+ const claimed = await Promise.all(
81
+ Array.from(
82
+ { length: 8 },
83
+ () => ctx.adapter.claimPendingDeliveries({ limit: 5, leaseMs: 6e4, claimToken: uid("t") })
84
+ )
85
+ );
86
+ const all = claimed.flatMap((batch) => batch.map((row) => row.id));
87
+ equal(new Set(all).size, all.length, "the same delivery was claimed by two callers");
88
+ assert(all.length <= 40, "claimed more rows than exist");
89
+ }
90
+ },
91
+ {
92
+ name: "a claimed row is invisible before its lease expires",
93
+ run: async (ctx) => {
94
+ const { rows } = await seed(ctx);
95
+ const target = rows[0]?.deliveries[0]?.id;
96
+ const first = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("t") });
97
+ assert(
98
+ first.some((row) => row.id === target),
99
+ "seeded delivery was not claimed"
100
+ );
101
+ const second = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("t") });
102
+ assert(!second.some((row) => row.id === target), "leased row was claimed twice");
103
+ }
104
+ },
105
+ {
106
+ name: "a claimed row is re-claimable after its lease expires",
107
+ run: async (ctx) => {
108
+ const { rows } = await seed(ctx);
109
+ const target = rows[0]?.deliveries[0]?.id;
110
+ await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("t") });
111
+ const later = new Date(Date.now() + CLAIM.leaseMs + 5e3);
112
+ const reclaimed = await ctx.adapter.claimPendingDeliveries({
113
+ ...CLAIM,
114
+ claimToken: uid("t"),
115
+ now: later
116
+ });
117
+ assert(
118
+ reclaimed.some((row) => row.id === target),
119
+ "expired lease was not reclaimed"
120
+ );
121
+ }
122
+ },
123
+ {
124
+ name: "not_before in the future is never claimed",
125
+ run: async (ctx) => {
126
+ const { rows } = await seed(ctx, { notBefore: new Date(Date.now() + 36e5) });
127
+ const target = rows[0]?.deliveries[0]?.id;
128
+ const claimed = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("t") });
129
+ assert(!claimed.some((row) => row.id === target), "claimed a delivery before its not_before");
130
+ }
131
+ },
132
+ {
133
+ name: "attempts at or beyond max_attempts is never claimed",
134
+ run: async (ctx) => {
135
+ const { rows } = await seed(ctx, { maxAttempts: 3 });
136
+ const target = rows[0]?.deliveries[0]?.id;
137
+ assert(target, "seed produced no delivery");
138
+ await ctx.setAttempts(target, 3);
139
+ const claimed = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("t") });
140
+ assert(!claimed.some((row) => row.id === target), "claimed an exhausted delivery");
141
+ }
142
+ },
143
+ {
144
+ name: "claims are ordered by not_before ascending",
145
+ run: async (ctx) => {
146
+ const userId = uid("user");
147
+ const older = uid("delivery");
148
+ const newer = uid("delivery");
149
+ await ctx.adapter.createNotifications([
150
+ {
151
+ id: uid("notif"),
152
+ userId,
153
+ type: "t",
154
+ payload: {},
155
+ deliveries: [
156
+ {
157
+ id: newer,
158
+ channel: "email",
159
+ maxAttempts: 5,
160
+ notBefore: new Date(Date.now() - 1e3)
161
+ }
162
+ ]
163
+ },
164
+ {
165
+ id: uid("notif"),
166
+ userId,
167
+ type: "t",
168
+ payload: {},
169
+ deliveries: [
170
+ {
171
+ id: older,
172
+ channel: "email",
173
+ maxAttempts: 5,
174
+ notBefore: new Date(Date.now() - 6e5)
175
+ }
176
+ ]
177
+ }
178
+ ]);
179
+ const claimed = await ctx.adapter.claimPendingDeliveries({
180
+ limit: 1,
181
+ leaseMs: 6e4,
182
+ claimToken: uid("t")
183
+ });
184
+ equal(claimed[0]?.id, older, "oldest eligible delivery was not claimed first");
185
+ }
186
+ },
187
+ {
188
+ name: "release with a stale token still writes the terminal state",
189
+ run: async (ctx) => {
190
+ const { rows } = await seed(ctx);
191
+ const target = rows[0]?.deliveries[0]?.id;
192
+ assert(target, "seed produced no delivery");
193
+ await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("real") });
194
+ await ctx.adapter.releaseDeliveries([
195
+ {
196
+ id: target,
197
+ claimToken: "a-token-that-never-held-this-lease",
198
+ outcome: { result: "sent" }
199
+ }
200
+ ]);
201
+ const stillClaimable = await ctx.adapter.claimPendingDeliveries({
202
+ ...CLAIM,
203
+ claimToken: uid("t"),
204
+ now: new Date(Date.now() + CLAIM.leaseMs + 5e3)
205
+ });
206
+ assert(
207
+ !stillClaimable.some((row) => row.id === target),
208
+ "row stayed claimable after being marked sent"
209
+ );
210
+ }
211
+ },
212
+ {
213
+ name: "retryable failure re-arms; non-retryable fails immediately",
214
+ run: async (ctx) => {
215
+ const retry = await seed(ctx, { maxAttempts: 5 });
216
+ const retryId = retry.rows[0]?.deliveries[0]?.id;
217
+ assert(retryId, "seed produced no delivery");
218
+ await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("t") });
219
+ const soon = new Date(Date.now() + 3e4);
220
+ await ctx.adapter.releaseDeliveries([
221
+ {
222
+ id: retryId,
223
+ claimToken: "t",
224
+ outcome: { result: "failed", error: "smtp timeout", retryable: true },
225
+ nextAttemptAt: soon
226
+ }
227
+ ]);
228
+ const tooEarly = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("t") });
229
+ assert(
230
+ !tooEarly.some((row) => row.id === retryId),
231
+ "re-armed delivery was claimable before its backoff elapsed"
232
+ );
233
+ const afterBackoff = await ctx.adapter.claimPendingDeliveries({
234
+ ...CLAIM,
235
+ claimToken: uid("t"),
236
+ now: new Date(soon.getTime() + 1e3)
237
+ });
238
+ assert(
239
+ afterBackoff.some((row) => row.id === retryId),
240
+ "re-armed delivery never became claimable"
241
+ );
242
+ const terminal = await seed(ctx, { maxAttempts: 5 });
243
+ const terminalId = terminal.rows[0]?.deliveries[0]?.id;
244
+ assert(terminalId, "seed produced no delivery");
245
+ await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid("t") });
246
+ await ctx.adapter.releaseDeliveries([
247
+ {
248
+ id: terminalId,
249
+ claimToken: "t",
250
+ outcome: { result: "failed", error: "invalid api key", retryable: false }
251
+ }
252
+ ]);
253
+ const failed = await ctx.adapter.getFailedDeliveries({
254
+ since: new Date(Date.now() - 6e4),
255
+ limit: 50
256
+ });
257
+ assert(
258
+ failed.some((row) => row.id === terminalId),
259
+ "non-retryable failure did not land in failed"
260
+ );
261
+ }
262
+ },
263
+ {
264
+ name: "duplicate dedupeKey is skipped, not thrown",
265
+ run: async (ctx) => {
266
+ const userId = uid("user");
267
+ const key = uid("dedupe");
268
+ const first = await seed(ctx, { userId, dedupeKey: key });
269
+ equal(first.result.created.length, 1, "first insert was not created");
270
+ const second = await seed(ctx, { userId, dedupeKey: key });
271
+ equal(second.result.created.length, 0, "duplicate was created");
272
+ equal(second.result.deduped.length, 1, "duplicate was not reported as deduped");
273
+ }
274
+ },
275
+ {
276
+ name: "notifications with no dedupeKey never collide",
277
+ run: async (ctx) => {
278
+ const userId = uid("user");
279
+ const result = await seed(ctx, { userId, count: 3 });
280
+ equal(result.result.created.length, 3, "undeduped notifications collided");
281
+ }
282
+ },
283
+ {
284
+ name: "markRead is scoped to the owning user",
285
+ run: async (ctx) => {
286
+ const owner = uid("user");
287
+ const { rows } = await seed(ctx, { userId: owner });
288
+ const notificationId = rows[0]?.id;
289
+ assert(notificationId, "seed produced no notification");
290
+ const stolen = await ctx.adapter.markRead(uid("attacker"), [notificationId]);
291
+ equal(stolen, 0, "another user marked a notification read");
292
+ const own = await ctx.adapter.markRead(owner, [notificationId]);
293
+ equal(own, 1, "owner could not mark their own notification read");
294
+ }
295
+ },
296
+ {
297
+ name: "unseen count and markSeen agree",
298
+ run: async (ctx) => {
299
+ const userId = uid("user");
300
+ await seed(ctx, { userId, count: 3 });
301
+ equal(await ctx.adapter.countUnseen(userId), 3, "unseen count wrong before markSeen");
302
+ await ctx.adapter.markSeen(userId, /* @__PURE__ */ new Date());
303
+ equal(await ctx.adapter.countUnseen(userId), 0, "unseen count wrong after markSeen");
304
+ }
305
+ },
306
+ {
307
+ /**
308
+ * createdAt must come from the same clock as the cutoff it is compared to.
309
+ *
310
+ * An adapter that leaves createdAt to a database DEFAULT now() is comparing
311
+ * the database clock against the application clock. On one machine they
312
+ * agree and this never fails; in production they are different hosts, and a
313
+ * database a second ahead means markSeen(now) silently misses the newest
314
+ * notifications and the unseen badge never clears. Found when a laptop
315
+ * resumed from sleep with its Docker VM 6s ahead.
316
+ */
317
+ name: "createdAt comes from the caller clock, not the database clock",
318
+ run: async (ctx) => {
319
+ const userId = uid("user");
320
+ const before = /* @__PURE__ */ new Date();
321
+ await seed(ctx, { userId, count: 1 });
322
+ const after = /* @__PURE__ */ new Date();
323
+ const { notifications } = await ctx.adapter.listNotifications({ userId, limit: 10 });
324
+ const createdAt = notifications[0]?.createdAt;
325
+ assert(createdAt, "seed produced no notification");
326
+ const skewMs = Math.max(
327
+ before.getTime() - createdAt.getTime(),
328
+ createdAt.getTime() - after.getTime()
329
+ );
330
+ assert(
331
+ skewMs <= 1e3,
332
+ `createdAt is ${skewMs}ms outside the window the caller observed \u2014 it is coming from another clock`
333
+ );
334
+ }
335
+ }
336
+ ];
337
+
338
+ export { adapterConformanceCases };
339
+ //# sourceMappingURL=testing.js.map
340
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing/conformance.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["import type { DatabaseAdapter, InsertNotification } from \"../core/adapter\";\nimport type { Channel } from \"../core/types\";\n\nexport type ConformanceContext = {\n adapter: DatabaseAdapter;\n /**\n * Forces a delivery's attempt count. Not reachable through the public API —\n * the adapter would have marked the row failed on the way there — so each\n * backend writes it directly.\n */\n setAttempts: (deliveryId: string, attempts: number) => Promise<void>;\n /**\n * Empties the tables. Cases must run against a clean table: claims are\n * ordered by not_before ASC and bounded by limit, so leftover rows from an\n * earlier case can push the row under test out of the claim window.\n */\n reset: () => Promise<void>;\n /**\n * Holds a row lock on a separate connection for the duration of `fn`.\n *\n * Required to test SKIP LOCKED deterministically. Two Promise.all'd claims\n * do not reliably overlap — they complete in single-digit milliseconds and\n * simply queue — so a race-based test passes even with no locking at all.\n *\n * Only meaningful where a claim can block on someone else's lock. A document\n * store whose claim is a single atomic update has nothing to skip, so cases\n * tagged `rowLock` do not apply to it.\n */\n lockRow?: (deliveryId: string, fn: () => Promise<void>) => Promise<void>;\n};\n\nexport type ConformanceCase = {\n name: string;\n /** Capability the case needs; a backend without it must filter the case out. */\n requires?: \"rowLock\";\n run: (ctx: ConformanceContext) => Promise<void>;\n};\n\nclass ConformanceError extends Error {}\n\nfunction assert(condition: unknown, message: string): asserts condition {\n if (!condition) throw new ConformanceError(message);\n}\n\nconst equal = (actual: unknown, expected: unknown, message: string) =>\n assert(actual === expected, `${message} (got ${String(actual)}, expected ${String(expected)})`);\n\nlet counter = 0;\nfunction uid(label: string): string {\n counter += 1;\n return `${label}_${Date.now().toString(36)}_${counter}`;\n}\n\ntype SeedOptions = {\n count?: number;\n notBefore?: Date;\n maxAttempts?: number;\n channel?: Channel;\n dedupeKey?: string;\n userId?: string;\n};\n\nasync function seed(ctx: ConformanceContext, options: SeedOptions = {}) {\n const count = options.count ?? 1;\n const userId = options.userId ?? uid(\"user\");\n\n const rows: InsertNotification[] = Array.from({ length: count }, () => {\n const id = uid(\"notif\");\n return {\n id,\n userId,\n type: \"commentReply\",\n payload: { authorName: \"Dana\" },\n ...(options.dedupeKey ? { dedupeKey: options.dedupeKey } : {}),\n deliveries: [\n {\n id: uid(\"delivery\"),\n channel: options.channel ?? \"email\",\n maxAttempts: options.maxAttempts ?? 5,\n notBefore: options.notBefore ?? new Date(Date.now() - 1000),\n },\n ],\n };\n });\n\n const result = await ctx.adapter.createNotifications(rows);\n return { userId, rows, result };\n}\n\nconst CLAIM = { limit: 10, leaseMs: 60_000 };\n\nexport const adapterConformanceCases: readonly ConformanceCase[] = [\n {\n /**\n * The case that actually distinguishes a correct adapter.\n *\n * A row locked by another transaction must be *skipped*, not waited on.\n * Without SKIP LOCKED the claim blocks until the holder commits, which on\n * a busy table serialises every worker behind the slowest one.\n */\n name: \"a row locked by another transaction is skipped, not waited on\",\n requires: \"rowLock\",\n run: async (ctx) => {\n const { lockRow } = ctx;\n assert(lockRow, \"case requires lockRow\");\n\n const { rows } = await seed(ctx, { count: 2 });\n const locked = rows[0]?.deliveries[0]?.id;\n const free = rows[1]?.deliveries[0]?.id;\n assert(locked && free, \"seed produced too few deliveries\");\n\n await lockRow(locked, async () => {\n const claimed = await Promise.race([\n ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"token\") }),\n new Promise<never>((_, reject) =>\n setTimeout(\n () =>\n reject(\n new ConformanceError(\n \"claim blocked on a row locked by another transaction — SKIP LOCKED is missing\",\n ),\n ),\n 2_000,\n ),\n ),\n ]);\n\n const ids = new Set(claimed.map((row) => row.id));\n assert(!ids.has(locked), \"claimed a row locked by another transaction\");\n assert(ids.has(free), \"skipped an unlocked row alongside the locked one\");\n });\n },\n },\n\n {\n name: \"concurrent claims return disjoint sets\",\n run: async (ctx) => {\n await seed(ctx, { count: 40 });\n\n // Eight callers over 40 rows, repeated — a single Promise.all pair is\n // too fast to overlap and proves nothing on its own.\n const claimed = await Promise.all(\n Array.from({ length: 8 }, () =>\n ctx.adapter.claimPendingDeliveries({ limit: 5, leaseMs: 60_000, claimToken: uid(\"t\") }),\n ),\n );\n\n const all = claimed.flatMap((batch) => batch.map((row) => row.id));\n equal(new Set(all).size, all.length, \"the same delivery was claimed by two callers\");\n assert(all.length <= 40, \"claimed more rows than exist\");\n },\n },\n\n {\n name: \"a claimed row is invisible before its lease expires\",\n run: async (ctx) => {\n const { rows } = await seed(ctx);\n const target = rows[0]?.deliveries[0]?.id;\n\n const first = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"t\") });\n assert(\n first.some((row) => row.id === target),\n \"seeded delivery was not claimed\",\n );\n\n const second = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"t\") });\n assert(!second.some((row) => row.id === target), \"leased row was claimed twice\");\n },\n },\n\n {\n name: \"a claimed row is re-claimable after its lease expires\",\n run: async (ctx) => {\n const { rows } = await seed(ctx);\n const target = rows[0]?.deliveries[0]?.id;\n\n await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"t\") });\n\n const later = new Date(Date.now() + CLAIM.leaseMs + 5_000);\n const reclaimed = await ctx.adapter.claimPendingDeliveries({\n ...CLAIM,\n claimToken: uid(\"t\"),\n now: later,\n });\n\n assert(\n reclaimed.some((row) => row.id === target),\n \"expired lease was not reclaimed\",\n );\n },\n },\n\n {\n name: \"not_before in the future is never claimed\",\n run: async (ctx) => {\n const { rows } = await seed(ctx, { notBefore: new Date(Date.now() + 3_600_000) });\n const target = rows[0]?.deliveries[0]?.id;\n\n const claimed = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"t\") });\n assert(!claimed.some((row) => row.id === target), \"claimed a delivery before its not_before\");\n },\n },\n\n {\n name: \"attempts at or beyond max_attempts is never claimed\",\n run: async (ctx) => {\n const { rows } = await seed(ctx, { maxAttempts: 3 });\n const target = rows[0]?.deliveries[0]?.id;\n assert(target, \"seed produced no delivery\");\n\n await ctx.setAttempts(target, 3);\n\n const claimed = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"t\") });\n assert(!claimed.some((row) => row.id === target), \"claimed an exhausted delivery\");\n },\n },\n\n {\n name: \"claims are ordered by not_before ascending\",\n run: async (ctx) => {\n const userId = uid(\"user\");\n const older = uid(\"delivery\");\n const newer = uid(\"delivery\");\n\n await ctx.adapter.createNotifications([\n {\n id: uid(\"notif\"),\n userId,\n type: \"t\",\n payload: {},\n deliveries: [\n {\n id: newer,\n channel: \"email\",\n maxAttempts: 5,\n notBefore: new Date(Date.now() - 1_000),\n },\n ],\n },\n {\n id: uid(\"notif\"),\n userId,\n type: \"t\",\n payload: {},\n deliveries: [\n {\n id: older,\n channel: \"email\",\n maxAttempts: 5,\n notBefore: new Date(Date.now() - 600_000),\n },\n ],\n },\n ]);\n\n const claimed = await ctx.adapter.claimPendingDeliveries({\n limit: 1,\n leaseMs: 60_000,\n claimToken: uid(\"t\"),\n });\n\n equal(claimed[0]?.id, older, \"oldest eligible delivery was not claimed first\");\n },\n },\n\n {\n name: \"release with a stale token still writes the terminal state\",\n run: async (ctx) => {\n const { rows } = await seed(ctx);\n const target = rows[0]?.deliveries[0]?.id;\n assert(target, \"seed produced no delivery\");\n\n await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"real\") });\n\n await ctx.adapter.releaseDeliveries([\n {\n id: target,\n claimToken: \"a-token-that-never-held-this-lease\",\n outcome: { result: \"sent\" },\n },\n ]);\n\n const stillClaimable = await ctx.adapter.claimPendingDeliveries({\n ...CLAIM,\n claimToken: uid(\"t\"),\n now: new Date(Date.now() + CLAIM.leaseMs + 5_000),\n });\n\n assert(\n !stillClaimable.some((row) => row.id === target),\n \"row stayed claimable after being marked sent\",\n );\n },\n },\n\n {\n name: \"retryable failure re-arms; non-retryable fails immediately\",\n run: async (ctx) => {\n const retry = await seed(ctx, { maxAttempts: 5 });\n const retryId = retry.rows[0]?.deliveries[0]?.id;\n assert(retryId, \"seed produced no delivery\");\n\n await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"t\") });\n\n const soon = new Date(Date.now() + 30_000);\n await ctx.adapter.releaseDeliveries([\n {\n id: retryId,\n claimToken: \"t\",\n outcome: { result: \"failed\", error: \"smtp timeout\", retryable: true },\n nextAttemptAt: soon,\n },\n ]);\n\n const tooEarly = await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"t\") });\n assert(\n !tooEarly.some((row) => row.id === retryId),\n \"re-armed delivery was claimable before its backoff elapsed\",\n );\n\n const afterBackoff = await ctx.adapter.claimPendingDeliveries({\n ...CLAIM,\n claimToken: uid(\"t\"),\n now: new Date(soon.getTime() + 1_000),\n });\n assert(\n afterBackoff.some((row) => row.id === retryId),\n \"re-armed delivery never became claimable\",\n );\n\n const terminal = await seed(ctx, { maxAttempts: 5 });\n const terminalId = terminal.rows[0]?.deliveries[0]?.id;\n assert(terminalId, \"seed produced no delivery\");\n\n await ctx.adapter.claimPendingDeliveries({ ...CLAIM, claimToken: uid(\"t\") });\n await ctx.adapter.releaseDeliveries([\n {\n id: terminalId,\n claimToken: \"t\",\n outcome: { result: \"failed\", error: \"invalid api key\", retryable: false },\n },\n ]);\n\n const failed = await ctx.adapter.getFailedDeliveries({\n since: new Date(Date.now() - 60_000),\n limit: 50,\n });\n assert(\n failed.some((row) => row.id === terminalId),\n \"non-retryable failure did not land in failed\",\n );\n },\n },\n\n {\n name: \"duplicate dedupeKey is skipped, not thrown\",\n run: async (ctx) => {\n const userId = uid(\"user\");\n const key = uid(\"dedupe\");\n\n const first = await seed(ctx, { userId, dedupeKey: key });\n equal(first.result.created.length, 1, \"first insert was not created\");\n\n const second = await seed(ctx, { userId, dedupeKey: key });\n equal(second.result.created.length, 0, \"duplicate was created\");\n equal(second.result.deduped.length, 1, \"duplicate was not reported as deduped\");\n },\n },\n\n {\n name: \"notifications with no dedupeKey never collide\",\n run: async (ctx) => {\n const userId = uid(\"user\");\n const result = await seed(ctx, { userId, count: 3 });\n equal(result.result.created.length, 3, \"undeduped notifications collided\");\n },\n },\n\n {\n name: \"markRead is scoped to the owning user\",\n run: async (ctx) => {\n const owner = uid(\"user\");\n const { rows } = await seed(ctx, { userId: owner });\n const notificationId = rows[0]?.id;\n assert(notificationId, \"seed produced no notification\");\n\n const stolen = await ctx.adapter.markRead(uid(\"attacker\"), [notificationId]);\n equal(stolen, 0, \"another user marked a notification read\");\n\n const own = await ctx.adapter.markRead(owner, [notificationId]);\n equal(own, 1, \"owner could not mark their own notification read\");\n },\n },\n\n {\n name: \"unseen count and markSeen agree\",\n run: async (ctx) => {\n const userId = uid(\"user\");\n await seed(ctx, { userId, count: 3 });\n\n equal(await ctx.adapter.countUnseen(userId), 3, \"unseen count wrong before markSeen\");\n\n await ctx.adapter.markSeen(userId, new Date());\n equal(await ctx.adapter.countUnseen(userId), 0, \"unseen count wrong after markSeen\");\n },\n },\n\n {\n /**\n * createdAt must come from the same clock as the cutoff it is compared to.\n *\n * An adapter that leaves createdAt to a database DEFAULT now() is comparing\n * the database clock against the application clock. On one machine they\n * agree and this never fails; in production they are different hosts, and a\n * database a second ahead means markSeen(now) silently misses the newest\n * notifications and the unseen badge never clears. Found when a laptop\n * resumed from sleep with its Docker VM 6s ahead.\n */\n name: \"createdAt comes from the caller clock, not the database clock\",\n run: async (ctx) => {\n const userId = uid(\"user\");\n const before = new Date();\n await seed(ctx, { userId, count: 1 });\n const after = new Date();\n\n const { notifications } = await ctx.adapter.listNotifications({ userId, limit: 10 });\n const createdAt = notifications[0]?.createdAt;\n assert(createdAt, \"seed produced no notification\");\n\n // A second of slack for a slow round trip, far under the skew that breaks\n // markSeen but tight enough to catch a foreign clock.\n const skewMs = Math.max(\n before.getTime() - createdAt.getTime(),\n createdAt.getTime() - after.getTime(),\n );\n assert(\n skewMs <= 1_000,\n `createdAt is ${skewMs}ms outside the window the caller observed — it is coming from another clock`,\n );\n },\n },\n];\n"]}
package/package.json ADDED
@@ -0,0 +1,168 @@
1
+ {
2
+ "name": "easy-ping",
3
+ "version": "0.1.0",
4
+ "description": "Self-hosted, type-safe notifications for TypeScript apps. Your database, your users.",
5
+ "license": "MIT",
6
+ "author": "Teklu Moges",
7
+ "homepage": "https://github.com/teklumt/easy-ping#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/teklumt/easy-ping.git",
11
+ "directory": "packages/easy-ping"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/teklumt/easy-ping/issues"
15
+ },
16
+ "keywords": [
17
+ "notifications",
18
+ "notification",
19
+ "in-app-notifications",
20
+ "notification-inbox",
21
+ "web-push",
22
+ "push-notifications",
23
+ "vapid",
24
+ "service-worker",
25
+ "email",
26
+ "transactional-email",
27
+ "resend",
28
+ "digest",
29
+ "notification-preferences",
30
+ "typescript",
31
+ "type-safe",
32
+ "self-hosted",
33
+ "drizzle",
34
+ "postgres",
35
+ "mongodb",
36
+ "react",
37
+ "nextjs"
38
+ ],
39
+ "type": "module",
40
+ "sideEffects": false,
41
+ "files": [
42
+ "dist",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "exports": {
47
+ ".": {
48
+ "types": "./dist/index.d.ts",
49
+ "import": "./dist/index.js",
50
+ "require": "./dist/index.cjs"
51
+ },
52
+ "./adapters/drizzle": {
53
+ "types": "./dist/adapters/drizzle.d.ts",
54
+ "import": "./dist/adapters/drizzle.js",
55
+ "require": "./dist/adapters/drizzle.cjs"
56
+ },
57
+ "./adapters/mongodb": {
58
+ "types": "./dist/adapters/mongodb.d.ts",
59
+ "import": "./dist/adapters/mongodb.js",
60
+ "require": "./dist/adapters/mongodb.cjs"
61
+ },
62
+ "./schema": {
63
+ "types": "./dist/schema.d.ts",
64
+ "import": "./dist/schema.js",
65
+ "require": "./dist/schema.cjs"
66
+ },
67
+ "./package.json": "./package.json",
68
+ "./providers/resend": {
69
+ "types": "./dist/providers/resend.d.ts",
70
+ "import": "./dist/providers/resend.js",
71
+ "require": "./dist/providers/resend.cjs"
72
+ },
73
+ "./testing": {
74
+ "types": "./dist/testing.d.ts",
75
+ "import": "./dist/testing.js",
76
+ "require": "./dist/testing.cjs"
77
+ },
78
+ "./client": {
79
+ "types": "./dist/client.d.ts",
80
+ "import": "./dist/client.js",
81
+ "require": "./dist/client.cjs"
82
+ },
83
+ "./react": {
84
+ "types": "./dist/react.d.ts",
85
+ "import": "./dist/react.js",
86
+ "require": "./dist/react.cjs"
87
+ },
88
+ "./plugins/preferences": {
89
+ "types": "./dist/plugins/preferences.d.ts",
90
+ "import": "./dist/plugins/preferences.js",
91
+ "require": "./dist/plugins/preferences.cjs"
92
+ },
93
+ "./plugins/preferences/client": {
94
+ "types": "./dist/plugins/preferences-client.d.ts",
95
+ "import": "./dist/plugins/preferences-client.js",
96
+ "require": "./dist/plugins/preferences-client.cjs"
97
+ },
98
+ "./plugins/preferences/react": {
99
+ "types": "./dist/plugins/preferences-react.d.ts",
100
+ "import": "./dist/plugins/preferences-react.js",
101
+ "require": "./dist/plugins/preferences-react.cjs"
102
+ },
103
+ "./node": {
104
+ "types": "./dist/node.d.ts",
105
+ "import": "./dist/node.js",
106
+ "require": "./dist/node.cjs"
107
+ },
108
+ "./plugins/digests": {
109
+ "types": "./dist/plugins/digests.d.ts",
110
+ "import": "./dist/plugins/digests.js",
111
+ "require": "./dist/plugins/digests.cjs"
112
+ },
113
+ "./plugins/push": {
114
+ "types": "./dist/plugins/push.d.ts",
115
+ "import": "./dist/plugins/push.js",
116
+ "require": "./dist/plugins/push.cjs"
117
+ },
118
+ "./providers/web-push": {
119
+ "types": "./dist/providers/web-push.d.ts",
120
+ "import": "./dist/providers/web-push.js",
121
+ "require": "./dist/providers/web-push.cjs"
122
+ },
123
+ "./browser": {
124
+ "types": "./dist/browser.d.ts",
125
+ "import": "./dist/browser.js",
126
+ "require": "./dist/browser.cjs"
127
+ }
128
+ },
129
+ "peerDependencies": {
130
+ "drizzle-orm": ">=0.36.0",
131
+ "react": ">=18"
132
+ },
133
+ "peerDependenciesMeta": {
134
+ "drizzle-orm": {
135
+ "optional": true
136
+ },
137
+ "react": {
138
+ "optional": true
139
+ }
140
+ },
141
+ "scripts": {
142
+ "build": "node scripts/clean.mjs && tsup",
143
+ "dev": "tsup --watch",
144
+ "test": "vitest run",
145
+ "typecheck": "tsc --noEmit",
146
+ "type-budget": "node scripts/check-type-budget.mjs",
147
+ "check-node-floor": "node scripts/check-node-floor.mjs",
148
+ "prepack": "node scripts/copy-docs.mjs"
149
+ },
150
+ "devDependencies": {
151
+ "@types/node": "^26.1.2",
152
+ "@types/react": "^19.2.18",
153
+ "drizzle-orm": "^0.45.2",
154
+ "http_ece": "^1.2.1",
155
+ "mongodb": "^7.6.0",
156
+ "postgres": "^3.4.9",
157
+ "react": "^19.2.8",
158
+ "tsup": "^8.3.0",
159
+ "typescript": "^5.7.0",
160
+ "vitest": "^2.1.0"
161
+ },
162
+ "engines": {
163
+ "node": ">=20"
164
+ },
165
+ "dependencies": {
166
+ "@standard-schema/spec": "^1.1.0"
167
+ }
168
+ }