@consenti/api 0.2.0 → 0.3.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 (79) hide show
  1. package/README.md +387 -90
  2. package/dist/chunk-IY4L6MNB.js +2 -0
  3. package/dist/chunk-IY4L6MNB.js.map +1 -0
  4. package/dist/chunk-O5UG4OYI.js +2 -0
  5. package/dist/chunk-O5UG4OYI.js.map +1 -0
  6. package/dist/dashboard/assets/ApiConfig-CE-Noelz.js +1 -0
  7. package/dist/dashboard/assets/ArchivedProfileList-Bw-fx-BD.js +1 -0
  8. package/dist/dashboard/assets/AuditLog-D6HiTQ1o.js +1 -0
  9. package/dist/dashboard/assets/ChangePassword-D2rPkzD1.js +1 -0
  10. package/dist/dashboard/assets/ConfirmDialog-DTtw7VJ-.js +1 -0
  11. package/dist/dashboard/assets/ConsentList-BWOvPOMG.js +1 -0
  12. package/dist/dashboard/assets/ConsentTemplateEditor-BKIEnLmA.js +1 -0
  13. package/dist/dashboard/assets/ConsentTemplateList-DCys2nM3.js +1 -0
  14. package/dist/dashboard/assets/HeadingTagInput-BTjrdzlS.js +1 -0
  15. package/dist/dashboard/assets/HowItWorks-AeyjfQGO.js +1 -0
  16. package/dist/dashboard/assets/PermissionGate-Bhhf7SMh.js +1 -0
  17. package/dist/dashboard/assets/PreviewPane-Dq5K1h2L.js +35 -0
  18. package/dist/dashboard/assets/ProfileEditor-BXtaE0tL.js +2 -0
  19. package/dist/dashboard/assets/ProfileList-CWKSAgu0.js +1 -0
  20. package/dist/dashboard/assets/ProfileVersionHistory-1h8sUgog.js +3 -0
  21. package/dist/dashboard/assets/RecordDetailModal-FaFhzQXi.js +1 -0
  22. package/dist/dashboard/assets/Reports-DWEjQOKh.js +1 -0
  23. package/dist/dashboard/assets/RoleList-BSGoqlzp.js +1 -0
  24. package/dist/dashboard/assets/Select-DjJNwlbY.js +1 -0
  25. package/dist/dashboard/assets/Settings-CVYrlyLm.js +1 -0
  26. package/dist/dashboard/assets/SetupWizard-4NTb5bZj.js +1 -0
  27. package/dist/dashboard/assets/Table-DH_jh8_N.js +1 -0
  28. package/dist/dashboard/assets/TenantList-5AwFtoHf.js +1 -0
  29. package/dist/dashboard/assets/UITemplateEditor-9A55scBe.js +1 -0
  30. package/dist/dashboard/assets/UITemplateList-BS7X4u4F.js +1 -0
  31. package/dist/dashboard/assets/UserList-SPkMyFRy.js +1 -0
  32. package/dist/dashboard/assets/VendorList-C5fPhr6U.js +1 -0
  33. package/dist/dashboard/assets/VisitorList-COP1SyNd.js +1 -0
  34. package/dist/dashboard/assets/check-o5cp1s2J.js +1 -0
  35. package/dist/dashboard/assets/client-ymc9nwWr.js +1 -0
  36. package/dist/dashboard/assets/copy-C01KoUUH.js +1 -0
  37. package/dist/dashboard/assets/createLucideIcon-MdjI3m3u.js +1 -0
  38. package/dist/dashboard/assets/de-DET92Jb5.js +5 -0
  39. package/dist/dashboard/assets/debounce-DlvAAT2V.js +1 -0
  40. package/dist/dashboard/assets/dist-DlhLroYC.js +1 -0
  41. package/dist/dashboard/assets/es-CJNunrnU.js +5 -0
  42. package/dist/dashboard/assets/external-link-DVe00ZBK.js +1 -0
  43. package/dist/dashboard/assets/fr-akfTuf5x.js +5 -0
  44. package/dist/dashboard/assets/history-CHTts_m2.js +1 -0
  45. package/dist/dashboard/assets/index-DW3qOrry.css +2 -0
  46. package/dist/dashboard/assets/index-H_Zm0X-u.js +6 -0
  47. package/dist/dashboard/assets/ja-B0nLWkOd.js +5 -0
  48. package/dist/dashboard/assets/profiles-DmrdGv7t.js +1 -0
  49. package/dist/dashboard/assets/roles-C0dJPFl4.js +1 -0
  50. package/dist/dashboard/assets/search-Bd2RdDRh.js +1 -0
  51. package/dist/dashboard/assets/templates-B2xiivh4.js +1 -0
  52. package/dist/dashboard/assets/templates-CnMoLavN.js +1 -0
  53. package/dist/dashboard/assets/trash-2-BBnkdgm_.js +1 -0
  54. package/dist/dashboard/assets/trash-Dj1jhzHJ.js +1 -0
  55. package/dist/dashboard/assets/useHashQuery-B5ytMIMp.js +1 -0
  56. package/dist/dashboard/assets/users-czK8LRVh.js +1 -0
  57. package/dist/dashboard/assets/x-BRuyBEQP.js +1 -0
  58. package/dist/dashboard/assets/zh-CN-Dxg3G-BL.js +5 -0
  59. package/dist/dashboard/index.html +4 -2
  60. package/dist/dist-Z5RVTVBT.js +2 -0
  61. package/dist/dist-Z5RVTVBT.js.map +1 -0
  62. package/dist/index.cjs +89 -100
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +663 -128
  65. package/dist/index.d.ts +663 -128
  66. package/dist/index.js +90 -101
  67. package/dist/index.js.map +1 -1
  68. package/dist/profiles-XTYPWLVC.js +2 -0
  69. package/dist/profiles-XTYPWLVC.js.map +1 -0
  70. package/dist/widget/widget.css +1 -1
  71. package/dist/widget/widget.js +1 -1
  72. package/package.json +14 -14
  73. package/dist/dashboard/assets/de-DwE2aQfW.js +0 -5
  74. package/dist/dashboard/assets/es-Dq88TWkB.js +0 -5
  75. package/dist/dashboard/assets/fr-o_Pyg8eR.js +0 -5
  76. package/dist/dashboard/assets/index-BSsYDdb9.js +0 -40
  77. package/dist/dashboard/assets/index-bOTGevzl.css +0 -2
  78. package/dist/dashboard/assets/ja-CwgXYmpD.js +0 -5
  79. package/dist/dashboard/assets/zh-CN-ety9KoQ0.js +0 -5
package/dist/index.d.cts CHANGED
@@ -1,7 +1,20 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
3
 
4
+ /**
5
+ * Deep-partial with JSON Merge Patch (RFC 7396) delete semantics: any key may be
6
+ * set to `null` instead of its usual type to signal "delete this key" when the
7
+ * partial is applied with `deepMerge()` — `undefined`/omitted keeps the base
8
+ * value, `null` removes it. Only meaningful for object/map keys (e.g. removing
9
+ * a `CategoryMap`/`CookieMap` entry via `profileOverride`); array elements are
10
+ * merged positionally and don't support deletion this way.
11
+ */
12
+ type DeepPartial<T> = T extends Function ? T : T extends Array<infer InferredArrayType> ? Array<DeepPartial<InferredArrayType>> : T extends ReadonlyArray<infer InferredReadonlyArrayType> ? ReadonlyArray<DeepPartial<InferredReadonlyArrayType>> : T extends object ? {
13
+ [Key in keyof T]?: DeepPartial<T[Key]> | null;
14
+ } : T;
15
+
4
16
  declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-strict", "opt-in-dpdpa", "opt-in-china", "opt-in-brazil", "general-privacy-consent", "notice-only"];
17
+ declare const COOKIE_PURPOSE_IDS: readonly ["necessary", "functional", "preferences", "analytics", "marketing"];
5
18
 
