@memberjunction/lists 0.0.1 → 5.37.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.
@@ -0,0 +1,568 @@
1
+ import { LogError, Metadata, RunView, } from '@memberjunction/core';
2
+ import { AuditLogTypeEngine, CreateShareNotification, ResourceTypeEngine, } from '@memberjunction/core-entities';
3
+ /**
4
+ * @memberjunction/lists — sharing surface.
5
+ *
6
+ * Sharing has two parallel paths:
7
+ *
8
+ * 1. **Direct share** — the recipient already has an MJ user account.
9
+ * We write a row to `MJResourcePermission` immediately, scoped to
10
+ * the List resource type. Either a user (`Type='User'`) or a role
11
+ * (`Type='Role'`) target.
12
+ *
13
+ * 2. **Email invitation** — the recipient may or may not have an
14
+ * account yet. We write a row to `MJ: List Invitations` with a
15
+ * signed token + expiry; `AcceptInvitation` later promotes that
16
+ * pending invite into a real `MJResourcePermission`.
17
+ *
18
+ * Every mutation also emits an `MJ: Audit Logs` entry — type names are
19
+ * resolved through `AuditLogTypeEngine.ByName(...)` at runtime so the
20
+ * seed data is the source of truth, not hardcoded UUIDs. Same pattern
21
+ * (`ResourceTypeEngine.ByName(...)`) for the `MJ: Resource Types` row
22
+ * that scopes `MJResourcePermission` records to the List resource type.
23
+ */
24
+ /** Default invitation TTL: 7 days. The plan calls for tunable expiry but
25
+ * keeps the spec simple — overridable at call time. */
26
+ export const DEFAULT_INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
27
+ /**
28
+ * Name of the row in `MJ: Resource Types` that scopes List shares.
29
+ * Centralized so a rename in the seed data is a one-line fix here, not
30
+ * a grep across the codebase.
31
+ */
32
+ const LIST_RESOURCE_TYPE_NAME = 'Lists';
33
+ /** Names of the rows in `MJ: Audit Log Types` for the seven list-sharing events. */
34
+ const LIST_AUDIT_LOG_TYPE_NAMES = {
35
+ Shared: 'List Shared',
36
+ Unshared: 'List Unshared',
37
+ InvitationSent: 'List Invitation Sent',
38
+ InvitationAccepted: 'List Invitation Accepted',
39
+ InvitationRevoked: 'List Invitation Revoked',
40
+ };
41
+ // ---------------------------------------------------------------------------
42
+ // ListSharing service
43
+ // ---------------------------------------------------------------------------
44
+ /**
45
+ * Sharing engine. Public methods follow MJ PascalCase convention; private
46
+ * helpers are camelCase. Mirrors the structure of `ListOperations`.
47
+ */
48
+ export class ListSharing {
49
+ constructor(contextUser, provider) {
50
+ this.contextUser = contextUser;
51
+ this.provider = provider;
52
+ }
53
+ /**
54
+ * Grant a direct share on a list to a user or role. Idempotent at the
55
+ * (list, target) level — re-sharing the same target updates the
56
+ * existing permission row's level rather than creating duplicates.
57
+ */
58
+ async Share(args) {
59
+ try {
60
+ const md = this.metadata();
61
+ const existing = await this.findExistingPermission(args.ListID, args.Target);
62
+ const perm = existing ??
63
+ (await md.GetEntityObject('MJ: Resource Permissions', this.contextUser));
64
+ if (!existing) {
65
+ perm.NewRecord();
66
+ perm.ResourceTypeID = await this.getListResourceTypeId();
67
+ perm.ResourceRecordID = args.ListID;
68
+ perm.Type = args.Target.kind === 'user' ? 'User' : 'Role';
69
+ if (args.Target.kind === 'user')
70
+ perm.UserID = args.Target.userId;
71
+ else
72
+ perm.RoleID = args.Target.roleId;
73
+ perm.SharedByUserID = this.contextUser.ID;
74
+ }
75
+ perm.PermissionLevel = args.PermissionLevel;
76
+ perm.Status = 'Approved';
77
+ const ok = await perm.Save();
78
+ if (!ok) {
79
+ return this.shareFailure('UNEXPECTED_ERROR', `Failed to grant permission: ${perm.LatestResult?.CompleteMessage ?? 'unknown'}`);
80
+ }
81
+ await this.emitAuditLog({
82
+ Type: LIST_AUDIT_LOG_TYPE_NAMES.Shared,
83
+ ListID: args.ListID,
84
+ Description: `${this.contextUser.Name ?? this.contextUser.Email} shared list with ${args.Target.kind} ${this.targetId(args.Target)} as ${args.PermissionLevel}`,
85
+ Details: { TargetKind: args.Target.kind, TargetID: this.targetId(args.Target), Level: args.PermissionLevel },
86
+ });
87
+ // Direct user shares route through the platform-wide share-notification
88
+ // dispatcher — `@memberjunction/notifications` auto-registers a handler
89
+ // that fans this out across the user's notification preferences (in-app
90
+ // + email + SMS). Role-target shares are fan-out at the role level by
91
+ // a separate worker; we don't try to enumerate every role member here.
92
+ if (args.Target.kind === 'user' && args.Target.userId !== this.contextUser.ID) {
93
+ await this.dispatchShareNotification(args.ListID, args.Target.userId, args.PermissionLevel);
94
+ }
95
+ return { Success: true, ResultCode: 'SUCCESS', Message: 'Permission granted', PermissionID: perm.ID };
96
+ }
97
+ catch (e) {
98
+ const message = e instanceof Error ? e.message : String(e);
99
+ LogError(`ListSharing.Share failed: ${message}`);
100
+ return this.shareFailure('UNEXPECTED_ERROR', message);
101
+ }
102
+ }
103
+ /**
104
+ * Revoke a previously-granted permission. We soft-revoke (Status =
105
+ * 'Revoked') rather than hard-delete so the audit trail stays
106
+ * meaningful — `GetSharesForList` filters out revoked rows by default.
107
+ */
108
+ async Unshare(permissionId) {
109
+ try {
110
+ const md = this.metadata();
111
+ const perm = await md.GetEntityObject('MJ: Resource Permissions', this.contextUser);
112
+ const loaded = await perm.Load(permissionId);
113
+ if (!loaded)
114
+ return this.shareFailure('PERMISSION_NOT_FOUND', `Permission ${permissionId} not found`);
115
+ const listId = perm.ResourceRecordID;
116
+ const targetSnapshot = this.snapshotTarget(perm);
117
+ perm.Status = 'Revoked';
118
+ const ok = await perm.Save();
119
+ if (!ok) {
120
+ return this.shareFailure('UNEXPECTED_ERROR', `Failed to revoke: ${perm.LatestResult?.CompleteMessage ?? 'unknown'}`);
121
+ }
122
+ await this.emitAuditLog({
123
+ Type: LIST_AUDIT_LOG_TYPE_NAMES.Unshared,
124
+ ListID: listId,
125
+ Description: `${this.contextUser.Name ?? this.contextUser.Email} revoked permission ${permissionId}`,
126
+ Details: targetSnapshot,
127
+ });
128
+ return { Success: true, ResultCode: 'SUCCESS', Message: 'Permission revoked', PermissionID: perm.ID };
129
+ }
130
+ catch (e) {
131
+ const message = e instanceof Error ? e.message : String(e);
132
+ LogError(`ListSharing.Unshare failed: ${message}`);
133
+ return this.shareFailure('UNEXPECTED_ERROR', message);
134
+ }
135
+ }
136
+ /**
137
+ * Send an email invitation to share a list with someone — who may or
138
+ * may not already have an MJ user account. We persist the invitation
139
+ * with a random token + expiry; the recipient later calls
140
+ * `AcceptInvitation(token)` to promote it into a real
141
+ * `MJResourcePermission`.
142
+ */
143
+ async Invite(args) {
144
+ if (!args.Email || !args.Email.includes('@')) {
145
+ return { Success: false, ResultCode: 'INVALID_PARAMETER', Message: 'Email is required' };
146
+ }
147
+ try {
148
+ const md = this.metadata();
149
+ const inv = await md.GetEntityObject('MJ: List Invitations', this.contextUser);
150
+ inv.NewRecord();
151
+ inv.ListID = args.ListID;
152
+ inv.Email = args.Email;
153
+ inv.Role = args.Role;
154
+ inv.Token = this.generateInvitationToken();
155
+ inv.ExpiresAt = new Date(Date.now() + (args.TtlMs ?? DEFAULT_INVITATION_TTL_MS));
156
+ inv.CreatedByUserID = this.contextUser.ID;
157
+ inv.Status = 'Pending';
158
+ const ok = await inv.Save();
159
+ if (!ok) {
160
+ return {
161
+ Success: false,
162
+ ResultCode: 'UNEXPECTED_ERROR',
163
+ Message: `Failed to create invitation: ${inv.LatestResult?.CompleteMessage ?? 'unknown'}`,
164
+ };
165
+ }
166
+ await this.emitAuditLog({
167
+ Type: LIST_AUDIT_LOG_TYPE_NAMES.InvitationSent,
168
+ ListID: args.ListID,
169
+ Description: `${this.contextUser.Name ?? this.contextUser.Email} invited ${args.Email} (${args.Role})`,
170
+ Details: { Email: args.Email, Role: args.Role, InvitationID: inv.ID },
171
+ });
172
+ return {
173
+ Success: true,
174
+ ResultCode: 'SUCCESS',
175
+ Message: `Invitation sent to ${args.Email}`,
176
+ InvitationID: inv.ID,
177
+ Token: inv.Token,
178
+ ExpiresAt: inv.ExpiresAt,
179
+ };
180
+ }
181
+ catch (e) {
182
+ const message = e instanceof Error ? e.message : String(e);
183
+ LogError(`ListSharing.Invite failed: ${message}`);
184
+ return { Success: false, ResultCode: 'UNEXPECTED_ERROR', Message: message };
185
+ }
186
+ }
187
+ /**
188
+ * Accept an invitation by its token. The caller becomes the recipient
189
+ * (we read `contextUser`); the invitation's email must match the
190
+ * caller's email — protects against token interception by a different
191
+ * MJ account.
192
+ *
193
+ * On success: creates the `MJResourcePermission` and marks the
194
+ * invitation `Accepted`.
195
+ */
196
+ async AcceptInvitation(token) {
197
+ try {
198
+ const md = this.metadata();
199
+ const rv = this.runView();
200
+ const lookup = await rv.RunView({
201
+ EntityName: 'MJ: List Invitations',
202
+ ExtraFilter: `Token='${this.escape(token)}'`,
203
+ ResultType: 'entity_object',
204
+ }, this.contextUser);
205
+ if (!lookup.Success || !lookup.Results || lookup.Results.length === 0) {
206
+ return { Success: false, ResultCode: 'INVITATION_NOT_FOUND', Message: 'Invitation not found' };
207
+ }
208
+ const inv = lookup.Results[0];
209
+ if (inv.Status === 'Revoked') {
210
+ return { Success: false, ResultCode: 'INVITATION_REVOKED', Message: 'Invitation has been revoked' };
211
+ }
212
+ if (inv.Status === 'Accepted') {
213
+ return { Success: false, ResultCode: 'INVITATION_ALREADY_USED', Message: 'Invitation has already been accepted' };
214
+ }
215
+ if (inv.ExpiresAt.getTime() < Date.now()) {
216
+ // Mark expired so subsequent reads see consistent state.
217
+ inv.Status = 'Expired';
218
+ await inv.Save();
219
+ return { Success: false, ResultCode: 'INVITATION_EXPIRED', Message: 'Invitation has expired' };
220
+ }
221
+ // Email-match check — best-effort: if context user has no email we
222
+ // skip and rely on the token itself as the auth gate.
223
+ const callerEmail = this.contextUser.Email?.toLowerCase();
224
+ if (callerEmail && inv.Email.toLowerCase() !== callerEmail) {
225
+ return {
226
+ Success: false,
227
+ ResultCode: 'EMAIL_RECIPIENT_NOT_FOUND',
228
+ Message: `Invitation was for ${inv.Email} but caller is ${this.contextUser.Email}`,
229
+ };
230
+ }
231
+ const share = await this.Share({
232
+ ListID: inv.ListID,
233
+ Target: { kind: 'user', userId: this.contextUser.ID },
234
+ PermissionLevel: inv.Role === 'Editor' ? 'Edit' : 'View',
235
+ });
236
+ if (!share.Success) {
237
+ return { Success: false, ResultCode: share.ResultCode, Message: share.Message };
238
+ }
239
+ inv.Status = 'Accepted';
240
+ await inv.Save();
241
+ await this.emitAuditLog({
242
+ Type: LIST_AUDIT_LOG_TYPE_NAMES.InvitationAccepted,
243
+ ListID: inv.ListID,
244
+ Description: `${this.contextUser.Name ?? this.contextUser.Email} accepted invitation ${inv.ID}`,
245
+ Details: { InvitationID: inv.ID, GrantedPermissionID: share.PermissionID },
246
+ });
247
+ return {
248
+ Success: true,
249
+ ResultCode: 'SUCCESS',
250
+ Message: 'Invitation accepted',
251
+ PermissionID: share.PermissionID,
252
+ ListID: inv.ListID,
253
+ };
254
+ }
255
+ catch (e) {
256
+ const message = e instanceof Error ? e.message : String(e);
257
+ LogError(`ListSharing.AcceptInvitation failed: ${message}`);
258
+ return { Success: false, ResultCode: 'UNEXPECTED_ERROR', Message: message };
259
+ }
260
+ }
261
+ /**
262
+ * Revoke a pending invitation. No-op if already accepted (the resulting
263
+ * permission can still be `Unshare`d separately).
264
+ */
265
+ async RevokeInvitation(invitationId) {
266
+ try {
267
+ const md = this.metadata();
268
+ const inv = await md.GetEntityObject('MJ: List Invitations', this.contextUser);
269
+ const loaded = await inv.Load(invitationId);
270
+ if (!loaded)
271
+ return this.shareFailure('INVITATION_NOT_FOUND', `Invitation ${invitationId} not found`);
272
+ if (inv.Status === 'Accepted') {
273
+ return this.shareFailure('INVITATION_ALREADY_USED', 'Invitation already accepted — Unshare the resulting permission instead');
274
+ }
275
+ inv.Status = 'Revoked';
276
+ const ok = await inv.Save();
277
+ if (!ok) {
278
+ return this.shareFailure('UNEXPECTED_ERROR', `Failed to revoke: ${inv.LatestResult?.CompleteMessage ?? 'unknown'}`);
279
+ }
280
+ await this.emitAuditLog({
281
+ Type: LIST_AUDIT_LOG_TYPE_NAMES.InvitationRevoked,
282
+ ListID: inv.ListID,
283
+ Description: `${this.contextUser.Name ?? this.contextUser.Email} revoked invitation ${inv.ID}`,
284
+ Details: { InvitationID: inv.ID, Email: inv.Email },
285
+ });
286
+ return { Success: true, ResultCode: 'SUCCESS', Message: 'Invitation revoked' };
287
+ }
288
+ catch (e) {
289
+ const message = e instanceof Error ? e.message : String(e);
290
+ LogError(`ListSharing.RevokeInvitation failed: ${message}`);
291
+ return this.shareFailure('UNEXPECTED_ERROR', message);
292
+ }
293
+ }
294
+ /**
295
+ * Return the current (non-revoked) shares for a list — both user and
296
+ * role grants, mapped into the canonical `ListShareSummary` shape.
297
+ */
298
+ async GetSharesForList(listId) {
299
+ const resourceTypeId = await this.getListResourceTypeId();
300
+ const rv = this.runView();
301
+ const result = await rv.RunView({
302
+ EntityName: 'MJ: Resource Permissions',
303
+ ExtraFilter: `ResourceTypeID='${resourceTypeId}' AND ResourceRecordID='${this.escape(listId)}' AND Status<>'Revoked'`,
304
+ OrderBy: '__mj_CreatedAt DESC',
305
+ ResultType: 'entity_object',
306
+ }, this.contextUser);
307
+ if (!result.Success || !result.Results)
308
+ return [];
309
+ return result.Results.map((p) => this.toShareSummary(p));
310
+ }
311
+ /**
312
+ * Resolve the effective permission level the context user has on a
313
+ * list. Returns `'Owner'` if they created the list (UserID match);
314
+ * otherwise checks for a direct user share. Role-based shares are
315
+ * resolved by `ResourcePermissionEngine` server-side — we return null
316
+ * here to indicate "no direct share" and let the caller fall back if
317
+ * needed. Phase 2 ships with direct user-shares only.
318
+ */
319
+ async ResolveEffectivePermission(listId) {
320
+ try {
321
+ const md = this.metadata();
322
+ const list = await md.GetEntityObject('MJ: Lists', this.contextUser);
323
+ const loaded = await list.Load(listId);
324
+ if (!loaded)
325
+ return null;
326
+ if (list.UserID === this.contextUser.ID)
327
+ return 'Owner';
328
+ const resourceTypeId = await this.getListResourceTypeId();
329
+ const rv = this.runView();
330
+ const result = await rv.RunView({
331
+ EntityName: 'MJ: Resource Permissions',
332
+ ExtraFilter: `ResourceTypeID='${resourceTypeId}' AND ResourceRecordID='${this.escape(listId)}' AND Type='User' AND UserID='${this.contextUser.ID}' AND Status='Approved'`,
333
+ ResultType: 'entity_object',
334
+ }, this.contextUser);
335
+ if (!result.Success || !result.Results || result.Results.length === 0)
336
+ return null;
337
+ return (result.Results[0].PermissionLevel ?? null);
338
+ }
339
+ catch (e) {
340
+ LogError(`ResolveEffectivePermission failed for list ${listId}: ${e instanceof Error ? e.message : String(e)}`);
341
+ return null;
342
+ }
343
+ }
344
+ /**
345
+ * Return all lists currently shared with the context user. Role-based
346
+ * shares are deferred to a future iteration — Phase 2 ships with
347
+ * direct user-level shares only.
348
+ */
349
+ async GetListsSharedWithUser() {
350
+ const resourceTypeId = await this.getListResourceTypeId();
351
+ const rv = this.runView();
352
+ const result = await rv.RunView({
353
+ EntityName: 'MJ: Resource Permissions',
354
+ ExtraFilter: `ResourceTypeID='${resourceTypeId}' AND Type='User' AND UserID='${this.contextUser.ID}' AND Status='Approved'`,
355
+ OrderBy: '__mj_CreatedAt DESC',
356
+ ResultType: 'entity_object',
357
+ }, this.contextUser);
358
+ if (!result.Success || !result.Results)
359
+ return [];
360
+ // Resolve list names in one pass via a single RunView. Avoids an N+1.
361
+ const listIds = result.Results.map((p) => p.ResourceRecordID);
362
+ if (listIds.length === 0)
363
+ return [];
364
+ const filter = listIds.map((id) => `'${this.escape(id)}'`).join(',');
365
+ const listLookup = await rv.RunView({
366
+ EntityName: 'MJ: Lists',
367
+ ExtraFilter: `ID IN (${filter})`,
368
+ Fields: ['ID', 'Name'],
369
+ ResultType: 'simple',
370
+ }, this.contextUser);
371
+ const nameById = new Map();
372
+ if (listLookup.Success && listLookup.Results) {
373
+ for (const row of listLookup.Results) {
374
+ nameById.set(String(row.ID), String(row.Name));
375
+ }
376
+ }
377
+ return result.Results.map((p) => ({
378
+ ListID: p.ResourceRecordID,
379
+ ListName: nameById.get(p.ResourceRecordID) ?? '(unknown list)',
380
+ PermissionLevel: (p.PermissionLevel ?? 'View'),
381
+ SharedByUserID: p.SharedByUserID,
382
+ SharedAt: p.__mj_CreatedAt,
383
+ }));
384
+ }
385
+ // ---------------------------------------------------------------------
386
+ // Private helpers
387
+ // ---------------------------------------------------------------------
388
+ async findExistingPermission(listId, target) {
389
+ const rv = this.runView();
390
+ const targetClause = target.kind === 'user'
391
+ ? `Type='User' AND UserID='${this.escape(target.userId)}'`
392
+ : `Type='Role' AND RoleID='${this.escape(target.roleId)}'`;
393
+ const resourceTypeId = await this.getListResourceTypeId();
394
+ const result = await rv.RunView({
395
+ EntityName: 'MJ: Resource Permissions',
396
+ ExtraFilter: `ResourceTypeID='${resourceTypeId}' AND ResourceRecordID='${this.escape(listId)}' AND ${targetClause}`,
397
+ ResultType: 'entity_object',
398
+ }, this.contextUser);
399
+ if (!result.Success || !result.Results || result.Results.length === 0)
400
+ return null;
401
+ return result.Results[0];
402
+ }
403
+ toShareSummary(p) {
404
+ const target = p.Type === 'User'
405
+ ? { kind: 'user', userId: p.UserID }
406
+ : { kind: 'role', roleId: p.RoleID };
407
+ return {
408
+ PermissionID: p.ID,
409
+ ListID: p.ResourceRecordID,
410
+ Target: target,
411
+ PermissionLevel: (p.PermissionLevel ?? 'View'),
412
+ Status: p.Status,
413
+ SharedByUserID: p.SharedByUserID,
414
+ CreatedAt: p.__mj_CreatedAt,
415
+ };
416
+ }
417
+ snapshotTarget(p) {
418
+ return {
419
+ TargetKind: p.Type === 'User' ? 'user' : 'role',
420
+ TargetID: p.Type === 'User' ? p.UserID : p.RoleID,
421
+ Level: p.PermissionLevel,
422
+ };
423
+ }
424
+ targetId(target) {
425
+ return target.kind === 'user' ? target.userId : target.roleId;
426
+ }
427
+ /**
428
+ * Generate a 32-byte URL-safe random token for invitations. 256 bits of
429
+ * entropy is overkill for a 7-day TTL but cheap, and keeps the token
430
+ * unguessable even if the database leaks Statuses/Emails separately.
431
+ *
432
+ * Uses the Web Crypto API (`globalThis.crypto.getRandomValues`) so the
433
+ * function works identically in Node 19+ and browser bundles — without
434
+ * the `node:crypto` import that breaks browser-side bundling.
435
+ */
436
+ generateInvitationToken() {
437
+ const bytes = new Uint8Array(32);
438
+ globalThis.crypto.getRandomValues(bytes);
439
+ // Base64-URL: standard base64 then swap `+` `/` and strip `=` padding.
440
+ let binary = '';
441
+ for (let i = 0; i < bytes.length; i++)
442
+ binary += String.fromCharCode(bytes[i]);
443
+ const b64 = (typeof btoa === 'function' ? btoa(binary) : Buffer.from(bytes).toString('base64'));
444
+ return b64.replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
445
+ }
446
+ /**
447
+ * Resolve the `MJ: Resource Types.ID` for the row named "Lists" via the
448
+ * shared `ResourceTypeEngine`. Throws if missing — that indicates a
449
+ * misconfigured environment (seed data never pushed) and we'd rather
450
+ * fail loud than write `MJResourcePermission` rows with an undefined
451
+ * `ResourceTypeID`. Engine `Config()` is idempotent.
452
+ */
453
+ async getListResourceTypeId() {
454
+ await ResourceTypeEngine.Instance.Config(false, this.contextUser, this.provider);
455
+ const rt = ResourceTypeEngine.Instance.ByName(LIST_RESOURCE_TYPE_NAME);
456
+ if (!rt) {
457
+ throw new Error(`MJ: Resource Types row "${LIST_RESOURCE_TYPE_NAME}" not found. ` +
458
+ `Seed metadata/resource-types/ via 'mj sync push' before using ListSharing.`);
459
+ }
460
+ return rt.ID;
461
+ }
462
+ /**
463
+ * Resolve an `MJ: Audit Log Types.ID` by row name via the shared
464
+ * `AuditLogTypeEngine`. Same fail-loud contract as
465
+ * {@link getListResourceTypeId}.
466
+ */
467
+ async getAuditLogTypeId(name) {
468
+ await AuditLogTypeEngine.Instance.Config(false, this.contextUser, this.provider);
469
+ const t = AuditLogTypeEngine.Instance.ByName(name);
470
+ if (!t) {
471
+ throw new Error(`MJ: Audit Log Types row "${name}" not found. ` +
472
+ `Seed metadata/audit-log-types/ via 'mj sync push' before using ListSharing.`);
473
+ }
474
+ return t.ID;
475
+ }
476
+ /**
477
+ * Best-effort audit-log emit. We never fail the parent operation on
478
+ * an audit-log failure; instead we LogError so the discrepancy is
479
+ * observable. Cross-cutting reliability concern.
480
+ *
481
+ * Takes the audit-log-type by NAME so call sites are grep-able and
482
+ * resilient to seed-UUID changes. Name → ID resolution goes through the
483
+ * `AuditLogTypeEngine`.
484
+ */
485
+ async emitAuditLog(args) {
486
+ try {
487
+ const typeId = await this.getAuditLogTypeId(args.Type);
488
+ const md = this.metadata();
489
+ const log = await md.GetEntityObject('MJ: Audit Logs', this.contextUser);
490
+ log.NewRecord();
491
+ log.UserID = this.contextUser.ID;
492
+ log.AuditLogTypeID = typeId;
493
+ log.Status = 'Success';
494
+ log.Description = args.Description;
495
+ log.Details = JSON.stringify(args.Details);
496
+ // EntityID for MJ: Lists via the O(1) by-name map. Entities.find
497
+ // is an O(N) scan and the documented anti-pattern.
498
+ const listEntity = md.EntityByName('MJ: Lists');
499
+ if (listEntity)
500
+ log.EntityID = listEntity.ID;
501
+ log.RecordID = args.ListID;
502
+ const ok = await log.Save();
503
+ if (!ok) {
504
+ LogError(`Audit log save failed: ${log.LatestResult?.CompleteMessage ?? 'unknown'}`);
505
+ }
506
+ }
507
+ catch (e) {
508
+ LogError(`emitAuditLog threw: ${e instanceof Error ? e.message : String(e)}`);
509
+ }
510
+ }
511
+ /**
512
+ * Fire-and-forget share notification via the platform-wide dispatcher.
513
+ * If the recipient has notification preferences for email, MJServer's
514
+ * registered handler will send an email; if they prefer in-app only,
515
+ * a `MJ: User Notifications` row is written instead. Either way the
516
+ * share itself has already succeeded — a notification failure is logged
517
+ * (inside `CreateShareNotification`) but does not propagate.
518
+ *
519
+ * Pre-condition: this is only called for `Target.kind === 'user'` and
520
+ * never for self-shares (the caller guards both).
521
+ */
522
+ async dispatchShareNotification(listId, granteeUserId, level) {
523
+ try {
524
+ const md = this.metadata();
525
+ // Resolve the list name for the notification copy. Best-effort — if
526
+ // it fails, the dispatcher falls back to a generic "shared a list".
527
+ let listName;
528
+ try {
529
+ const list = await md.GetEntityObject('MJ: Lists', this.contextUser);
530
+ const loaded = await list.Load(listId);
531
+ if (loaded)
532
+ listName = list.Name;
533
+ }
534
+ catch {
535
+ // ignore — listName stays undefined
536
+ }
537
+ await CreateShareNotification({
538
+ Provider: this.metadata(),
539
+ ContextUser: this.contextUser,
540
+ GrantorUserID: this.contextUser.ID,
541
+ GranteeUserID: granteeUserId,
542
+ ResourceTypeLabel: 'List',
543
+ ResourceTypeName: 'Lists',
544
+ ResourceName: listName,
545
+ ResourceRecordID: listId,
546
+ ActionsSummary: `granted ${level} access`,
547
+ ExtraConfiguration: { PermissionLevel: level },
548
+ });
549
+ }
550
+ catch (e) {
551
+ LogError(`dispatchShareNotification failed for list ${listId}: ${e instanceof Error ? e.message : String(e)}`);
552
+ }
553
+ }
554
+ shareFailure(code, message) {
555
+ return { Success: false, ResultCode: code, Message: message };
556
+ }
557
+ /** Escape a value for inline-literal use in `ExtraFilter`. */
558
+ escape(value) {
559
+ return value.replace(/'/g, "''");
560
+ }
561
+ metadata() {
562
+ return this.provider ?? new Metadata();
563
+ }
564
+ runView() {
565
+ return this.provider ? RunView.FromMetadataProvider(this.provider) : new RunView();
566
+ }
567
+ }
568
+ //# sourceMappingURL=ListSharing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListSharing.js","sourceRoot":"","sources":["../src/ListSharing.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,QAAQ,EACR,OAAO,GAER,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EAMvB,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAYvC;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;uDACuD;AACvD,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEjE;;;;GAIG;AACH,MAAM,uBAAuB,GAAG,OAAO,CAAC;AAExC,oFAAoF;AACpF,MAAM,yBAAyB,GAAG;IAChC,MAAM,EAAE,aAAa;IACrB,QAAQ,EAAE,eAAe;IACzB,cAAc,EAAE,sBAAsB;IACtC,kBAAkB,EAAE,0BAA0B;IAC9C,iBAAiB,EAAE,yBAAyB;CACpC,CAAC;AAEX,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,OAAO,WAAW;IAItB,YAAY,WAAqB,EAAE,QAA4B;QAC7D,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,KAAK,CAAC,IAIlB;QACC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YAE7E,MAAM,IAAI,GACR,QAAQ;gBACR,CAAC,MAAM,EAAE,CAAC,eAAe,CAA6B,0BAA0B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;YACvG,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,IAAI,CAAC,SAAS,EAAE,CAAC;gBACjB,IAAI,CAAC,cAAc,GAAG,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBACzD,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC;gBACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;gBAC1D,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM;oBAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;;oBAC7D,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;gBACtC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5C,CAAC;YACD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;YAC5C,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC;YAEzB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,+BAA+B,IAAI,CAAC,YAAY,EAAE,eAAe,IAAI,SAAS,EAAE,CAAC,CAAC;YACjI,CAAC;YAED,MAAM,IAAI,CAAC,YAAY,CAAC;gBACtB,IAAI,EAAE,yBAAyB,CAAC,MAAM;gBACtC,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,qBAAqB,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,eAAe,EAAE;gBAC/J,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE;aAC7G,CAAC,CAAC;YAEH,wEAAwE;YACxE,wEAAwE;YACxE,wEAAwE;YACxE,sEAAsE;YACtE,uEAAuE;YACvE,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC;gBAC9E,MAAM,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;YAC9F,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;QACxG,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC3D,QAAQ,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,OAAO,CAAC,YAAoB;QACvC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CAA6B,0BAA0B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YAChH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC7C,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC,YAAY,CAAC,sBAAsB,EAAE,cAAc,YAAY,YAAY,CAAC,CAAC;YAEtG,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC;YACrC,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YACjD,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;YACxB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,qBAAqB,IAAI,CAAC,YAAY,EAAE,eAAe,IAAI,SAAS,EAAE,CAAC,CAAC;YACvH,CAAC;YAED,MAAM,IAAI,CAAC,YAAY,CAAC;gBACtB,IAAI,EAAE,yBAAyB,CAAC,QAAQ;gBACxC,MAAM,EAAE,MAAM;gBACd,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,uBAAuB,YAAY,EAAE;gBACpG,OAAO,EAAE,cAAc;aACxB,CAAC,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;QACxG,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC3D,QAAQ,CAAC,+BAA+B,OAAO,EAAE,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAC,MAAM,CAAC,IAKnB;QACC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC;QAC3F,CAAC;QACD,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,eAAe,CAAyB,sBAAsB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACvG,GAAG,CAAC,SAAS,EAAE,CAAC;YAChB,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YACzB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YACvB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACrB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC3C,GAAG,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,yBAAyB,CAAC,CAAC,CAAC;YACjF,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;YAEvB,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,UAAU,EAAE,kBAAkB;oBAC9B,OAAO,EAAE,gCAAgC,GAAG,CAAC,YAAY,EAAE,eAAe,IAAI,SAAS,EAAE;iBAC1F,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,CAAC,YAAY,CAAC;gBACtB,IAAI,EAAE,yBAAyB,CAAC,cAAc;gBAC9C,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,YAAY,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,GAAG;gBACtG,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,EAAE;aACtE,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,SAAS;gBACrB,OAAO,EAAE,sBAAsB,IAAI,CAAC,KAAK,EAAE;gBAC3C,YAAY,EAAE,GAAG,CAAC,EAAE;gBACpB,KAAK,EAAE,GAAG,CAAC,KAAK;gBAChB,SAAS,EAAE,GAAG,CAAC,SAAS;aACzB,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC3D,QAAQ,CAAC,8BAA8B,OAAO,EAAE,CAAC,CAAC;YAClD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QAC9E,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACI,KAAK,CAAC,gBAAgB,CAAC,KAAa;QACzC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAAyB;gBACtD,UAAU,EAAE,sBAAsB;gBAClC,WAAW,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG;gBAC5C,UAAU,EAAE,eAAe;aAC5B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YAErB,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,OAAO,EAAE,sBAAsB,EAAE,CAAC;YACjG,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAE9B,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC7B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;YACtG,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAC9B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,yBAAyB,EAAE,OAAO,EAAE,sCAAsC,EAAE,CAAC;YACpH,CAAC;YACD,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;gBACzC,yDAAyD;gBACzD,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;gBACvB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBACjB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;YACjG,CAAC;YAED,mEAAmE;YACnE,sDAAsD;YACtD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC;YAC1D,IAAI,WAAW,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,WAAW,EAAE,CAAC;gBAC3D,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,UAAU,EAAE,2BAA2B;oBACvC,OAAO,EAAE,sBAAsB,GAAG,CAAC,KAAK,kBAAkB,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE;iBACnF,CAAC;YACJ,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC;gBAC7B,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE;gBACrD,eAAe,EAAE,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;aACzD,CAAC,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YAClF,CAAC;YAED,GAAG,CAAC,MAAM,GAAG,UAAU,CAAC;YACxB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAEjB,MAAM,IAAI,CAAC,YAAY,CAAC;gBACtB,IAAI,EAAE,yBAAyB,CAAC,kBAAkB;gBAClD,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,wBAAwB,GAAG,CAAC,EAAE,EAAE;gBAC/F,OAAO,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,EAAE,mBAAmB,EAAE,KAAK,CAAC,YAAY,EAAE;aAC3E,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,UAAU,EAAE,SAAS;gBACrB,OAAO,EAAE,qBAAqB;gBAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;gBAChC,MAAM,EAAE,GAAG,CAAC,MAAM;aACnB,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC3D,QAAQ,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;YAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QAC9E,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,gBAAgB,CAAC,YAAoB;QAChD,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,eAAe,CAAyB,sBAAsB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACvG,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC,YAAY,CAAC,sBAAsB,EAAE,cAAc,YAAY,YAAY,CAAC,CAAC;YACtG,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAC9B,OAAO,IAAI,CAAC,YAAY,CACtB,yBAAyB,EACzB,wEAAwE,CACzE,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;YACvB,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,OAAO,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,qBAAqB,GAAG,CAAC,YAAY,EAAE,eAAe,IAAI,SAAS,EAAE,CAAC,CAAC;YACtH,CAAC;YACD,MAAM,IAAI,CAAC,YAAY,CAAC;gBACtB,IAAI,EAAE,yBAAyB,CAAC,iBAAiB;gBACjD,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,WAAW,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,uBAAuB,GAAG,CAAC,EAAE,EAAE;gBAC9F,OAAO,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE;aACpD,CAAC,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;QACjF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC3D,QAAQ,CAAC,wCAAwC,OAAO,EAAE,CAAC,CAAC;YAC5D,OAAO,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,gBAAgB,CAAC,MAAc;QAC1C,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC1D,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAA6B;YAC1D,UAAU,EAAE,0BAA0B;YACtC,WAAW,EAAE,mBAAmB,cAAc,2BAA2B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,yBAAyB;YACrH,OAAO,EAAE,qBAAqB;YAC9B,UAAU,EAAE,eAAe;SAC5B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAClD,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED;;;;;;;OAOG;IACI,KAAK,CAAC,0BAA0B,CAAC,MAAc;QACpD,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CAAe,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACnF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvC,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YACzB,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,WAAW,CAAC,EAAE;gBAAE,OAAO,OAAO,CAAC;YAExD,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC1D,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAA6B;gBAC1D,UAAU,EAAE,0BAA0B;gBACtC,WAAW,EAAE,mBAAmB,cAAc,2BAA2B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,iCAAiC,IAAI,CAAC,WAAW,CAAC,EAAE,yBAAyB;gBACzK,UAAU,EAAE,eAAe;aAC5B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACrB,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACnF,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,eAAe,IAAI,IAAI,CAAgC,CAAC;QACpF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,8CAA8C,MAAM,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAChH,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,sBAAsB;QACjC,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC1D,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAA6B;YAC1D,UAAU,EAAE,0BAA0B;YACtC,WAAW,EAAE,mBAAmB,cAAc,iCAAiC,IAAI,CAAC,WAAW,CAAC,EAAE,yBAAyB;YAC3H,OAAO,EAAE,qBAAqB;YAC9B,UAAU,EAAE,eAAe;SAC5B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAClD,sEAAsE;QACtE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;QAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrE,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC,OAAO,CAA+B;YAChE,UAAU,EAAE,WAAW;YACvB,WAAW,EAAE,UAAU,MAAM,GAAG;YAChC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;YACtB,UAAU,EAAE,QAAQ;SACrB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACrB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC3C,IAAI,UAAU,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YAC7C,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACrC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChC,MAAM,EAAE,CAAC,CAAC,gBAAgB;YAC1B,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,gBAAgB;YAC9D,eAAe,EAAE,CAAC,CAAC,CAAC,eAAe,IAAI,MAAM,CAAyB;YACtE,cAAc,EAAE,CAAC,CAAC,cAAc;YAChC,QAAQ,EAAE,CAAC,CAAC,cAAc;SAC3B,CAAC,CAAC,CAAC;IACN,CAAC;IAED,wEAAwE;IACxE,kBAAkB;IAClB,wEAAwE;IAEhE,KAAK,CAAC,sBAAsB,CAClC,MAAc,EACd,MAAmB;QAEnB,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,YAAY,GAChB,MAAM,CAAC,IAAI,KAAK,MAAM;YACpB,CAAC,CAAC,2BAA2B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG;YAC1D,CAAC,CAAC,2BAA2B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;QAC/D,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC1D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,OAAO,CAA6B;YAC1D,UAAU,EAAE,0BAA0B;YACtC,WAAW,EAAE,mBAAmB,cAAc,2BAA2B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,YAAY,EAAE;YACnH,UAAU,EAAE,eAAe;SAC5B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACnF,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAEO,cAAc,CAAC,CAA6B;QAClD,MAAM,MAAM,GACV,CAAC,CAAC,IAAI,KAAK,MAAM;YACf,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,MAAO,EAAE;YACrC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,MAAO,EAAE,CAAC;QAC1C,OAAO;YACL,YAAY,EAAE,CAAC,CAAC,EAAE;YAClB,MAAM,EAAE,CAAC,CAAC,gBAAgB;YAC1B,MAAM,EAAE,MAAM;YACd,eAAe,EAAE,CAAC,CAAC,CAAC,eAAe,IAAI,MAAM,CAAyB;YACtE,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,cAAc,EAAE,CAAC,CAAC,cAAc;YAChC,SAAS,EAAE,CAAC,CAAC,cAAc;SAC5B,CAAC;IACJ,CAAC;IAEO,cAAc,CAAC,CAA6B;QAClD,OAAO;YACL,UAAU,EAAE,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YAC/C,QAAQ,EAAE,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM;YACjD,KAAK,EAAE,CAAC,CAAC,eAAe;SACzB,CAAC;IACJ,CAAC;IAEO,QAAQ,CAAC,MAAmB;QAClC,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;IAChE,CAAC;IAED;;;;;;;;OAQG;IACK,uBAAuB;QAC7B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;QACjC,UAAU,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QACzC,uEAAuE;QACvE,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/E,MAAM,GAAG,GAAG,CAAC,OAAO,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAChG,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACxE,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,qBAAqB;QACjC,MAAM,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjF,MAAM,EAAE,GAAG,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC;QACvE,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,IAAI,KAAK,CACb,2BAA2B,uBAAuB,eAAe;gBAC/D,4EAA4E,CAC/E,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC,EAAE,CAAC;IACf,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,iBAAiB,CAAC,IAAY;QAC1C,MAAM,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjF,MAAM,CAAC,GAAG,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,IAAI,KAAK,CACb,4BAA4B,IAAI,eAAe;gBAC7C,6EAA6E,CAChF,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,CAAC,EAAE,CAAC;IACd,CAAC;IAED;;;;;;;;OAQG;IACK,KAAK,CAAC,YAAY,CAAC,IAK1B;QACC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvD,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,eAAe,CAAmB,gBAAgB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YAC3F,GAAG,CAAC,SAAS,EAAE,CAAC;YAChB,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,GAAG,CAAC,cAAc,GAAG,MAAM,CAAC;YAC5B,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;YACvB,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;YACnC,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3C,iEAAiE;YACjE,mDAAmD;YACnD,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAChD,IAAI,UAAU;gBAAE,GAAG,CAAC,QAAQ,GAAG,UAAU,CAAC,EAAE,CAAC;YAC7C,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;YAC3B,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,QAAQ,CAAC,0BAA0B,GAAG,CAAC,YAAY,EAAE,eAAe,IAAI,SAAS,EAAE,CAAC,CAAC;YACvF,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,uBAAuB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;IAED;;;;;;;;;;OAUG;IACK,KAAK,CAAC,yBAAyB,CACrC,MAAc,EACd,aAAqB,EACrB,KAA2B;QAE3B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3B,oEAAoE;YACpE,oEAAoE;YACpE,IAAI,QAA4B,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,eAAe,CAAe,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;gBACnF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACvC,IAAI,MAAM;oBAAE,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,oCAAoC;YACtC,CAAC;YACD,MAAM,uBAAuB,CAAC;gBAC5B,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAkC;gBACzD,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,aAAa,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE;gBAClC,aAAa,EAAE,aAAa;gBAC5B,iBAAiB,EAAE,MAAM;gBACzB,gBAAgB,EAAE,OAAO;gBACzB,YAAY,EAAE,QAAQ;gBACtB,gBAAgB,EAAE,MAAM;gBACxB,cAAc,EAAE,WAAW,KAAK,SAAS;gBACzC,kBAAkB,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE;aAC/C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,QAAQ,CAAC,6CAA6C,MAAM,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjH,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,IAAqB,EAAE,OAAe;QACzD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAChE,CAAC;IAED,8DAA8D;IACtD,MAAM,CAAC,KAAa;QAC1B,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAEO,QAAQ;QACd,OAAQ,IAAI,CAAC,QAA4C,IAAI,IAAI,QAAQ,EAAE,CAAC;IAC9E,CAAC;IAEO,OAAO;QACb,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;IACrF,CAAC;CACF"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Server-signed token attached to every `ListDelta`. The token is the only
3
+ * thing `ApplyDelta` will accept as proof that:
4
+ * - the preview was produced server-side (not forged by a client),
5
+ * - the preview describes a known target + source pairing,
6
+ * - the preview is fresh (within `TOKEN_TTL_MS`).
7
+ *
8
+ * Token format is `<base64url(payload-json)>.<base64url(hmac-sha256)>`. We
9
+ * deliberately avoid a JWT dependency — the payload shape is fixed, the
10
+ * algorithm is fixed, and we own both ends.
11
+ *
12
+ * Uses Node's built-in `node:crypto`. This module is server-only — clients
13
+ * never sign or verify tokens (they receive them inside a `ListDelta` and
14
+ * hand them back unchanged to `ApplyDelta`).
15
+ *
16
+ * Public functions remain async for backwards compatibility with callers
17
+ * that already `await` them; the underlying node:crypto APIs are synchronous,
18
+ * which is fine — HMAC on a tiny payload is sub-millisecond.
19
+ */
20
+ import { type DeltaTokenError, type DeltaTokenPayload } from '@memberjunction/lists-base';
21
+ export declare class DeltaTokenVerificationError extends Error {
22
+ readonly Code: DeltaTokenError;
23
+ constructor(code: DeltaTokenError, message: string);
24
+ }
25
+ /**
26
+ * Produce a deterministic fingerprint of a record-ID set. Used as the `ssig`
27
+ * field on the token payload — server-side `ApplyDelta` recomputes the
28
+ * fingerprint of the freshly resolved source and compares against the token
29
+ * to detect `STALE_DELTA`.
30
+ */
31
+ export declare function ComputeSourceSignature(recordIds: readonly string[]): Promise<string>;
32
+ export declare function SetDeltaTokenSecret(secret: string | undefined): void;
33
+ /**
34
+ * Sign a payload. Callers should pass `iat: Date.now()` and an `ssig`
35
+ * computed via `ComputeSourceSignature`. Returns the wire form.
36
+ */
37
+ export declare function SignDeltaToken(payload: DeltaTokenPayload): Promise<string>;
38
+ /**
39
+ * Verify a token: checks structural shape, HMAC, and TTL. Returns the parsed
40
+ * payload on success; throws `DeltaTokenVerificationError` otherwise.
41
+ *
42
+ * `timingSafeEqual` compares in time independent of the mismatch position,
43
+ * so a token-fuzzing attacker can't learn the secret from comparison-time
44
+ * side channels.
45
+ */
46
+ export declare function VerifyDeltaToken(token: string, now?: number): Promise<DeltaTokenPayload>;
47
+ //# sourceMappingURL=deltaToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deltaToken.d.ts","sourceRoot":"","sources":["../src/deltaToken.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACvB,MAAM,4BAA4B,CAAC;AAEpC,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,SAAgB,IAAI,EAAE,eAAe,CAAC;gBAC1B,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM;CAInD;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAG1F;AASD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAEpE;AA8BD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAKhF;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsC1G"}