@blamejs/blamejs-shop 0.0.72 → 0.0.75

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 (44) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/announcement-bar.js +753 -0
  3. package/lib/banner-ab-tests.js +806 -0
  4. package/lib/bin-locations.js +791 -0
  5. package/lib/blog-articles.js +1173 -0
  6. package/lib/carrier-accounts.js +805 -0
  7. package/lib/cart-recovery.js +1133 -0
  8. package/lib/category-navigation.js +934 -0
  9. package/lib/consent-ledger.js +539 -0
  10. package/lib/customer-impersonation.js +743 -0
  11. package/lib/customer-merge.js +879 -0
  12. package/lib/demand-forecast.js +1121 -0
  13. package/lib/dispute-resolution.js +886 -0
  14. package/lib/email-ab-tests.js +918 -0
  15. package/lib/email-engagement-score.js +649 -0
  16. package/lib/event-log.js +713 -0
  17. package/lib/fulfillment-sla.js +791 -0
  18. package/lib/index.js +41 -0
  19. package/lib/inventory-audits.js +852 -0
  20. package/lib/line-gift-wrap.js +430 -0
  21. package/lib/marketing-budget.js +792 -0
  22. package/lib/operator-activity-feed.js +977 -0
  23. package/lib/operator-approvals.js +942 -0
  24. package/lib/operator-help-center.js +1020 -0
  25. package/lib/operator-inbox.js +889 -0
  26. package/lib/operator-sessions.js +701 -0
  27. package/lib/order-exchanges.js +602 -0
  28. package/lib/product-compare.js +804 -0
  29. package/lib/pwa-manifest.js +1005 -0
  30. package/lib/referral-leaderboard.js +612 -0
  31. package/lib/sales-tax-filings.js +807 -0
  32. package/lib/search-ranking.js +859 -0
  33. package/lib/shipping-insurance.js +757 -0
  34. package/lib/shrinkage-report.js +1182 -0
  35. package/lib/sidebar-widgets.js +952 -0
  36. package/lib/smart-restocking.js +1048 -0
  37. package/lib/stock-receipts.js +834 -0
  38. package/lib/subscription-analytics.js +1032 -0
  39. package/lib/suggestion-box.js +921 -0
  40. package/lib/tax-remittance.js +625 -0
  41. package/lib/vendor-invoices.js +1021 -0
  42. package/lib/winback-campaigns.js +1350 -0
  43. package/lib/wishlist-digest.js +1133 -0
  44. package/package.json +1 -1
