@happyvertical/smrt-sales 0.40.9 → 0.40.10
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.
- package/AGENTS.md +2 -1
- package/dist/agreements.js +1 -1
- package/dist/chunks/__smrt-register__-CZE4Gm8_.js +6 -0
- package/dist/chunks/{__smrt-register__-D08SaNJE.js.map → __smrt-register__-CZE4Gm8_.js.map} +1 -1
- package/dist/chunks/{referrals-BXqD65KR.js → referrals-CJUOZxpa.js} +429 -74
- package/dist/chunks/referrals-CJUOZxpa.js.map +1 -0
- package/dist/commissions.js +1 -1
- package/dist/crm.js +1 -1
- package/dist/index.js +3 -3
- package/dist/manifest.json +329 -2
- package/dist/referrals/collections/ReferralClickOperationCollection.d.ts +29 -0
- package/dist/referrals/collections/ReferralClickOperationCollection.d.ts.map +1 -0
- package/dist/referrals/collections/ReferralLinkCollection.d.ts +56 -5
- package/dist/referrals/collections/ReferralLinkCollection.d.ts.map +1 -1
- package/dist/referrals/index.d.ts +1 -1
- package/dist/referrals/index.d.ts.map +1 -1
- package/dist/referrals/models/ReferralClickOperation.d.ts +39 -0
- package/dist/referrals/models/ReferralClickOperation.d.ts.map +1 -0
- package/dist/referrals/models/ReferralLink.d.ts +3 -2
- package/dist/referrals/models/ReferralLink.d.ts.map +1 -1
- package/dist/referrals.js +3 -3
- package/dist/smrt-knowledge.json +89 -7
- package/package.json +6 -6
- package/dist/chunks/__smrt-register__-D08SaNJE.js +0 -6
- package/dist/chunks/referrals-BXqD65KR.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"__smrt-register__-
|
|
1
|
+
{"version":3,"file":"__smrt-register__-CZE4Gm8_.js","names":[],"sources":["../../src/__smrt-register__.ts"],"sourcesContent":["import { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n"],"mappings":""}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { P as CommissionCollection, i as CommissionCalculationService, y as CommissionPlanCollection } from "./commissions-CelWwvjZ.js";
|
|
2
2
|
import { SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
3
3
|
import { TenantScoped, requireTenantId, tenantId } from "@happyvertical/smrt-tenancy";
|
|
4
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
4
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
//#region src/referrals/models/AttributionException.ts
|
|
6
|
-
var __defProp$
|
|
7
|
-
var __getOwnPropDesc$
|
|
8
|
-
var __decorateClass$
|
|
9
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
6
|
+
var __defProp$9 = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc$9 = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __decorateClass$9 = (decorators, target, key, kind) => {
|
|
9
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$9(target, key) : target;
|
|
10
10
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
11
|
-
if (kind && result) __defProp$
|
|
11
|
+
if (kind && result) __defProp$9(target, key, result);
|
|
12
12
|
return result;
|
|
13
13
|
};
|
|
14
14
|
var AttributionException = class extends SmrtObject {
|
|
@@ -146,12 +146,12 @@ var AttributionException = class extends SmrtObject {
|
|
|
146
146
|
return null;
|
|
147
147
|
}
|
|
148
148
|
};
|
|
149
|
-
__decorateClass$
|
|
150
|
-
__decorateClass$
|
|
151
|
-
__decorateClass$
|
|
152
|
-
__decorateClass$
|
|
153
|
-
__decorateClass$
|
|
154
|
-
AttributionException = __decorateClass$
|
|
149
|
+
__decorateClass$9([tenantId({ nullable: true })], AttributionException.prototype, "tenantId", 2);
|
|
150
|
+
__decorateClass$9([field({ required: true })], AttributionException.prototype, "targetKind", 2);
|
|
151
|
+
__decorateClass$9([field({ required: true })], AttributionException.prototype, "targetId", 2);
|
|
152
|
+
__decorateClass$9([foreignKey("ReferralProgram")], AttributionException.prototype, "programId", 2);
|
|
153
|
+
__decorateClass$9([crossPackageRef("@happyvertical/smrt-profiles:Profile")], AttributionException.prototype, "resolvedByProfileId", 2);
|
|
154
|
+
AttributionException = __decorateClass$9([TenantScoped({ mode: "optional" }), smrt({
|
|
155
155
|
api: { include: ["list", "get"] },
|
|
156
156
|
mcp: { include: ["list", "get"] },
|
|
157
157
|
cli: false
|
|
@@ -247,12 +247,12 @@ var REFERRAL_AGREEMENT_STATUSES = [
|
|
|
247
247
|
var REFERRAL_AGREEMENT_APPROVAL_MODES = ["manual", "auto"];
|
|
248
248
|
//#endregion
|
|
249
249
|
//#region src/referrals/models/AttributionPolicy.ts
|
|
250
|
-
var __defProp$
|
|
251
|
-
var __getOwnPropDesc$
|
|
252
|
-
var __decorateClass$
|
|
253
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
250
|
+
var __defProp$8 = Object.defineProperty;
|
|
251
|
+
var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
|
|
252
|
+
var __decorateClass$8 = (decorators, target, key, kind) => {
|
|
253
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
|
|
254
254
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
255
|
-
if (kind && result) __defProp$
|
|
255
|
+
if (kind && result) __defProp$8(target, key, result);
|
|
256
256
|
return result;
|
|
257
257
|
};
|
|
258
258
|
var POLICY_STATUS_TRANSITIONS = {
|
|
@@ -519,9 +519,9 @@ var AttributionPolicy = class extends SmrtObject {
|
|
|
519
519
|
return null;
|
|
520
520
|
}
|
|
521
521
|
};
|
|
522
|
-
__decorateClass$
|
|
523
|
-
__decorateClass$
|
|
524
|
-
AttributionPolicy = __decorateClass$
|
|
522
|
+
__decorateClass$8([tenantId({ nullable: true })], AttributionPolicy.prototype, "tenantId", 2);
|
|
523
|
+
__decorateClass$8([field({ required: true })], AttributionPolicy.prototype, "policyKey", 2);
|
|
524
|
+
AttributionPolicy = __decorateClass$8([TenantScoped({ mode: "optional" }), smrt({
|
|
525
525
|
conflictColumns: [
|
|
526
526
|
"tenant_id",
|
|
527
527
|
"policy_key",
|
|
@@ -605,12 +605,12 @@ var AttributionPolicyCollection = class extends SmrtCollection {
|
|
|
605
605
|
};
|
|
606
606
|
//#endregion
|
|
607
607
|
//#region src/referrals/models/ReferralAgreement.ts
|
|
608
|
-
var __defProp$
|
|
609
|
-
var __getOwnPropDesc$
|
|
610
|
-
var __decorateClass$
|
|
611
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
608
|
+
var __defProp$7 = Object.defineProperty;
|
|
609
|
+
var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
|
|
610
|
+
var __decorateClass$7 = (decorators, target, key, kind) => {
|
|
611
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
|
|
612
612
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
613
|
-
if (kind && result) __defProp$
|
|
613
|
+
if (kind && result) __defProp$7(target, key, result);
|
|
614
614
|
return result;
|
|
615
615
|
};
|
|
616
616
|
var AGREEMENT_STATUS_TRANSITIONS = {
|
|
@@ -847,12 +847,12 @@ var ReferralAgreement = class extends SmrtObject {
|
|
|
847
847
|
return null;
|
|
848
848
|
}
|
|
849
849
|
};
|
|
850
|
-
__decorateClass$
|
|
851
|
-
__decorateClass$
|
|
852
|
-
__decorateClass$
|
|
853
|
-
__decorateClass$
|
|
854
|
-
__decorateClass$
|
|
855
|
-
ReferralAgreement = __decorateClass$
|
|
850
|
+
__decorateClass$7([tenantId({ nullable: true })], ReferralAgreement.prototype, "tenantId", 2);
|
|
851
|
+
__decorateClass$7([foreignKey("Referrer", { required: true })], ReferralAgreement.prototype, "referrerId", 2);
|
|
852
|
+
__decorateClass$7([foreignKey("ReferralProgram", { required: true })], ReferralAgreement.prototype, "programId", 2);
|
|
853
|
+
__decorateClass$7([foreignKey("AgreementExecution")], ReferralAgreement.prototype, "executionId", 2);
|
|
854
|
+
__decorateClass$7([foreignKey("ExecutedAgreement")], ReferralAgreement.prototype, "executedAgreementId", 2);
|
|
855
|
+
ReferralAgreement = __decorateClass$7([TenantScoped({ mode: "optional" }), smrt({
|
|
856
856
|
conflictColumns: [
|
|
857
857
|
"referrer_id",
|
|
858
858
|
"program_id",
|
|
@@ -931,12 +931,12 @@ var ReferralAgreementCollection = class extends SmrtCollection {
|
|
|
931
931
|
};
|
|
932
932
|
//#endregion
|
|
933
933
|
//#region src/referrals/models/Referral.ts
|
|
934
|
-
var __defProp$
|
|
935
|
-
var __getOwnPropDesc$
|
|
936
|
-
var __decorateClass$
|
|
937
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
934
|
+
var __defProp$6 = Object.defineProperty;
|
|
935
|
+
var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
|
|
936
|
+
var __decorateClass$6 = (decorators, target, key, kind) => {
|
|
937
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
|
|
938
938
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
939
|
-
if (kind && result) __defProp$
|
|
939
|
+
if (kind && result) __defProp$6(target, key, result);
|
|
940
940
|
return result;
|
|
941
941
|
};
|
|
942
942
|
var REFERRAL_STATUS_TRANSITIONS = {
|
|
@@ -1143,11 +1143,11 @@ var Referral = class extends SmrtObject {
|
|
|
1143
1143
|
return null;
|
|
1144
1144
|
}
|
|
1145
1145
|
};
|
|
1146
|
-
__decorateClass$
|
|
1147
|
-
__decorateClass$
|
|
1148
|
-
__decorateClass$
|
|
1149
|
-
__decorateClass$
|
|
1150
|
-
Referral = __decorateClass$
|
|
1146
|
+
__decorateClass$6([tenantId({ nullable: true })], Referral.prototype, "tenantId", 2);
|
|
1147
|
+
__decorateClass$6([foreignKey("Referrer", { required: true })], Referral.prototype, "referrerId", 2);
|
|
1148
|
+
__decorateClass$6([foreignKey("ReferralProgram", { required: true })], Referral.prototype, "programId", 2);
|
|
1149
|
+
__decorateClass$6([foreignKey("ReferralTouch")], Referral.prototype, "primaryTouchId", 2);
|
|
1150
|
+
Referral = __decorateClass$6([TenantScoped({ mode: "optional" }), smrt({
|
|
1151
1151
|
api: { include: [
|
|
1152
1152
|
"list",
|
|
1153
1153
|
"get",
|
|
@@ -1217,12 +1217,12 @@ var ReferralCollection = class extends SmrtCollection {
|
|
|
1217
1217
|
};
|
|
1218
1218
|
//#endregion
|
|
1219
1219
|
//#region src/referrals/models/ReferralLink.ts
|
|
1220
|
-
var __defProp$
|
|
1221
|
-
var __getOwnPropDesc$
|
|
1222
|
-
var __decorateClass$
|
|
1223
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
1220
|
+
var __defProp$5 = Object.defineProperty;
|
|
1221
|
+
var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
|
|
1222
|
+
var __decorateClass$5 = (decorators, target, key, kind) => {
|
|
1223
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
|
|
1224
1224
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1225
|
-
if (kind && result) __defProp$
|
|
1225
|
+
if (kind && result) __defProp$5(target, key, result);
|
|
1226
1226
|
return result;
|
|
1227
1227
|
};
|
|
1228
1228
|
function assertHttpTargetUrl(targetUrl) {
|
|
@@ -1308,11 +1308,11 @@ var ReferralLink = class extends SmrtObject {
|
|
|
1308
1308
|
return await super.save();
|
|
1309
1309
|
}
|
|
1310
1310
|
};
|
|
1311
|
-
__decorateClass$
|
|
1312
|
-
__decorateClass$
|
|
1313
|
-
__decorateClass$
|
|
1314
|
-
__decorateClass$
|
|
1315
|
-
ReferralLink = __decorateClass$
|
|
1311
|
+
__decorateClass$5([tenantId({ nullable: true })], ReferralLink.prototype, "tenantId", 2);
|
|
1312
|
+
__decorateClass$5([foreignKey("Referrer", { required: true })], ReferralLink.prototype, "referrerId", 2);
|
|
1313
|
+
__decorateClass$5([foreignKey("ReferralProgram", { required: true })], ReferralLink.prototype, "programId", 2);
|
|
1314
|
+
__decorateClass$5([field({ required: true })], ReferralLink.prototype, "code", 2);
|
|
1315
|
+
ReferralLink = __decorateClass$5([TenantScoped({ mode: "optional" }), smrt({
|
|
1316
1316
|
conflictColumns: ["code"],
|
|
1317
1317
|
api: { include: [
|
|
1318
1318
|
"list",
|
|
@@ -1328,6 +1328,113 @@ ReferralLink = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
|
|
|
1328
1328
|
cli: true
|
|
1329
1329
|
})], ReferralLink);
|
|
1330
1330
|
//#endregion
|
|
1331
|
+
//#region src/referrals/models/ReferralClickOperation.ts
|
|
1332
|
+
var __defProp$4 = Object.defineProperty;
|
|
1333
|
+
var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
|
|
1334
|
+
var __decorateClass$4 = (decorators, target, key, kind) => {
|
|
1335
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
|
|
1336
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1337
|
+
if (kind && result) __defProp$4(target, key, result);
|
|
1338
|
+
return result;
|
|
1339
|
+
};
|
|
1340
|
+
var ReferralClickOperation = class extends SmrtObject {
|
|
1341
|
+
tenantId = null;
|
|
1342
|
+
keyHash;
|
|
1343
|
+
linkId;
|
|
1344
|
+
touchId;
|
|
1345
|
+
intentHash;
|
|
1346
|
+
callerEvidenceHash;
|
|
1347
|
+
requestedOccurredAt;
|
|
1348
|
+
constructor(options = {}) {
|
|
1349
|
+
super(options);
|
|
1350
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
1351
|
+
if (options.keyHash !== void 0) this.keyHash = options.keyHash;
|
|
1352
|
+
if (options.linkId !== void 0) this.linkId = options.linkId;
|
|
1353
|
+
if (options.touchId !== void 0) this.touchId = options.touchId;
|
|
1354
|
+
if (options.intentHash !== void 0) this.intentHash = options.intentHash;
|
|
1355
|
+
if (options.callerEvidenceHash !== void 0) this.callerEvidenceHash = options.callerEvidenceHash;
|
|
1356
|
+
if (options.requestedOccurredAt !== void 0) this.requestedOccurredAt = options.requestedOccurredAt;
|
|
1357
|
+
}
|
|
1358
|
+
/** Keep the TEXT replay marker a string when adapters parse `*_at`. */
|
|
1359
|
+
async initialize() {
|
|
1360
|
+
await super.initialize();
|
|
1361
|
+
const value = this.requestedOccurredAt;
|
|
1362
|
+
if (value instanceof Date) this.requestedOccurredAt = value.toISOString();
|
|
1363
|
+
return this;
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
__decorateClass$4([tenantId({ nullable: true })], ReferralClickOperation.prototype, "tenantId", 2);
|
|
1367
|
+
__decorateClass$4([field({
|
|
1368
|
+
required: true,
|
|
1369
|
+
readonly: true
|
|
1370
|
+
})], ReferralClickOperation.prototype, "keyHash", 2);
|
|
1371
|
+
__decorateClass$4([field({
|
|
1372
|
+
sqlType: "UUID",
|
|
1373
|
+
required: true,
|
|
1374
|
+
readonly: true,
|
|
1375
|
+
indexed: true
|
|
1376
|
+
})], ReferralClickOperation.prototype, "linkId", 2);
|
|
1377
|
+
__decorateClass$4([field({
|
|
1378
|
+
sqlType: "UUID",
|
|
1379
|
+
required: true,
|
|
1380
|
+
readonly: true,
|
|
1381
|
+
indexed: true
|
|
1382
|
+
})], ReferralClickOperation.prototype, "touchId", 2);
|
|
1383
|
+
__decorateClass$4([field({
|
|
1384
|
+
required: true,
|
|
1385
|
+
readonly: true
|
|
1386
|
+
})], ReferralClickOperation.prototype, "intentHash", 2);
|
|
1387
|
+
__decorateClass$4([field({
|
|
1388
|
+
required: true,
|
|
1389
|
+
readonly: true
|
|
1390
|
+
})], ReferralClickOperation.prototype, "callerEvidenceHash", 2);
|
|
1391
|
+
__decorateClass$4([field({
|
|
1392
|
+
required: true,
|
|
1393
|
+
readonly: true
|
|
1394
|
+
})], ReferralClickOperation.prototype, "requestedOccurredAt", 2);
|
|
1395
|
+
ReferralClickOperation = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
|
|
1396
|
+
api: false,
|
|
1397
|
+
mcp: false,
|
|
1398
|
+
cli: false
|
|
1399
|
+
})], ReferralClickOperation);
|
|
1400
|
+
//#endregion
|
|
1401
|
+
//#region src/referrals/collections/ReferralClickOperationCollection.ts
|
|
1402
|
+
var ReferralClickOperationCollection = class extends SmrtCollection {
|
|
1403
|
+
static _itemClass = ReferralClickOperation;
|
|
1404
|
+
/** Tenant-filtered lookup; a foreign operation payload remains invisible. */
|
|
1405
|
+
async findByOperationId(operationId) {
|
|
1406
|
+
return await this.get({ id: operationId }, { cache: false });
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Claim the deterministic operation UUID without changing a winner.
|
|
1410
|
+
* Must run in the transaction that creates the touch and increments the
|
|
1411
|
+
* link counter.
|
|
1412
|
+
*/
|
|
1413
|
+
async claim(input) {
|
|
1414
|
+
const inserted = await this.query(`INSERT INTO ${this.tableName} (
|
|
1415
|
+
id, slug, context, tenant_id, key_hash, link_id, touch_id,
|
|
1416
|
+
intent_hash, caller_evidence_hash, requested_occurred_at
|
|
1417
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1418
|
+
ON CONFLICT (id) DO NOTHING
|
|
1419
|
+
RETURNING id`, [
|
|
1420
|
+
input.operationId,
|
|
1421
|
+
input.operationId,
|
|
1422
|
+
"",
|
|
1423
|
+
input.tenantId,
|
|
1424
|
+
input.keyHash,
|
|
1425
|
+
input.linkId,
|
|
1426
|
+
input.touchId,
|
|
1427
|
+
input.intentHash,
|
|
1428
|
+
input.callerEvidenceHash,
|
|
1429
|
+
input.requestedOccurredAt
|
|
1430
|
+
], { allowRawOnTenantScoped: true });
|
|
1431
|
+
return {
|
|
1432
|
+
operation: await this.findByOperationId(input.operationId),
|
|
1433
|
+
claimed: inserted.length === 1
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
//#endregion
|
|
1331
1438
|
//#region src/referrals/models/ReferralTouch.ts
|
|
1332
1439
|
var __defProp$3 = Object.defineProperty;
|
|
1333
1440
|
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
@@ -1487,6 +1594,8 @@ var ReferralTouchCollection = class extends SmrtCollection {
|
|
|
1487
1594
|
var REFERRAL_CODE_LENGTH = 10;
|
|
1488
1595
|
var REFERRAL_CODE_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
1489
1596
|
var MAX_CODE_GENERATION_ATTEMPTS = 5;
|
|
1597
|
+
var DEFAULT_REFERRAL_CLICK_EVIDENCE_MAX_BYTES = 4096;
|
|
1598
|
+
var MAX_REFERRAL_CLICK_IDEMPOTENCY_KEY_BYTES = 256;
|
|
1490
1599
|
function generateReferralCode() {
|
|
1491
1600
|
const alphabetSize = 36;
|
|
1492
1601
|
const rejectionBound = 256 - 256 % alphabetSize;
|
|
@@ -1501,7 +1610,29 @@ function generateReferralCode() {
|
|
|
1501
1610
|
}
|
|
1502
1611
|
return code;
|
|
1503
1612
|
}
|
|
1504
|
-
var
|
|
1613
|
+
var ReferralClickReplayConflictError = class extends Error {
|
|
1614
|
+
constructor(idempotencyKey, mismatches) {
|
|
1615
|
+
super(`Referral click idempotency key '${idempotencyKey}' was already used with different ${mismatches.length === 1 ? "intent field" : "intent fields"}: ` + mismatches.join(", "));
|
|
1616
|
+
this.idempotencyKey = idempotencyKey;
|
|
1617
|
+
this.mismatches = mismatches;
|
|
1618
|
+
this.name = "ReferralClickReplayConflictError";
|
|
1619
|
+
}
|
|
1620
|
+
idempotencyKey;
|
|
1621
|
+
mismatches;
|
|
1622
|
+
code = "REFERRAL_CLICK_REPLAY_CONFLICT";
|
|
1623
|
+
};
|
|
1624
|
+
var ReferralClickValidationError = class extends Error {
|
|
1625
|
+
constructor(reason, message, details) {
|
|
1626
|
+
super(message);
|
|
1627
|
+
this.reason = reason;
|
|
1628
|
+
this.details = details;
|
|
1629
|
+
this.name = "ReferralClickValidationError";
|
|
1630
|
+
}
|
|
1631
|
+
reason;
|
|
1632
|
+
details;
|
|
1633
|
+
code = "REFERRAL_CLICK_VALIDATION_ERROR";
|
|
1634
|
+
};
|
|
1635
|
+
var ReferralLinkCollection = class ReferralLinkCollection extends SmrtCollection {
|
|
1505
1636
|
static _itemClass = ReferralLink;
|
|
1506
1637
|
/** All links for a referrer, newest first. */
|
|
1507
1638
|
async findByReferrer(referrerId) {
|
|
@@ -1552,10 +1683,16 @@ var ReferralLinkCollection = class extends SmrtCollection {
|
|
|
1552
1683
|
throw new Error(`ReferralLinkCollection.createWithUniqueCode: could not mint a unique code after 5 attempts \u2014 the code generator is returning colliding codes`);
|
|
1553
1684
|
}
|
|
1554
1685
|
/**
|
|
1555
|
-
* Record a click on a share code
|
|
1556
|
-
* {@link ReferralLink.clickCount}
|
|
1557
|
-
*
|
|
1558
|
-
*
|
|
1686
|
+
* Record a click on a share code. A private operation fence, immutable
|
|
1687
|
+
* ReferralTouch, and {@link ReferralLink.clickCount} increment commit in
|
|
1688
|
+
* one supported database transaction. An exact `idempotencyKey` replay
|
|
1689
|
+
* returns the original touch without another increment; changed immutable
|
|
1690
|
+
* intent raises {@link ReferralClickReplayConflictError}.
|
|
1691
|
+
*
|
|
1692
|
+
* Evidence is canonicalized, Sales-owned `code`/`linkId`/`targetUrl` fields
|
|
1693
|
+
* are applied, and the UTF-8 bytes of that exact persisted JSON are checked
|
|
1694
|
+
* before any write. The default bound is
|
|
1695
|
+
* {@link DEFAULT_REFERRAL_CLICK_EVIDENCE_MAX_BYTES}.
|
|
1559
1696
|
*
|
|
1560
1697
|
* Refusals return a typed result instead of throwing (clicks arrive from
|
|
1561
1698
|
* the edge — an unknown or disabled code is an expected outcome, not an
|
|
@@ -1564,7 +1701,41 @@ var ReferralLinkCollection = class extends SmrtCollection {
|
|
|
1564
1701
|
* returned, nothing is written).
|
|
1565
1702
|
*/
|
|
1566
1703
|
async recordClick(input) {
|
|
1567
|
-
const
|
|
1704
|
+
const request = canonicalizeClickRequest(input);
|
|
1705
|
+
const db = this.db;
|
|
1706
|
+
if (typeof db.transaction !== "function") throw new ReferralClickValidationError("transaction_unavailable", "Referral click recording requires a transaction-capable database adapter");
|
|
1707
|
+
const transactionResult = await db.transaction(async (tx) => {
|
|
1708
|
+
const deps = {
|
|
1709
|
+
links: await ReferralLinkCollection.create({ db: tx }),
|
|
1710
|
+
touches: await ReferralTouchCollection.create({ db: tx }),
|
|
1711
|
+
operations: await ReferralClickOperationCollection.create({ db: tx })
|
|
1712
|
+
};
|
|
1713
|
+
return await this.recordClickInTransaction(deps, request);
|
|
1714
|
+
});
|
|
1715
|
+
return await this.rehydrateRecordClickResult(transactionResult);
|
|
1716
|
+
}
|
|
1717
|
+
/** Rebind public result models to the caller's database after commit. */
|
|
1718
|
+
async rehydrateRecordClickResult(result) {
|
|
1719
|
+
let link = null;
|
|
1720
|
+
if (result.link?.id) {
|
|
1721
|
+
link = await this.get({ id: result.link.id }, { cache: false });
|
|
1722
|
+
if (!link) throw new ReferralClickValidationError("operation_link_missing", "Committed referral click result has no visible link");
|
|
1723
|
+
}
|
|
1724
|
+
let touch = null;
|
|
1725
|
+
if (result.touch?.id) {
|
|
1726
|
+
touch = await (await ReferralTouchCollection.create({ db: this.db })).get({ id: result.touch.id }, { cache: false });
|
|
1727
|
+
if (!touch) throw new ReferralClickValidationError("operation_touch_missing", "Committed referral click result has no visible touch");
|
|
1728
|
+
}
|
|
1729
|
+
return {
|
|
1730
|
+
...result,
|
|
1731
|
+
link,
|
|
1732
|
+
touch
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
async recordClickInTransaction(deps, request) {
|
|
1736
|
+
const existingOperation = await deps.operations.findByOperationId(request.operationId);
|
|
1737
|
+
if (existingOperation) return await this.replayClick(deps, existingOperation, request);
|
|
1738
|
+
const link = await deps.links.findByCode(request.normalizedCode);
|
|
1568
1739
|
if (!link) return {
|
|
1569
1740
|
link: null,
|
|
1570
1741
|
touch: null,
|
|
@@ -1575,31 +1746,215 @@ var ReferralLinkCollection = class extends SmrtCollection {
|
|
|
1575
1746
|
touch: null,
|
|
1576
1747
|
refused: "link_disabled"
|
|
1577
1748
|
};
|
|
1578
|
-
|
|
1579
|
-
const
|
|
1749
|
+
if (!link.id) throw new ReferralClickValidationError("link_update_conflict", "Referral click link is missing its persisted identifier");
|
|
1750
|
+
const intent = buildClickIntent(link, request);
|
|
1751
|
+
assertEvidenceBound(intent.finalEvidence, request.maxEvidenceBytes);
|
|
1752
|
+
const touchId = randomUUID();
|
|
1753
|
+
const claimed = await deps.operations.claim({
|
|
1754
|
+
operationId: request.operationId,
|
|
1580
1755
|
tenantId: link.tenantId,
|
|
1581
|
-
|
|
1756
|
+
keyHash: request.keyHash,
|
|
1757
|
+
linkId: link.id,
|
|
1758
|
+
touchId,
|
|
1759
|
+
intentHash: intent.intentHash,
|
|
1760
|
+
callerEvidenceHash: request.callerEvidenceHash,
|
|
1761
|
+
requestedOccurredAt: request.requestedOccurredAt
|
|
1762
|
+
});
|
|
1763
|
+
if (!claimed.operation) throw new ReferralClickReplayConflictError(request.idempotencyKey, ["tenantId"]);
|
|
1764
|
+
if (!claimed.claimed) return await this.replayClick(deps, claimed.operation, request);
|
|
1765
|
+
const touch = await deps.touches.create({
|
|
1766
|
+
id: touchId,
|
|
1767
|
+
tenantId: link.tenantId,
|
|
1768
|
+
linkId: link.id,
|
|
1582
1769
|
code: link.code,
|
|
1583
1770
|
referrerId: link.referrerId,
|
|
1584
1771
|
programId: link.programId,
|
|
1585
1772
|
kind: "click",
|
|
1586
|
-
subjectKind:
|
|
1587
|
-
subjectId:
|
|
1588
|
-
occurredAt,
|
|
1589
|
-
evidence:
|
|
1590
|
-
...input.evidence ?? {},
|
|
1591
|
-
code: link.code,
|
|
1592
|
-
linkId: link.id ?? "",
|
|
1593
|
-
targetUrl: link.targetUrl
|
|
1594
|
-
})
|
|
1773
|
+
subjectKind: request.subjectKind,
|
|
1774
|
+
subjectId: request.subjectId,
|
|
1775
|
+
occurredAt: request.occurredAt ?? /* @__PURE__ */ new Date(),
|
|
1776
|
+
evidence: intent.finalEvidence
|
|
1595
1777
|
});
|
|
1596
|
-
await
|
|
1778
|
+
if ((await deps.links.query(`UPDATE ${deps.links.tableName}
|
|
1779
|
+
SET click_count = click_count + 1
|
|
1780
|
+
WHERE id = ?
|
|
1781
|
+
AND status = 'active'
|
|
1782
|
+
AND COALESCE(CAST(tenant_id AS TEXT), '') = ?
|
|
1783
|
+
RETURNING id`, [link.id, link.tenantId ?? ""], { allowRawOnTenantScoped: true })).length !== 1) throw new ReferralClickValidationError("link_update_conflict", "Referral link changed or was disabled while its click was being recorded; the transaction was rolled back");
|
|
1784
|
+
const fresh = await deps.links.get({ id: link.id }, { cache: false });
|
|
1785
|
+
if (!fresh || !sameClickLinkSnapshot(link, fresh)) throw new ReferralClickValidationError("link_update_conflict", "Referral link attribution changed while its click was being recorded; the transaction was rolled back");
|
|
1597
1786
|
return {
|
|
1598
|
-
link:
|
|
1599
|
-
touch
|
|
1787
|
+
link: fresh,
|
|
1788
|
+
touch,
|
|
1789
|
+
idempotencyKey: request.idempotencyKey,
|
|
1790
|
+
replayed: false
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
async replayClick(deps, operation, request) {
|
|
1794
|
+
if (operation.keyHash !== request.keyHash) throw new ReferralClickReplayConflictError(request.idempotencyKey, ["idempotencyKey"]);
|
|
1795
|
+
const link = await deps.links.get({ id: operation.linkId }, { cache: false });
|
|
1796
|
+
if (!link) throw new ReferralClickValidationError("operation_link_missing", `Referral click operation '${request.idempotencyKey}' has no visible link`);
|
|
1797
|
+
const touch = await deps.touches.get({ id: operation.touchId }, { cache: false });
|
|
1798
|
+
if (!touch) throw new ReferralClickValidationError("operation_touch_missing", `Referral click operation '${request.idempotencyKey}' has no visible touch`);
|
|
1799
|
+
const mismatches = collectReplayMismatches(operation, link, touch, request);
|
|
1800
|
+
if (mismatches.length > 0) throw new ReferralClickReplayConflictError(request.idempotencyKey, mismatches);
|
|
1801
|
+
return {
|
|
1802
|
+
link,
|
|
1803
|
+
touch,
|
|
1804
|
+
idempotencyKey: request.idempotencyKey,
|
|
1805
|
+
replayed: true
|
|
1600
1806
|
};
|
|
1601
1807
|
}
|
|
1602
1808
|
};
|
|
1809
|
+
function canonicalizeClickRequest(input) {
|
|
1810
|
+
const idempotencyKey = input.idempotencyKey ?? randomUUID();
|
|
1811
|
+
if (typeof idempotencyKey !== "string" || idempotencyKey.length === 0 || idempotencyKey.trim() !== idempotencyKey || !isWellFormedUnicode(idempotencyKey) || utf8ByteLength(idempotencyKey) > 256) throw new ReferralClickValidationError("invalid_idempotency_key", `Referral click idempotencyKey must be non-empty, unpadded, well-formed Unicode, and at most 256 UTF-8 bytes`);
|
|
1812
|
+
const maxEvidenceBytes = input.maxEvidenceBytes ?? 4096;
|
|
1813
|
+
if (!Number.isSafeInteger(maxEvidenceBytes) || maxEvidenceBytes <= 0) throw new ReferralClickValidationError("invalid_max_evidence_bytes", "Referral click maxEvidenceBytes must be a positive safe integer");
|
|
1814
|
+
let requestedOccurredAt = "";
|
|
1815
|
+
let occurredAt = null;
|
|
1816
|
+
if (input.occurredAt !== void 0) {
|
|
1817
|
+
if (!(input.occurredAt instanceof Date) || Number.isNaN(input.occurredAt.getTime())) throw new ReferralClickValidationError("invalid_occurred_at", "Referral click occurredAt must be a valid Date");
|
|
1818
|
+
occurredAt = new Date(input.occurredAt.getTime());
|
|
1819
|
+
requestedOccurredAt = occurredAt.toISOString();
|
|
1820
|
+
}
|
|
1821
|
+
const callerEvidence = canonicalizeCallerEvidence(input.evidence ?? {});
|
|
1822
|
+
const callerEvidenceJson = stableJson(callerEvidence);
|
|
1823
|
+
const keyDigest = createHash("sha256").update(idempotencyKey, "utf8").digest();
|
|
1824
|
+
return {
|
|
1825
|
+
idempotencyKey,
|
|
1826
|
+
operationId: uuidFromDigest(keyDigest),
|
|
1827
|
+
keyHash: keyDigest.toString("hex"),
|
|
1828
|
+
normalizedCode: String(input.code ?? "").trim().toLowerCase(),
|
|
1829
|
+
callerEvidence,
|
|
1830
|
+
callerEvidenceHash: hashText(callerEvidenceJson),
|
|
1831
|
+
requestedOccurredAt,
|
|
1832
|
+
occurredAt,
|
|
1833
|
+
subjectKind: input.subjectKind ?? "",
|
|
1834
|
+
subjectId: input.subjectId ?? "",
|
|
1835
|
+
maxEvidenceBytes
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
function canonicalizeCallerEvidence(evidence) {
|
|
1839
|
+
try {
|
|
1840
|
+
const serialized = JSON.stringify(evidence);
|
|
1841
|
+
if (serialized === void 0) throw new TypeError("evidence is undefined");
|
|
1842
|
+
const parsed = JSON.parse(serialized);
|
|
1843
|
+
if (!isPlainObject(parsed)) throw new TypeError("evidence must be a JSON object");
|
|
1844
|
+
return JSON.parse(stableJson(parsed));
|
|
1845
|
+
} catch (error) {
|
|
1846
|
+
throw new ReferralClickValidationError("invalid_evidence", `Referral click evidence must be a JSON-serializable object: ${error instanceof Error ? error.message : String(error)}`);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
function buildClickIntent(link, request) {
|
|
1850
|
+
const finalEvidence = stableJson(Object.assign(/* @__PURE__ */ Object.create(null), request.callerEvidence, {
|
|
1851
|
+
code: link.code,
|
|
1852
|
+
linkId: link.id ?? "",
|
|
1853
|
+
targetUrl: link.targetUrl
|
|
1854
|
+
}));
|
|
1855
|
+
return {
|
|
1856
|
+
finalEvidence,
|
|
1857
|
+
intentHash: hashText(stableJson({
|
|
1858
|
+
tenantId: link.tenantId,
|
|
1859
|
+
linkId: link.id ?? "",
|
|
1860
|
+
code: link.code,
|
|
1861
|
+
targetUrl: link.targetUrl,
|
|
1862
|
+
referrerId: link.referrerId,
|
|
1863
|
+
programId: link.programId,
|
|
1864
|
+
subjectKind: request.subjectKind,
|
|
1865
|
+
subjectId: request.subjectId,
|
|
1866
|
+
requestedOccurredAt: request.requestedOccurredAt,
|
|
1867
|
+
callerEvidenceHash: request.callerEvidenceHash,
|
|
1868
|
+
finalEvidence
|
|
1869
|
+
}))
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
function collectReplayMismatches(operation, link, touch, request) {
|
|
1873
|
+
const mismatches = /* @__PURE__ */ new Set();
|
|
1874
|
+
if (request.normalizedCode !== touch.code) mismatches.add("code");
|
|
1875
|
+
if (operation.linkId !== link.id || touch.linkId !== link.id || operation.touchId !== touch.id) mismatches.add("linkId");
|
|
1876
|
+
if (operation.tenantId !== touch.tenantId) mismatches.add("tenantId");
|
|
1877
|
+
if (touch.subjectKind !== request.subjectKind) mismatches.add("subjectKind");
|
|
1878
|
+
if (touch.subjectId !== request.subjectId) mismatches.add("subjectId");
|
|
1879
|
+
if (normalizePersistedInstant(operation.requestedOccurredAt) !== request.requestedOccurredAt) mismatches.add("occurredAt");
|
|
1880
|
+
if (operation.callerEvidenceHash !== request.callerEvidenceHash) mismatches.add("evidence");
|
|
1881
|
+
const persisted = touch.getEvidence();
|
|
1882
|
+
if (persisted.code !== touch.code) mismatches.add("code");
|
|
1883
|
+
if (persisted.linkId !== touch.linkId) mismatches.add("linkId");
|
|
1884
|
+
if (operation.intentHash !== hashPersistedClickIntent(operation, touch, persisted.targetUrl) && mismatches.size === 0) mismatches.add("intent");
|
|
1885
|
+
return [...mismatches];
|
|
1886
|
+
}
|
|
1887
|
+
function hashPersistedClickIntent(operation, touch, targetUrl) {
|
|
1888
|
+
return hashText(stableJson({
|
|
1889
|
+
tenantId: touch.tenantId,
|
|
1890
|
+
linkId: touch.linkId,
|
|
1891
|
+
code: touch.code,
|
|
1892
|
+
targetUrl,
|
|
1893
|
+
referrerId: touch.referrerId,
|
|
1894
|
+
programId: touch.programId,
|
|
1895
|
+
subjectKind: touch.subjectKind,
|
|
1896
|
+
subjectId: touch.subjectId,
|
|
1897
|
+
requestedOccurredAt: normalizePersistedInstant(operation.requestedOccurredAt),
|
|
1898
|
+
callerEvidenceHash: operation.callerEvidenceHash,
|
|
1899
|
+
finalEvidence: touch.evidence
|
|
1900
|
+
}));
|
|
1901
|
+
}
|
|
1902
|
+
function assertEvidenceBound(evidence, maxBytes) {
|
|
1903
|
+
const actualBytes = utf8ByteLength(evidence);
|
|
1904
|
+
if (actualBytes > maxBytes) throw new ReferralClickValidationError("evidence_too_large", `Referral click final evidence is ${actualBytes} UTF-8 bytes and exceeds the ${maxBytes}-byte limit`, {
|
|
1905
|
+
actualBytes,
|
|
1906
|
+
maxBytes
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
function sameClickLinkSnapshot(left, right) {
|
|
1910
|
+
return left.id === right.id && left.tenantId === right.tenantId && left.code === right.code && left.targetUrl === right.targetUrl && left.referrerId === right.referrerId && left.programId === right.programId;
|
|
1911
|
+
}
|
|
1912
|
+
function hashText(value) {
|
|
1913
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
1914
|
+
}
|
|
1915
|
+
function normalizePersistedInstant(value) {
|
|
1916
|
+
if (value instanceof Date) return value.toISOString();
|
|
1917
|
+
return typeof value === "string" ? value : String(value ?? "");
|
|
1918
|
+
}
|
|
1919
|
+
function uuidFromDigest(digest) {
|
|
1920
|
+
const bytes = Buffer.from(digest.subarray(0, 16));
|
|
1921
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
1922
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1923
|
+
const hex = bytes.toString("hex");
|
|
1924
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1925
|
+
}
|
|
1926
|
+
function utf8ByteLength(value) {
|
|
1927
|
+
return Buffer.byteLength(value, "utf8");
|
|
1928
|
+
}
|
|
1929
|
+
function isWellFormedUnicode(value) {
|
|
1930
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1931
|
+
const code = value.charCodeAt(index);
|
|
1932
|
+
if (code >= 55296 && code <= 56319) {
|
|
1933
|
+
if (index + 1 >= value.length) return false;
|
|
1934
|
+
const next = value.charCodeAt(index + 1);
|
|
1935
|
+
if (next < 56320 || next > 57343) return false;
|
|
1936
|
+
index += 1;
|
|
1937
|
+
} else if (code >= 56320 && code <= 57343) return false;
|
|
1938
|
+
}
|
|
1939
|
+
return true;
|
|
1940
|
+
}
|
|
1941
|
+
function stableJson(value) {
|
|
1942
|
+
return JSON.stringify(sortJsonValue(value));
|
|
1943
|
+
}
|
|
1944
|
+
function sortJsonValue(value) {
|
|
1945
|
+
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
1946
|
+
if (isPlainObject(value)) {
|
|
1947
|
+
const sorted = /* @__PURE__ */ Object.create(null);
|
|
1948
|
+
for (const key of Object.keys(value).sort()) sorted[key] = sortJsonValue(value[key]);
|
|
1949
|
+
return sorted;
|
|
1950
|
+
}
|
|
1951
|
+
return value;
|
|
1952
|
+
}
|
|
1953
|
+
function isPlainObject(value) {
|
|
1954
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1955
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1956
|
+
return prototype === Object.prototype || prototype === null;
|
|
1957
|
+
}
|
|
1603
1958
|
//#endregion
|
|
1604
1959
|
//#region src/referrals/models/ReferralProgram.ts
|
|
1605
1960
|
var __defProp$2 = Object.defineProperty;
|
|
@@ -2998,6 +3353,6 @@ var ReferralQualificationService = class ReferralQualificationService {
|
|
|
2998
3353
|
}
|
|
2999
3354
|
};
|
|
3000
3355
|
//#endregion
|
|
3001
|
-
export {
|
|
3356
|
+
export { ReferralAgreement as A, REFERRAL_AGREEMENT_STATUSES as B, ReferralTouchCollection as C, ReferralCollection as D, assertHttpTargetUrl as E, ATTRIBUTION_CREDIT_MODES as F, REFERRER_STATUSES as G, REFERRAL_PROGRAM_STATUSES as H, ATTRIBUTION_EXCEPTION_STATUSES as I, AttributionExceptionCollection as K, ATTRIBUTION_POLICY_STATUSES as L, AttributionPolicy as M, validateAttributionPolicyTerms as N, Referral as O, ATTRIBUTION_CONFLICT_BEHAVIORS as P, ATTRIBUTION_RESOLUTION_MODES as R, generateReferralCode as S, ReferralLink as T, REFERRAL_STATUSES as U, REFERRAL_LINK_STATUSES as V, REFERRAL_TOUCH_KINDS as W, REFERRAL_CODE_ALPHABET as _, ReferralAgreementExecutionService as a, ReferralClickValidationError as b, ReferrerCollection as c, ReferralTermSnapshot as d, ReferralProgramCollection as f, MAX_REFERRAL_CLICK_IDEMPOTENCY_KEY_BYTES as g, MAX_CODE_GENERATION_ATTEMPTS as h, REFERRAL_AGREEMENT_SOURCE_KIND as i, AttributionPolicyCollection as j, ReferralAgreementCollection as k, Referrer as l, DEFAULT_REFERRAL_CLICK_EVIDENCE_MAX_BYTES as m, REFERRAL_TERMS_SNAPSHOT_KIND as n, AttributionService as o, ReferralProgram as p, AttributionException as q, ReferralCommissionService as r, QualifiedReferralOverrideError as s, ReferralQualificationService as t, ReferralTermSnapshotCollection as u, REFERRAL_CODE_LENGTH as v, ReferralTouch as w, ReferralLinkCollection as x, ReferralClickReplayConflictError as y, REFERRAL_AGREEMENT_APPROVAL_MODES as z };
|
|
3002
3357
|
|
|
3003
|
-
//# sourceMappingURL=referrals-
|
|
3358
|
+
//# sourceMappingURL=referrals-CJUOZxpa.js.map
|