@josephomills/esign 0.2.2

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.
@@ -0,0 +1,48 @@
1
+ import { P as PersistencePort } from '../ports-DtICqgEf.js';
2
+ import 'zod';
3
+
4
+ interface Delegate {
5
+ create(args: {
6
+ data: unknown;
7
+ }): Promise<unknown>;
8
+ createMany(args: {
9
+ data: unknown[];
10
+ }): Promise<unknown>;
11
+ findUnique(args: {
12
+ where: unknown;
13
+ select?: unknown;
14
+ }): Promise<unknown>;
15
+ findFirst(args: {
16
+ where?: unknown;
17
+ orderBy?: unknown;
18
+ select?: unknown;
19
+ }): Promise<unknown>;
20
+ findMany(args: {
21
+ where?: unknown;
22
+ orderBy?: unknown;
23
+ take?: number;
24
+ select?: unknown;
25
+ }): Promise<unknown[]>;
26
+ update(args: {
27
+ where: unknown;
28
+ data: unknown;
29
+ }): Promise<unknown>;
30
+ count(args: {
31
+ where?: unknown;
32
+ }): Promise<number>;
33
+ groupBy(args: {
34
+ by: string[];
35
+ where?: unknown;
36
+ _count?: unknown;
37
+ }): Promise<unknown[]>;
38
+ }
39
+ interface PrismaLike {
40
+ signingDocument: Delegate;
41
+ signingDocumentVersion: Delegate;
42
+ signingCampaign: Delegate;
43
+ signingRequest: Delegate;
44
+ signingAuditEvent: Delegate;
45
+ }
46
+ declare function createPrismaPersistence(prisma: PrismaLike): PersistencePort;
47
+
48
+ export { type PrismaLike, createPrismaPersistence };
@@ -0,0 +1,508 @@
1
+ import { randomBytes } from 'crypto';
2
+
3
+ // src/server/flow.ts
4
+ var ESIGN_STATUSES = [
5
+ "PENDING",
6
+ "VIEWED",
7
+ "SIGNED",
8
+ "DECLINED",
9
+ "EXPIRED",
10
+ "REVOKED"
11
+ ];
12
+ var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
13
+ function uid(size = 16) {
14
+ let out = "";
15
+ while (out.length < size) {
16
+ for (const byte of randomBytes(size)) {
17
+ if (byte < 248) {
18
+ out += ALPHABET[byte % 62];
19
+ if (out.length === size) break;
20
+ }
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+
26
+ // src/adapters/prisma/adapter.ts
27
+ var iso = (d) => d.toISOString();
28
+ var isoN = (d) => d ? d.toISOString() : null;
29
+ var mapDoc = (r) => ({
30
+ id: r.id,
31
+ scopeId: r.scopeId,
32
+ documentType: r.documentType,
33
+ title: r.title,
34
+ audience: r.audience ?? null,
35
+ currentVersionId: r.currentVersionId,
36
+ createdById: r.createdById,
37
+ createdAt: iso(r.createdAt),
38
+ updatedAt: iso(r.updatedAt)
39
+ });
40
+ var mapVer = (r) => ({
41
+ id: r.id,
42
+ documentId: r.documentId,
43
+ version: r.version,
44
+ sourceObjectKey: r.sourceObjectKey,
45
+ sourceSha256: r.sourceSha256,
46
+ placement: r.placement,
47
+ changeNote: r.changeNote,
48
+ createdById: r.createdById,
49
+ createdAt: iso(r.createdAt)
50
+ });
51
+ var mapCamp = (r) => ({
52
+ id: r.id,
53
+ documentId: r.documentId,
54
+ documentVersionId: r.documentVersionId,
55
+ scopeId: r.scopeId,
56
+ note: r.note,
57
+ emailReceipt: r.emailReceipt,
58
+ targeting: r.targeting,
59
+ expiresAt: iso(r.expiresAt),
60
+ createdById: r.createdById,
61
+ createdAt: iso(r.createdAt)
62
+ });
63
+ var mapReq = (r) => ({
64
+ id: r.id,
65
+ campaignId: r.campaignId,
66
+ documentId: r.documentId,
67
+ documentVersionId: r.documentVersionId,
68
+ scopeId: r.scopeId,
69
+ subjectType: r.subjectType,
70
+ subjectId: r.subjectId,
71
+ subjectGroup: r.subjectGroup,
72
+ recipientName: r.recipientName,
73
+ recipientEmail: r.recipientEmail,
74
+ recipientWhatsapp: r.recipientWhatsapp,
75
+ channels: r.channels ?? [],
76
+ status: r.status,
77
+ viewedAt: isoN(r.viewedAt),
78
+ signedAt: isoN(r.signedAt),
79
+ declinedAt: isoN(r.declinedAt),
80
+ expiresAt: iso(r.expiresAt),
81
+ revokedAt: isoN(r.revokedAt),
82
+ sealedObjectKey: r.sealedObjectKey,
83
+ sealedSha256: r.sealedSha256,
84
+ createdAt: iso(r.createdAt)
85
+ });
86
+ var emptyCounts = () => Object.fromEntries(ESIGN_STATUSES.map((s) => [s, 0]));
87
+ var totalOf = (c) => ESIGN_STATUSES.reduce((sum, s) => sum + c[s], 0);
88
+ var completion = (c) => {
89
+ const total = totalOf(c);
90
+ return total === 0 ? 0 : c.SIGNED / total;
91
+ };
92
+ function foldBreakdown(rows, key) {
93
+ const map = /* @__PURE__ */ new Map();
94
+ for (const row of rows) {
95
+ const k = row[key];
96
+ let counts = map.get(k);
97
+ if (!counts) {
98
+ counts = emptyCounts();
99
+ map.set(k, counts);
100
+ }
101
+ counts[row.status] += row._count._all;
102
+ }
103
+ return [...map.entries()].map(([value, counts]) => ({
104
+ value,
105
+ counts,
106
+ total: totalOf(counts)
107
+ }));
108
+ }
109
+ var ACTIVE = ["PENDING", "VIEWED"];
110
+ function createPrismaPersistence(prisma) {
111
+ async function statusCounts(where) {
112
+ const rows = await prisma.signingRequest.groupBy({
113
+ by: ["status"],
114
+ where,
115
+ _count: { _all: true }
116
+ });
117
+ const counts = emptyCounts();
118
+ for (const r of rows) counts[r.status] = r._count._all;
119
+ return counts;
120
+ }
121
+ async function outstandingFor(where, now, limit) {
122
+ const rows = await prisma.signingRequest.findMany({
123
+ where: { ...where, status: { in: ACTIVE }, expiresAt: { gt: now } },
124
+ orderBy: { createdAt: "asc" },
125
+ take: limit
126
+ });
127
+ return rows.map((r) => ({
128
+ id: r.id,
129
+ campaignId: r.campaignId,
130
+ documentId: r.documentId,
131
+ subjectType: r.subjectType,
132
+ subjectId: r.subjectId,
133
+ recipientName: r.recipientName,
134
+ channels: r.channels ?? [],
135
+ status: r.status,
136
+ viewedAt: isoN(r.viewedAt),
137
+ createdAt: iso(r.createdAt),
138
+ expiresAt: iso(r.expiresAt)
139
+ }));
140
+ }
141
+ return {
142
+ // Documents
143
+ async createDocument(input) {
144
+ const row = await prisma.signingDocument.create({
145
+ data: {
146
+ id: uid(),
147
+ scopeId: input.scopeId,
148
+ documentType: input.documentType,
149
+ title: input.title,
150
+ audience: input.audience ?? void 0,
151
+ createdById: input.createdById
152
+ }
153
+ });
154
+ return mapDoc(row);
155
+ },
156
+ async getDocument(id) {
157
+ const row = await prisma.signingDocument.findFirst({
158
+ where: { id, deletedAt: null }
159
+ });
160
+ return row ? mapDoc(row) : null;
161
+ },
162
+ async updateDocument(id, patch) {
163
+ const data = {};
164
+ if (patch.title !== void 0) data.title = patch.title;
165
+ if (patch.currentVersionId !== void 0) data.currentVersionId = patch.currentVersionId;
166
+ if (patch.audience !== void 0 && patch.audience !== null) data.audience = patch.audience;
167
+ const row = await prisma.signingDocument.update({ where: { id }, data });
168
+ return mapDoc(row);
169
+ },
170
+ async listDocuments(opts) {
171
+ const rows = await prisma.signingDocument.findMany({
172
+ where: { deletedAt: null, scopeId: opts.scopeId ?? void 0, documentType: opts.documentType ?? void 0 },
173
+ orderBy: { createdAt: "desc" }
174
+ });
175
+ return rows.map(mapDoc);
176
+ },
177
+ // Versions
178
+ async createVersion(input) {
179
+ const row = await prisma.signingDocumentVersion.create({
180
+ data: {
181
+ id: uid(),
182
+ documentId: input.documentId,
183
+ version: input.version,
184
+ sourceObjectKey: input.sourceObjectKey,
185
+ sourceSha256: input.sourceSha256,
186
+ placement: input.placement,
187
+ changeNote: input.changeNote,
188
+ createdById: input.createdById
189
+ }
190
+ });
191
+ return mapVer(row);
192
+ },
193
+ async getVersion(id) {
194
+ const row = await prisma.signingDocumentVersion.findUnique({ where: { id } });
195
+ return row ? mapVer(row) : null;
196
+ },
197
+ async listVersions(documentId) {
198
+ const rows = await prisma.signingDocumentVersion.findMany({
199
+ where: { documentId },
200
+ orderBy: { version: "desc" }
201
+ });
202
+ return rows.map(mapVer);
203
+ },
204
+ async latestVersion(documentId) {
205
+ const row = await prisma.signingDocumentVersion.findFirst({
206
+ where: { documentId },
207
+ orderBy: { version: "desc" }
208
+ });
209
+ return row ? mapVer(row) : null;
210
+ },
211
+ // Campaigns
212
+ async createCampaign(input) {
213
+ const row = await prisma.signingCampaign.create({
214
+ data: {
215
+ id: uid(),
216
+ documentId: input.documentId,
217
+ documentVersionId: input.documentVersionId,
218
+ scopeId: input.scopeId,
219
+ note: input.note,
220
+ emailReceipt: input.emailReceipt,
221
+ targeting: input.targeting,
222
+ expiresAt: input.expiresAt,
223
+ createdById: input.createdById
224
+ }
225
+ });
226
+ return mapCamp(row);
227
+ },
228
+ async getCampaign(id) {
229
+ const row = await prisma.signingCampaign.findFirst({ where: { id, deletedAt: null } });
230
+ return row ? mapCamp(row) : null;
231
+ },
232
+ async listCampaigns(documentId) {
233
+ const rows = await prisma.signingCampaign.findMany({
234
+ where: { documentId, deletedAt: null },
235
+ orderBy: { createdAt: "desc" }
236
+ });
237
+ return rows.map(mapCamp);
238
+ },
239
+ // Requests
240
+ async createRequests(rows) {
241
+ if (rows.length === 0) return;
242
+ await prisma.signingRequest.createMany({
243
+ data: rows.map((r) => ({
244
+ id: r.id,
245
+ campaignId: r.campaignId,
246
+ documentId: r.documentId,
247
+ documentVersionId: r.documentVersionId,
248
+ scopeId: r.scopeId,
249
+ subjectType: r.subjectType,
250
+ subjectId: r.subjectId,
251
+ subjectGroup: r.subjectGroup,
252
+ recipientName: r.recipientName,
253
+ recipientEmail: r.recipientEmail,
254
+ recipientWhatsapp: r.recipientWhatsapp,
255
+ channels: r.channels,
256
+ tokenHash: r.tokenHash,
257
+ expiresAt: r.expiresAt
258
+ }))
259
+ });
260
+ },
261
+ async getRequest(id) {
262
+ const row = await prisma.signingRequest.findUnique({ where: { id } });
263
+ return row ? mapReq(row) : null;
264
+ },
265
+ async findRequestByTokenHash(tokenHash) {
266
+ const row = await prisma.signingRequest.findUnique({ where: { tokenHash } });
267
+ return row ? mapReq(row) : null;
268
+ },
269
+ async updateRequest(id, patch) {
270
+ const row = await prisma.signingRequest.update({ where: { id }, data: patch });
271
+ return mapReq(row);
272
+ },
273
+ async listRequests(opts) {
274
+ const rows = await prisma.signingRequest.findMany({
275
+ where: {
276
+ campaignId: opts.campaignId,
277
+ documentId: opts.documentId,
278
+ subjectType: opts.subjectType,
279
+ subjectId: opts.subjectId
280
+ },
281
+ orderBy: { createdAt: "desc" }
282
+ });
283
+ return rows.map(mapReq);
284
+ },
285
+ async requestSummaryBySubject(documentId) {
286
+ const rows = await prisma.signingRequest.findMany({
287
+ where: { documentId },
288
+ orderBy: { createdAt: "desc" },
289
+ select: {
290
+ subjectId: true,
291
+ status: true,
292
+ signedAt: true,
293
+ version: { select: { version: true } }
294
+ }
295
+ });
296
+ const seen = /* @__PURE__ */ new Map();
297
+ for (const r of rows) {
298
+ if (seen.has(r.subjectId)) continue;
299
+ seen.set(r.subjectId, {
300
+ subjectId: r.subjectId,
301
+ status: r.status,
302
+ version: r.version.version,
303
+ signedAt: isoN(r.signedAt)
304
+ });
305
+ }
306
+ return [...seen.values()];
307
+ },
308
+ // Audit chain
309
+ async appendAuditEvent(row) {
310
+ const created = await prisma.signingAuditEvent.create({
311
+ data: {
312
+ id: uid(),
313
+ requestId: row.requestId,
314
+ seq: row.seq,
315
+ type: row.type,
316
+ payload: row.payload,
317
+ prevHash: row.prevHash,
318
+ hash: row.hash
319
+ }
320
+ });
321
+ return {
322
+ id: created.id,
323
+ requestId: created.requestId,
324
+ seq: created.seq,
325
+ type: created.type,
326
+ payload: created.payload,
327
+ prevHash: created.prevHash,
328
+ hash: created.hash,
329
+ occurredAt: iso(created.occurredAt)
330
+ };
331
+ },
332
+ async listAuditEvents(requestId) {
333
+ const rows = await prisma.signingAuditEvent.findMany({
334
+ where: { requestId },
335
+ orderBy: { seq: "asc" }
336
+ });
337
+ return rows.map((r) => ({
338
+ id: r.id,
339
+ requestId: r.requestId,
340
+ seq: r.seq,
341
+ type: r.type,
342
+ payload: r.payload,
343
+ prevHash: r.prevHash,
344
+ hash: r.hash,
345
+ occurredAt: iso(r.occurredAt)
346
+ }));
347
+ },
348
+ async lastAuditHash(requestId) {
349
+ const row = await prisma.signingAuditEvent.findFirst({
350
+ where: { requestId },
351
+ orderBy: { seq: "desc" },
352
+ select: { seq: true, hash: true }
353
+ });
354
+ return row ?? null;
355
+ },
356
+ // Green-check + stats
357
+ async subjectSigningStatus(args) {
358
+ const where = {
359
+ subjectType: args.subjectType,
360
+ subjectId: args.subjectId
361
+ };
362
+ if (args.documentId) where.documentId = args.documentId;
363
+ else if (args.documentType) where.document = { documentType: args.documentType };
364
+ const latest = await prisma.signingRequest.findFirst({
365
+ where,
366
+ orderBy: { createdAt: "desc" },
367
+ select: {
368
+ id: true,
369
+ status: true,
370
+ signedAt: true,
371
+ sealedObjectKey: true,
372
+ documentId: true,
373
+ version: { select: { version: true } }
374
+ }
375
+ });
376
+ if (!latest) {
377
+ return { state: "NONE", signedAt: null, signedVersion: null, requestId: null, sealedObjectKey: null };
378
+ }
379
+ const base = {
380
+ signedAt: isoN(latest.signedAt),
381
+ signedVersion: latest.version.version,
382
+ requestId: latest.id,
383
+ sealedObjectKey: latest.sealedObjectKey
384
+ };
385
+ if (latest.status !== "SIGNED") {
386
+ const state = latest.status === "PENDING" || latest.status === "VIEWED" || latest.status === "DECLINED" ? latest.status : "NONE";
387
+ return { state, ...base, signedAt: null };
388
+ }
389
+ if (args.versionPolicy === "any") return { state: "SIGNED", ...base };
390
+ const doc = await prisma.signingDocument.findFirst({
391
+ where: { id: latest.documentId },
392
+ select: { currentVersionId: true }
393
+ });
394
+ let currentNumber = latest.version.version;
395
+ if (doc?.currentVersionId) {
396
+ const cur = await prisma.signingDocumentVersion.findUnique({
397
+ where: { id: doc.currentVersionId },
398
+ select: { version: true }
399
+ });
400
+ if (cur) currentNumber = cur.version;
401
+ }
402
+ return {
403
+ state: latest.version.version >= currentNumber ? "SIGNED" : "NEEDS_RESIGN",
404
+ ...base
405
+ };
406
+ },
407
+ async campaignStats(campaignId) {
408
+ const counts = await statusCounts({ campaignId });
409
+ const total = totalOf(counts);
410
+ const groupRows = await prisma.signingRequest.groupBy({
411
+ by: ["subjectGroup", "status"],
412
+ where: { campaignId },
413
+ _count: { _all: true }
414
+ });
415
+ const signed = await prisma.signingRequest.findMany({
416
+ where: { campaignId, status: "SIGNED" },
417
+ select: { createdAt: true, signedAt: true }
418
+ });
419
+ const durations = signed.filter((r) => r.signedAt).map((r) => r.signedAt.getTime() - r.createdAt.getTime());
420
+ const avgTimeToSignMs = durations.length === 0 ? null : Math.round(durations.reduce((a, b) => a + b, 0) / durations.length);
421
+ const outstanding = await outstandingFor({ campaignId }, /* @__PURE__ */ new Date(0), 100).catch(() => []);
422
+ return {
423
+ campaignId,
424
+ counts,
425
+ total,
426
+ viewedNotSigned: counts.VIEWED,
427
+ completionRate: completion(counts),
428
+ avgTimeToSignMs,
429
+ byGroup: foldBreakdown(groupRows, "subjectGroup").map(
430
+ (b) => ({ group: b.value, counts: b.counts, total: b.total })
431
+ ),
432
+ outstanding
433
+ };
434
+ },
435
+ async documentStats(documentId) {
436
+ const counts = await statusCounts({ documentId });
437
+ const groupRows = await prisma.signingRequest.groupBy({
438
+ by: ["subjectGroup", "status"],
439
+ where: { documentId },
440
+ _count: { _all: true }
441
+ });
442
+ const typeRows = await prisma.signingRequest.groupBy({
443
+ by: ["subjectType", "status"],
444
+ where: { documentId },
445
+ _count: { _all: true }
446
+ });
447
+ return {
448
+ documentId,
449
+ counts,
450
+ total: totalOf(counts),
451
+ completionRate: completion(counts),
452
+ byGroup: foldBreakdown(groupRows, "subjectGroup").map(
453
+ (b) => ({ group: b.value, counts: b.counts, total: b.total })
454
+ ),
455
+ bySubjectType: foldBreakdown(typeRows, "subjectType").map((b) => ({
456
+ subjectType: b.value ?? "",
457
+ counts: b.counts,
458
+ total: b.total
459
+ }))
460
+ };
461
+ },
462
+ async scopeStats(scopeId, range) {
463
+ const createdAt = range?.from || range?.to ? { gte: range?.from ?? void 0, lte: range?.to ?? void 0 } : void 0;
464
+ const reqWhere = { scopeId: scopeId ?? void 0, createdAt };
465
+ const counts = await statusCounts(reqWhere);
466
+ const campaignsSent = await prisma.signingCampaign.count({
467
+ where: { scopeId: scopeId ?? void 0, deletedAt: null, createdAt }
468
+ });
469
+ const typeRows = await prisma.signingRequest.groupBy({
470
+ by: ["subjectType", "status"],
471
+ where: reqWhere,
472
+ _count: { _all: true }
473
+ });
474
+ const oldestOutstanding = await outstandingFor(
475
+ { scopeId: scopeId ?? void 0 },
476
+ /* @__PURE__ */ new Date(0),
477
+ 20
478
+ );
479
+ return {
480
+ campaignsSent,
481
+ requestsSent: totalOf(counts),
482
+ counts,
483
+ completionRate: completion(counts),
484
+ bySubjectType: foldBreakdown(typeRows, "subjectType").map((b) => ({
485
+ subjectType: b.value ?? "",
486
+ counts: b.counts,
487
+ total: b.total
488
+ })),
489
+ oldestOutstanding
490
+ };
491
+ },
492
+ async outstandingRequests(filter) {
493
+ return outstandingFor(
494
+ {
495
+ scopeId: filter.scopeId ?? void 0,
496
+ documentId: filter.documentId,
497
+ campaignId: filter.campaignId
498
+ },
499
+ filter.now,
500
+ filter.limit ?? 100
501
+ );
502
+ }
503
+ };
504
+ }
505
+
506
+ export { createPrismaPersistence };
507
+ //# sourceMappingURL=index.js.map
508
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/server/flow.ts","../../src/server/uid.ts","../../src/adapters/prisma/adapter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { EsignError } from \"./errors\";\n\n/**\n * Lifecycle of a single signing request. PENDING/VIEWED are the only active\n * states; SIGNED/DECLINED/EXPIRED/REVOKED are terminal. The submit handler is\n * idempotent, so a no-op transition to the same state is always allowed.\n */\nexport type EsignStatus =\n | \"PENDING\"\n | \"VIEWED\"\n | \"SIGNED\"\n | \"DECLINED\"\n | \"EXPIRED\"\n | \"REVOKED\";\n\nexport const ESIGN_STATUSES: readonly EsignStatus[] = [\n \"PENDING\",\n \"VIEWED\",\n \"SIGNED\",\n \"DECLINED\",\n \"EXPIRED\",\n \"REVOKED\",\n];\n\n/** Active (still-actionable) states — eligible for view/sign/decline. */\nexport const ACTIVE_STATUSES: readonly EsignStatus[] = [\"PENDING\", \"VIEWED\"];\n\n/** Terminal states — no further transitions. */\nexport const TERMINAL_STATUSES: readonly EsignStatus[] = [\n \"SIGNED\",\n \"DECLINED\",\n \"EXPIRED\",\n \"REVOKED\",\n];\n\nconst TRANSITIONS: Record<EsignStatus, readonly EsignStatus[]> = {\n PENDING: [\"VIEWED\", \"SIGNED\", \"DECLINED\", \"EXPIRED\", \"REVOKED\"],\n VIEWED: [\"SIGNED\", \"DECLINED\", \"EXPIRED\", \"REVOKED\"],\n SIGNED: [],\n DECLINED: [],\n EXPIRED: [],\n REVOKED: [],\n};\n\nexport function isActive(status: EsignStatus): boolean {\n return ACTIVE_STATUSES.includes(status);\n}\n\nexport function isTerminal(status: EsignStatus): boolean {\n return TERMINAL_STATUSES.includes(status);\n}\n\n/** Whether `from → to` is a legal move (a same-state no-op counts as legal). */\nexport function canTransition(from: EsignStatus, to: EsignStatus): boolean {\n if (from === to) return true;\n return TRANSITIONS[from].includes(to);\n}\n\n/** Throw `EsignError('CONFLICT')` on an illegal transition. */\nexport function assertTransition(from: EsignStatus, to: EsignStatus): void {\n if (!canTransition(from, to)) {\n throw new EsignError(\n \"CONFLICT\",\n `Illegal signing-request transition: ${from} → ${to}.`,\n );\n }\n}\n","import { randomBytes } from \"node:crypto\";\n\n// 62-char URL-safe alphabet. 16 chars ≈ 95 bits of entropy — ample for row ids.\nconst ALPHABET =\n \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\n/**\n * A 16-char unique id, matching the host `@db.VarChar(16)` convention. The\n * package generates ids in code (the Prisma fragment carries no `@default`), so\n * every esign-owned row gets a uid the same way across dhm-estates / flc-missions\n * / flc-hr. Rejection sampling keeps the distribution uniform (no modulo bias).\n */\nexport function uid(size = 16): string {\n let out = \"\";\n while (out.length < size) {\n for (const byte of randomBytes(size)) {\n if (byte < 248) {\n // 248 = floor(256 / 62) * 62 — discard the biased tail.\n out += ALPHABET[byte % 62];\n if (out.length === size) break;\n }\n }\n }\n return out;\n}\n","import type { EsignStatus } from \"../../server/flow\";\nimport type { NewRequestRow, PersistencePort, RequestSummary } from \"../../server/ports\";\nimport { ESIGN_STATUSES } from \"../../server/flow\";\nimport { uid } from \"../../server/uid\";\nimport type {\n CampaignStatsRow,\n DocumentStatsRow,\n EsignChannel,\n GroupBreakdown,\n OutstandingRequest,\n Placement,\n ScopeStatsRow,\n SigningAuditEventDTO,\n SigningCampaignDTO,\n SigningDocumentDTO,\n SigningDocumentVersionDTO,\n SigningRequestDTO,\n StatsRange,\n StatusCountMap,\n SubjectSelection,\n SubjectSigningStatus,\n} from \"../../server/types\";\n\n// Structural Prisma client — accepts any client exposing the five esign\n// delegates. Keeps `@prisma/client` out of the package's dependency graph\n// (mirrors @firstlovecenter/milestone-grid). The host passes its own `prisma`.\ninterface Delegate {\n create(args: { data: unknown }): Promise<unknown>;\n createMany(args: { data: unknown[] }): Promise<unknown>;\n findUnique(args: { where: unknown; select?: unknown }): Promise<unknown>;\n findFirst(args: {\n where?: unknown;\n orderBy?: unknown;\n select?: unknown;\n }): Promise<unknown>;\n findMany(args: {\n where?: unknown;\n orderBy?: unknown;\n take?: number;\n select?: unknown;\n }): Promise<unknown[]>;\n update(args: { where: unknown; data: unknown }): Promise<unknown>;\n count(args: { where?: unknown }): Promise<number>;\n groupBy(args: { by: string[]; where?: unknown; _count?: unknown }): Promise<unknown[]>;\n}\n\nexport interface PrismaLike {\n signingDocument: Delegate;\n signingDocumentVersion: Delegate;\n signingCampaign: Delegate;\n signingRequest: Delegate;\n signingAuditEvent: Delegate;\n}\n\nconst iso = (d: Date): string => d.toISOString();\nconst isoN = (d: Date | null): string | null => (d ? d.toISOString() : null);\n\ninterface DocRow {\n id: string;\n scopeId: string | null;\n documentType: string | null;\n title: string;\n audience: unknown;\n currentVersionId: string | null;\n createdById: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\nconst mapDoc = (r: DocRow): SigningDocumentDTO => ({\n id: r.id,\n scopeId: r.scopeId,\n documentType: r.documentType,\n title: r.title,\n audience: (r.audience as SubjectSelection | null) ?? null,\n currentVersionId: r.currentVersionId,\n createdById: r.createdById,\n createdAt: iso(r.createdAt),\n updatedAt: iso(r.updatedAt),\n});\n\ninterface VerRow {\n id: string;\n documentId: string;\n version: number;\n sourceObjectKey: string;\n sourceSha256: string;\n placement: unknown;\n changeNote: string | null;\n createdById: string | null;\n createdAt: Date;\n}\nconst mapVer = (r: VerRow): SigningDocumentVersionDTO => ({\n id: r.id,\n documentId: r.documentId,\n version: r.version,\n sourceObjectKey: r.sourceObjectKey,\n sourceSha256: r.sourceSha256,\n placement: r.placement as Placement,\n changeNote: r.changeNote,\n createdById: r.createdById,\n createdAt: iso(r.createdAt),\n});\n\ninterface CampRow {\n id: string;\n documentId: string;\n documentVersionId: string;\n scopeId: string | null;\n note: string | null;\n emailReceipt: boolean;\n targeting: unknown;\n expiresAt: Date;\n createdById: string | null;\n createdAt: Date;\n}\nconst mapCamp = (r: CampRow): SigningCampaignDTO => ({\n id: r.id,\n documentId: r.documentId,\n documentVersionId: r.documentVersionId,\n scopeId: r.scopeId,\n note: r.note,\n emailReceipt: r.emailReceipt,\n targeting: r.targeting as SubjectSelection,\n expiresAt: iso(r.expiresAt),\n createdById: r.createdById,\n createdAt: iso(r.createdAt),\n});\n\ninterface ReqRow {\n id: string;\n campaignId: string;\n documentId: string;\n documentVersionId: string;\n scopeId: string | null;\n subjectType: string;\n subjectId: string;\n subjectGroup: string | null;\n recipientName: string;\n recipientEmail: string | null;\n recipientWhatsapp: string | null;\n channels: unknown;\n status: EsignStatus;\n viewedAt: Date | null;\n signedAt: Date | null;\n declinedAt: Date | null;\n expiresAt: Date;\n revokedAt: Date | null;\n sealedObjectKey: string | null;\n sealedSha256: string | null;\n createdAt: Date;\n}\nconst mapReq = (r: ReqRow): SigningRequestDTO => ({\n id: r.id,\n campaignId: r.campaignId,\n documentId: r.documentId,\n documentVersionId: r.documentVersionId,\n scopeId: r.scopeId,\n subjectType: r.subjectType,\n subjectId: r.subjectId,\n subjectGroup: r.subjectGroup,\n recipientName: r.recipientName,\n recipientEmail: r.recipientEmail,\n recipientWhatsapp: r.recipientWhatsapp,\n channels: (r.channels as EsignChannel[]) ?? [],\n status: r.status,\n viewedAt: isoN(r.viewedAt),\n signedAt: isoN(r.signedAt),\n declinedAt: isoN(r.declinedAt),\n expiresAt: iso(r.expiresAt),\n revokedAt: isoN(r.revokedAt),\n sealedObjectKey: r.sealedObjectKey,\n sealedSha256: r.sealedSha256,\n createdAt: iso(r.createdAt),\n});\n\nconst emptyCounts = (): StatusCountMap =>\n Object.fromEntries(ESIGN_STATUSES.map((s) => [s, 0])) as StatusCountMap;\n\ninterface GroupRow {\n status: EsignStatus;\n _count: { _all: number };\n}\nconst totalOf = (c: StatusCountMap): number =>\n ESIGN_STATUSES.reduce((sum, s) => sum + c[s], 0);\nconst completion = (c: StatusCountMap): number => {\n const total = totalOf(c);\n return total === 0 ? 0 : c.SIGNED / total;\n};\n\n/** Fold groupBy([key,status]) rows into per-key status-count breakdowns. */\nfunction foldBreakdown<K extends string>(\n rows: ({ status: EsignStatus; _count: { _all: number } } & Record<K, string | null>)[],\n key: K,\n): { value: string | null; counts: StatusCountMap; total: number }[] {\n const map = new Map<string | null, StatusCountMap>();\n for (const row of rows) {\n const k = row[key];\n let counts = map.get(k);\n if (!counts) {\n counts = emptyCounts();\n map.set(k, counts);\n }\n counts[row.status] += row._count._all;\n }\n return [...map.entries()].map(([value, counts]) => ({\n value,\n counts,\n total: totalOf(counts),\n }));\n}\n\nconst ACTIVE: EsignStatus[] = [\"PENDING\", \"VIEWED\"];\n\nexport function createPrismaPersistence(prisma: PrismaLike): PersistencePort {\n async function statusCounts(where: unknown): Promise<StatusCountMap> {\n const rows = (await prisma.signingRequest.groupBy({\n by: [\"status\"],\n where,\n _count: { _all: true },\n })) as GroupRow[];\n const counts = emptyCounts();\n for (const r of rows) counts[r.status] = r._count._all;\n return counts;\n }\n\n async function outstandingFor(\n where: Record<string, unknown>,\n now: Date,\n limit: number,\n ): Promise<OutstandingRequest[]> {\n const rows = (await prisma.signingRequest.findMany({\n where: { ...where, status: { in: ACTIVE }, expiresAt: { gt: now } },\n orderBy: { createdAt: \"asc\" },\n take: limit,\n })) as ReqRow[];\n return rows.map((r) => ({\n id: r.id,\n campaignId: r.campaignId,\n documentId: r.documentId,\n subjectType: r.subjectType,\n subjectId: r.subjectId,\n recipientName: r.recipientName,\n channels: (r.channels as EsignChannel[]) ?? [],\n status: r.status as \"PENDING\" | \"VIEWED\",\n viewedAt: isoN(r.viewedAt),\n createdAt: iso(r.createdAt),\n expiresAt: iso(r.expiresAt),\n }));\n }\n\n return {\n // Documents\n async createDocument(input) {\n const row = (await prisma.signingDocument.create({\n data: {\n id: uid(),\n scopeId: input.scopeId,\n documentType: input.documentType,\n title: input.title,\n audience: input.audience ?? undefined,\n createdById: input.createdById,\n },\n })) as DocRow;\n return mapDoc(row);\n },\n async getDocument(id) {\n const row = (await prisma.signingDocument.findFirst({\n where: { id, deletedAt: null },\n })) as DocRow | null;\n return row ? mapDoc(row) : null;\n },\n async updateDocument(id, patch) {\n const data: Record<string, unknown> = {};\n if (patch.title !== undefined) data.title = patch.title;\n if (patch.currentVersionId !== undefined) data.currentVersionId = patch.currentVersionId;\n if (patch.audience !== undefined && patch.audience !== null) data.audience = patch.audience;\n const row = (await prisma.signingDocument.update({ where: { id }, data })) as DocRow;\n return mapDoc(row);\n },\n async listDocuments(opts) {\n const rows = (await prisma.signingDocument.findMany({\n where: { deletedAt: null, scopeId: opts.scopeId ?? undefined, documentType: opts.documentType ?? undefined },\n orderBy: { createdAt: \"desc\" },\n })) as DocRow[];\n return rows.map(mapDoc);\n },\n\n // Versions\n async createVersion(input) {\n const row = (await prisma.signingDocumentVersion.create({\n data: {\n id: uid(),\n documentId: input.documentId,\n version: input.version,\n sourceObjectKey: input.sourceObjectKey,\n sourceSha256: input.sourceSha256,\n placement: input.placement,\n changeNote: input.changeNote,\n createdById: input.createdById,\n },\n })) as VerRow;\n return mapVer(row);\n },\n async getVersion(id) {\n const row = (await prisma.signingDocumentVersion.findUnique({ where: { id } })) as VerRow | null;\n return row ? mapVer(row) : null;\n },\n async listVersions(documentId) {\n const rows = (await prisma.signingDocumentVersion.findMany({\n where: { documentId },\n orderBy: { version: \"desc\" },\n })) as VerRow[];\n return rows.map(mapVer);\n },\n async latestVersion(documentId) {\n const row = (await prisma.signingDocumentVersion.findFirst({\n where: { documentId },\n orderBy: { version: \"desc\" },\n })) as VerRow | null;\n return row ? mapVer(row) : null;\n },\n\n // Campaigns\n async createCampaign(input) {\n const row = (await prisma.signingCampaign.create({\n data: {\n id: uid(),\n documentId: input.documentId,\n documentVersionId: input.documentVersionId,\n scopeId: input.scopeId,\n note: input.note,\n emailReceipt: input.emailReceipt,\n targeting: input.targeting,\n expiresAt: input.expiresAt,\n createdById: input.createdById,\n },\n })) as CampRow;\n return mapCamp(row);\n },\n async getCampaign(id) {\n const row = (await prisma.signingCampaign.findFirst({ where: { id, deletedAt: null } })) as CampRow | null;\n return row ? mapCamp(row) : null;\n },\n async listCampaigns(documentId) {\n const rows = (await prisma.signingCampaign.findMany({\n where: { documentId, deletedAt: null },\n orderBy: { createdAt: \"desc\" },\n })) as CampRow[];\n return rows.map(mapCamp);\n },\n\n // Requests\n async createRequests(rows: NewRequestRow[]) {\n if (rows.length === 0) return;\n await prisma.signingRequest.createMany({\n data: rows.map((r) => ({\n id: r.id,\n campaignId: r.campaignId,\n documentId: r.documentId,\n documentVersionId: r.documentVersionId,\n scopeId: r.scopeId,\n subjectType: r.subjectType,\n subjectId: r.subjectId,\n subjectGroup: r.subjectGroup,\n recipientName: r.recipientName,\n recipientEmail: r.recipientEmail,\n recipientWhatsapp: r.recipientWhatsapp,\n channels: r.channels,\n tokenHash: r.tokenHash,\n expiresAt: r.expiresAt,\n })),\n });\n },\n async getRequest(id) {\n const row = (await prisma.signingRequest.findUnique({ where: { id } })) as ReqRow | null;\n return row ? mapReq(row) : null;\n },\n async findRequestByTokenHash(tokenHash) {\n const row = (await prisma.signingRequest.findUnique({ where: { tokenHash } })) as ReqRow | null;\n return row ? mapReq(row) : null;\n },\n async updateRequest(id, patch) {\n const row = (await prisma.signingRequest.update({ where: { id }, data: patch })) as ReqRow;\n return mapReq(row);\n },\n async listRequests(opts) {\n const rows = (await prisma.signingRequest.findMany({\n where: {\n campaignId: opts.campaignId,\n documentId: opts.documentId,\n subjectType: opts.subjectType,\n subjectId: opts.subjectId,\n },\n orderBy: { createdAt: \"desc\" },\n })) as ReqRow[];\n return rows.map(mapReq);\n },\n async requestSummaryBySubject(documentId): Promise<RequestSummary[]> {\n const rows = (await prisma.signingRequest.findMany({\n where: { documentId },\n orderBy: { createdAt: \"desc\" },\n select: {\n subjectId: true,\n status: true,\n signedAt: true,\n version: { select: { version: true } },\n },\n })) as {\n subjectId: string;\n status: EsignStatus;\n signedAt: Date | null;\n version: { version: number };\n }[];\n const seen = new Map<string, RequestSummary>();\n for (const r of rows) {\n if (seen.has(r.subjectId)) continue; // first = latest (desc order)\n seen.set(r.subjectId, {\n subjectId: r.subjectId,\n status: r.status,\n version: r.version.version,\n signedAt: isoN(r.signedAt),\n });\n }\n return [...seen.values()];\n },\n\n // Audit chain\n async appendAuditEvent(row) {\n const created = (await prisma.signingAuditEvent.create({\n data: {\n id: uid(),\n requestId: row.requestId,\n seq: row.seq,\n type: row.type,\n payload: row.payload,\n prevHash: row.prevHash,\n hash: row.hash,\n },\n })) as {\n id: string;\n requestId: string;\n seq: number;\n type: string;\n payload: unknown;\n prevHash: string | null;\n hash: string;\n occurredAt: Date;\n };\n return {\n id: created.id,\n requestId: created.requestId,\n seq: created.seq,\n type: created.type,\n payload: created.payload,\n prevHash: created.prevHash,\n hash: created.hash,\n occurredAt: iso(created.occurredAt),\n } satisfies SigningAuditEventDTO;\n },\n async listAuditEvents(requestId) {\n const rows = (await prisma.signingAuditEvent.findMany({\n where: { requestId },\n orderBy: { seq: \"asc\" },\n })) as {\n id: string;\n requestId: string;\n seq: number;\n type: string;\n payload: unknown;\n prevHash: string | null;\n hash: string;\n occurredAt: Date;\n }[];\n return rows.map((r) => ({\n id: r.id,\n requestId: r.requestId,\n seq: r.seq,\n type: r.type,\n payload: r.payload,\n prevHash: r.prevHash,\n hash: r.hash,\n occurredAt: iso(r.occurredAt),\n }));\n },\n async lastAuditHash(requestId) {\n const row = (await prisma.signingAuditEvent.findFirst({\n where: { requestId },\n orderBy: { seq: \"desc\" },\n select: { seq: true, hash: true },\n })) as { seq: number; hash: string } | null;\n return row ?? null;\n },\n\n // Green-check + stats\n async subjectSigningStatus(args): Promise<SubjectSigningStatus> {\n const where: Record<string, unknown> = {\n subjectType: args.subjectType,\n subjectId: args.subjectId,\n };\n if (args.documentId) where.documentId = args.documentId;\n else if (args.documentType) where.document = { documentType: args.documentType };\n\n const latest = (await prisma.signingRequest.findFirst({\n where,\n orderBy: { createdAt: \"desc\" },\n select: {\n id: true,\n status: true,\n signedAt: true,\n sealedObjectKey: true,\n documentId: true,\n version: { select: { version: true } },\n },\n })) as {\n id: string;\n status: EsignStatus;\n signedAt: Date | null;\n sealedObjectKey: string | null;\n documentId: string;\n version: { version: number };\n } | null;\n\n if (!latest) {\n return { state: \"NONE\", signedAt: null, signedVersion: null, requestId: null, sealedObjectKey: null };\n }\n\n const base = {\n signedAt: isoN(latest.signedAt),\n signedVersion: latest.version.version,\n requestId: latest.id,\n sealedObjectKey: latest.sealedObjectKey,\n };\n\n if (latest.status !== \"SIGNED\") {\n const state =\n latest.status === \"PENDING\" || latest.status === \"VIEWED\" || latest.status === \"DECLINED\"\n ? latest.status\n : \"NONE\";\n return { state, ...base, signedAt: null };\n }\n\n if (args.versionPolicy === \"any\") return { state: \"SIGNED\", ...base };\n\n // \"current\": needs re-sign if signed an older version than the doc's current.\n const doc = (await prisma.signingDocument.findFirst({\n where: { id: latest.documentId },\n select: { currentVersionId: true },\n })) as { currentVersionId: string | null } | null;\n let currentNumber = latest.version.version;\n if (doc?.currentVersionId) {\n const cur = (await prisma.signingDocumentVersion.findUnique({\n where: { id: doc.currentVersionId },\n select: { version: true },\n })) as { version: number } | null;\n if (cur) currentNumber = cur.version;\n }\n return {\n state: latest.version.version >= currentNumber ? \"SIGNED\" : \"NEEDS_RESIGN\",\n ...base,\n };\n },\n\n async campaignStats(campaignId): Promise<CampaignStatsRow> {\n const counts = await statusCounts({ campaignId });\n const total = totalOf(counts);\n const groupRows = (await prisma.signingRequest.groupBy({\n by: [\"subjectGroup\", \"status\"],\n where: { campaignId },\n _count: { _all: true },\n })) as (GroupRow & { subjectGroup: string | null })[];\n const signed = (await prisma.signingRequest.findMany({\n where: { campaignId, status: \"SIGNED\" },\n select: { createdAt: true, signedAt: true },\n })) as { createdAt: Date; signedAt: Date | null }[];\n const durations = signed\n .filter((r) => r.signedAt)\n .map((r) => r.signedAt!.getTime() - r.createdAt.getTime());\n const avgTimeToSignMs =\n durations.length === 0 ? null : Math.round(durations.reduce((a, b) => a + b, 0) / durations.length);\n const outstanding = await outstandingFor({ campaignId }, new Date(0), 100).catch(() => []);\n\n return {\n campaignId,\n counts,\n total,\n viewedNotSigned: counts.VIEWED,\n completionRate: completion(counts),\n avgTimeToSignMs,\n byGroup: foldBreakdown(groupRows, \"subjectGroup\").map(\n (b): GroupBreakdown => ({ group: b.value, counts: b.counts, total: b.total }),\n ),\n outstanding,\n };\n },\n\n async documentStats(documentId): Promise<DocumentStatsRow> {\n const counts = await statusCounts({ documentId });\n const groupRows = (await prisma.signingRequest.groupBy({\n by: [\"subjectGroup\", \"status\"],\n where: { documentId },\n _count: { _all: true },\n })) as (GroupRow & { subjectGroup: string | null })[];\n const typeRows = (await prisma.signingRequest.groupBy({\n by: [\"subjectType\", \"status\"],\n where: { documentId },\n _count: { _all: true },\n })) as (GroupRow & { subjectType: string })[];\n return {\n documentId,\n counts,\n total: totalOf(counts),\n completionRate: completion(counts),\n byGroup: foldBreakdown(groupRows, \"subjectGroup\").map(\n (b): GroupBreakdown => ({ group: b.value, counts: b.counts, total: b.total }),\n ),\n bySubjectType: foldBreakdown(typeRows, \"subjectType\").map((b) => ({\n subjectType: b.value ?? \"\",\n counts: b.counts,\n total: b.total,\n })),\n };\n },\n\n async scopeStats(scopeId, range?: StatsRange): Promise<ScopeStatsRow> {\n const createdAt =\n range?.from || range?.to\n ? { gte: range?.from ?? undefined, lte: range?.to ?? undefined }\n : undefined;\n const reqWhere = { scopeId: scopeId ?? undefined, createdAt };\n const counts = await statusCounts(reqWhere);\n const campaignsSent = await prisma.signingCampaign.count({\n where: { scopeId: scopeId ?? undefined, deletedAt: null, createdAt },\n });\n const typeRows = (await prisma.signingRequest.groupBy({\n by: [\"subjectType\", \"status\"],\n where: reqWhere,\n _count: { _all: true },\n })) as (GroupRow & { subjectType: string })[];\n const oldestOutstanding = await outstandingFor(\n { scopeId: scopeId ?? undefined },\n new Date(0),\n 20,\n );\n return {\n campaignsSent,\n requestsSent: totalOf(counts),\n counts,\n completionRate: completion(counts),\n bySubjectType: foldBreakdown(typeRows, \"subjectType\").map((b) => ({\n subjectType: b.value ?? \"\",\n counts: b.counts,\n total: b.total,\n })),\n oldestOutstanding,\n };\n },\n\n async outstandingRequests(filter) {\n return outstandingFor(\n {\n scopeId: filter.scopeId ?? undefined,\n documentId: filter.documentId,\n campaignId: filter.campaignId,\n },\n filter.now,\n filter.limit ?? 100,\n );\n },\n };\n}\n"]}