@@ -0,0 +1,539 @@
1
+ "use strict";
2
+ /**
3
+ * @module shop.consentLedger
4
+ * @title Consent ledger — append-only per-customer record of every
5
+ * consent decision for GDPR / ePrivacy / CCPA audit.
6
+ *
7
+ * @intro
8
+ * Per-customer historical record of every consent grant /
9
+ * withdrawal across the nine categories that map to a supervisory-
10
+ * authority audit: cookie functional / analytics / marketing /
11
+ * preferences, marketing email, marketing SMS, third-party data
12
+ * sharing (partners + analytics), and a catch-all data_processing
13
+ * bucket for purposes that don't fit the eight category-specific
14
+ * kinds. Distinct from `cookieConsent`, which stores per-SESSION
15
+ * decisions keyed by a hashed session id — that primitive is the
16
+ * browser-side gate; this primitive is the durable audit trail.
17
+ *
18
+ * GDPR art. 7(1) requires the controller "be able to demonstrate
19
+ * that the data subject has consented to processing of his or her
20
+ * personal data." consentLedger is that demonstration: every
21
+ * decision a customer makes lands as a new row; the table is
22
+ * append-only from the primitive surface; the current effective
23
+ * state is the latest row by `occurred_at`. A Subject Access
24
+ * Request (`auditExport`) returns every row for one customer
25
+ * newest-first in CSV or JSON. A supervisory-authority sweep
26
+ * (`bulkExportByJurisdiction`) returns every row in a country
27
+ * over a closed time window. The compliance summary
28
+ * (`summarizeForCompliance`) returns aggregate granted /
29
+ * withdrawn counts per (consent_kind, source) across the window
30
+ * so the operator can answer "how many EU buyers opted into
31
+ * marketing email last quarter" without exposing per-row PII.
32
+ *
33
+ * Withdrawal is non-destructive. `recordConsentChange` with
34
+ * `state: "withdrawn"` writes a NEW row; the prior granted row
35
+ * survives so the timeline reads as a sequence rather than a
36
+ * silent revocation. Operators never DELETE / UPDATE rows in
37
+ * this table — the primitive surface only exposes INSERT.
38
+ *
39
+ * `consent_kind` is one of the nine listed under
40
+ * `CONSENT_KINDS`. `state` is `granted` or `withdrawn`. `source`
41
+ * identifies how the decision arrived (signup_form,
42
+ * preference_center, cookie_banner, customer_support,
43
+ * system_default, data_subject_request). `jurisdiction` is an
44
+ * optional ISO-3166-1 alpha-2 uppercase country code; the
45
+ * bulk-export and compliance-summary paths filter on it.
46
+ * `evidence_ref` is an operator-supplied opaque string pointing
47
+ * at the row that proves the decision (form-submission id,
48
+ * support-ticket id, audit-log row id) — the ledger doesn't
49
+ * resolve it.
50
+ *
51
+ * Composes:
52
+ * - `b.guardUuid.sanitize` — strict UUID validation on
53
+ * customer_id.
54
+ * - `b.uuid.v7` — row id, lexicographically
55
+ * sortable for tie-break on
56
+ * occurred_at.
57
+ * - `b.csv.stringify` — RFC 4180 CSV emission for
58
+ * `auditExport({ format: "csv" })`
59
+ * and `bulkExportByJurisdiction`.
60
+ *
61
+ * Monotonic per-process clock: two writes in the same
62
+ * millisecond would tie on `occurred_at` and make the "latest
63
+ * state per kind" read ambiguous. `_now` bumps to `prior + 1` on
64
+ * collision so the per-customer timeline carries a strict
65
+ * ordering even on a fast runner.
66
+ *
67
+ * Surface:
68
+ * - recordConsentChange({ customer_id, consent_kind, state,
69
+ * source, jurisdiction?, evidence_ref? })
70
+ * → the persisted row.
71
+ * - currentStateForCustomer(customer_id)
72
+ * → object mapping each consent_kind that has at least
73
+ * one row to its latest { state, source, jurisdiction,
74
+ * evidence_ref, occurred_at }. Kinds with no rows are
75
+ * omitted (the caller decides the default — typically
76
+ * "withdrawn" / "not given").
77
+ * - historyForCustomer(customer_id)
78
+ * → array of every row for the customer, newest first.
79
+ * - auditExport({ customer_id, format })
80
+ * → SAR-ready dump. `format` is `"csv"` or `"json"`.
81
+ * - bulkExportByJurisdiction({ jurisdiction, from, to,
82
+ * format? })
83
+ * → every row whose jurisdiction matches the requested
84
+ * code, occurred_at in [from, to). Default format CSV.
85
+ * - summarizeForCompliance({ from, to, jurisdiction? })
86
+ * → per-(consent_kind, source) granted / withdrawn
87
+ * counts across the window. Operator-facing aggregate
88
+ * that doesn't expose row-level customer_ids.
89
+ *
90
+ * Storage: `consent_ledger` (migration
91
+ * `0185_consent_ledger.sql`).
92
+ *
93
+ * @primitive consentLedger
94
+ * @related b.guardUuid, b.uuid.v7, b.csv, shop.cookieConsent
95
+ */
96
+
97
+ var CONSENT_KINDS = Object.freeze([
98
+ "cookies_functional",
99
+ "cookies_analytics",
100
+ "cookies_marketing",
101
+ "cookies_preferences",
102
+ "marketing_email",
103
+ "marketing_sms",
104
+ "data_sharing_partners",
105
+ "data_sharing_analytics",
106
+ "data_processing",
107
+ ]);
108
+
109
+ var STATES = Object.freeze(["granted", "withdrawn"]);
110
+
111
+ var SOURCES = Object.freeze([
112
+ "signup_form",
113
+ "preference_center",
114
+ "cookie_banner",
115
+ "customer_support",
116
+ "system_default",
117
+ "data_subject_request",
118
+ ]);
119
+
120
+ var EXPORT_FORMATS = Object.freeze(["csv", "json"]);
121
+
122
+ var JURISDICTION_RE = /^[A-Z]{2}$/;
123
+ var EVIDENCE_REF_MAX_LEN = 256;
124
+ var EVIDENCE_REF_RE = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
125
+
126
+ var CONTROL_BYTE_RE = /[\x00-\x1f\x7f]/;
127
+
128
+ var CSV_COLUMNS = Object.freeze([
129
+ "id",
130
+ "customer_id",
131
+ "consent_kind",
132
+ "state",
133
+ "source",
134
+ "jurisdiction",
135
+ "evidence_ref",
136
+ "occurred_at",
137
+ ]);
138
+
139
+ // Lazy framework handle — matches the rest of the shop primitives;
140
+ // avoids the require cycle that would arise from importing `./index`
141
+ // at module-eval time.
142
+ var bShop;
143
+ function _b() {
144
+ if (!bShop) bShop = require("./index");
145
+ return bShop.framework;
146
+ }
147
+
148
+ // ---- monotonic clock ----------------------------------------------------
149
+ //
150
+ // Operator-driven writes can land in the same millisecond on fast
151
+ // machines. Bumping by 1ms on a tie keeps the per-customer timeline
152
+ // strictly increasing so the "latest state per kind" read returns
153
+ // the row the caller actually issued last.
154
+
155
+ var _lastTs = 0;
156
+ function _now() {
157
+ var t = Date.now();
158
+ if (t <= _lastTs) t = _lastTs + 1;
159
+ _lastTs = t;
160
+ return t;
161
+ }
162
+
163
+ // ---- validators ---------------------------------------------------------
164
+
165
+ function _customerId(s) {
166
+ try {
167
+ return _b().guardUuid.sanitize(s, { profile: "strict" });
168
+ } catch (e) {
169
+ throw new TypeError(
170
+ "consentLedger: customer_id — " + (e && e.message || "invalid UUID")
171
+ );
172
+ }
173
+ }
174
+
175
+ function _consentKind(s) {
176
+ if (typeof s !== "string" || CONSENT_KINDS.indexOf(s) === -1) {
177
+ throw new TypeError(
178
+ "consentLedger: consent_kind must be one of " + CONSENT_KINDS.join(", ") +
179
+ ", got " + JSON.stringify(s)
180
+ );
181
+ }
182
+ return s;
183
+ }
184
+
185
+ function _state(s) {
186
+ if (typeof s !== "string" || STATES.indexOf(s) === -1) {
187
+ throw new TypeError(
188
+ "consentLedger: state must be one of " + STATES.join(", ") +
189
+ ", got " + JSON.stringify(s)
190
+ );
191
+ }
192
+ return s;
193
+ }
194
+
195
+ function _source(s) {
196
+ if (typeof s !== "string" || SOURCES.indexOf(s) === -1) {
197
+ throw new TypeError(
198
+ "consentLedger: source must be one of " + SOURCES.join(", ") +
199
+ ", got " + JSON.stringify(s)
200
+ );
201
+ }
202
+ return s;
203
+ }
204
+
205
+ function _optJurisdiction(s) {
206
+ if (s == null || s === "") return null;
207
+ if (typeof s !== "string") {
208
+ throw new TypeError("consentLedger: jurisdiction must be a string");
209
+ }
210
+ if (!JURISDICTION_RE.test(s)) {
211
+ throw new TypeError(
212
+ "consentLedger: jurisdiction must be ISO-3166-1 alpha-2 uppercase (e.g. 'DE', 'US'), " +
213
+ "got " + JSON.stringify(s)
214
+ );
215
+ }
216
+ return s;
217
+ }
218
+
219
+ function _reqJurisdiction(s) {
220
+ if (typeof s !== "string" || !s.length) {
221
+ throw new TypeError("consentLedger: jurisdiction required (non-empty string)");
222
+ }
223
+ if (!JURISDICTION_RE.test(s)) {
224
+ throw new TypeError(
225
+ "consentLedger: jurisdiction must be ISO-3166-1 alpha-2 uppercase (e.g. 'DE', 'US'), " +
226
+ "got " + JSON.stringify(s)
227
+ );
228
+ }
229
+ return s;
230
+ }
231
+
232
+ function _optEvidenceRef(s) {
233
+ if (s == null || s === "") return null;
234
+ if (typeof s !== "string") {
235
+ throw new TypeError("consentLedger: evidence_ref must be a string");
236
+ }
237
+ if (s.length > EVIDENCE_REF_MAX_LEN) {
238
+ throw new TypeError(
239
+ "consentLedger: evidence_ref must be <= " + EVIDENCE_REF_MAX_LEN + " characters"
240
+ );
241
+ }
242
+ if (CONTROL_BYTE_RE.test(s)) {
243
+ throw new TypeError("consentLedger: evidence_ref must not contain control bytes");
244
+ }
245
+ if (!EVIDENCE_REF_RE.test(s)) {
246
+ throw new TypeError(
247
+ "consentLedger: evidence_ref must match /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/"
248
+ );
249
+ }
250
+ return s;
251
+ }
252
+
253
+ function _format(s) {
254
+ if (typeof s !== "string" || EXPORT_FORMATS.indexOf(s) === -1) {
255
+ throw new TypeError(
256
+ "consentLedger: format must be one of " + EXPORT_FORMATS.join(", ") +
257
+ ", got " + JSON.stringify(s)
258
+ );
259
+ }
260
+ return s;
261
+ }
262
+
263
+ function _tsBound(n, label) {
264
+ if (!Number.isInteger(n) || n < 0) {
265
+ throw new TypeError(
266
+ "consentLedger: " + label + " must be a non-negative integer (ms epoch)"
267
+ );
268
+ }
269
+ return n;
270
+ }
271
+
272
+ function _windowBounds(from, to, label) {
273
+ _tsBound(from, label + ".from");
274
+ _tsBound(to, label + ".to");
275
+ if (to <= from) {
276
+ throw new TypeError(
277
+ "consentLedger." + label + ": to must be > from"
278
+ );
279
+ }
280
+ }
281
+
282
+ // ---- row hydration ------------------------------------------------------
283
+
284
+ function _rowToRecord(row) {
285
+ if (!row) return null;
286
+ return {
287
+ id: row.id,
288
+ customer_id: row.customer_id,
289
+ consent_kind: row.consent_kind,
290
+ state: row.state,
291
+ source: row.source,
292
+ jurisdiction: row.jurisdiction == null ? null : row.jurisdiction,
293
+ evidence_ref: row.evidence_ref == null ? null : row.evidence_ref,
294
+ occurred_at: Number(row.occurred_at),
295
+ };
296
+ }
297
+
298
+ // ---- CSV emission -------------------------------------------------------
299
+ //
300
+ // Compose `b.csv.stringify` so the audit / bulk-export output is RFC
301
+ // 4180-shaped (quoting only on delimiter / quote / CR / LF). Header
302
+ // row is always emitted so a downstream auditor can ingest the file
303
+ // without external column metadata.
304
+
305
+ function _toCsv(rows) {
306
+ return _b().csv.stringify(rows, {
307
+ columns: CSV_COLUMNS.slice(),
308
+ header: true,
309
+ eol: "\n",
310
+ });
311
+ }
312
+
313
+ // ---- factory ------------------------------------------------------------
314
+
315
+ function create(opts) {
316
+ opts = opts || {};
317
+ var query = opts.query;
318
+ if (!query) {
319
+ query = function (sql, params) { return _b().externalDb.query(sql, params); };
320
+ }
321
+
322
+ // Append-only INSERT. The row is persisted with a fresh UUIDv7 id
323
+ // (lexicographically sortable, tie-breaks occurred_at ordering on
324
+ // the per-customer history walk). The customer_id is validated as
325
+ // a strict UUID — any non-UUID identifier is refused at the door
326
+ // so a typo can't quietly land an orphan row.
327
+ async function recordConsentChange(input) {
328
+ if (!input || typeof input !== "object") {
329
+ throw new TypeError("consentLedger.recordConsentChange: input object required");
330
+ }
331
+ var customerId = _customerId(input.customer_id);
332
+ var consentKind = _consentKind(input.consent_kind);
333
+ var state = _state(input.state);
334
+ var source = _source(input.source);
335
+ var jurisdiction = _optJurisdiction(input.jurisdiction);
336
+ var evidenceRef = _optEvidenceRef(input.evidence_ref);
337
+
338
+ var id = _b().uuid.v7();
339
+ var ts = _now();
340
+
341
+ await query(
342
+ "INSERT INTO consent_ledger " +
343
+ "(id, customer_id, consent_kind, state, source, jurisdiction, evidence_ref, occurred_at) " +
344
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
345
+ [id, customerId, consentKind, state, source, jurisdiction, evidenceRef, ts],
346
+ );
347
+
348
+ return {
349
+ id: id,
350
+ customer_id: customerId,
351
+ consent_kind: consentKind,
352
+ state: state,
353
+ source: source,
354
+ jurisdiction: jurisdiction,
355
+ evidence_ref: evidenceRef,
356
+ occurred_at: ts,
357
+ };
358
+ }
359
+
360
+ // Latest decision per consent_kind for this customer. Returns an
361
+ // object keyed by consent_kind; kinds with no row are omitted so
362
+ // the caller can decide what "no record" means (a missing
363
+ // marketing_email row typically reads as "not opted in").
364
+ async function currentStateForCustomer(customerId) {
365
+ customerId = _customerId(customerId);
366
+ var r = await query(
367
+ "SELECT * FROM consent_ledger " +
368
+ "WHERE customer_id = ?1 " +
369
+ "ORDER BY occurred_at ASC, id ASC",
370
+ [customerId],
371
+ );
372
+ var out = {};
373
+ for (var i = 0; i < r.rows.length; i += 1) {
374
+ var rec = _rowToRecord(r.rows[i]);
375
+ // Later rows overwrite earlier ones for the same kind — ASC
376
+ // order means the last write wins, which is exactly the
377
+ // "latest state" semantics.
378
+ out[rec.consent_kind] = {
379
+ state: rec.state,
380
+ source: rec.source,
381
+ jurisdiction: rec.jurisdiction,
382
+ evidence_ref: rec.evidence_ref,
383
+ occurred_at: rec.occurred_at,
384
+ };
385
+ }
386
+ return out;
387
+ }
388
+
389
+ // Newest-first row dump for one customer. Used by the SAR audit
390
+ // export path AND by an operator-facing "buyer's consent history"
391
+ // UI under /admin/customers/:id.
392
+ async function historyForCustomer(customerId) {
393
+ customerId = _customerId(customerId);
394
+ var r = await query(
395
+ "SELECT * FROM consent_ledger " +
396
+ "WHERE customer_id = ?1 " +
397
+ "ORDER BY occurred_at DESC, id DESC",
398
+ [customerId],
399
+ );
400
+ var out = [];
401
+ for (var i = 0; i < r.rows.length; i += 1) {
402
+ out.push(_rowToRecord(r.rows[i]));
403
+ }
404
+ return out;
405
+ }
406
+
407
+ // SAR export — the buyer (or their authorised representative)
408
+ // requests every row of consent activity. CSV is the supervisory-
409
+ // authority-friendly default; JSON is the structured shape an
410
+ // operator's portal can render in-page.
411
+ async function auditExport(input) {
412
+ if (!input || typeof input !== "object") {
413
+ throw new TypeError("consentLedger.auditExport: input object required");
414
+ }
415
+ var customerId = _customerId(input.customer_id);
416
+ var format = _format(input.format);
417
+ var rows = await historyForCustomer(customerId);
418
+ if (format === "json") {
419
+ return { format: "json", rows: rows };
420
+ }
421
+ return { format: "csv", body: _toCsv(rows) };
422
+ }
423
+
424
+ // Supervisory-authority sweep. The operator's DPO receives a
425
+ // request from a country's data-protection authority asking for
426
+ // every consent decision recorded for that jurisdiction in a time
427
+ // window. The default output is CSV (it's what the regulator will
428
+ // ingest); JSON is available for downstream tooling.
429
+ async function bulkExportByJurisdiction(input) {
430
+ if (!input || typeof input !== "object") {
431
+ throw new TypeError("consentLedger.bulkExportByJurisdiction: input object required");
432
+ }
433
+ var jurisdiction = _reqJurisdiction(input.jurisdiction);
434
+ _windowBounds(input.from, input.to, "bulkExportByJurisdiction");
435
+ var format = input.format == null ? "csv" : _format(input.format);
436
+
437
+ var r = await query(
438
+ "SELECT * FROM consent_ledger " +
439
+ "WHERE jurisdiction = ?1 AND occurred_at >= ?2 AND occurred_at < ?3 " +
440
+ "ORDER BY occurred_at ASC, id ASC",
441
+ [jurisdiction, input.from, input.to],
442
+ );
443
+ var rows = [];
444
+ for (var i = 0; i < r.rows.length; i += 1) {
445
+ rows.push(_rowToRecord(r.rows[i]));
446
+ }
447
+ if (format === "json") {
448
+ return { format: "json", jurisdiction: jurisdiction, rows: rows };
449
+ }
450
+ return { format: "csv", jurisdiction: jurisdiction, body: _toCsv(rows) };
451
+ }
452
+
453
+ // Operator-facing aggregate over a closed window. Returns one
454
+ // entry per (consent_kind, source) tuple with granted /
455
+ // withdrawn counts. The summary doesn't expose row-level
456
+ // customer_ids — it's the shape a quarterly compliance review
457
+ // consumes ("how many marketing_email opt-ins were recorded
458
+ // through preference_center last quarter"). Jurisdiction filter
459
+ // is optional; omitting it summarises every jurisdiction.
460
+ async function summarizeForCompliance(input) {
461
+ if (!input || typeof input !== "object") {
462
+ throw new TypeError("consentLedger.summarizeForCompliance: input object required");
463
+ }
464
+ _windowBounds(input.from, input.to, "summarizeForCompliance");
465
+ var jurisdiction = input.jurisdiction == null
466
+ ? null
467
+ : _reqJurisdiction(input.jurisdiction);
468
+
469
+ var sql, params;
470
+ if (jurisdiction == null) {
471
+ sql =
472
+ "SELECT consent_kind, source, state, COUNT(*) AS n " +
473
+ "FROM consent_ledger " +
474
+ "WHERE occurred_at >= ?1 AND occurred_at < ?2 " +
475
+ "GROUP BY consent_kind, source, state " +
476
+ "ORDER BY consent_kind ASC, source ASC, state ASC";
477
+ params = [input.from, input.to];
478
+ } else {
479
+ sql =
480
+ "SELECT consent_kind, source, state, COUNT(*) AS n " +
481
+ "FROM consent_ledger " +
482
+ "WHERE occurred_at >= ?1 AND occurred_at < ?2 AND jurisdiction = ?3 " +
483
+ "GROUP BY consent_kind, source, state " +
484
+ "ORDER BY consent_kind ASC, source ASC, state ASC";
485
+ params = [input.from, input.to, jurisdiction];
486
+ }
487
+ var r = await query(sql, params);
488
+
489
+ // Collapse into a stable nested map keyed by consent_kind ->
490
+ // source -> { granted, withdrawn, total }. Tuples with no
491
+ // observations are omitted (operators reading the summary
492
+ // shouldn't infer "zero" from "absent" without thinking).
493
+ var summary = {};
494
+ for (var i = 0; i < r.rows.length; i += 1) {
495
+ var row = r.rows[i];
496
+ var kind = row.consent_kind;
497
+ var src = row.source;
498
+ var st = row.state;
499
+ var n = Number(row.n) || 0;
500
+ if (!summary[kind]) summary[kind] = {};
501
+ if (!summary[kind][src]) summary[kind][src] = { granted: 0, withdrawn: 0, total: 0 };
502
+ summary[kind][src][st] = (summary[kind][src][st] || 0) + n;
503
+ summary[kind][src].total += n;
504
+ }
505
+
506
+ return {
507
+ from: input.from,
508
+ to: input.to,
509
+ jurisdiction: jurisdiction,
510
+ summary: summary,
511
+ };
512
+ }
513
+
514
+ return {
515
+ CONSENT_KINDS: CONSENT_KINDS,
516
+ STATES: STATES,
517
+ SOURCES: SOURCES,
518
+ EXPORT_FORMATS: EXPORT_FORMATS,
519
+ CSV_COLUMNS: CSV_COLUMNS,
520
+ EVIDENCE_REF_MAX_LEN: EVIDENCE_REF_MAX_LEN,
521
+
522
+ recordConsentChange: recordConsentChange,
523
+ currentStateForCustomer: currentStateForCustomer,
524
+ historyForCustomer: historyForCustomer,
525
+ auditExport: auditExport,
526
+ bulkExportByJurisdiction: bulkExportByJurisdiction,
527
+ summarizeForCompliance: summarizeForCompliance,
528
+ };
529
+ }
530
+
531
+ module.exports = {
532
+ create: create,
533
+ CONSENT_KINDS: CONSENT_KINDS,
534
+ STATES: STATES,
535
+ SOURCES: SOURCES,
536
+ EXPORT_FORMATS: EXPORT_FORMATS,
537
+ CSV_COLUMNS: CSV_COLUMNS,
538
+ EVIDENCE_REF_MAX_LEN: EVIDENCE_REF_MAX_LEN,
539
+ };