@coldtea/pr-lens-schema 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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/dist/apply.d.ts +42 -0
  4. package/dist/apply.d.ts.map +1 -0
  5. package/dist/apply.js +315 -0
  6. package/dist/apply.js.map +1 -0
  7. package/dist/config.d.ts +58 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +61 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/errors.d.ts +24 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +12 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/examples/baseline.d.ts +18 -0
  16. package/dist/examples/baseline.d.ts.map +1 -0
  17. package/dist/examples/baseline.js +433 -0
  18. package/dist/examples/baseline.js.map +1 -0
  19. package/dist/examples/index.d.ts +1194 -0
  20. package/dist/examples/index.d.ts.map +1 -0
  21. package/dist/examples/index.js +20 -0
  22. package/dist/examples/index.js.map +1 -0
  23. package/dist/examples/minimal.d.ts +4 -0
  24. package/dist/examples/minimal.d.ts.map +1 -0
  25. package/dist/examples/minimal.js +25 -0
  26. package/dist/examples/minimal.js.map +1 -0
  27. package/dist/examples/postmark-refactor.d.ts +16 -0
  28. package/dist/examples/postmark-refactor.d.ts.map +1 -0
  29. package/dist/examples/postmark-refactor.js +468 -0
  30. package/dist/examples/postmark-refactor.js.map +1 -0
  31. package/dist/graph.d.ts +568 -0
  32. package/dist/graph.d.ts.map +1 -0
  33. package/dist/graph.js +266 -0
  34. package/dist/graph.js.map +1 -0
  35. package/dist/index.d.ts +12 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +12 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/integrity.d.ts +20 -0
  40. package/dist/integrity.d.ts.map +1 -0
  41. package/dist/integrity.js +167 -0
  42. package/dist/integrity.js.map +1 -0
  43. package/dist/manifest.d.ts +65 -0
  44. package/dist/manifest.d.ts.map +1 -0
  45. package/dist/manifest.js +60 -0
  46. package/dist/manifest.js.map +1 -0
  47. package/dist/patch.d.ts +824 -0
  48. package/dist/patch.d.ts.map +1 -0
  49. package/dist/patch.js +84 -0
  50. package/dist/patch.js.map +1 -0
  51. package/dist/primitives.d.ts +92 -0
  52. package/dist/primitives.d.ts.map +1 -0
  53. package/dist/primitives.js +120 -0
  54. package/dist/primitives.js.map +1 -0
  55. package/dist/utils.d.ts +6 -0
  56. package/dist/utils.d.ts.map +1 -0
  57. package/dist/utils.js +8 -0
  58. package/dist/utils.js.map +1 -0
  59. package/dist/validate.d.ts +18 -0
  60. package/dist/validate.d.ts.map +1 -0
  61. package/dist/validate.js +95 -0
  62. package/dist/validate.js.map +1 -0
  63. package/dist/version.d.ts +21 -0
  64. package/dist/version.d.ts.map +1 -0
  65. package/dist/version.js +24 -0
  66. package/dist/version.js.map +1 -0
  67. package/examples/broadcast-baseline.graph.json +289 -0
  68. package/examples/broadcast-baseline.patch.json +321 -0
  69. package/examples/minimal.graph.json +45 -0
  70. package/examples/postmark-refactor.graph.json +580 -0
  71. package/examples/postmark-refactor.render-manifest.json +67 -0
  72. package/examples/pr-lens.config.json +32 -0
  73. package/json-schema/config.schema.json +153 -0
  74. package/json-schema/graph-doc.schema.json +1032 -0
  75. package/json-schema/patch-doc.schema.json +1383 -0
  76. package/json-schema/render-manifest.schema.json +183 -0
  77. package/package.json +66 -0
  78. package/src/apply.ts +399 -0
  79. package/src/config.ts +69 -0
  80. package/src/errors.ts +34 -0
  81. package/src/examples/baseline.ts +437 -0
  82. package/src/examples/index.ts +25 -0
  83. package/src/examples/minimal.ts +26 -0
  84. package/src/examples/postmark-refactor.ts +480 -0
  85. package/src/graph.ts +331 -0
  86. package/src/index.ts +82 -0
  87. package/src/integrity.ts +216 -0
  88. package/src/manifest.ts +64 -0
  89. package/src/patch.ts +100 -0
  90. package/src/primitives.ts +146 -0
  91. package/src/utils.ts +7 -0
  92. package/src/validate.ts +132 -0
  93. package/src/version.ts +31 -0