6
19
  /**
7
20
  * Compliance-related literal types, derived from the regulation/group ID data
@@ -18,6 +31,18 @@ declare const COMPLIANCE_GROUP_IDS: readonly ["opt-in", "opt-out", "opt-out-stri
18
31
 
19
32
  type ComplianceGroupId = typeof COMPLIANCE_GROUP_IDS[number];
20
33
 
34
+ /** Resolved content for one non-default locale — template shape merged with that locale's
35
+ * author text, computed client-side (dashboard) the same way the default locale's content on
36
+ * `StoredProfileJson` is. Never persisted in the DB row; `ProfileService` writes it straight to
37
+ * that locale's on-disk version file. Keyed by locale code on `CreateProfileInput`/
38
+ * `UpdateProfileInput.localeContent` — only locales actually touched this save need an entry;
39
+ * any other locale already in `profileJson.locales` is carried forward unchanged from the
40
+ * previous version. */
41
+ interface LocaleContentInput {
42
+ mainBanner: MainBanner;
43
+ gpcBanner?: GpcBanner;
44
+ preferenceModal: PreferenceModal;
45
+ }
21
46
  interface StorageConfig {
22
47
  driver: 'sqlite' | 'better-sqlite3' | 'node-sqlite3-wasm' | 'node:sqlite' | 'mongodb' | 'mysql' | 'postgresql' | 'json';
23
48
  path?: string;
@@ -27,6 +52,20 @@ interface StorageConfig {
27
52
  port?: number;
28
53
  user?: string;
29
54
  password?: string;
55
+ /** Postgres/MySQL only. Max pool connections. Default: 10 (both drivers' own default — set
56
+ * explicitly here so it's a documented, tunable value rather than an implicit library default). */
57
+ poolMax?: number;
58
+ /**
59
+ * Postgres/MySQL only. Milliseconds before an in-flight query is aborted, so one slow/runaway
60
+ * query can't hold a pool connection indefinitely. Unset by default: a forced default could
61
+ * silently break legitimate long-running operations (e.g. exporting millions of records) that
62
+ * this project has no way to load-test — opt in once you've measured your own worst-case query.
63
+ */
64
+ statementTimeoutMs?: number;
65
+ /** Postgres only. Milliseconds a client may sit idle inside an open transaction before being
66
+ * terminated — protects against a `BEGIN` with no matching `COMMIT`/`ROLLBACK` holding a
67
+ * connection forever. Unset by default, same reasoning as `statementTimeoutMs`. */
68
+ idleInTransactionTimeoutMs?: number;
30
69
  }
31
70
  interface OidcConfig {
32
71
  issuer: string;
@@ -81,18 +120,33 @@ interface ProfileSummary {
81
120
  name: string;
82
121
  defaultLocale: string;
83
122
  complianceGroup: ComplianceGroupId | null;
84
- version: number;
123
+ customComplianceGroup: string | null;
85
124
  isActive: boolean;
86
- cookieTemplateName: string | null;
125
+ consentTemplateName: string | null;
87
126
  uiTemplateName: string | null;
88
127
  createdAt: string;
89
128
  updatedAt: string;
90
129
  }
130
+ /**
131
+ * One snapshot in a profile's edit history. `Profile.id` is stable across edits; each save
132
+ * increments `Profile.version` in place and writes a new snapshot under that version number.
133
+ */
91
134
  interface ProfileVersionEntry {
92
135
  version: number;
93
136
  createdAt: string;
94
137
  locales: string[];
95
138
  }
139
+ /**
140
+ * A profile-id directory found on disk with no matching DB row — the profile was deleted
141
+ * (or predates the current versioning model) but its version-snapshot history wasn't. Built
142
+ * from a directory listing only (id, version count, last-modified) — no file content is read
143
+ * until a specific version is opened via the normal version-history endpoints.
144
+ */
145
+ interface ArchivedProfileSummary {
146
+ id: string;
147
+ versionCount: number;
148
+ lastModified: string;
149
+ }
96
150
  interface OptInFilters {
97
151
  profileId?: string;
98
152
  complianceGroup?: string;
@@ -169,6 +223,11 @@ interface TcfConfig {
169
223
  }
170
224
  interface DataRetentionConfig {
171
225
  purgeAfterDays: number;
226
+ /** When set, audit log entries older than this are purged on the same daily timer as consent
227
+ * record retention. Independent of `purgeAfterDays` since audit trails are often required to
228
+ * be kept longer than the consent records they reference for compliance/investigation
229
+ * purposes. Unset by default — audit logs are kept indefinitely unless explicitly configured. */
230
+ auditLogPurgeAfterDays?: number;
172
231
  }
173
232
  interface BrandingConfig {
174
233
  /** Display name shown in the dashboard (login page, header, browser tab). Default: 'Consenti'. */
@@ -198,8 +257,13 @@ interface ConsentiServerConfig {
198
257
  trustedProxies?: string[];
199
258
  branding?: BrandingConfig;
200
259
  s3Api?: S3ApiConfig;
260
+ /** When set, every consent record is HMAC-SHA256 signed at create/update time (hex-encoded,
261
+ * stored in `signature`) and the signature is checked on `GET /consent/:visitorId/verify`.
262
+ * No-op and no schema burden when unset — opt-in tamper-evidence for server-stored records,
263
+ * independent of the widget's own cookie-signing (`core.cookieSigningKey`). */
264
+ consentSigningKey?: string;
201
265
  /** Called after every profile activate/deactivate/delete. isPurge=true means invalidate; false means warm. */
202
- handleCache?: (paths: string[], currentVersion: number, isPurge: boolean) => void;
266
+ handleCache?: (paths: string[], profileId: string, isPurge: boolean) => void;
203
267
  }
204
268
  interface Tenant {
205
269
  id: string;
@@ -208,21 +272,42 @@ interface Tenant {
208
272
  createdAt: string;
209
273
  updatedAt: string;
210
274
  }
275
+ /** Tenant-wide dashboard-managed defaults, shown on the API Config page. */
276
+ interface TenantSettings {
277
+ /** Public API origin allowlist. Distinct from `ProfileConfig.allowedOrigins`, which the
278
+ * origin check on `POST /consent` prefers when a profile sets its own list; this is the
279
+ * fallback. The public API has no auth token, so this is its only access gate. */
280
+ allowedOrigins?: string[];
281
+ /** Admin API origin allowlist — an additional CORS-layer check on top of Bearer-token auth
282
+ * for browser-originated `/consenti/admin/*` requests. Empty/unset means no restriction
283
+ * (Bearer token auth alone still applies); only enforced when the request carries an
284
+ * `Origin` header — server-to-server callers are unaffected. */
285
+ adminAllowedOrigins?: string[];
286
+ /** Whether this tenant has completed (or skipped) the first-run setup wizard. Gates the
287
+ * one-time `#/setup` redirect in the dashboard — never reset once true. Default: false. */
288
+ setupCompleted?: boolean;
289
+ }
211
290
  interface ApiKey {
212
291
  id: string;
213
292
  tenantId: string;
214
293
  keyHash: string;
215
294
  name: string;
216
295
  isActive: boolean;
296
+ /** User id of the admin who created this key. Undefined for keys created before this field existed. */
297
+ createdBy?: string;
298
+ /** Optional expiry set at creation. Checked lazily at auth time and on list — no background sweep. */
299
+ expireBy?: string;
217
300
  createdAt: string;
301
+ updatedAt?: string;
218
302
  }
219
303
  interface Profile {
220
304
  id: string;
221
305
  tenantId: string;
222
306
  name: string;
223
307
  defaultLocale: string;
308
+ /** Starts at 1, incremented in place on every save. The row itself is mutated, never replaced — see {@link ProfileVersionEntry} for the on-disk snapshot history this produces. */
224
309
  version: number;
225
- profileJson: ProfileConfig;
310
+ profileJson: StoredProfileJson;
226
311
  createdAt: string;
227
312
  updatedAt: string;
228
313
  }
@@ -231,7 +316,6 @@ interface ConsentDbRecord {
231
316
  tenantId: string;
232
317
  visitorId: string;
233
318
  profileId: string;
234
- profileVersion: number;
235
319
  locale: string;
236
320
  consentJson: ConsentValue;
237
321
  gpcDetected: boolean;
@@ -239,6 +323,10 @@ interface ConsentDbRecord {
239
323
  ageVerified?: boolean;
240
324
  parentalConsentToken?: string;
241
325
  tcfString?: string;
326
+ /** HMAC-SHA256 signature over the record's core fields, hex-encoded. Only present when
327
+ * `consentSigningKey` is configured — opt-in tamper-evidence for server-stored records,
328
+ * independent of the widget's own cookie-signing (`core.cookieSigningKey`). */
329
+ signature?: string;
242
330
  createdAt: string;
243
331
  updatedAt: string;
244
332
  }
@@ -289,6 +377,22 @@ interface Permission {
289
377
  name: string;
290
378
  description?: string;
291
379
  }
380
+ /** Proof that the consent banner was rendered to a visitor — recorded even if they never
381
+ * interact with it, distinct from (and never merged into) an actual consent decision. */
382
+ interface NoticeShownRecord {
383
+ id: string;
384
+ tenantId: string;
385
+ visitorId: string;
386
+ profileId: string;
387
+ locale: string;
388
+ createdAt: string;
389
+ }
390
+ interface CreateNoticeShownInput {
391
+ tenantId: string;
392
+ visitorId: string;
393
+ profileId: string;
394
+ locale: string;
395
+ }
292
396
  interface AuditLog {
293
397
  id: string;
294
398
  tenantId: string;
@@ -300,22 +404,38 @@ interface AuditLog {
300
404
  newData?: unknown;
301
405
  createdAt: string;
302
406
  }
407
+ /** List-view shape for consents — omits the fields not shown in a table row (`consentJson`,
408
+ * `parentalConsentToken`, `tcfString`, `signature`), so paginated list queries don't have to pull
409
+ * those blobs off disk for every row. Fetch the full `ConsentDbRecord` via `getConsent(visitorId)`
410
+ * when a single record's full detail is actually needed (e.g. opening a detail modal). */
411
+ type ConsentSummary = Omit<ConsentDbRecord, 'consentJson' | 'parentalConsentToken' | 'tcfString' | 'signature'>;
412
+ /** List-view shape for audit logs — omits `oldData`/`newData` (full before/after snapshots) for
413
+ * the same reason as `ConsentSummary`. Fetch the full `AuditLog` via `getAuditLogById(id)` when a
414
+ * single entry's full detail is needed. */
415
+ type AuditLogSummary = Omit<AuditLog, 'oldData' | 'newData'>;
303
416
  interface CreateProfileInput {
304
417
  tenantId: string;
305
418
  name: string;
306
419
  defaultLocale: string;
307
- profileJson: ProfileConfig;
420
+ profileJson: StoredProfileJson;
421
+ /** Non-default locales' resolved content for this save — see {@link LocaleContentInput}. */
422
+ localeContent?: Record<string, LocaleContentInput>;
308
423
  }
309
424
  interface UpdateProfileInput {
310
425
  name?: string;
311
426
  defaultLocale?: string;
312
- profileJson?: ProfileConfig;
427
+ profileJson?: StoredProfileJson;
428
+ /** Explicit new version number — set by `ProfileService.update()` to `old.version + 1` for a content edit. Omitted for isActive-only flips (activate/deactivate), which leave the version unchanged. */
429
+ version?: number;
430
+ /** Non-default locales' resolved content for this save — see {@link LocaleContentInput}. Any
431
+ * locale in `profileJson.locales` not present here is carried forward unchanged from the
432
+ * previous version's on-disk file. */
433
+ localeContent?: Record<string, LocaleContentInput>;
313
434
  }
314
435
  interface CreateConsentInput {
315
436
  tenantId: string;
316
437
  visitorId: string;
317
438
  profileId: string;
318
- profileVersion: number;
319
439
  locale: string;
320
440
  consentJson: ConsentValue;
321
441
  gpcDetected: boolean;
@@ -323,11 +443,13 @@ interface CreateConsentInput {
323
443
  ageVerified?: boolean;
324
444
  parentalConsentToken?: string;
325
445
  tcfString?: string;
446
+ signature?: string;
326
447
  }
327
448
  interface UpdateConsentInput {
328
449
  consentJson: ConsentValue;
329
450
  locale?: string;
330
451
  gpcDetected?: boolean;
452
+ signature?: string;
331
453
  }
332
454
  interface CreateVisitorInput {
333
455
  tenantId: string;
@@ -376,6 +498,8 @@ interface ConsentFilters {
376
498
  to?: string;
377
499
  page?: number;
378
500
  limit?: number;
501
+ /** Free-text search across visitorId, profileId, locale, source. */
502
+ q?: string;
379
503
  }
380
504
  interface VisitorFilters {
381
505
  tenantId: string;
@@ -383,6 +507,8 @@ interface VisitorFilters {
383
507
  to?: string;
384
508
  page?: number;
385
509
  limit?: number;
510
+ /** Free-text search across visitorId, country. */
511
+ q?: string;
386
512
  }
387
513
  interface AuditFilters {
388
514
  tenantId: string;
@@ -392,12 +518,24 @@ interface AuditFilters {
392
518
  to?: string;
393
519
  page?: number;
394
520
  limit?: number;
521
+ /** Free-text search across action, resourceType, resourceId, userId. */
522
+ q?: string;
523
+ }
524
+ /** Page of results plus the total row count matching the same filters (ignoring page/limit) —
525
+ * lets callers compute total page count for numbered pagination without a second round trip. */
526
+ interface PagedResult<T> {
527
+ items: T[];
528
+ total: number;
529
+ page: number;
530
+ limit: number;
395
531
  }
396
532
  interface ConsentVerifyResult {
397
533
  valid: boolean;
398
- reasons: Array<'profile_version_mismatch' | 'consent_expired' | 'hmac_invalid'>;
399
- currentProfileVersion?: number;
400
- consentProfileVersion?: number;
534
+ reasons: Array<'profile_changed' | 'consent_expired' | 'hmac_invalid'>;
535
+ /** The id of the profile currently active for this consent's compliance group, if any. */
536
+ currentProfileId?: string;
537
+ /** The id of the profile this consent was originally collected against. */
538
+ consentProfileId?: string;
401
539
  }
402
540
  type SafeAdminUser = Omit<AdminUser, 'passwordHash'>;
403
541
  interface CreateRoleInput {
@@ -438,6 +576,8 @@ interface CreateApiKeyInput {
438
576
  tenantId: string;
439
577
  name: string;
440
578
  keyHash: string;
579
+ createdBy?: string;
580
+ expireBy?: string;
441
581
  }
442
582
  interface CreateTenantInput {
443
583
  name: string;
@@ -459,22 +599,24 @@ interface StorageAdapter {
459
599
  findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
460
600
  /** Lightweight list — no profile_json blob; single JOIN for template names. */
461
601
  listProfilesSummary(tenantId: string): Promise<ProfileSummary[]>;
462
- findProfilesUsingCookieTemplate(templateId: string): Promise<ProfileSummary[]>;
602
+ findProfilesUsingConsentTemplate(templateId: string): Promise<ProfileSummary[]>;
463
603
  findProfilesUsingUITemplate(templateId: string): Promise<ProfileSummary[]>;
464
604
  getOptInStats(tenantId: string, filters: OptInFilters): Promise<OptInStats>;
465
605
  createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
466
606
  updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
467
607
  deleteConsent(visitorId: string): Promise<void>;
468
608
  getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
469
- getConsents(filters: ConsentFilters): Promise<ConsentDbRecord[]>;
609
+ getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
470
610
  streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
471
611
  createVisitor(data: CreateVisitorInput): Promise<Visitor>;
472
612
  updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
473
613
  deleteVisitor(visitorId: string): Promise<void>;
474
614
  getVisitor(visitorId: string): Promise<Visitor | null>;
475
- getVisitors(filters: VisitorFilters): Promise<Visitor[]>;
615
+ getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
476
616
  getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
477
617
  streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
618
+ createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
619
+ getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
478
620
  getOverviewStats(tenantId: string): Promise<OverviewStats>;
479
621
  getCategoryStats(tenantId: string): Promise<CategoryStats>;
480
622
  getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
@@ -484,17 +626,26 @@ interface StorageAdapter {
484
626
  createTenant(data: CreateTenantInput): Promise<Tenant>;
485
627
  updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
486
628
  deleteTenant(id: string): Promise<void>;
629
+ /** Returns `{}` (all fields unset) if no settings row exists yet for this tenant. */
630
+ getSettings(tenantId: string): Promise<TenantSettings>;
631
+ updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
487
632
  createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
488
633
  getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
489
634
  revokeApiKey(id: string): Promise<void>;
635
+ /** Re-enables a previously revoked key (same hash, no new secret). Clears `expireBy` if it's
636
+ * in the past, so a lazily-expired key doesn't immediately re-expire on the next check. */
637
+ reactivateApiKey(id: string): Promise<void>;
638
+ /** Permanently removes the key row — unlike `revokeApiKey`, this cannot be undone. */
639
+ deleteApiKey(id: string): Promise<void>;
490
640
  getApiKeys(tenantId: string): Promise<ApiKey[]>;
491
641
  purgeExpiredConsents(olderThanDays: number): Promise<number>;
492
- createCookieTemplate(data: CreateCookieTemplateInput): Promise<ServerCookieTemplate>;
493
- updateCookieTemplate(id: string, data: UpdateCookieTemplateInput): Promise<ServerCookieTemplate>;
494
- deleteCookieTemplate(id: string): Promise<void>;
495
- getCookieTemplate(id: string): Promise<ServerCookieTemplate | null>;
496
- getCookieTemplates(tenantId: string): Promise<ServerCookieTemplate[]>;
497
- copyCookieTemplate(id: string, newName: string): Promise<ServerCookieTemplate>;
642
+ purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
643
+ createConsentTemplate(data: CreateConsentTemplateInput): Promise<ServerConsentTemplate>;
644
+ updateConsentTemplate(id: string, data: UpdateConsentTemplateInput): Promise<ServerConsentTemplate>;
645
+ deleteConsentTemplate(id: string): Promise<void>;
646
+ getConsentTemplate(id: string): Promise<ServerConsentTemplate | null>;
647
+ getConsentTemplates(tenantId: string): Promise<ServerConsentTemplate[]>;
648
+ copyConsentTemplate(id: string, newName: string): Promise<ServerConsentTemplate>;
498
649
  createUITemplate(data: CreateUITemplateInput): Promise<ServerUITemplate>;
499
650
  updateUITemplate(id: string, data: UpdateUITemplateInput): Promise<ServerUITemplate>;
500
651
  deleteUITemplate(id: string): Promise<void>;
@@ -520,7 +671,8 @@ interface StorageAdapter {
520
671
  assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
521
672
  revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
522
673
  createLog(data: CreateAuditLogInput): Promise<void>;
523
- getLogs(filters: AuditFilters): Promise<AuditLog[]>;
674
+ getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
675
+ getAuditLogById(id: string): Promise<AuditLog | null>;
524
676
  }
525
677
  interface PluginContext {
526
678
  storage: StorageAdapter;
@@ -539,23 +691,54 @@ declare abstract class ConsentiServerPlugin {
539
691
  beforeUserCreate?(data: CreateUserInput): Promise<CreateUserInput>;
540
692
  afterUserCreate?(user: AdminUser): Promise<void>;
541
693
  }
694
+ /**
695
+ * The server's stored shape of `Profile.profileJson` — extends the shared `ProfileConfig`
696
+ * (`./ui`, also used by `apps/ui`'s local `ConsentiProfile`/`registerProfile()` authoring, which
697
+ * genuinely needs the full multi-locale `translations`/`localeContents` blob for its own
698
+ * no-backend use case and must stay untouched).
699
+ *
700
+ * On the server, only `defaultLocale`'s own resolved content is ever stored here —
701
+ * `mainBanner`/`gpcBanner`/`preferenceModal` (inherited from `ProfileConfig` as-is: template shape
702
+ * merged with the author's text, optional fields simply omitted rather than blank — never a
703
+ * multi-locale map). Every other locale's content lives only in its own per-version JSON file on
704
+ * disk (`profiles/{tenant}/{id}/{version}/{locale}.json`, see `ProfileService`), never in the DB
705
+ * row — `translations`/`localeContents` (both full multi-locale blobs) are dropped entirely.
706
+ */
707
+ interface StoredProfileJson extends Omit<ProfileConfig, 'translations' | 'localeContents'> {
708
+ /** Every locale this profile has content for — drives the dashboard's tab list. Content for any
709
+ * locale other than `defaultLocale` lives only in that locale's on-disk version file, not here. */
710
+ locales: string[];
711
+ }
542
712
 
543
713
  type ConsentStatus = 'granted' | 'denied' | 'objected';
544
714
  type ConsentValue = Record<string, ConsentStatus>;
715
+ type GpcMode = 'ignore' | 'honor' | 'strict';
716
+ type LegalBasis = 'mandatory' | 'consent' | 'legitimate_interest';
717
+ type CookiePurpose = typeof COOKIE_PURPOSE_IDS[number];
718
+ /**
719
+ * A single consent parameter (tracker/cookie definition).
720
+ * Keyed by its own id inside `Record<string, Cookie>` — the map key IS the id,
721
+ * there is no `id` field on the value itself.
722
+ */
545
723
  interface Cookie {
546
- id: string;
547
- legalBasis?: 'mandatory' | 'consent' | 'legitimate_interest';
548
- /** @deprecated use legalBasis: 'mandatory' */
549
- mandatory?: boolean;
550
- /** @deprecated use legalBasis */
551
- type?: 'consent' | 'legitimate_interest';
724
+ purpose?: CookiePurpose;
552
725
  listenGpc?: boolean;
553
- expiry?: number;
726
+ /**
727
+ * Pre-grant this parameter's default consent to 'granted' (instead of the
728
+ * compliance-group-driven default) when no stored decision exists yet.
729
+ * Only meaningful when the owning category's `legalBasis === 'consent'` —
730
+ * `mandatory`/`legitimate_interest` categories are already effectively
731
+ * pre-granted regardless of this flag. Never overrides an active GPC signal.
732
+ * Default: false.
733
+ */
734
+ preGrant?: boolean;
554
735
  tcfVendorId?: number;
555
736
  tcfPurposes?: number[];
556
737
  tcfSpecialFeatures?: number[];
557
738
  cpraCategory?: 'sale' | 'sharing' | 'sensitive';
558
739
  }
740
+ /** `Cookie` keyed by its own id — the standard shape used everywhere a parameter list is passed around. */
741
+ type CookieMap = Record<string, Cookie>;
559
742
  type ButtonStyle = 'primary' | 'secondary' | 'text' | 'accent';
560
743
  type ButtonAction = 'submit' | 'manage' | 'close' | 'custom' | 'link';
561
744
  interface Button {
@@ -564,8 +747,11 @@ interface Button {
564
747
  action: ButtonAction;
565
748
  cookies?: string[] | '*' | '!';
566
749
  url?: string;
567
- type?: string;
568
750
  }
751
+ /** `Button` keyed by its own id — e.g. `'accept-all'`. The map key IS the id, rendered as the DOM
752
+ * `id` (`consenti-btn-{id}`) so integrators can target specific buttons, and is what
753
+ * `profileOverride`'s `deepMerge` deletes by (`{ 'reject-optional': null }` removes one button). */
754
+ type ButtonMap = Record<string, Button>;
569
755
  interface MainBanner {
570
756
  position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
571
757
  overlayOpacity?: number;
@@ -574,34 +760,84 @@ interface MainBanner {
574
760
  heading?: string;
575
761
  headingTag?: string;
576
762
  htmlText: string;
577
- buttons: Button[];
763
+ buttons: ButtonMap;
764
+ /** Stack buttons vertically below this viewport width in px. 0 or undefined = disabled. Default: 576. */
765
+ stackButtonsOnBreakpoint?: number;
766
+ /** Trap keyboard Tab focus within the consent root while this banner is visible. */
767
+ trapFocus?: boolean;
578
768
  }
579
769
  type GpcBanner = MainBanner;
770
+ /**
771
+ * A consent category — the single source of legal basis for every parameter
772
+ * it lists in `cookies`. Keyed by its own id inside `Record<string, Category>` —
773
+ * the map key IS the id, there is no `id` field on the value itself.
774
+ *
775
+ * A parameter must belong to exactly one category (enforced at authoring time,
776
+ * not by this type) — that relationship is what `getCookieLegalBasis`/
777
+ * `isMandatoryCookie` (`@consenti/utils`) resolve at runtime.
778
+ */
580
779
  interface Category {
581
- id: string;
582
780
  heading: string;
583
781
  headingTag?: string;
584
782
  htmlText: string;
585
- mandatory?: boolean;
586
- type?: 'consent' | 'legitimate_interest';
587
- legitimateInterest?: {
588
- enabled?: boolean;
589
- description?: string;
590
- };
783
+ legalBasis: LegalBasis;
784
+ /** Only meaningful when legalBasis === 'legitimate_interest' — the GDPR balancing-test justification shown to visitors. */
785
+ legitimateInterestDescription?: string;
786
+ /** IDs of the parameters (from the profile's `cookies` map) that belong to this category. */
591
787
  cookies: string[];
592
788
  }
789
+ /** `Category` keyed by its own id — the standard shape used everywhere a category list is passed around. */
790
+ type CategoryMap = Record<string, Category>;
593
791
  interface PreferenceModal extends Omit<MainBanner, "position"> {
594
792
  position?: 'left' | 'right' | 'center';
595
793
  subheading?: string;
596
- categories: Category[];
794
+ categories: CategoryMap;
597
795
  persistent?: boolean;
598
796
  /** Screen width in px below which the modal expands to full screen. Default: 576. Set to 0 to disable. */
599
797
  mobileFullScreenBreakpoint?: number;
798
+ /** Label for the optional consent-receipt download checkbox (shown when `allowReceipt` is true). Falls back to a default when omitted. */
799
+ receiptLabel?: string;
800
+ /** Description shown beneath the consent-receipt checkbox. Falls back to a default when omitted. */
801
+ receiptDescription?: string;
802
+ }
803
+ /**
804
+ * A UI-template button definition. Keyed by its own machine id (e.g. `'accept-all'`) inside
805
+ * `TemplateButtonMap` — the map key IS the id, not display text. UI templates own layout/behavior
806
+ * only, never text. The visitor-facing label is authored per-locale on the profile instead
807
+ * (`LocaleTextContent.*.buttonLabels`, keyed by the same button id) and composed into a resolved
808
+ * `Button.text` at profile-build time. Distinct from `Button` (the resolved-profile shape), which
809
+ * always carries real `text`.
810
+ */
811
+ interface TemplateButtonDef {
812
+ type?: ButtonStyle;
813
+ action: ButtonAction;
814
+ cookies?: string[] | '*' | '!';
815
+ url?: string;
816
+ }
817
+ /** `TemplateButtonDef` keyed by its own id — the standard shape everywhere a UI template's
818
+ * button set is passed around. */
819
+ type TemplateButtonMap = Record<string, TemplateButtonDef>;
820
+ interface TemplateBannerDef {
821
+ position: 'top' | 'bottom' | 'middle' | 'left-bottom' | 'right-bottom';
822
+ overlayOpacity?: number;
823
+ showClose?: boolean;
824
+ showLocaleSwitcher?: boolean;
825
+ headingTag?: string;
826
+ buttons: TemplateButtonMap;
827
+ stackButtonsOnBreakpoint?: number;
828
+ trapFocus?: boolean;
829
+ }
830
+ type TemplateGpcBannerDef = TemplateBannerDef;
831
+ interface TemplateModalDef extends Omit<TemplateBannerDef, 'position'> {
832
+ position?: 'left' | 'right' | 'center';
833
+ persistent?: boolean;
834
+ hasSubheading?: boolean;
835
+ mobileFullScreenBreakpoint?: number;
600
836
  }
601
837
  interface LocaleTranslations {
602
- mainBanner: MainBanner;
603
- gpcBanner?: GpcBanner;
604
- preferenceModal: PreferenceModal;
838
+ mainBanner: DeepPartial<MainBanner>;
839
+ gpcBanner?: DeepPartial<GpcBanner>;
840
+ preferenceModal: DeepPartial<PreferenceModal>;
605
841
  }
606
842
  interface ProfileTranslations {
607
843
  [locale: string]: LocaleTranslations;
@@ -615,109 +851,162 @@ interface LocaleTextContent {
615
851
  mainBanner: {
616
852
  heading?: string;
617
853
  htmlText: string;
618
- buttonLabels?: string[];
854
+ /** Keyed by the linked UI template's button id — was a positional array matched to
855
+ * `buttons[i]` by index; now matched by key, robust against button reordering. */
856
+ buttonLabels?: Record<string, string>;
619
857
  };
620
858
  gpcBanner: {
621
859
  heading?: string;
622
860
  htmlText: string;
623
- buttonLabels?: string[];
861
+ buttonLabels?: Record<string, string>;
624
862
  };
625
863
  preferenceModal: {
626
864
  heading: string;
627
865
  subheading?: string;
628
866
  htmlText?: string;
629
- buttonLabels?: string[];
630
- categories: {
631
- id: string;
867
+ buttonLabels?: Record<string, string>;
868
+ /** `legitimateInterestDescription` is only meaningful for categories whose legalBasis is 'legitimate_interest'; optional GDPR balancing-test text, translated per locale like heading/htmlText. */
869
+ categories: Record<string, {
632
870
  heading: string;
633
871
  htmlText: string;
634
- }[];
872
+ legitimateInterestDescription?: string;
873
+ }>;
874
+ receiptLabel?: string;
875
+ receiptDescription?: string;
635
876
  };
636
877
  }
637
878
  interface ProfileConfig {
638
- cookies?: Cookie[];
879
+ id: string;
880
+ cookies?: CookieMap;
639
881
  defaultLocale: string;
882
+ /** Days until consent expires and the visitor is asked again (profile-wide). Default: 365. */
883
+ expiryDays?: number;
640
884
  translations?: ProfileTranslations;
641
- gpcBanner?: boolean;
885
+ mainBanner: MainBanner;
886
+ gpcBanner?: GpcBanner;
887
+ preferenceModal: PreferenceModal;
642
888
  darkMode?: boolean;
643
889
  allowedOrigins?: string[];
644
890
  dpdpa?: DpdpaConfig;
645
891
  regulation?: 'gdpr' | 'ccpa' | 'cpra' | 'dpdpa' | 'uk-gdpr' | 'lgpd' | 'pipeda' | 'popia' | 'pdpa-th' | 'appi' | 'kvkk';
646
892
  regulations?: string[];
647
- cookieTemplateId?: string;
893
+ consentTemplateId?: string;
648
894
  uiTemplateId?: string;
649
895
  localeContents?: Record<string, LocaleTextContent>;
650
- complianceGroup?: string;
651
- /** @deprecated use gpcMode */
652
- gpcCompliance?: 'strict' | 'honor' | 'ignore';
653
- gpcMode?: 'ignore' | 'honor' | 'strict';
896
+ complianceGroup?: ComplianceGroupId;
897
+ /**
898
+ * Free-form identifier (lower-kebab-case) for profiles that don't map to one of the
899
+ * built-in `complianceGroup` values — the identifier the widget's `compliance.type`
900
+ * config targets to fetch this profile. Participates in activation, deactivation, and
901
+ * "one active profile per group" conflict detection the same way `complianceGroup` does;
902
+ * no `COMPLIANCE_GROUPS` validation rules or GPC defaults apply to it, since none exist
903
+ * for a free-form name.
904
+ */
905
+ customComplianceGroup?: string;
906
+ /**
907
+ * Per-profile deltas applied on top of the consent template's authored `Cookie` values
908
+ * (e.g. an overridden `preGrant`) when resolving this profile — see the dashboard's Step 2
909
+ * pre-grant override UX. Keyed by cookie id; only the overridden fields need to be present.
910
+ */
911
+ cookiesOverride?: Record<string, Partial<Cookie>>;
912
+ /**
913
+ * Reserved for a future phase — stored but not yet applied when resolving a profile.
914
+ * Intended to mirror `cookiesOverride` for per-category deltas.
915
+ */
916
+ categoriesOverride?: Record<string, Partial<Category>>;
917
+ /**
918
+ * Reserved for a future phase — stored but not yet applied when resolving a profile.
919
+ * Intended to hold per-profile UI-template deltas.
920
+ */
921
+ uiOverride?: Record<string, unknown>;
922
+ /**
923
+ * When registered via `registerProfile()` and `complianceGroup` matches one of
924
+ * Consenti's built-in groups: `false` (default) fully replaces the built-in
925
+ * embedded profile for that group; `true` deep-merges this config onto the
926
+ * built-in profile instead (only meaningful together with `complianceGroup`).
927
+ */
928
+ deepMerge?: boolean;
929
+ gpcMode?: GpcMode;
654
930
  isActive?: boolean;
655
931
  hidePoweredBy?: boolean;
656
932
  allowReceipt?: boolean;
657
933
  /** Per-compliance extra config (e.g. DPDPA data fiduciary name). */
658
934
  complianceConfig?: Record<string, string>;
935
+ /** Show a metadata footer strip in the banner/modal with Consent ID, Date, Version, Privacy Settings link. */
936
+ showFooterMetadata?: boolean;
937
+ /** Apply WCAG 2.1 AA button sizing (44px min-height), visible focus rings, and screen-reader labels. */
938
+ enhanceAccessibility?: boolean;
659
939
  }
660
940
  interface ServerUITemplate {
661
941
  id: string;
662
942
  tenantId: string;
663
943
  name: string;
664
- mainBanner: MainBanner;
665
- gpcBanner: GpcBanner;
666
- preferenceModal: PreferenceModal;
944
+ mainBanner: TemplateBannerDef;
945
+ gpcBanner: TemplateGpcBannerDef;
946
+ preferenceModal: TemplateModalDef;
667
947
  createdAt: string;
668
948
  updatedAt: string;
669
949
  }
670
- interface ServerCookieTemplate {
950
+ interface ServerConsentTemplate {
671
951
  id: string;
672
952
  tenantId: string;
673
953
  name: string;
674
- cookies: Cookie[];
954
+ cookies: CookieMap;
955
+ categories: CategoryMap;
675
956
  createdAt: string;
676
957
  updatedAt: string;
677
958
  }
678
- interface CreateCookieTemplateInput {
959
+ interface CreateConsentTemplateInput {
679
960
  tenantId: string;
680
961
  name: string;
681
- cookies: Cookie[];
962
+ cookies: CookieMap;
963
+ categories: CategoryMap;
682
964
  }
683
- interface UpdateCookieTemplateInput {
965
+ interface UpdateConsentTemplateInput {
684
966
  name?: string;
685
- cookies?: Cookie[];
967
+ cookies?: CookieMap;
968
+ categories?: CategoryMap;
686
969
  }
687
970
  interface CreateUITemplateInput {
688
971
  tenantId: string;
689
972
  name: string;
690
- mainBanner: MainBanner;
691
- gpcBanner: MainBanner;
692
- preferenceModal: PreferenceModal;
973
+ mainBanner: TemplateBannerDef;
974
+ gpcBanner: TemplateGpcBannerDef;
975
+ preferenceModal: TemplateModalDef;
693
976
  }
694
977
  interface UpdateUITemplateInput {
695
978
  name?: string;
696
- mainBanner?: MainBanner;
697
- gpcBanner?: MainBanner;
698
- preferenceModal?: PreferenceModal;
979
+ mainBanner?: TemplateBannerDef;
980
+ gpcBanner?: TemplateGpcBannerDef;
981
+ preferenceModal?: TemplateModalDef;
699
982
  }
700
983
  interface PublicProfileResponse {
701
984
  id: string;
985
+ /** Incremented in place on every save of this profile. */
986
+ version: number;
702
987
  name?: string;
703
988
  tenantId?: string;
704
- version: number;
705
989
  defaultLocale: string;
706
990
  currentLocale: string;
707
991
  locales: string[];
708
- cookies: Cookie[];
992
+ cookies: CookieMap;
993
+ /** Days until consent expires and the visitor is asked again (profile-wide). */
994
+ expiryDays?: number;
709
995
  mainBanner: MainBanner;
710
996
  gpcBanner?: GpcBanner;
711
997
  preferenceModal: PreferenceModal;
712
998
  resolvedComplianceGroup?: string;
713
- complianceGroup?: string;
999
+ complianceGroup?: ComplianceGroupId;
714
1000
  compliances?: string[];
715
1001
  complianceConfig?: Record<string, string>;
716
1002
  allowedOrigins?: string[];
717
- gpcMode?: boolean | 'ignore' | 'honor' | 'strict';
1003
+ gpcMode?: GpcMode;
718
1004
  hidePoweredBy?: boolean;
719
1005
  allowReceipt?: boolean;
720
1006
  darkMode?: boolean;
1007
+ dpdpa?: DpdpaConfig;
1008
+ showFooterMetadata?: boolean;
1009
+ enhanceAccessibility?: boolean;
721
1010
  createdAt?: string;
722
1011
  updatedAt?: string;
723
1012
  }
@@ -737,7 +1026,8 @@ declare class AuditRepo {
737
1026
  private adapter;
738
1027
  constructor(adapter: StorageAdapter);
739
1028
  log(data: CreateAuditLogInput): Promise<void>;
740
- list(filters: AuditFilters): Promise<AuditLog[]>;
1029
+ list(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
1030
+ get(id: string): Promise<AuditLog | null>;
741
1031
  }
742
1032
 
743
1033
  declare class LocaleJsonCacheService {
@@ -760,23 +1050,96 @@ declare class ProfileService {
760
1050
  private profilesDir?;
761
1051
  private handleCache?;
762
1052
  private s3Config?;
763
- constructor(profiles: ProfileRepo, audit: AuditRepo, tenantId?: string, storage?: StorageAdapter | undefined, eventBus?: EventEmitter | undefined, localeCache?: LocaleJsonCacheService | undefined, profilesDir?: string | undefined, handleCache?: ((paths: string[], version: number, isPurge: boolean) => void) | undefined, s3Config?: S3ApiConfig | undefined);
764
- private profileDir;
1053
+ constructor(profiles: ProfileRepo, audit: AuditRepo, tenantId?: string, storage?: StorageAdapter | undefined, eventBus?: EventEmitter | undefined, localeCache?: LocaleJsonCacheService | undefined, profilesDir?: string | undefined, handleCache?: ((paths: string[], profileId: string, isPurge: boolean) => void) | undefined, s3Config?: S3ApiConfig | undefined);
1054
+ /** Root directory holding every version snapshot of one logical profile, keyed by its stable id. */
1055
+ private versionsRootDir;
765
1056
  private complianceGroupDir;
1057
+ /**
1058
+ * The directory key used for activation/hot-serve writes and the "one active profile
1059
+ * per group" conflict check — the real `complianceGroup` when set, otherwise the
1060
+ * free-form `customComplianceGroup` (which the widget's `compliance.type` targets the
1061
+ * same way). Compliance *validation* still only ever runs against a real `complianceGroup`.
1062
+ */
1063
+ private groupKey;
766
1064
  private profileFilePaths;
1065
+ /** Resolves this profile's cookie map (consent-template lookup + `cookiesOverride` applied) —
1066
+ * the one piece of content still resolved live rather than baked at save time, since cookies
1067
+ * are small and never the source of the row-size problem `mainBanner`/`preferenceModal` were. */
1068
+ private resolveCookies;
1069
+ /**
1070
+ * Writes every locale's on-disk version file for one save (create or an edit) in a single
1071
+ * atomic worker call — default locale content comes from `profile.profileJson` itself, every
1072
+ * other touched locale from `localeContent`. A locale already in `profileJson.locales` but not
1073
+ * present in `localeContent` (a tab the user never opened this session) is carried forward
1074
+ * unchanged from `previousVersion` by the worker — never silently dropped.
1075
+ */
1076
+ private writeLocaleFiles;
767
1077
  create(input: Omit<CreateProfileInput, 'tenantId'>): Promise<Profile>;
1078
+ /**
1079
+ * Duplicates a profile as a brand new, always-inactive profile (own `id`, `version` starts at
1080
+ * 1 again). Delegates to {@link create} rather than copying on-disk files directly — the
1081
+ * resolved-JSON snapshot embeds its own `id`/`version`, so it has to be regenerated for the
1082
+ * new profile rather than byte-copied from the source's. Non-default locale content is read
1083
+ * back off the source's current version files so the copy carries every locale forward too.
1084
+ */
1085
+ copy(id: string, name?: string): Promise<Profile>;
1086
+ /**
1087
+ * Mutates the existing row in place — `id` is stable across every edit, `version` is
1088
+ * incremented. The prior state isn't kept as a separate DB row; its resolved-JSON snapshot
1089
+ * on disk (see {@link listVersions}) is the audit trail for that edit.
1090
+ */
768
1091
  update(id: string, input: UpdateProfileInput): Promise<Profile>;
769
1092
  delete(id: string): Promise<void>;
770
1093
  activate(id: string): Promise<Profile>;
771
1094
  deactivate(id: string): Promise<Profile>;
772
- private runWrite;
773
- get(id: string, locale?: string): Promise<Profile | null>;
1095
+ private runDeactivate;
1096
+ get(id: string): Promise<Profile | null>;
1097
+ /**
1098
+ * Resolves one locale's full public response. The default locale's content lives directly on
1099
+ * the DB row (`profileJson.mainBanner`/`gpcBanner`/`preferenceModal`) — no template lookups, no
1100
+ * live resolution. Any other locale's content lives only in that locale's on-disk version file;
1101
+ * this reads it directly, falling back to the default locale's content if the file is missing
1102
+ * (e.g. a locale listed but never actually authored yet). This method is not a hot path — the
1103
+ * public `/profiles/:tenant/:group/:locale` route serves the static file directly and only
1104
+ * falls back to this for previewing an inactive/versioned profile.
1105
+ */
774
1106
  getResolved(id: string, locale?: string): Promise<PublicProfileResponse | null>;
775
1107
  findActiveByComplianceGroup(complianceGroup: string): Promise<Profile | null>;
776
1108
  list(): Promise<Profile[]>;
777
1109
  listSummary(): Promise<ProfileSummary[]>;
778
- listVersions(profileId: string): ProfileVersionEntry[];
779
- getVersionFile(profileId: string, version: number, locale: string): string | null;
1110
+ /**
1111
+ * Lists every version snapshot of `profileId` on disk (newest first), read straight from
1112
+ * the version-directory tree — no DB query, `Profile.version` is just the current pointer.
1113
+ * Works for archived profiles too (DB row deleted, files still on disk) — the directory
1114
+ * tree is the only thing this reads.
1115
+ */
1116
+ listVersions(profileId: string): Promise<ProfileVersionEntry[]>;
1117
+ /** Works for archived profiles too — see `listVersions()`. */
1118
+ getVersionFile(profileId: string, version: string, locale: string): Promise<string | null>;
1119
+ /**
1120
+ * Profile-id directories on disk with no matching DB row — deleted profiles whose version
1121
+ * snapshots `delete()` never removes. Directory-only read (id, version count, last-modified
1122
+ * mtime) — no file content is opened here; that only happens once a specific version is
1123
+ * requested via `getVersionFile()`, which works for these ids too.
1124
+ */
1125
+ listArchivedProfiles(): Promise<ArchivedProfileSummary[]>;
1126
+ /**
1127
+ * Seeds a default profile for a compliance group from the embedded English profile in
1128
+ * `@consenti/utils`, merged with locale text overlays for de/es/fr/ja. Every locale — including
1129
+ * non-English ones — is passed through `create()`'s normal `localeContent` path, the same as an
1130
+ * author-submitted profile; there is no separate "seeded profile" code path for locale content
1131
+ * anymore, which is what previously let a locale silently never make it into the served file.
1132
+ * Only creates the profile if no active profile already exists for the compliance group.
1133
+ * Safe to call repeatedly (idempotent).
1134
+ *
1135
+ * @param complianceGroup - One of the 8 compliance group IDs.
1136
+ */
1137
+ seedDefaultProfile(complianceGroup: string): Promise<void>;
1138
+ /**
1139
+ * Seeds default profiles for all 8 compliance groups.
1140
+ * Skips any group that already has an active profile (idempotent).
1141
+ */
1142
+ seedAllDefaults(): Promise<void>;
780
1143
  }
781
1144
 
782
1145
  declare class ConsentRepo {
@@ -786,7 +1149,7 @@ declare class ConsentRepo {
786
1149
  update(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
787
1150
  delete(visitorId: string): Promise<void>;
788
1151
  get(visitorId: string): Promise<ConsentDbRecord | null>;
789
- list(filters: ConsentFilters): Promise<ConsentDbRecord[]>;
1152
+ list(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
790
1153
  }
791
1154
 
792
1155
  declare class VisitorRepo {
@@ -822,7 +1185,20 @@ declare class ConsentService {
822
1185
  private pluginEngine?;
823
1186
  private eventBus?;
824
1187
  private tcfConfig?;
825
- constructor(consents: ConsentRepo, visitors: VisitorRepo, profiles: ProfileRepo, audit: AuditRepo, tenantId?: string, compliance?: ComplianceConfig | undefined, pluginEngine?: PluginEngine | undefined, eventBus?: EventEmitter | undefined, tcfConfig?: TcfConfig | undefined);
1188
+ private storage?;
1189
+ private consentSigningKey?;
1190
+ constructor(consents: ConsentRepo, visitors: VisitorRepo, profiles: ProfileRepo, audit: AuditRepo, tenantId?: string, compliance?: ComplianceConfig | undefined, pluginEngine?: PluginEngine | undefined, eventBus?: EventEmitter | undefined, tcfConfig?: TcfConfig | undefined, storage?: StorageAdapter | undefined, consentSigningKey?: string | undefined);
1191
+ private sign;
1192
+ /**
1193
+ * Profiles created via the dashboard wizard reference a consent template (`consentTemplateId`)
1194
+ * rather than embedding `cookies`/`preferenceModal.categories` directly on `profileJson` — those
1195
+ * fields are only populated for the embedded/seeded default profiles (see
1196
+ * `ProfileService.seedDefaultProfile`). Cookie/category membership and `legalBasis` are
1197
+ * locale-independent, so the raw consent template's data is sufficient here — no need to
1198
+ * resolve per-locale text via `ProfileService.getResolved()`. Mirrors the same fallback
1199
+ * `ProfileService.getResolved()` and the admin route validators already apply.
1200
+ */
1201
+ private resolveTemplateData;
826
1202
  create(input: Omit<CreateConsentInput, 'tenantId'>): Promise<ConsentDbRecord>;
827
1203
  update(visitorId: string, input: UpdateConsentInput): Promise<ConsentDbRecord>;
828
1204
  get(visitorId: string): Promise<ConsentDbRecord | null>;
@@ -904,19 +1280,23 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
904
1280
  getProfiles(tenantId: string): Promise<Profile[]>;
905
1281
  findActiveProfileByComplianceGroup(tenantId: string, complianceGroup: string): Promise<Profile | null>;
906
1282
  private mapConsent;
1283
+ private static readonly CONSENT_SUMMARY_COLS;
1284
+ private mapConsentSummary;
907
1285
  createConsent(data: CreateConsentInput): Promise<ConsentDbRecord>;
908
1286
  updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
909
1287
  deleteConsent(visitorId: string): Promise<void>;
910
1288
  getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
911
- getConsents(filters: ConsentFilters): Promise<ConsentDbRecord[]>;
1289
+ getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
912
1290
  streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
913
1291
  getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
1292
+ createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
1293
+ getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
914
1294
  private mapVisitor;
915
1295
  createVisitor(data: CreateVisitorInput): Promise<Visitor>;
916
1296
  updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
917
1297
  deleteVisitor(visitorId: string): Promise<void>;
918
1298
  getVisitor(visitorId: string): Promise<Visitor | null>;
919
- getVisitors(filters: VisitorFilters): Promise<Visitor[]>;
1299
+ getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
920
1300
  private mapUser;
921
1301
  createUser(data: CreateUserInput): Promise<AdminUser>;
922
1302
  updateUser(id: string, data: UpdateUserInput): Promise<AdminUser>;
@@ -937,7 +1317,10 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
937
1317
  assignRole(userId: string, roleId: string, tenantId?: string): Promise<void>;
938
1318
  revokeRole(userId: string, roleId: string, tenantId?: string): Promise<void>;
939
1319
  createLog(data: CreateAuditLogInput): Promise<void>;
940
- getLogs(filters: AuditFilters): Promise<AuditLog[]>;
1320
+ getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
1321
+ getAuditLogById(id: string): Promise<AuditLog | null>;
1322
+ private static readonly AUDIT_SUMMARY_COLS;
1323
+ private mapAuditSummary;
941
1324
  private mapAuditLog;
942
1325
  streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
943
1326
  getOverviewStats(tenantId: string): Promise<OverviewStats>;
@@ -949,19 +1332,24 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
949
1332
  createTenant(data: CreateTenantInput): Promise<Tenant>;
950
1333
  updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
951
1334
  deleteTenant(id: string): Promise<void>;
1335
+ getSettings(tenantId: string): Promise<TenantSettings>;
1336
+ updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
952
1337
  purgeExpiredConsents(olderThanDays: number): Promise<number>;
1338
+ purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
953
1339
  private mapApiKey;
954
1340
  createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
955
1341
  getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
956
1342
  revokeApiKey(id: string): Promise<void>;
1343
+ reactivateApiKey(id: string): Promise<void>;
1344
+ deleteApiKey(id: string): Promise<void>;
957
1345
  getApiKeys(tenantId: string): Promise<ApiKey[]>;
958
- private mapCookieTemplate;
959
- createCookieTemplate(data: CreateCookieTemplateInput): Promise<ServerCookieTemplate>;
960
- updateCookieTemplate(id: string, data: UpdateCookieTemplateInput): Promise<ServerCookieTemplate>;
961
- deleteCookieTemplate(id: string): Promise<void>;
962
- getCookieTemplate(id: string): Promise<ServerCookieTemplate | null>;
963
- getCookieTemplates(tenantId: string): Promise<ServerCookieTemplate[]>;
964
- copyCookieTemplate(id: string, newName: string): Promise<ServerCookieTemplate>;
1346
+ private mapConsentTemplate;
1347
+ createConsentTemplate(data: CreateConsentTemplateInput): Promise<ServerConsentTemplate>;
1348
+ updateConsentTemplate(id: string, data: UpdateConsentTemplateInput): Promise<ServerConsentTemplate>;
1349
+ deleteConsentTemplate(id: string): Promise<void>;
1350
+ getConsentTemplate(id: string): Promise<ServerConsentTemplate | null>;
1351
+ getConsentTemplates(tenantId: string): Promise<ServerConsentTemplate[]>;
1352
+ copyConsentTemplate(id: string, newName: string): Promise<ServerConsentTemplate>;
965
1353
  private mapUITemplate;
966
1354
  createUITemplate(data: CreateUITemplateInput): Promise<ServerUITemplate>;
967
1355
  updateUITemplate(id: string, data: UpdateUITemplateInput): Promise<ServerUITemplate>;
@@ -972,7 +1360,7 @@ declare class BetterSqlite3Adapter implements StorageAdapter {
972
1360
  private mapProfileSummary;
973
1361
  private readonly PROFILE_SUMMARY_SQL;
974
1362
  listProfilesSummary(tenantId: string): Promise<ProfileSummary[]>;
975
- findProfilesUsingCookieTemplate(templateId: string): Promise<ProfileSummary[]>;
1363
+ findProfilesUsingConsentTemplate(templateId: string): Promise<ProfileSummary[]>;
976
1364
  findProfilesUsingUITemplate(templateId: string): Promise<ProfileSummary[]>;
977
1365
  getOptInStats(tenantId: string, filters: OptInFilters): Promise<OptInStats>;
978
1366
  }
@@ -998,16 +1386,18 @@ declare class MongoDBAdapter implements StorageAdapter {
998
1386
  updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
999
1387
  deleteConsent(visitorId: string): Promise<void>;
1000
1388
  getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
1001
- getConsents(filters: ConsentFilters): Promise<ConsentDbRecord[]>;
1389
+ getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
1002
1390
  streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
1003
1391
  private buildConsentFilter;
1004
1392
  private writeHistory;
1005
1393
  getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
1394
+ createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
1395
+ getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
1006
1396
  createVisitor(data: CreateVisitorInput): Promise<Visitor>;
1007
1397
  updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
1008
1398
  deleteVisitor(visitorId: string): Promise<void>;
1009
1399
  getVisitor(visitorId: string): Promise<Visitor | null>;
1010
- getVisitors(filters: VisitorFilters): Promise<Visitor[]>;
1400
+ getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
1011
1401
  getOverviewStats(tenantId: string): Promise<OverviewStats>;
1012
1402
  getCategoryStats(tenantId: string): Promise<CategoryStats>;
1013
1403
  getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
@@ -1030,26 +1420,33 @@ declare class MongoDBAdapter implements StorageAdapter {
1030
1420
  assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
1031
1421
  revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
1032
1422
  createLog(data: CreateAuditLogInput): Promise<void>;
1033
- getLogs(filters: AuditFilters): Promise<AuditLog[]>;
1423
+ getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
1424
+ getAuditLogById(id: string): Promise<AuditLog | null>;
1034
1425
  streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
1035
1426
  private buildAuditFilter;
1036
1427
  getCountries(tenantId: string): Promise<CountryStat[]>;
1037
1428
  getGpcStats(tenantId: string): Promise<GpcStats>;
1038
1429
  getTenants(): Promise<Tenant[]>;
1430
+ private mapApiKey;
1039
1431
  createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
1040
1432
  getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
1041
1433
  revokeApiKey(id: string): Promise<void>;
1434
+ reactivateApiKey(id: string): Promise<void>;
1435
+ deleteApiKey(id: string): Promise<void>;
1042
1436
  getApiKeys(tenantId: string): Promise<ApiKey[]>;
1043
1437
  createTenant(data: CreateTenantInput): Promise<Tenant>;
1044
1438
  updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
1045
1439
  deleteTenant(id: string): Promise<void>;
1440
+ getSettings(tenantId: string): Promise<TenantSettings>;
1441
+ updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
1046
1442
  purgeExpiredConsents(olderThanDays: number): Promise<number>;
1047
- createCookieTemplate(): Promise<never>;
1048
- updateCookieTemplate(): Promise<never>;
1049
- deleteCookieTemplate(): Promise<void>;
1050
- getCookieTemplate(): Promise<never>;
1051
- getCookieTemplates(): Promise<never>;
1052
- copyCookieTemplate(): Promise<never>;
1443
+ purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
1444
+ createConsentTemplate(): Promise<never>;
1445
+ updateConsentTemplate(): Promise<never>;
1446
+ deleteConsentTemplate(): Promise<void>;
1447
+ getConsentTemplate(): Promise<never>;
1448
+ getConsentTemplates(): Promise<never>;
1449
+ copyConsentTemplate(): Promise<never>;
1053
1450
  createUITemplate(): Promise<never>;
1054
1451
  updateUITemplate(): Promise<never>;
1055
1452
  deleteUITemplate(): Promise<void>;
@@ -1057,7 +1454,7 @@ declare class MongoDBAdapter implements StorageAdapter {
1057
1454
  getUITemplates(): Promise<never>;
1058
1455
  copyUITemplate(): Promise<never>;
1059
1456
  listProfilesSummary(): Promise<never[]>;
1060
- findProfilesUsingCookieTemplate(): Promise<never[]>;
1457
+ findProfilesUsingConsentTemplate(): Promise<never[]>;
1061
1458
  findProfilesUsingUITemplate(): Promise<never[]>;
1062
1459
  getOptInStats(): Promise<never>;
1063
1460
  }
@@ -1082,16 +1479,18 @@ declare class MySQLAdapter implements StorageAdapter {
1082
1479
  updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
1083
1480
  deleteConsent(visitorId: string): Promise<void>;
1084
1481
  getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
1085
- getConsents(filters: ConsentFilters): Promise<ConsentDbRecord[]>;
1482
+ getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
1086
1483
  streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
1087
- private buildConsentQuery;
1484
+ private buildConsentWhere;
1088
1485
  private writeHistory;
1089
1486
  getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
1487
+ createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
1488
+ getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
1090
1489
  createVisitor(data: CreateVisitorInput): Promise<Visitor>;
1091
1490
  updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
1092
1491
  deleteVisitor(visitorId: string): Promise<void>;
1093
1492
  getVisitor(visitorId: string): Promise<Visitor | null>;
1094
- getVisitors(filters: VisitorFilters): Promise<Visitor[]>;
1493
+ getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
1095
1494
  getOverviewStats(tenantId: string): Promise<OverviewStats>;
1096
1495
  getCategoryStats(tenantId: string): Promise<CategoryStats>;
1097
1496
  getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
@@ -1114,26 +1513,33 @@ declare class MySQLAdapter implements StorageAdapter {
1114
1513
  assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
1115
1514
  revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
1116
1515
  createLog(data: CreateAuditLogInput): Promise<void>;
1117
- getLogs(filters: AuditFilters): Promise<AuditLog[]>;
1516
+ getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
1517
+ getAuditLogById(id: string): Promise<AuditLog | null>;
1118
1518
  streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
1119
- private buildAuditQuery;
1519
+ private buildAuditWhere;
1120
1520
  getCountries(tenantId: string): Promise<CountryStat[]>;
1121
1521
  getGpcStats(tenantId: string): Promise<GpcStats>;
1122
1522
  getTenants(): Promise<Tenant[]>;
1523
+ private mapApiKey;
1123
1524
  createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
1124
1525
  getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
1125
1526
  revokeApiKey(id: string): Promise<void>;
1527
+ reactivateApiKey(id: string): Promise<void>;
1528
+ deleteApiKey(id: string): Promise<void>;
1126
1529
  getApiKeys(tenantId: string): Promise<ApiKey[]>;
1127
1530
  createTenant(data: CreateTenantInput): Promise<Tenant>;
1128
1531
  updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
1532
+ getSettings(tenantId: string): Promise<TenantSettings>;
1533
+ updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
1129
1534
  deleteTenant(id: string): Promise<void>;
1130
1535
  purgeExpiredConsents(olderThanDays: number): Promise<number>;
1131
- createCookieTemplate(): Promise<never>;
1132
- updateCookieTemplate(): Promise<never>;
1133
- deleteCookieTemplate(): Promise<void>;
1134
- getCookieTemplate(): Promise<never>;
1135
- getCookieTemplates(): Promise<never>;
1136
- copyCookieTemplate(): Promise<never>;
1536
+ purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
1537
+ createConsentTemplate(): Promise<never>;
1538
+ updateConsentTemplate(): Promise<never>;
1539
+ deleteConsentTemplate(): Promise<void>;
1540
+ getConsentTemplate(): Promise<never>;
1541
+ getConsentTemplates(): Promise<never>;
1542
+ copyConsentTemplate(): Promise<never>;
1137
1543
  createUITemplate(): Promise<never>;
1138
1544
  updateUITemplate(): Promise<never>;
1139
1545
  deleteUITemplate(): Promise<void>;
@@ -1141,7 +1547,7 @@ declare class MySQLAdapter implements StorageAdapter {
1141
1547
  getUITemplates(): Promise<never>;
1142
1548
  copyUITemplate(): Promise<never>;
1143
1549
  listProfilesSummary(): Promise<never[]>;
1144
- findProfilesUsingCookieTemplate(): Promise<never[]>;
1550
+ findProfilesUsingConsentTemplate(): Promise<never[]>;
1145
1551
  findProfilesUsingUITemplate(): Promise<never[]>;
1146
1552
  getOptInStats(): Promise<never>;
1147
1553
  }
@@ -1165,16 +1571,18 @@ declare class PostgreSQLAdapter implements StorageAdapter {
1165
1571
  updateConsent(visitorId: string, data: UpdateConsentInput): Promise<ConsentDbRecord>;
1166
1572
  deleteConsent(visitorId: string): Promise<void>;
1167
1573
  getConsent(visitorId: string): Promise<ConsentDbRecord | null>;
1168
- getConsents(filters: ConsentFilters): Promise<ConsentDbRecord[]>;
1574
+ getConsents(filters: ConsentFilters): Promise<PagedResult<ConsentSummary>>;
1169
1575
  streamConsents(filters: ConsentFilters): AsyncIterable<ConsentDbRecord>;
1170
- private buildConsentQuery;
1576
+ private buildConsentWhere;
1171
1577
  private writeHistory;
1172
1578
  getConsentHistory(visitorId: string): Promise<ConsentHistoryEntry[]>;
1579
+ createNoticeShown(data: CreateNoticeShownInput): Promise<NoticeShownRecord>;
1580
+ getNoticeShownForVisitor(visitorId: string): Promise<NoticeShownRecord[]>;
1173
1581
  createVisitor(data: CreateVisitorInput): Promise<Visitor>;
1174
1582
  updateVisitor(visitorId: string, data: UpdateVisitorInput): Promise<Visitor>;
1175
1583
  deleteVisitor(visitorId: string): Promise<void>;
1176
1584
  getVisitor(visitorId: string): Promise<Visitor | null>;
1177
- getVisitors(filters: VisitorFilters): Promise<Visitor[]>;
1585
+ getVisitors(filters: VisitorFilters): Promise<PagedResult<Visitor>>;
1178
1586
  getOverviewStats(tenantId: string): Promise<OverviewStats>;
1179
1587
  getCategoryStats(tenantId: string): Promise<CategoryStats>;
1180
1588
  getTimeline(tenantId: string, days?: number): Promise<TimelineEntry[]>;
@@ -1197,26 +1605,33 @@ declare class PostgreSQLAdapter implements StorageAdapter {
1197
1605
  assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
1198
1606
  revokePermissionFromRole(roleId: string, permissionId: string): Promise<void>;
1199
1607
  createLog(data: CreateAuditLogInput): Promise<void>;
1200
- getLogs(filters: AuditFilters): Promise<AuditLog[]>;
1608
+ getLogs(filters: AuditFilters): Promise<PagedResult<AuditLogSummary>>;
1609
+ getAuditLogById(id: string): Promise<AuditLog | null>;
1201
1610
  streamAuditLogs(filters: AuditFilters): AsyncIterable<AuditLog>;
1202
- private buildAuditQuery;
1611
+ private buildAuditWhere;
1203
1612
  getCountries(tenantId: string): Promise<CountryStat[]>;
1204
1613
  getGpcStats(tenantId: string): Promise<GpcStats>;
1205
1614
  getTenants(): Promise<Tenant[]>;
1615
+ private mapApiKey;
1206
1616
  createApiKey(data: CreateApiKeyInput): Promise<ApiKey>;
1207
1617
  getApiKeyByHash(keyHash: string): Promise<ApiKey | null>;
1208
1618
  revokeApiKey(id: string): Promise<void>;
1619
+ reactivateApiKey(id: string): Promise<void>;
1620
+ deleteApiKey(id: string): Promise<void>;
1209
1621
  getApiKeys(tenantId: string): Promise<ApiKey[]>;
1210
1622
  createTenant(data: CreateTenantInput): Promise<Tenant>;
1211
1623
  updateTenant(id: string, data: UpdateTenantInput): Promise<Tenant>;
1212
1624
  deleteTenant(id: string): Promise<void>;
1625
+ getSettings(tenantId: string): Promise<TenantSettings>;
1626
+ updateSettings(tenantId: string, data: Partial<TenantSettings>): Promise<TenantSettings>;
1213
1627
  purgeExpiredConsents(olderThanDays: number): Promise<number>;
1214
- createCookieTemplate(): Promise<never>;
1215
- updateCookieTemplate(): Promise<never>;
1216
- deleteCookieTemplate(): Promise<void>;
1217
- getCookieTemplate(): Promise<never>;
1218
- getCookieTemplates(): Promise<never>;
1219
- copyCookieTemplate(): Promise<never>;
1628
+ purgeExpiredAuditLogs(olderThanDays: number): Promise<number>;
1629
+ createConsentTemplate(): Promise<never>;
1630
+ updateConsentTemplate(): Promise<never>;
1631
+ deleteConsentTemplate(): Promise<void>;
1632
+ getConsentTemplate(): Promise<never>;
1633
+ getConsentTemplates(): Promise<never>;
1634
+ copyConsentTemplate(): Promise<never>;
1220
1635
  createUITemplate(): Promise<never>;
1221
1636
  updateUITemplate(): Promise<never>;
1222
1637
  deleteUITemplate(): Promise<void>;
@@ -1224,11 +1639,131 @@ declare class PostgreSQLAdapter implements StorageAdapter {
1224
1639
  getUITemplates(): Promise<never>;
1225
1640
  copyUITemplate(): Promise<never>;
1226
1641
  listProfilesSummary(): Promise<never[]>;
1227
- findProfilesUsingCookieTemplate(): Promise<never[]>;
1642
+ findProfilesUsingConsentTemplate(): Promise<never[]>;
1228
1643
  findProfilesUsingUITemplate(): Promise<never[]>;
1229
1644
  getOptInStats(): Promise<never>;
1230
1645
  }
1231
1646
 
1647
+ /**
1648
+ * ConsentAction — server-side counterpart to `@consenti/ui`'s `ConsentAction`.
1649
+ *
1650
+ * Watches a single consent parameter across *every* visitor's submission (not just one
1651
+ * browser's own state) and fires `onGrant`/`onDeny` when that parameter's status actually
1652
+ * changes — via the same `eventBus` `createConsenti()` already emits `consent.created`/
1653
+ * `consent.updated` on. Useful for server-side integrations (CRM sync, suppression lists,
1654
+ * downstream webhooks) that need to react to consent decisions without polling the database.
1655
+ *
1656
+ * There is no server equivalent of the widget's `ConsentScript` — injecting a `<script>` tag
1657
+ * only makes sense in a browser DOM the server doesn't have. `services.consent.create()`/
1658
+ * `.update()` are already the server-side "submit"/"update consent" methods this hook reacts to.
1659
+ *
1660
+ * @example
1661
+ * ```ts
1662
+ * import { createConsenti, ConsentAction } from '@consenti/api'
1663
+ *
1664
+ * const { eventBus } = createConsenti({ ... })
1665
+ *
1666
+ * new ConsentAction({
1667
+ * id: 'analytics_storage',
1668
+ * eventBus,
1669
+ * onGrant: ({ visitorId }) => crm.optIn(visitorId),
1670
+ * onDeny: ({ visitorId }) => crm.optOut(visitorId),
1671
+ * })
1672
+ * ```
1673
+ */
1674
+
1675
+ /** Passed to `onGrant`/`onDeny` — the transitioning parameter's status plus the full consent record. */
1676
+ interface ConsentActionParams {
1677
+ visitorId: string;
1678
+ cookieId: string;
1679
+ status: ConsentStatus;
1680
+ record: ConsentDbRecord;
1681
+ }
1682
+ /** Options for constructing a {@link ConsentAction} instance. */
1683
+ interface ConsentActionOptions {
1684
+ /** The consent parameter ID to watch (e.g. `'analytics_storage'`). */
1685
+ id: string;
1686
+ /** The `eventBus` returned by `createConsenti()`. */
1687
+ eventBus: EventEmitter;
1688
+ /** Called when the parameter's status transitions to `'granted'`, for any visitor. */
1689
+ onGrant?: (params: ConsentActionParams) => void;
1690
+ /** Called when the parameter's status transitions away from `'granted'` (`'denied'` or `'objected'`). */
1691
+ onDeny?: (params: ConsentActionParams) => void;
1692
+ }
1693
+ /**
1694
+ * Watches a single consent parameter across every incoming `consent.created`/`consent.updated`
1695
+ * event and fires `onGrant`/`onDeny` on transitions. Call {@link ConsentAction.destroy} to
1696
+ * remove the event listeners when the hook is no longer needed.
1697
+ */
1698
+ declare class ConsentAction {
1699
+ private readonly options;
1700
+ private readonly onCreated;
1701
+ private readonly onUpdated;
1702
+ constructor(options: ConsentActionOptions);
1703
+ private fire;
1704
+ /** Removes the event listeners. Call when the hook is no longer needed. */
1705
+ destroy(): void;
1706
+ }
1707
+
1708
+ /**
1709
+ * CategoryAction — server-side counterpart to `@consenti/ui`'s `CategoryAction`.
1710
+ *
1711
+ * Same idea as {@link ConsentAction} but watches a whole category's rollup consent state
1712
+ * (granted only when *every* cookie in the category is granted) instead of one parameter —
1713
+ * matching the preference modal's tri-state rollup rule and the widget's `CategoryAction`.
1714
+ *
1715
+ * @example
1716
+ * ```ts
1717
+ * import { createConsenti, CategoryAction } from '@consenti/api'
1718
+ *
1719
+ * const { eventBus, services } = createConsenti({ ... })
1720
+ *
1721
+ * new CategoryAction({
1722
+ * categoryId: 'marketing',
1723
+ * eventBus,
1724
+ * profiles: services.profile,
1725
+ * onGrant: ({ visitorId }) => adsPlatform.optIn(visitorId),
1726
+ * onDeny: ({ visitorId }) => adsPlatform.optOut(visitorId),
1727
+ * })
1728
+ * ```
1729
+ */
1730
+
1731
+ /** Passed to `onGrant`/`onDeny` — the category's rollup status plus the full consent record. */
1732
+ interface CategoryActionParams {
1733
+ visitorId: string;
1734
+ categoryId: string;
1735
+ status: ConsentStatus;
1736
+ record: ConsentDbRecord;
1737
+ }
1738
+ /** Options for constructing a {@link CategoryAction} instance. */
1739
+ interface CategoryActionOptions {
1740
+ /** The category ID to watch (e.g. `'marketing'`). */
1741
+ categoryId: string;
1742
+ /** The `eventBus` returned by `createConsenti()`. */
1743
+ eventBus: EventEmitter;
1744
+ /** The `services.profile` returned by `createConsenti()` — resolves which cookies belong to
1745
+ * this category for the record's profile (categories are defined on the consent template). */
1746
+ profiles: ProfileService;
1747
+ /** Called when every cookie in the category transitions to `'granted'`. */
1748
+ onGrant?: (params: CategoryActionParams) => void;
1749
+ /** Called when the category transitions away from fully granted. */
1750
+ onDeny?: (params: CategoryActionParams) => void;
1751
+ }
1752
+ /**
1753
+ * Watches a category's rollup consent status across every incoming `consent.created`/
1754
+ * `consent.updated` event and fires `onGrant`/`onDeny` on transitions. Call
1755
+ * {@link CategoryAction.destroy} to remove the event listeners when no longer needed.
1756
+ */
1757
+ declare class CategoryAction {
1758
+ private readonly options;
1759
+ private readonly onCreated;
1760
+ private readonly onUpdated;
1761
+ constructor(options: CategoryActionOptions);
1762
+ private fire;
1763
+ /** Removes the event listeners. Call when the hook is no longer needed. */
1764
+ destroy(): void;
1765
+ }
1766
+
1232
1767
  declare const dashboardDir: string;
1233
1768
  declare function createConsenti(userConfig: ConsentiServerConfig): {
1234
1769
  router: (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
@@ -1256,4 +1791,4 @@ declare function createConsenti(userConfig: ConsentiServerConfig): {
1256
1791
  destroy: () => Promise<void>;
1257
1792
  };
1258
1793
 
1259
- export { type AdminUser, type AuditLog, type BrandingConfig, type ConsentDbRecord, type ConsentVerifyResult, type ConsentiServerConfig, MongoDBAdapter, MySQLAdapter, type Permission, PostgreSQLAdapter, type Profile, type Role, BetterSqlite3Adapter as SQLiteAdapter, type SafeAdminUser, type StorageAdapter, type Visitor, createConsenti, dashboardDir };
1794
+ export { type AdminUser, type AuditLog, type BrandingConfig, CategoryAction, type CategoryActionOptions, type CategoryActionParams, ConsentAction, type ConsentActionOptions, type ConsentActionParams, type ConsentDbRecord, type ConsentVerifyResult, type ConsentiServerConfig, MongoDBAdapter, MySQLAdapter, type Permission, PostgreSQLAdapter, type Profile, type Role, BetterSqlite3Adapter as SQLiteAdapter, type SafeAdminUser, type StorageAdapter, type Visitor, createConsenti, dashboardDir };