@@ -0,0 +1,480 @@
1
+ import type { GraphDocInput } from "../graph.js";
2
+ import type { RenderManifestInput } from "../manifest.js";
3
+ import type { ConfigInput } from "../config.js";
4
+ import { SCHEMA_VERSION } from "../version.js";
5
+
6
+ /**
7
+ * The reference pull-request document, hand-authored from the validated bestregards Postmark
8
+ * refactor: broadcast sending moved from one Postmark request per recipient
9
+ * to batched requests of 500, behind a shared library. Every downstream
10
+ * renderer golden is measured against this document, so it exercises all
11
+ * four delta states, a hero edge, and a full data-flow sequence.
12
+ */
13
+ export const postmarkRefactorGraphInput: GraphDocInput = {
14
+ schemaVersion: SCHEMA_VERSION,
15
+ kind: "graph",
16
+ generatedAt: "2026-08-19T18:24:00.000Z",
17
+ title: "Batch broadcast sending through Postmark",
18
+ summary:
19
+ "Broadcast delivery moves from one Postmark request per recipient to batched requests of 500, with suppression filtering pulled in front of the send and the payload builder extracted into a shared library.",
20
+ lenses: ["architecture", "data-flow"],
21
+ provenance: {
22
+ repo: { owner: "ohansemmanuel", name: "bestregards", host: "github.com" },
23
+ base: { sha: "3f5c1ab9d24e7f08c6b1a5d3e9074c2b8a6f1d40", ref: "main" },
24
+ head: { sha: "b71e0d4c8a92f5361de7c0b4a8f2593d6c1e8a77", ref: "batch-broadcast-send" },
25
+ pullRequest: {
26
+ number: 128,
27
+ title: "Send broadcasts in batches of 500",
28
+ url: "https://github.com/ohansemmanuel/bestregards/pull/128",
29
+ },
30
+ generator: { name: "pr-lens-examples", version: "0.1.0" },
31
+ },
32
+ lanes: [
33
+ { id: "web", label: "Next.js", subtitle: "Vercel", order: 0 },
34
+ { id: "functions", label: "Cloud Functions", subtitle: "Firebase", order: 1 },
35
+ { id: "external", label: "External", subtitle: "Postmark", order: 2 },
36
+ ],
37
+ nodes: [
38
+ {
39
+ id: "broadcast-composer",
40
+ label: "Broadcast composer",
41
+ kind: "ui",
42
+ delta: "unchanged",
43
+ lane: "web",
44
+ subtitle: "app/broadcasts/new",
45
+ summary: "Where an author writes a broadcast and hits send. Untouched by this change.",
46
+ files: [{ path: "app/broadcasts/new/page.tsx" }],
47
+ },
48
+ {
49
+ id: "queue-route",
50
+ label: "POST /api/broadcasts/queue",
51
+ kind: "route",
52
+ delta: "modified",
53
+ lane: "web",
54
+ summary:
55
+ "Writes the queue document. Now stamps the recipient count and batch size the sender will use instead of leaving batching to the worker.",
56
+ files: [{ path: "app/api/broadcasts/queue/route.ts", startLine: 24, endLine: 96 }],
57
+ badges: ["+38 / -12"],
58
+ },
59
+ {
60
+ id: "broadcast-queue",
61
+ label: "broadcastQueue",
62
+ kind: "datastore",
63
+ delta: "modified",
64
+ lane: "functions",
65
+ subtitle: "Firestore collection",
66
+ summary:
67
+ "Queue documents gained batchSize and suppressedCount fields, and results are now written back per batch rather than per recipient.",
68
+ files: [{ path: "functions/src/broadcast/schema.ts", startLine: 12, endLine: 48 }],
69
+ },
70
+ {
71
+ id: "send-broadcast-bulk",
72
+ label: "sendBroadcastBulk",
73
+ kind: "function",
74
+ delta: "added",
75
+ lane: "functions",
76
+ subtitle: "onWrite trigger",
77
+ summary:
78
+ "New trigger handler. Fetches suppressions once, builds batched payloads, and posts them to Postmark in chunks of 500.",
79
+ files: [{ path: "functions/src/broadcast/sendBroadcastBulk.ts", startLine: 1, endLine: 142 }],
80
+ badges: ["new"],
81
+ },
82
+ {
83
+ id: "build-bulk-payload",
84
+ label: "buildBulkPayload",
85
+ kind: "function",
86
+ delta: "added",
87
+ lane: "functions",
88
+ summary: "Turns a broadcast and its recipient slice into a Postmark batch request body.",
89
+ files: [{ path: "packages/broadcast-lib/src/buildBulkPayload.ts", startLine: 1, endLine: 74 }],
90
+ },
91
+ {
92
+ id: "get-suppressed-emails",
93
+ label: "getSuppressedEmails",
94
+ kind: "function",
95
+ delta: "added",
96
+ lane: "functions",
97
+ summary:
98
+ "Pulls the Postmark suppression dump once per broadcast so suppressed addresses are filtered before any batch is sent.",
99
+ files: [{ path: "packages/broadcast-lib/src/getSuppressedEmails.ts", startLine: 1, endLine: 58 }],
100
+ },
101
+ {
102
+ id: "broadcast-lib",
103
+ label: "broadcast-lib",
104
+ kind: "package",
105
+ delta: "added",
106
+ lane: "functions",
107
+ subtitle: "packages/broadcast-lib",
108
+ summary:
109
+ "New shared package so the queue route and the sender agree on payload shape and batch size.",
110
+ files: [{ path: "packages/broadcast-lib/src/index.ts" }],
111
+ badges: ["new package"],
112
+ },
113
+ {
114
+ id: "process-broadcast",
115
+ label: "processBroadcast",
116
+ kind: "function",
117
+ delta: "removed",
118
+ lane: "functions",
119
+ subtitle: "onWrite trigger",
120
+ summary: "The per-recipient loop this change replaces.",
121
+ files: [
122
+ {
123
+ path: "functions/src/broadcast/processBroadcast.ts",
124
+ startLine: 1,
125
+ endLine: 118,
126
+ revision: "base",
127
+ },
128
+ ],
129
+ },
130
+ {
131
+ id: "send-single-email",
132
+ label: "sendSingleEmail",
133
+ kind: "function",
134
+ delta: "removed",
135
+ lane: "functions",
136
+ summary: "One Postmark request per recipient. Gone with the loop that called it.",
137
+ files: [
138
+ {
139
+ path: "functions/src/broadcast/sendSingleEmail.ts",
140
+ startLine: 1,
141
+ endLine: 46,
142
+ revision: "base",
143
+ },
144
+ ],
145
+ },
146
+ {
147
+ id: "postmark",
148
+ label: "Postmark",
149
+ kind: "external",
150
+ delta: "modified",
151
+ lane: "external",
152
+ subtitle: "Email API",
153
+ summary:
154
+ "Same provider, different endpoints: the batch endpoint and the suppression dump replace repeated single sends.",
155
+ },
156
+ ],
157
+ edges: [
158
+ {
159
+ id: "composer-to-queue",
160
+ from: "broadcast-composer",
161
+ to: "queue-route",
162
+ kind: "http",
163
+ delta: "unchanged",
164
+ label: "send broadcast",
165
+ },
166
+ {
167
+ id: "queue-to-firestore",
168
+ from: "queue-route",
169
+ to: "broadcast-queue",
170
+ kind: "data",
171
+ delta: "modified",
172
+ label: "enqueue job",
173
+ },
174
+ {
175
+ id: "queue-to-lib",
176
+ from: "queue-route",
177
+ to: "broadcast-lib",
178
+ kind: "dependency",
179
+ delta: "added",
180
+ label: "batch size",
181
+ },
182
+ {
183
+ id: "firestore-to-bulk",
184
+ from: "broadcast-queue",
185
+ to: "send-broadcast-bulk",
186
+ kind: "event",
187
+ delta: "added",
188
+ label: "onWrite",
189
+ },
190
+ {
191
+ id: "firestore-to-process",
192
+ from: "broadcast-queue",
193
+ to: "process-broadcast",
194
+ kind: "event",
195
+ delta: "removed",
196
+ label: "onWrite",
197
+ },
198
+ {
199
+ id: "process-to-single",
200
+ from: "process-broadcast",
201
+ to: "send-single-email",
202
+ kind: "call",
203
+ delta: "removed",
204
+ label: "per recipient",
205
+ },
206
+ {
207
+ id: "single-to-postmark",
208
+ from: "send-single-email",
209
+ to: "postmark",
210
+ kind: "http",
211
+ delta: "removed",
212
+ label: "POST /email · 1 msg/call",
213
+ },
214
+ {
215
+ id: "bulk-to-payload",
216
+ from: "send-broadcast-bulk",
217
+ to: "build-bulk-payload",
218
+ kind: "call",
219
+ delta: "added",
220
+ },
221
+ {
222
+ id: "bulk-to-suppressions",
223
+ from: "send-broadcast-bulk",
224
+ to: "get-suppressed-emails",
225
+ kind: "call",
226
+ delta: "added",
227
+ },
228
+ {
229
+ id: "bulk-to-lib",
230
+ from: "send-broadcast-bulk",
231
+ to: "broadcast-lib",
232
+ kind: "dependency",
233
+ delta: "added",
234
+ },
235
+ {
236
+ id: "suppressions-to-postmark",
237
+ from: "get-suppressed-emails",
238
+ to: "postmark",
239
+ kind: "http",
240
+ delta: "added",
241
+ label: "GET suppression dump",
242
+ animated: true,
243
+ },
244
+ {
245
+ id: "bulk-to-postmark",
246
+ from: "send-broadcast-bulk",
247
+ to: "postmark",
248
+ kind: "http",
249
+ delta: "added",
250
+ label: "500 msgs/call",
251
+ emphasis: "hero",
252
+ animated: true,
253
+ summary: "The change in one edge: a broadcast to 10,000 recipients drops from 10,000 requests to 20.",
254
+ },
255
+ {
256
+ id: "bulk-to-firestore",
257
+ from: "send-broadcast-bulk",
258
+ to: "broadcast-queue",
259
+ kind: "data",
260
+ delta: "added",
261
+ label: "write results",
262
+ },
263
+ ],
264
+ flows: [
265
+ {
266
+ id: "send-pipeline",
267
+ title: "Sending a broadcast",
268
+ summary: "The path a queued broadcast takes now, from enqueue to per-message results.",
269
+ delta: "modified",
270
+ participants: [
271
+ { node: "queue-route", label: "queue route" },
272
+ { node: "broadcast-queue", label: "Firestore" },
273
+ { node: "send-broadcast-bulk", label: "sendBroadcastBulk" },
274
+ { node: "postmark", label: "Postmark" },
275
+ ],
276
+ messages: [
277
+ {
278
+ id: "enqueue",
279
+ from: "queue-route",
280
+ to: "broadcast-queue",
281
+ label: "enqueue broadcast job",
282
+ kind: "async",
283
+ delta: "modified",
284
+ },
285
+ {
286
+ id: "trigger",
287
+ from: "broadcast-queue",
288
+ to: "send-broadcast-bulk",
289
+ label: "onWrite trigger",
290
+ kind: "async",
291
+ delta: "added",
292
+ },
293
+ {
294
+ id: "suppressions-request",
295
+ from: "send-broadcast-bulk",
296
+ to: "postmark",
297
+ label: "GET suppression dump",
298
+ kind: "sync",
299
+ delta: "added",
300
+ },
301
+ {
302
+ id: "suppressions-response",
303
+ from: "postmark",
304
+ to: "send-broadcast-bulk",
305
+ label: "suppressed addresses",
306
+ kind: "return",
307
+ delta: "added",
308
+ note: "Fetched once per broadcast, not once per recipient.",
309
+ },
310
+ {
311
+ id: "batch-post",
312
+ from: "send-broadcast-bulk",
313
+ to: "postmark",
314
+ label: "POST /email/batch · 500 msgs",
315
+ kind: "sync",
316
+ delta: "added",
317
+ repeat: 4,
318
+ note: "One request per 500 recipients; four for this 2,000-recipient broadcast.",
319
+ },
320
+ {
321
+ id: "batch-results",
322
+ from: "postmark",
323
+ to: "send-broadcast-bulk",
324
+ label: "per-message results",
325
+ kind: "return",
326
+ delta: "added",
327
+ },
328
+ {
329
+ id: "write-results",
330
+ from: "send-broadcast-bulk",
331
+ to: "broadcast-queue",
332
+ label: "write results",
333
+ kind: "async",
334
+ delta: "added",
335
+ },
336
+ ],
337
+ },
338
+ ],
339
+ stats: {
340
+ filesChanged: 14,
341
+ additions: 486,
342
+ deletions: 212,
343
+ chips: [
344
+ { label: "Postmark calls", value: "500× fewer", tone: "hero" },
345
+ { label: "New", value: "4 units", tone: "added" },
346
+ { label: "Retired", value: "2 units", tone: "removed" },
347
+ ],
348
+ },
349
+ views: [
350
+ {
351
+ id: "overview",
352
+ title: "Architecture — blast radius",
353
+ lens: "architecture",
354
+ summary: "Everything this change touches, across all three lanes.",
355
+ defaultOpen: true,
356
+ scope: { kind: "all" },
357
+ children: [
358
+ {
359
+ id: "new-batch-path",
360
+ title: "The new batch path",
361
+ lens: "architecture",
362
+ summary: "What replaced the per-recipient loop.",
363
+ scope: {
364
+ kind: "selection",
365
+ nodes: ["send-broadcast-bulk", "build-bulk-payload", "get-suppressed-emails", "broadcast-lib", "postmark"],
366
+ edges: [
367
+ "bulk-to-payload",
368
+ "bulk-to-suppressions",
369
+ "bulk-to-lib",
370
+ "suppressions-to-postmark",
371
+ "bulk-to-postmark",
372
+ "bulk-to-firestore",
373
+ ],
374
+ },
375
+ },
376
+ {
377
+ id: "retired-path",
378
+ title: "What was retired",
379
+ lens: "architecture",
380
+ summary: "The single-send path, kept visible so a reviewer can confirm nothing else called it.",
381
+ scope: {
382
+ kind: "selection",
383
+ nodes: ["process-broadcast", "send-single-email"],
384
+ edges: ["firestore-to-process", "process-to-single", "single-to-postmark"],
385
+ },
386
+ },
387
+ ],
388
+ },
389
+ {
390
+ id: "send-pipeline-view",
391
+ title: "Data flow — sending a broadcast",
392
+ lens: "data-flow",
393
+ scope: { kind: "selection", flows: ["send-pipeline"] },
394
+ },
395
+ ],
396
+ layout: {
397
+ direction: "right",
398
+ laneOrder: ["web", "functions", "external"],
399
+ rank: { "queue-route": 0, "send-broadcast-bulk": 1, postmark: 2 },
400
+ },
401
+ };
402
+
403
+ /** What a repository may commit as `.github/pr-lens.yml`. */
404
+ export const exampleConfigInput: ConfigInput = {
405
+ schemaVersion: SCHEMA_VERSION,
406
+ lenses: ["architecture", "data-flow"],
407
+ map: {
408
+ rename: [{ match: "functions/src/broadcast/sendBroadcastBulk.ts", to: "Broadcast sender" }],
409
+ exclude: ["**/*.test.ts", "functions/src/legacy/**"],
410
+ lane: [{ match: "packages/broadcast-lib/**", lane: "functions" }],
411
+ group: [{ match: "id:build-bulk-payload", group: "broadcast-lib" }],
412
+ },
413
+ branding: true,
414
+ };
415
+
416
+ /** The inventory a render of the pull-request document produces. */
417
+ export const postmarkRefactorManifestInput: RenderManifestInput = {
418
+ schemaVersion: SCHEMA_VERSION,
419
+ kind: "render-manifest",
420
+ generatedAt: "2026-08-19T18:32:10.000Z",
421
+ graph: {
422
+ headSha: "b71e0d4c8a92f5361de7c0b4a8f2593d6c1e8a77",
423
+ contentHash: "6a1f0b8c7d2e4359",
424
+ },
425
+ renderer: { name: "@coldtea/pr-lens-renderer", version: "0.1.0" },
426
+ assets: [
427
+ {
428
+ id: "overview-light",
429
+ lens: "architecture",
430
+ theme: "light",
431
+ view: "overview",
432
+ mediaType: "image/svg+xml",
433
+ contentHash: "0c9d2e6b1a4f7385",
434
+ bytes: 48213,
435
+ width: 1280,
436
+ height: 720,
437
+ animated: true,
438
+ url: "https://cdn.example.com/pr-lens/6a1f0b8c7d2e4359/overview-light-0c9d2e6b1a4f7385.svg",
439
+ },
440
+ {
441
+ id: "overview-dark",
442
+ lens: "architecture",
443
+ theme: "dark",
444
+ view: "overview",
445
+ mediaType: "image/svg+xml",
446
+ contentHash: "7b3e5a0c9d1f6482",
447
+ bytes: 48477,
448
+ width: 1280,
449
+ height: 720,
450
+ animated: true,
451
+ url: "https://cdn.example.com/pr-lens/6a1f0b8c7d2e4359/overview-dark-7b3e5a0c9d1f6482.svg",
452
+ },
453
+ {
454
+ id: "send-pipeline-light",
455
+ lens: "data-flow",
456
+ theme: "light",
457
+ view: "send-pipeline-view",
458
+ mediaType: "image/svg+xml",
459
+ contentHash: "2f8a4c6e0b9d1375",
460
+ bytes: 31904,
461
+ width: 1120,
462
+ height: 640,
463
+ animated: true,
464
+ url: "https://cdn.example.com/pr-lens/6a1f0b8c7d2e4359/send-pipeline-light-2f8a4c6e0b9d1375.svg",
465
+ },
466
+ {
467
+ id: "send-pipeline-dark",
468
+ lens: "data-flow",
469
+ theme: "dark",
470
+ view: "send-pipeline-view",
471
+ mediaType: "image/svg+xml",
472
+ contentHash: "5d1b7f3a8c0e2946",
473
+ bytes: 32011,
474
+ width: 1120,
475
+ height: 640,
476
+ animated: true,
477
+ url: "https://cdn.example.com/pr-lens/6a1f0b8c7d2e4359/send-pipeline-dark-5d1b7f3a8c0e2946.svg",
478
+ },
479
+ ],
480
+ };