@odla-ai/chapter 0.44.0 → 0.46.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.
- package/README.md +59 -0
- package/dist/{copy-context-B4SCy6vx.d.ts → copy-context-GaxckY3T.d.ts} +33 -0
- package/dist/index.cjs +842 -285
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +221 -3
- package/dist/index.d.ts +221 -3
- package/dist/index.js +842 -285
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/worker/index.cjs +1215 -318
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +33 -0
- package/dist/worker/index.d.ts +33 -0
- package/dist/worker/index.js +1215 -318
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ function resolveApplication(a) {
|
|
|
36
36
|
throw new Error(`defineChapter.application.${name}: field "${unsafe}" must match ${SCHEMA_FIELD.source}`);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
const duplicate = [...required, ...optional].find((field,
|
|
39
|
+
const duplicate = [...required, ...optional].find((field, index2, fields) => fields.indexOf(field) !== index2);
|
|
40
40
|
if (duplicate) {
|
|
41
41
|
throw new Error(`defineChapter.application: duplicate field "${duplicate}" across required/optional lists`);
|
|
42
42
|
}
|
|
@@ -115,8 +115,8 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
115
115
|
if (app.requireDisclaimerAck && !acked) {
|
|
116
116
|
return { ok: false, error: "disclaimerAck is required" };
|
|
117
117
|
}
|
|
118
|
-
const
|
|
119
|
-
const row = { id:
|
|
118
|
+
const id6 = opts.submissionId ? await applicationIdForSubmission(opts.submissionId) : opts.newId();
|
|
119
|
+
const row = { id: id6, status: chapter.pipeline.initial, createdAt: opts.now };
|
|
120
120
|
for (const f of [...app.required, ...app.optional]) {
|
|
121
121
|
if (fieldStates[f]?.visible === false) continue;
|
|
122
122
|
if (typeof fields[f] === "string") row[f] = fields[f].trim();
|
|
@@ -140,19 +140,19 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
140
140
|
}
|
|
141
141
|
if (acked) row.disclaimerAckAt = opts.now;
|
|
142
142
|
const ops = [
|
|
143
|
-
{ t: "update", ns: "applications", id:
|
|
143
|
+
{ t: "update", ns: "applications", id: id6, attrs: row }
|
|
144
144
|
];
|
|
145
145
|
if (opts.admissionGrant?.decision === "waive_interview") {
|
|
146
146
|
ops.push({ t: "update", ns: "admissionGrants", id: opts.admissionGrant.id, attrs: {
|
|
147
147
|
consumedAt: opts.now,
|
|
148
|
-
consumedApplicationId:
|
|
148
|
+
consumedApplicationId: id6
|
|
149
149
|
} });
|
|
150
150
|
}
|
|
151
151
|
const { duplicate } = await db.transact(
|
|
152
152
|
ops,
|
|
153
153
|
opts.submissionId ? { mutationId: `join:${opts.submissionId}` } : void 0
|
|
154
154
|
);
|
|
155
|
-
return { ok: true, id:
|
|
155
|
+
return { ok: true, id: id6, duplicate, status: chapter.pipeline.initial, disclaimerAckAt: acked ? opts.now : null };
|
|
156
156
|
}
|
|
157
157
|
function joinConfig(group, paymentsReady2, tiers2 = [], conditions = {}) {
|
|
158
158
|
return {
|
|
@@ -548,6 +548,64 @@ var networkPersonEntities = {
|
|
|
548
548
|
networkPersonEvents: networkPersonEventsEntity
|
|
549
549
|
};
|
|
550
550
|
|
|
551
|
+
// src/schema-network-commercial.ts
|
|
552
|
+
var string2 = (flags = {}) => ({
|
|
553
|
+
type: "string",
|
|
554
|
+
unique: flags.unique ?? false,
|
|
555
|
+
indexed: flags.indexed ?? false,
|
|
556
|
+
optional: flags.optional ?? false
|
|
557
|
+
});
|
|
558
|
+
var number2 = (flags = {}) => ({
|
|
559
|
+
type: "number",
|
|
560
|
+
unique: false,
|
|
561
|
+
indexed: flags.indexed ?? false,
|
|
562
|
+
optional: flags.optional ?? false
|
|
563
|
+
});
|
|
564
|
+
var boolean = (flags = {}) => ({
|
|
565
|
+
type: "boolean",
|
|
566
|
+
unique: false,
|
|
567
|
+
indexed: flags.indexed ?? false,
|
|
568
|
+
optional: false
|
|
569
|
+
});
|
|
570
|
+
var json2 = () => ({ type: "json", unique: false, indexed: false, optional: false });
|
|
571
|
+
var id4 = () => string2({ unique: true, indexed: true });
|
|
572
|
+
var events = { attrs: {
|
|
573
|
+
id: id4(),
|
|
574
|
+
eventId: string2({ unique: true, indexed: true }),
|
|
575
|
+
digest: string2({ indexed: true }),
|
|
576
|
+
eventType: string2({ indexed: true }),
|
|
577
|
+
eventCreated: number2({ indexed: true }),
|
|
578
|
+
livemode: boolean({ indexed: true }),
|
|
579
|
+
fact: json2(),
|
|
580
|
+
receivedAt: number2({ indexed: true })
|
|
581
|
+
} };
|
|
582
|
+
var projection = () => ({ attrs: {
|
|
583
|
+
id: id4(),
|
|
584
|
+
providerId: string2({ indexed: true }),
|
|
585
|
+
sourceKey: string2({ indexed: true }),
|
|
586
|
+
status: string2({ indexed: true }),
|
|
587
|
+
snapshot: json2(),
|
|
588
|
+
eventCreated: number2({ indexed: true }),
|
|
589
|
+
updatedAt: number2({ indexed: true })
|
|
590
|
+
} });
|
|
591
|
+
var exceptions = { attrs: {
|
|
592
|
+
id: id4(),
|
|
593
|
+
eventId: string2({ indexed: true }),
|
|
594
|
+
kind: string2({ indexed: true }),
|
|
595
|
+
sourceKey: string2({ indexed: true, optional: true }),
|
|
596
|
+
status: string2({ indexed: true }),
|
|
597
|
+
detail: json2(),
|
|
598
|
+
updatedAt: number2({ indexed: true })
|
|
599
|
+
} };
|
|
600
|
+
var networkCommercialEntities = {
|
|
601
|
+
networkCommercialEvents: events,
|
|
602
|
+
networkCommercialCustomers: projection(),
|
|
603
|
+
networkCommercialSubscriptions: projection(),
|
|
604
|
+
networkCommercialInvoices: projection(),
|
|
605
|
+
networkCommercialRefunds: projection(),
|
|
606
|
+
networkCommercialExceptions: exceptions
|
|
607
|
+
};
|
|
608
|
+
|
|
551
609
|
// src/schema.ts
|
|
552
610
|
function attr5(type, flags = {}) {
|
|
553
611
|
return {
|
|
@@ -557,10 +615,10 @@ function attr5(type, flags = {}) {
|
|
|
557
615
|
optional: flags.optional ?? false
|
|
558
616
|
};
|
|
559
617
|
}
|
|
560
|
-
var
|
|
618
|
+
var id5 = () => attr5("string", { unique: true, indexed: true });
|
|
561
619
|
var admins = {
|
|
562
620
|
attrs: {
|
|
563
|
-
id:
|
|
621
|
+
id: id5(),
|
|
564
622
|
email: attr5("string", { unique: true, indexed: true }),
|
|
565
623
|
name: attr5("string", { optional: true }),
|
|
566
624
|
note: attr5("string", { optional: true })
|
|
@@ -568,7 +626,7 @@ var admins = {
|
|
|
568
626
|
};
|
|
569
627
|
var superAdmins = {
|
|
570
628
|
attrs: {
|
|
571
|
-
id:
|
|
629
|
+
id: id5(),
|
|
572
630
|
email: attr5("string", { unique: true, indexed: true }),
|
|
573
631
|
note: attr5("string", { optional: true }),
|
|
574
632
|
createdAt: attr5("number", { indexed: true })
|
|
@@ -576,7 +634,7 @@ var superAdmins = {
|
|
|
576
634
|
};
|
|
577
635
|
var networkNotes = {
|
|
578
636
|
attrs: {
|
|
579
|
-
id:
|
|
637
|
+
id: id5(),
|
|
580
638
|
recordKey: attr5("string", { indexed: true }),
|
|
581
639
|
targetId: attr5("string", { indexed: true }),
|
|
582
640
|
recordType: attr5("string", { indexed: true }),
|
|
@@ -589,7 +647,7 @@ var networkNotes = {
|
|
|
589
647
|
};
|
|
590
648
|
var signupControls = {
|
|
591
649
|
attrs: {
|
|
592
|
-
id:
|
|
650
|
+
id: id5(),
|
|
593
651
|
sourceId: attr5("string", { indexed: true }),
|
|
594
652
|
targetId: attr5("string", { indexed: true }),
|
|
595
653
|
environment: attr5("string", { indexed: true }),
|
|
@@ -602,7 +660,7 @@ var signupControls = {
|
|
|
602
660
|
};
|
|
603
661
|
var memberContentControls = {
|
|
604
662
|
attrs: {
|
|
605
|
-
id:
|
|
663
|
+
id: id5(),
|
|
606
664
|
sourceId: attr5("string", { indexed: true }),
|
|
607
665
|
targetId: attr5("string", { indexed: true }),
|
|
608
666
|
environment: attr5("string", { indexed: true }),
|
|
@@ -615,7 +673,7 @@ var memberContentControls = {
|
|
|
615
673
|
};
|
|
616
674
|
var memberContentLocal = {
|
|
617
675
|
attrs: {
|
|
618
|
-
id:
|
|
676
|
+
id: id5(),
|
|
619
677
|
chapterId: attr5("string", { indexed: true }),
|
|
620
678
|
environment: attr5("string", { indexed: true }),
|
|
621
679
|
revision: attr5("number", { indexed: true }),
|
|
@@ -626,7 +684,7 @@ var memberContentLocal = {
|
|
|
626
684
|
};
|
|
627
685
|
var builderLibraryControls = {
|
|
628
686
|
attrs: {
|
|
629
|
-
id:
|
|
687
|
+
id: id5(),
|
|
630
688
|
sourceId: attr5("string", { indexed: true }),
|
|
631
689
|
targetId: attr5("string", { indexed: true }),
|
|
632
690
|
environment: attr5("string", { indexed: true }),
|
|
@@ -639,7 +697,7 @@ var builderLibraryControls = {
|
|
|
639
697
|
};
|
|
640
698
|
var builderLibraryLocal = {
|
|
641
699
|
attrs: {
|
|
642
|
-
id:
|
|
700
|
+
id: id5(),
|
|
643
701
|
appId: attr5("string", { indexed: true }),
|
|
644
702
|
environment: attr5("string", { indexed: true }),
|
|
645
703
|
revision: attr5("number", { indexed: true }),
|
|
@@ -650,7 +708,7 @@ var builderLibraryLocal = {
|
|
|
650
708
|
};
|
|
651
709
|
var groups = {
|
|
652
710
|
attrs: {
|
|
653
|
-
id:
|
|
711
|
+
id: id5(),
|
|
654
712
|
name: attr5("string"),
|
|
655
713
|
standardPriceCents: attr5("number"),
|
|
656
714
|
foundingDiscountCents: attr5("number"),
|
|
@@ -673,7 +731,7 @@ var groups = {
|
|
|
673
731
|
};
|
|
674
732
|
var tiers = {
|
|
675
733
|
attrs: {
|
|
676
|
-
id:
|
|
734
|
+
id: id5(),
|
|
677
735
|
groupId: attr5("string", { indexed: true }),
|
|
678
736
|
name: attr5("string"),
|
|
679
737
|
priceCents: attr5("number"),
|
|
@@ -686,7 +744,7 @@ var tiers = {
|
|
|
686
744
|
};
|
|
687
745
|
var meetings = {
|
|
688
746
|
attrs: {
|
|
689
|
-
id:
|
|
747
|
+
id: id5(),
|
|
690
748
|
applicationId: attr5("string", { indexed: true }),
|
|
691
749
|
groupId: attr5("string", { indexed: true }),
|
|
692
750
|
startAt: attr5("number", { indexed: true }),
|
|
@@ -705,7 +763,7 @@ var meetings = {
|
|
|
705
763
|
};
|
|
706
764
|
var emailLog = {
|
|
707
765
|
attrs: {
|
|
708
|
-
id:
|
|
766
|
+
id: id5(),
|
|
709
767
|
groupId: attr5("string", { indexed: true }),
|
|
710
768
|
applicationId: attr5("string", { indexed: true, optional: true }),
|
|
711
769
|
to: attr5("string", { indexed: true }),
|
|
@@ -720,7 +778,7 @@ var emailLog = {
|
|
|
720
778
|
sentAt: attr5("number", { indexed: true })
|
|
721
779
|
}
|
|
722
780
|
};
|
|
723
|
-
function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false, includeBuilderLibrary = false, includeLeaderAdmissions = false, includeNetworkPersonRegistry = false) {
|
|
781
|
+
function chapterDb(mode, auth, includeNetworkNotes = false, application = resolveApplication(void 0), includeSignupControl = false, includeMemberContent = false, includeBuilderLibrary = false, includeLeaderAdmissions = false, includeNetworkPersonRegistry = false, includeNetworkCommercialReadModel = false) {
|
|
724
782
|
const entities = {};
|
|
725
783
|
if (mode === "chapter") {
|
|
726
784
|
entities.applications = applicationsFor(application);
|
|
@@ -757,6 +815,7 @@ function chapterDb(mode, auth, includeNetworkNotes = false, application = resolv
|
|
|
757
815
|
if (includeNetworkPersonRegistry) {
|
|
758
816
|
Object.assign(entities, networkPersonEntities);
|
|
759
817
|
}
|
|
818
|
+
if (includeNetworkCommercialReadModel) Object.assign(entities, networkCommercialEntities);
|
|
760
819
|
const schema = { entities, links: {} };
|
|
761
820
|
const rules = {};
|
|
762
821
|
for (const ns of Object.keys(entities)) {
|
|
@@ -928,8 +987,8 @@ function resolveAuth(mode, auth) {
|
|
|
928
987
|
return { source, claim, ladder, adminRole, superAdmins: superAdmins2 };
|
|
929
988
|
}
|
|
930
989
|
function roleFromClaim(payload, auth) {
|
|
931
|
-
const
|
|
932
|
-
return typeof
|
|
990
|
+
const raw2 = payload[auth.claim];
|
|
991
|
+
return typeof raw2 === "string" && auth.ladder.includes(raw2) ? raw2 : auth.ladder[0];
|
|
933
992
|
}
|
|
934
993
|
function isAdminRole(role, auth) {
|
|
935
994
|
return role === auth.adminRole;
|
|
@@ -1408,9 +1467,9 @@ var APPLICATION_LABELS = {
|
|
|
1408
1467
|
phone: "Phone",
|
|
1409
1468
|
state: "State"
|
|
1410
1469
|
};
|
|
1411
|
-
function humanize(
|
|
1412
|
-
const
|
|
1413
|
-
return
|
|
1470
|
+
function humanize(id6) {
|
|
1471
|
+
const text6 = id6.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
1472
|
+
return text6 ? `${text6[0].toUpperCase()}${text6.slice(1)}` : id6;
|
|
1414
1473
|
}
|
|
1415
1474
|
function applicationDefs(chapter) {
|
|
1416
1475
|
try {
|
|
@@ -1419,23 +1478,23 @@ function applicationDefs(chapter) {
|
|
|
1419
1478
|
return {};
|
|
1420
1479
|
}
|
|
1421
1480
|
}
|
|
1422
|
-
function applicationType(
|
|
1481
|
+
function applicationType(id6, def) {
|
|
1423
1482
|
if (def && ["string", "email", "url", "enum"].includes(def.type)) return def.type;
|
|
1424
|
-
return
|
|
1483
|
+
return id6 === "email" ? "email" : id6 === "linkedin" ? "url" : "string";
|
|
1425
1484
|
}
|
|
1426
1485
|
function applicationFlowFields(chapter, application = chapter.application) {
|
|
1427
1486
|
const defs = applicationDefs(chapter);
|
|
1428
|
-
return [...application.required, ...application.optional].map((
|
|
1429
|
-
const def = defs[
|
|
1430
|
-
const conditions = application.conditions[
|
|
1431
|
-
const type = applicationType(
|
|
1487
|
+
return [...application.required, ...application.optional].map((id6, order2) => {
|
|
1488
|
+
const def = defs[id6];
|
|
1489
|
+
const conditions = application.conditions[id6];
|
|
1490
|
+
const type = applicationType(id6, def);
|
|
1432
1491
|
return {
|
|
1433
|
-
id:
|
|
1434
|
-
label: def?.label ?? APPLICATION_LABELS[
|
|
1492
|
+
id: id6,
|
|
1493
|
+
label: def?.label ?? APPLICATION_LABELS[id6] ?? humanize(id6),
|
|
1435
1494
|
type,
|
|
1436
|
-
required: application.required.includes(
|
|
1437
|
-
order,
|
|
1438
|
-
maxLength: application.maxLen[
|
|
1495
|
+
required: application.required.includes(id6),
|
|
1496
|
+
order: order2,
|
|
1497
|
+
maxLength: application.maxLen[id6] ?? application.defaultMaxLen,
|
|
1439
1498
|
...type === "enum" && def?.options ? { options: [...def.options] } : {},
|
|
1440
1499
|
...conditions ? { conditions: { ...conditions } } : {},
|
|
1441
1500
|
...type === "email" ? { emailIdentity: "literal" } : {}
|
|
@@ -1445,17 +1504,17 @@ function applicationFlowFields(chapter, application = chapter.application) {
|
|
|
1445
1504
|
function resolveFormationFlowFields(crm, formation) {
|
|
1446
1505
|
if (!formation.enabled) return [];
|
|
1447
1506
|
const def = crm.type(formation.type);
|
|
1448
|
-
return [...formation.required, ...formation.optional].map((
|
|
1449
|
-
const field = def.fields[
|
|
1507
|
+
return [...formation.required, ...formation.optional].map((id6, order2) => {
|
|
1508
|
+
const field = def.fields[id6];
|
|
1450
1509
|
return {
|
|
1451
|
-
id:
|
|
1452
|
-
label: field.label ?? humanize(
|
|
1510
|
+
id: id6,
|
|
1511
|
+
label: field.label ?? humanize(id6),
|
|
1453
1512
|
type: field.type,
|
|
1454
|
-
required: formation.required.includes(
|
|
1455
|
-
order,
|
|
1456
|
-
maxLength: formation.maxLen[
|
|
1513
|
+
required: formation.required.includes(id6),
|
|
1514
|
+
order: order2,
|
|
1515
|
+
maxLength: formation.maxLen[id6] ?? formation.defaultMaxLen,
|
|
1457
1516
|
...field.options ? { options: [...field.options] } : {},
|
|
1458
|
-
...
|
|
1517
|
+
...id6 === def.emailField || field.type === "email" ? { emailIdentity: "literal" } : {}
|
|
1459
1518
|
};
|
|
1460
1519
|
});
|
|
1461
1520
|
}
|
|
@@ -1495,7 +1554,7 @@ function resolveLeaderFormation(input, crm) {
|
|
|
1495
1554
|
throw new Error(`defineChapter.formation.${name}: must be an array of field-name strings`);
|
|
1496
1555
|
}
|
|
1497
1556
|
}
|
|
1498
|
-
const duplicates = [...required, ...optional].filter((field,
|
|
1557
|
+
const duplicates = [...required, ...optional].filter((field, index2, fields) => fields.indexOf(field) !== index2);
|
|
1499
1558
|
if (duplicates.length) throw new Error(`defineChapter.formation: duplicate public field "${duplicates[0]}"`);
|
|
1500
1559
|
const unknown = [...required, ...optional].filter((field) => !def.fields[field]);
|
|
1501
1560
|
if (unknown.length) throw new Error(`defineChapter.formation: field "${unknown[0]}" is not declared on CRM type "${type}"`);
|
|
@@ -1546,7 +1605,7 @@ var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
|
1546
1605
|
var FIELD = /^[a-z][a-zA-Z0-9_]*$/;
|
|
1547
1606
|
var BINDING = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
1548
1607
|
function validateSharedNotes(opts) {
|
|
1549
|
-
const duplicate = opts.values.some((type,
|
|
1608
|
+
const duplicate = opts.values.some((type, index2) => opts.values.indexOf(type) !== index2);
|
|
1550
1609
|
const invalid = opts.values.some((type) => !FIELD.test(type) || !opts.crm.config.types[type] || (opts.fields ? !opts.fields[type] : false));
|
|
1551
1610
|
if (duplicate || invalid) {
|
|
1552
1611
|
throw new Error(`${opts.path}: must contain unique CRM types allowed by fields`);
|
|
@@ -1556,7 +1615,7 @@ function validateEditableFields(opts) {
|
|
|
1556
1615
|
for (const [type, names] of Object.entries(opts.values)) {
|
|
1557
1616
|
const readable = opts.readable?.[type];
|
|
1558
1617
|
const def = opts.crm.config.types[type];
|
|
1559
|
-
const duplicate = names.some((name,
|
|
1618
|
+
const duplicate = names.some((name, index2) => names.indexOf(name) !== index2);
|
|
1560
1619
|
const unknown = names.some((name) => !FIELD.test(name) || !def?.fields[name]);
|
|
1561
1620
|
const hidden = opts.readable ? !readable || names.some((name) => !readable.includes(name)) : false;
|
|
1562
1621
|
if (!FIELD.test(type) || names.length === 0 || duplicate || !def || unknown || hidden) {
|
|
@@ -1567,7 +1626,7 @@ function validateEditableFields(opts) {
|
|
|
1567
1626
|
}
|
|
1568
1627
|
}
|
|
1569
1628
|
function validateStageTransitions(opts) {
|
|
1570
|
-
const duplicate = opts.values.some((type,
|
|
1629
|
+
const duplicate = opts.values.some((type, index2) => opts.values.indexOf(type) !== index2);
|
|
1571
1630
|
const invalid = opts.values.some((type) => !FIELD.test(type) || !opts.crm.config.types[type]?.pipeline || (opts.readable ? !opts.readable[type] : false));
|
|
1572
1631
|
if (duplicate || invalid) {
|
|
1573
1632
|
throw new Error(`${opts.path}: must contain unique, readable CRM types with pipelines`);
|
|
@@ -1668,7 +1727,8 @@ function resolveNetwork(config, crm) {
|
|
|
1668
1727
|
sharedNotes,
|
|
1669
1728
|
editableFields,
|
|
1670
1729
|
stageTransitions,
|
|
1671
|
-
...target.admissionGrants === true ? { admissionGrants: true } : {}
|
|
1730
|
+
...target.admissionGrants === true ? { admissionGrants: true } : {},
|
|
1731
|
+
...target.commercialParity === true ? { commercialParity: true } : {}
|
|
1672
1732
|
});
|
|
1673
1733
|
}
|
|
1674
1734
|
const readerIds = /* @__PURE__ */ new Set();
|
|
@@ -1683,9 +1743,9 @@ function resolveNetwork(config, crm) {
|
|
|
1683
1743
|
readerIds.add(reader.id);
|
|
1684
1744
|
const readerFields = reader.fields ?? {};
|
|
1685
1745
|
const entries = Object.entries(readerFields);
|
|
1686
|
-
if (entries.length === 0 && !reader.admissionGrants) {
|
|
1746
|
+
if (entries.length === 0 && !reader.admissionGrants && !reader.commercialParity) {
|
|
1687
1747
|
throw new Error(
|
|
1688
|
-
`defineChapter.network.readers.${reader.id}.fields: at least one CRM type is required unless this is an admission-only reader`
|
|
1748
|
+
`defineChapter.network.readers.${reader.id}.fields: at least one CRM type is required unless this is an admission-only or commercial-parity reader`
|
|
1689
1749
|
);
|
|
1690
1750
|
}
|
|
1691
1751
|
for (const [type, names] of entries) {
|
|
@@ -1743,7 +1803,8 @@ function resolveNetwork(config, crm) {
|
|
|
1743
1803
|
sharedNotes,
|
|
1744
1804
|
editableFields,
|
|
1745
1805
|
stageTransitions,
|
|
1746
|
-
...reader.admissionGrants === true ? { admissionGrants: true } : {}
|
|
1806
|
+
...reader.admissionGrants === true ? { admissionGrants: true } : {},
|
|
1807
|
+
...reader.commercialParity === true ? { commercialParity: true } : {}
|
|
1747
1808
|
});
|
|
1748
1809
|
}
|
|
1749
1810
|
return { targets, readers };
|
|
@@ -1757,22 +1818,50 @@ function resolveNetworkPersonRegistry(mode, input, targetIds) {
|
|
|
1757
1818
|
if (input.mode !== "shadow") throw new Error('defineChapter.networkPersonRegistry.mode: must be "shadow"');
|
|
1758
1819
|
const chapterIds = input.chapterIds ? [...input.chapterIds] : [...targetIds];
|
|
1759
1820
|
if (chapterIds.length === 0) throw new Error("defineChapter.networkPersonRegistry.chapterIds: at least one chapter is required");
|
|
1760
|
-
if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((
|
|
1821
|
+
if (new Set(chapterIds).size !== chapterIds.length || chapterIds.some((id6) => !SLUG2.test(id6))) {
|
|
1761
1822
|
throw new Error("defineChapter.networkPersonRegistry.chapterIds: expected distinct lowercase slugs");
|
|
1762
1823
|
}
|
|
1763
|
-
const unknown = chapterIds.filter((
|
|
1824
|
+
const unknown = chapterIds.filter((id6) => !targetIds.includes(id6));
|
|
1764
1825
|
if (unknown.length) {
|
|
1765
|
-
throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((
|
|
1826
|
+
throw new Error(`defineChapter.networkPersonRegistry.chapterIds: ${unknown.map((id6) => `"${id6}"`).join(", ")} are not configured network targets`);
|
|
1766
1827
|
}
|
|
1767
1828
|
return { mode: "shadow", chapterIds };
|
|
1768
1829
|
}
|
|
1769
1830
|
|
|
1831
|
+
// src/config-network-commercial.ts
|
|
1832
|
+
var SLUG3 = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
1833
|
+
var TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
1834
|
+
function networkCommercialSourceKey(source) {
|
|
1835
|
+
return [source.stripeMode, source.appId, source.environment, source.dataEnvironment, source.runtime, source.chapterId].join(":");
|
|
1836
|
+
}
|
|
1837
|
+
function resolveNetworkCommercialReadModel(mode, input, targetIds) {
|
|
1838
|
+
if (!input) return null;
|
|
1839
|
+
if (mode !== "hub") throw new Error("defineChapter.networkCommercialReadModel: only hub mode may own the network commercial projection");
|
|
1840
|
+
if (input.mode !== "shadow") throw new Error('defineChapter.networkCommercialReadModel.mode: must be "shadow"');
|
|
1841
|
+
if (!Array.isArray(input.sources) || input.sources.length === 0) {
|
|
1842
|
+
throw new Error("defineChapter.networkCommercialReadModel.sources: at least one exact source is required");
|
|
1843
|
+
}
|
|
1844
|
+
const sources = input.sources.map((source) => ({ ...source }));
|
|
1845
|
+
for (const source of sources) {
|
|
1846
|
+
if (!SLUG3.test(source.appId) || !SLUG3.test(source.chapterId) || !TOKEN.test(source.environment) || !TOKEN.test(source.dataEnvironment) || !TOKEN.test(source.runtime) || source.stripeMode !== "test" && source.stripeMode !== "live") {
|
|
1847
|
+
throw new Error("defineChapter.networkCommercialReadModel.sources: every source requires valid app, chapter, environment, data-environment, runtime, and Stripe mode values");
|
|
1848
|
+
}
|
|
1849
|
+
if (!targetIds.includes(source.chapterId)) {
|
|
1850
|
+
throw new Error(`defineChapter.networkCommercialReadModel.sources: chapter "${source.chapterId}" is not a configured network target`);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
if (new Set(sources.map(networkCommercialSourceKey)).size !== sources.length) {
|
|
1854
|
+
throw new Error("defineChapter.networkCommercialReadModel.sources: exact source partitions must be distinct");
|
|
1855
|
+
}
|
|
1856
|
+
return { mode: "shadow", sources };
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1770
1859
|
// src/config-signup-control.ts
|
|
1771
|
-
var
|
|
1860
|
+
var SLUG4 = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
1772
1861
|
var SECRET_NAME = /^[a-z][a-z0-9_]{1,127}$/;
|
|
1773
1862
|
function resolveSignupControl(input) {
|
|
1774
1863
|
if (!input) return null;
|
|
1775
|
-
if (!
|
|
1864
|
+
if (!SLUG4.test(input.sourceId)) {
|
|
1776
1865
|
throw new Error("defineChapter.signupControl.sourceId: must be a lowercase app slug");
|
|
1777
1866
|
}
|
|
1778
1867
|
if (input.stripeMode !== "test" && input.stripeMode !== "live") {
|
|
@@ -1786,12 +1875,12 @@ function resolveSignupControl(input) {
|
|
|
1786
1875
|
}
|
|
1787
1876
|
|
|
1788
1877
|
// src/config-member-content.ts
|
|
1789
|
-
var
|
|
1878
|
+
var SLUG5 = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
1790
1879
|
var SECRET_NAME2 = /^[a-z][a-z0-9_]{1,127}$/;
|
|
1791
1880
|
function resolveMemberContent(input) {
|
|
1792
1881
|
if (!input) return null;
|
|
1793
1882
|
const parent = input.parent;
|
|
1794
|
-
if (parent && !
|
|
1883
|
+
if (parent && !SLUG5.test(parent.sourceId)) {
|
|
1795
1884
|
throw new Error("defineChapter.memberContent.parent.sourceId: must be a lowercase app slug");
|
|
1796
1885
|
}
|
|
1797
1886
|
const secretName = parent?.secretName?.trim() || "member_content_control_secret";
|
|
@@ -1806,22 +1895,22 @@ function resolveMemberContent(input) {
|
|
|
1806
1895
|
}
|
|
1807
1896
|
|
|
1808
1897
|
// src/config-builder-library.ts
|
|
1809
|
-
var
|
|
1898
|
+
var SLUG6 = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
1810
1899
|
function resolveBuilderLibrary(mode, input, network = { targets: [], readers: [] }) {
|
|
1811
1900
|
if (!input) return null;
|
|
1812
1901
|
if (input.sources !== void 0 && !Array.isArray(input.sources)) {
|
|
1813
1902
|
throw new Error("defineChapter.builderLibrary.sources: must be an array");
|
|
1814
1903
|
}
|
|
1815
1904
|
const seen = /* @__PURE__ */ new Set();
|
|
1816
|
-
const sources = (input.sources ?? []).map((source,
|
|
1817
|
-
if (!source || typeof source !== "object" || !
|
|
1818
|
-
throw new Error(`defineChapter.builderLibrary.sources[${
|
|
1905
|
+
const sources = (input.sources ?? []).map((source, index2) => {
|
|
1906
|
+
if (!source || typeof source !== "object" || !SLUG6.test(source.sourceId)) {
|
|
1907
|
+
throw new Error(`defineChapter.builderLibrary.sources[${index2}].sourceId: must be a lowercase app slug`);
|
|
1819
1908
|
}
|
|
1820
1909
|
if (source.sourceId === "built-not-found" && mode === "hub") {
|
|
1821
|
-
throw new Error(`defineChapter.builderLibrary.sources[${
|
|
1910
|
+
throw new Error(`defineChapter.builderLibrary.sources[${index2}]: a hub accepts Chapter sources, not itself`);
|
|
1822
1911
|
}
|
|
1823
1912
|
if (mode === "chapter" && source.sourceId !== "built-not-found") {
|
|
1824
|
-
throw new Error(`defineChapter.builderLibrary.sources[${
|
|
1913
|
+
throw new Error(`defineChapter.builderLibrary.sources[${index2}]: Chapters may accept only the built-not-found source`);
|
|
1825
1914
|
}
|
|
1826
1915
|
if (seen.has(source.sourceId)) {
|
|
1827
1916
|
throw new Error(`defineChapter.builderLibrary.sources: duplicate source "${source.sourceId}"`);
|
|
@@ -1829,14 +1918,14 @@ function resolveBuilderLibrary(mode, input, network = { targets: [], readers: []
|
|
|
1829
1918
|
seen.add(source.sourceId);
|
|
1830
1919
|
if ("secretName" in source) {
|
|
1831
1920
|
throw new Error(
|
|
1832
|
-
`defineChapter.builderLibrary.sources[${
|
|
1921
|
+
`defineChapter.builderLibrary.sources[${index2}].secretName: remove this field; Builder Library reuses the Chapter network edge`
|
|
1833
1922
|
);
|
|
1834
1923
|
}
|
|
1835
1924
|
const networkPeer = mode === "hub" ? network.targets.some((target) => target.id === source.sourceId) : network.readers.some((reader) => reader.id === source.sourceId);
|
|
1836
1925
|
if (!networkPeer) {
|
|
1837
1926
|
const side = mode === "hub" ? "targets" : "readers";
|
|
1838
1927
|
throw new Error(
|
|
1839
|
-
`defineChapter.builderLibrary.sources[${
|
|
1928
|
+
`defineChapter.builderLibrary.sources[${index2}]: "${source.sourceId}" must already be configured in network.${side}`
|
|
1840
1929
|
);
|
|
1841
1930
|
}
|
|
1842
1931
|
return { sourceId: source.sourceId };
|
|
@@ -1845,15 +1934,15 @@ function resolveBuilderLibrary(mode, input, network = { targets: [], readers: []
|
|
|
1845
1934
|
}
|
|
1846
1935
|
|
|
1847
1936
|
// src/config.ts
|
|
1848
|
-
var
|
|
1937
|
+
var SLUG7 = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
1849
1938
|
function isResolvedCrm(x) {
|
|
1850
1939
|
return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
|
|
1851
1940
|
}
|
|
1852
1941
|
function defineChapter(config) {
|
|
1853
1942
|
if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
|
|
1854
|
-
const { id:
|
|
1855
|
-
if (typeof
|
|
1856
|
-
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(
|
|
1943
|
+
const { id: id6, name } = config;
|
|
1944
|
+
if (typeof id6 !== "string" || !SLUG7.test(id6)) {
|
|
1945
|
+
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id6)}`);
|
|
1857
1946
|
}
|
|
1858
1947
|
if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
|
|
1859
1948
|
const mode = config.mode ?? "chapter";
|
|
@@ -1881,6 +1970,11 @@ function defineChapter(config) {
|
|
|
1881
1970
|
config.networkPersonRegistry,
|
|
1882
1971
|
network.targets.map((target) => target.id)
|
|
1883
1972
|
);
|
|
1973
|
+
const networkCommercialReadModel = resolveNetworkCommercialReadModel(
|
|
1974
|
+
mode,
|
|
1975
|
+
config.networkCommercialReadModel,
|
|
1976
|
+
network.targets.map((target) => target.id)
|
|
1977
|
+
);
|
|
1884
1978
|
const signupControl = resolveSignupControl(config.signupControl);
|
|
1885
1979
|
const memberContent = resolveMemberContent(config.memberContent);
|
|
1886
1980
|
const builderLibrary = resolveBuilderLibrary(mode, config.builderLibrary, network);
|
|
@@ -1900,7 +1994,8 @@ function defineChapter(config) {
|
|
|
1900
1994
|
Boolean(memberContent),
|
|
1901
1995
|
Boolean(builderLibrary),
|
|
1902
1996
|
network.targets.some((target) => target.admissionGrants),
|
|
1903
|
-
Boolean(networkPersonRegistry)
|
|
1997
|
+
Boolean(networkPersonRegistry),
|
|
1998
|
+
Boolean(networkCommercialReadModel)
|
|
1904
1999
|
);
|
|
1905
2000
|
const services = config.services ?? ["db", "calendar", "o11y"];
|
|
1906
2001
|
const account = config.account ?? "none";
|
|
@@ -1950,7 +2045,7 @@ function defineChapter(config) {
|
|
|
1950
2045
|
};
|
|
1951
2046
|
const chapter = {
|
|
1952
2047
|
config,
|
|
1953
|
-
id:
|
|
2048
|
+
id: id6,
|
|
1954
2049
|
name,
|
|
1955
2050
|
mode,
|
|
1956
2051
|
brand,
|
|
@@ -1960,6 +2055,7 @@ function defineChapter(config) {
|
|
|
1960
2055
|
memberContent,
|
|
1961
2056
|
builderLibrary,
|
|
1962
2057
|
networkPersonRegistry,
|
|
2058
|
+
networkCommercialReadModel,
|
|
1963
2059
|
formation,
|
|
1964
2060
|
crm,
|
|
1965
2061
|
auth,
|
|
@@ -1977,6 +2073,447 @@ function defineChapter(config) {
|
|
|
1977
2073
|
return chapter;
|
|
1978
2074
|
}
|
|
1979
2075
|
|
|
2076
|
+
// src/network-commercial.ts
|
|
2077
|
+
var raw = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2078
|
+
var text = (value) => typeof value === "string" && value.length > 0 && value.length <= 255 ? value : void 0;
|
|
2079
|
+
var integer = (value) => typeof value === "number" && Number.isSafeInteger(value) ? value : void 0;
|
|
2080
|
+
var bool = (value) => typeof value === "boolean" ? value : void 0;
|
|
2081
|
+
var providerId = (value, prefix) => {
|
|
2082
|
+
const valueText = text(value);
|
|
2083
|
+
return valueText && (!prefix || valueText.startsWith(prefix)) ? valueText : void 0;
|
|
2084
|
+
};
|
|
2085
|
+
function metadataCandidates(object) {
|
|
2086
|
+
const parent = raw(object.parent);
|
|
2087
|
+
const subscriptionDetails = raw(object.subscription_details);
|
|
2088
|
+
const parentSubscription = raw(parent.subscription_details);
|
|
2089
|
+
return [raw(object.metadata), raw(subscriptionDetails.metadata), raw(parentSubscription.metadata)];
|
|
2090
|
+
}
|
|
2091
|
+
function metadataValue(candidates, key) {
|
|
2092
|
+
return candidates.map((candidate) => text(candidate[key])).find(Boolean);
|
|
2093
|
+
}
|
|
2094
|
+
function refId(value, prefix) {
|
|
2095
|
+
if (typeof value === "string") return providerId(value, prefix);
|
|
2096
|
+
return providerId(raw(value).id, prefix);
|
|
2097
|
+
}
|
|
2098
|
+
function subscriptionRef(object) {
|
|
2099
|
+
return refId(object.subscription, "sub_") ?? refId(raw(object.subscription_details).subscription, "sub_") ?? refId(raw(raw(object.parent).subscription_details).subscription, "sub_");
|
|
2100
|
+
}
|
|
2101
|
+
function firstPrice(object) {
|
|
2102
|
+
const data = raw(object.items).data;
|
|
2103
|
+
const item = Array.isArray(data) ? raw(data[0]) : {};
|
|
2104
|
+
const priceId = refId(item.price, "price_");
|
|
2105
|
+
const quantity = integer(item.quantity);
|
|
2106
|
+
return { ...priceId ? { priceId } : {}, ...quantity !== void 0 ? { quantity } : {} };
|
|
2107
|
+
}
|
|
2108
|
+
function normalizeNetworkCommercialStripeEvent(input, digest) {
|
|
2109
|
+
const event = raw(input);
|
|
2110
|
+
const eventId = providerId(event.id, "evt_") ?? "";
|
|
2111
|
+
const eventType = text(event.type) ?? "";
|
|
2112
|
+
const object = raw(raw(event.data).object);
|
|
2113
|
+
const objectId = providerId(object.id) ?? "";
|
|
2114
|
+
const candidates = metadataCandidates(object);
|
|
2115
|
+
const identity = {};
|
|
2116
|
+
const identityKeys = {
|
|
2117
|
+
appId: "odlaAppId",
|
|
2118
|
+
chapterId: "chapterId",
|
|
2119
|
+
environment: "odlaEnvironment",
|
|
2120
|
+
dataEnvironment: "odlaDataEnvironment",
|
|
2121
|
+
runtime: "odlaRuntime"
|
|
2122
|
+
};
|
|
2123
|
+
for (const [target, source] of Object.entries(identityKeys)) {
|
|
2124
|
+
const value = metadataValue(candidates, source);
|
|
2125
|
+
if (value) identity[target] = value;
|
|
2126
|
+
}
|
|
2127
|
+
let objectKind = "ignored";
|
|
2128
|
+
if (eventType.startsWith("customer.subscription.")) objectKind = "subscription";
|
|
2129
|
+
else if (eventType.startsWith("customer.")) objectKind = "customer";
|
|
2130
|
+
else if (eventType.startsWith("invoice.")) objectKind = "invoice";
|
|
2131
|
+
else if (eventType.startsWith("refund.")) objectKind = "refund";
|
|
2132
|
+
else if (eventType === "charge.refunded") objectKind = "refund";
|
|
2133
|
+
const customerId = refId(object.customer, "cus_");
|
|
2134
|
+
const subscriptionId = objectKind === "subscription" ? providerId(objectId, "sub_") : subscriptionRef(object);
|
|
2135
|
+
const invoiceId = objectKind === "invoice" ? providerId(objectId, "in_") : refId(object.invoice, "in_");
|
|
2136
|
+
const chargeId = eventType === "charge.refunded" ? providerId(objectId, "ch_") : refId(object.charge, "ch_");
|
|
2137
|
+
const paymentIntentId = refId(object.payment_intent, "pi_");
|
|
2138
|
+
const price = firstPrice(object);
|
|
2139
|
+
const amountCents = integer(eventType === "charge.refunded" ? object.amount_refunded : object.amount);
|
|
2140
|
+
const currency = text(object.currency)?.toLowerCase();
|
|
2141
|
+
const status = text(object.status);
|
|
2142
|
+
return {
|
|
2143
|
+
eventId,
|
|
2144
|
+
digest,
|
|
2145
|
+
eventType,
|
|
2146
|
+
eventCreated: integer(event.created) ?? 0,
|
|
2147
|
+
livemode: event.livemode === true,
|
|
2148
|
+
objectKind,
|
|
2149
|
+
objectId,
|
|
2150
|
+
identity,
|
|
2151
|
+
...metadataValue(candidates, "applicationId") ? { applicationId: metadataValue(candidates, "applicationId") } : {},
|
|
2152
|
+
...metadataValue(candidates, "personId") ? { personId: metadataValue(candidates, "personId") } : {},
|
|
2153
|
+
...metadataValue(candidates, "membershipId") ? { membershipId: metadataValue(candidates, "membershipId") } : {},
|
|
2154
|
+
...customerId ? { customerId } : {},
|
|
2155
|
+
...subscriptionId ? { subscriptionId } : {},
|
|
2156
|
+
...invoiceId ? { invoiceId } : {},
|
|
2157
|
+
...chargeId ? { chargeId } : {},
|
|
2158
|
+
...paymentIntentId ? { paymentIntentId } : {},
|
|
2159
|
+
...price,
|
|
2160
|
+
...status ? { status } : {},
|
|
2161
|
+
...currency ? { currency } : {},
|
|
2162
|
+
...amountCents !== void 0 ? { amountCents } : {},
|
|
2163
|
+
...integer(object.amount_paid) !== void 0 ? { amountPaidCents: integer(object.amount_paid) } : {},
|
|
2164
|
+
...integer(object.amount_remaining) !== void 0 ? { amountRemainingCents: integer(object.amount_remaining) } : {},
|
|
2165
|
+
...integer(object.current_period_start) !== void 0 ? { currentPeriodStart: integer(object.current_period_start) } : {},
|
|
2166
|
+
...integer(object.current_period_end) !== void 0 ? { currentPeriodEnd: integer(object.current_period_end) } : {},
|
|
2167
|
+
...bool(object.cancel_at_period_end) !== void 0 ? { cancelAtPeriodEnd: bool(object.cancel_at_period_end) } : {}
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
var identityFields = ["appId", "chapterId", "environment", "dataEnvironment", "runtime"];
|
|
2171
|
+
function directSource(fact, sources) {
|
|
2172
|
+
const mode = fact.livemode ? "live" : "test";
|
|
2173
|
+
const supplied = identityFields.filter((field) => fact.identity[field] !== void 0);
|
|
2174
|
+
const candidates = sources.filter((source) => source.stripeMode === mode && supplied.every((field) => source[field] === fact.identity[field]));
|
|
2175
|
+
if (supplied.length === identityFields.length) return candidates.length === 1 ? candidates[0] : "wrong";
|
|
2176
|
+
if (supplied.length > 0 && candidates.length === 0) return "wrong";
|
|
2177
|
+
return null;
|
|
2178
|
+
}
|
|
2179
|
+
var objectRefs = (fact) => [
|
|
2180
|
+
fact.customerId && `customer:${fact.customerId}`,
|
|
2181
|
+
fact.subscriptionId && `subscription:${fact.subscriptionId}`,
|
|
2182
|
+
fact.invoiceId && `invoice:${fact.invoiceId}`,
|
|
2183
|
+
fact.chargeId && `charge:${fact.chargeId}`,
|
|
2184
|
+
fact.paymentIntentId && `payment_intent:${fact.paymentIntentId}`
|
|
2185
|
+
].filter((value) => Boolean(value));
|
|
2186
|
+
function ownRef(fact) {
|
|
2187
|
+
if (!fact.objectId) return null;
|
|
2188
|
+
if (fact.objectKind === "customer") return `customer:${fact.objectId}`;
|
|
2189
|
+
if (fact.objectKind === "subscription") return `subscription:${fact.objectId}`;
|
|
2190
|
+
if (fact.objectKind === "invoice") return `invoice:${fact.objectId}`;
|
|
2191
|
+
if (fact.eventType.startsWith("refund.")) return `refund:${fact.objectId}`;
|
|
2192
|
+
return fact.eventType === "charge.refunded" ? `charge:${fact.objectId}` : null;
|
|
2193
|
+
}
|
|
2194
|
+
var order = (left, right) => left.eventCreated - right.eventCreated || left.eventId.localeCompare(right.eventId);
|
|
2195
|
+
function projectNetworkCommercialEvents(input, sources) {
|
|
2196
|
+
const facts2 = [...input].sort(order);
|
|
2197
|
+
const exceptions2 = [];
|
|
2198
|
+
const unique = [];
|
|
2199
|
+
const digests = /* @__PURE__ */ new Map();
|
|
2200
|
+
for (const fact of facts2) {
|
|
2201
|
+
const prior = digests.get(fact.eventId);
|
|
2202
|
+
if (prior === void 0) {
|
|
2203
|
+
digests.set(fact.eventId, fact.digest);
|
|
2204
|
+
unique.push(fact);
|
|
2205
|
+
} else if (prior !== fact.digest) exceptions2.push({ eventId: fact.eventId, kind: "collision", detail: { eventType: fact.eventType, objectId: fact.objectId } });
|
|
2206
|
+
}
|
|
2207
|
+
const assignments = /* @__PURE__ */ new Map();
|
|
2208
|
+
const refs = /* @__PURE__ */ new Map();
|
|
2209
|
+
for (const fact of unique) {
|
|
2210
|
+
const direct = directSource(fact, sources);
|
|
2211
|
+
if (direct && direct !== "wrong") assignments.set(fact.eventId, direct);
|
|
2212
|
+
}
|
|
2213
|
+
let changed = true;
|
|
2214
|
+
while (changed) {
|
|
2215
|
+
changed = false;
|
|
2216
|
+
for (const fact of unique) {
|
|
2217
|
+
const assigned = assignments.get(fact.eventId);
|
|
2218
|
+
if (assigned) {
|
|
2219
|
+
const key = networkCommercialSourceKey(assigned);
|
|
2220
|
+
for (const ref of [...objectRefs(fact), ownRef(fact)].filter((value) => Boolean(value))) {
|
|
2221
|
+
const keys2 = refs.get(ref) ?? /* @__PURE__ */ new Set();
|
|
2222
|
+
const before = keys2.size;
|
|
2223
|
+
keys2.add(key);
|
|
2224
|
+
refs.set(ref, keys2);
|
|
2225
|
+
if (keys2.size !== before) changed = true;
|
|
2226
|
+
}
|
|
2227
|
+
continue;
|
|
2228
|
+
}
|
|
2229
|
+
if (directSource(fact, sources) === "wrong") continue;
|
|
2230
|
+
const keys = new Set(objectRefs(fact).flatMap((ref) => [...refs.get(ref) ?? []]));
|
|
2231
|
+
if (keys.size !== 1) continue;
|
|
2232
|
+
const candidate = sources.find((source) => networkCommercialSourceKey(source) === [...keys][0]);
|
|
2233
|
+
if (candidate && directSource({ ...fact, identity: fact.identity }, [candidate]) !== "wrong") {
|
|
2234
|
+
assignments.set(fact.eventId, candidate);
|
|
2235
|
+
changed = true;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
const latest = /* @__PURE__ */ new Map();
|
|
2240
|
+
for (const fact of unique) {
|
|
2241
|
+
if (fact.objectKind === "ignored" || !fact.eventId || !fact.objectId) {
|
|
2242
|
+
exceptions2.push({ eventId: fact.eventId, kind: "unsupported", detail: { eventType: fact.eventType, objectId: fact.objectId } });
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
const source = assignments.get(fact.eventId);
|
|
2246
|
+
if (!source) {
|
|
2247
|
+
const direct = directSource(fact, sources);
|
|
2248
|
+
const keys = new Set(objectRefs(fact).flatMap((ref) => [...refs.get(ref) ?? []]));
|
|
2249
|
+
exceptions2.push({ eventId: fact.eventId, kind: direct === "wrong" ? "wrong_partition" : keys.size > 1 ? "ambiguous_partition" : "unmatched_partition", detail: { eventType: fact.eventType, objectId: fact.objectId } });
|
|
2250
|
+
continue;
|
|
2251
|
+
}
|
|
2252
|
+
const row = { ...fact, sourceKey: networkCommercialSourceKey(source) };
|
|
2253
|
+
const key = `${fact.objectKind}:${fact.objectId}`;
|
|
2254
|
+
const prior = latest.get(key);
|
|
2255
|
+
if (!prior || order(prior, fact) <= 0) latest.set(key, row);
|
|
2256
|
+
}
|
|
2257
|
+
const rows3 = [...latest.values()];
|
|
2258
|
+
const customers = rows3.filter((row) => row.objectKind === "customer");
|
|
2259
|
+
const subscriptions = rows3.filter((row) => row.objectKind === "subscription");
|
|
2260
|
+
const invoices = rows3.filter((row) => row.objectKind === "invoice");
|
|
2261
|
+
const refunds = rows3.filter((row) => row.objectKind === "refund" && row.eventType.startsWith("refund."));
|
|
2262
|
+
for (const charge of rows3.filter((row) => row.eventType === "charge.refunded")) {
|
|
2263
|
+
if (!refunds.some((refund) => refund.chargeId === charge.objectId)) {
|
|
2264
|
+
exceptions2.push({ eventId: charge.eventId, kind: "charge_refund_without_refund_object", sourceKey: charge.sourceKey, detail: { eventType: charge.eventType, objectId: charge.objectId } });
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
const totalsMap = /* @__PURE__ */ new Map();
|
|
2268
|
+
const add = (row, field, amount) => {
|
|
2269
|
+
if (!row.currency) return;
|
|
2270
|
+
const key = `${row.sourceKey}:${row.currency}`;
|
|
2271
|
+
const total = totalsMap.get(key) ?? { sourceKey: row.sourceKey, currency: row.currency, paidCents: 0, refundedCents: 0, netCents: 0 };
|
|
2272
|
+
total[field] += amount;
|
|
2273
|
+
total.netCents = total.paidCents - total.refundedCents;
|
|
2274
|
+
totalsMap.set(key, total);
|
|
2275
|
+
};
|
|
2276
|
+
for (const row of invoices) add(row, "paidCents", row.amountPaidCents ?? 0);
|
|
2277
|
+
for (const row of refunds) add(row, "refundedCents", row.amountCents ?? 0);
|
|
2278
|
+
return { customers, subscriptions, invoices, refunds, exceptions: exceptions2, totals: [...totalsMap.values()].sort((a, b) => `${a.sourceKey}:${a.currency}`.localeCompare(`${b.sourceKey}:${b.currency}`)) };
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// src/network-commercial-reconcile.ts
|
|
2282
|
+
var asRaw = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2283
|
+
async function listStripeObjects(client, path, params = {}, maxPages = 20) {
|
|
2284
|
+
const objects = [];
|
|
2285
|
+
let startingAfter;
|
|
2286
|
+
for (let pageNumber = 0; pageNumber < maxPages; pageNumber += 1) {
|
|
2287
|
+
const page = await client.request("GET", path, {
|
|
2288
|
+
...params,
|
|
2289
|
+
limit: 100,
|
|
2290
|
+
...startingAfter ? { starting_after: startingAfter } : {}
|
|
2291
|
+
});
|
|
2292
|
+
if (!Array.isArray(page.data) || typeof page.has_more !== "boolean") {
|
|
2293
|
+
throw new Error("Stripe returned an invalid commercial list response");
|
|
2294
|
+
}
|
|
2295
|
+
const rows3 = page.data.map(asRaw).filter((row) => row !== null);
|
|
2296
|
+
if (rows3.length !== page.data.length) throw new Error("Stripe returned a malformed commercial object");
|
|
2297
|
+
objects.push(...rows3);
|
|
2298
|
+
if (!page.has_more) return { objects, complete: true };
|
|
2299
|
+
const cursor = rows3.at(-1)?.id;
|
|
2300
|
+
if (typeof cursor !== "string" || !cursor) throw new Error("Stripe pagination did not provide a cursor");
|
|
2301
|
+
startingAfter = cursor;
|
|
2302
|
+
}
|
|
2303
|
+
return { objects, complete: false };
|
|
2304
|
+
}
|
|
2305
|
+
function facts(snapshots) {
|
|
2306
|
+
const groups2 = [
|
|
2307
|
+
["customer.updated", snapshots.customers.objects],
|
|
2308
|
+
["customer.subscription.updated", snapshots.subscriptions.objects],
|
|
2309
|
+
["invoice.updated", snapshots.invoices.objects],
|
|
2310
|
+
["refund.updated", snapshots.refunds.objects]
|
|
2311
|
+
];
|
|
2312
|
+
return groups2.flatMap(([eventType, objects]) => objects.map((object) => {
|
|
2313
|
+
const id6 = typeof object.id === "string" ? object.id : "invalid";
|
|
2314
|
+
return normalizeNetworkCommercialStripeEvent({
|
|
2315
|
+
id: `evt_snapshot_${eventType.replaceAll(".", "_")}_${id6}`,
|
|
2316
|
+
type: eventType,
|
|
2317
|
+
created: typeof object.created === "number" ? object.created : 0,
|
|
2318
|
+
livemode: object.livemode === true,
|
|
2319
|
+
data: { object }
|
|
2320
|
+
}, `stripe-snapshot:${eventType}:${id6}`);
|
|
2321
|
+
}));
|
|
2322
|
+
}
|
|
2323
|
+
var selected = (row) => ({
|
|
2324
|
+
status: row.status ?? null,
|
|
2325
|
+
currency: row.currency ?? null,
|
|
2326
|
+
amountCents: row.amountCents ?? null,
|
|
2327
|
+
amountPaidCents: row.amountPaidCents ?? null,
|
|
2328
|
+
amountRemainingCents: row.amountRemainingCents ?? null,
|
|
2329
|
+
customerId: row.customerId ?? null,
|
|
2330
|
+
subscriptionId: row.subscriptionId ?? null,
|
|
2331
|
+
invoiceId: row.invoiceId ?? null,
|
|
2332
|
+
priceId: row.priceId ?? null,
|
|
2333
|
+
quantity: row.quantity ?? null,
|
|
2334
|
+
applicationId: row.applicationId ?? null,
|
|
2335
|
+
personId: row.personId ?? null,
|
|
2336
|
+
membershipId: row.membershipId ?? null,
|
|
2337
|
+
currentPeriodStart: row.currentPeriodStart ?? null,
|
|
2338
|
+
currentPeriodEnd: row.currentPeriodEnd ?? null,
|
|
2339
|
+
cancelAtPeriodEnd: row.cancelAtPeriodEnd ?? null
|
|
2340
|
+
});
|
|
2341
|
+
var index = (projection2) => new Map(
|
|
2342
|
+
[...projection2.customers, ...projection2.subscriptions, ...projection2.invoices, ...projection2.refunds].map((row) => [`${row.sourceKey}:${row.objectKind}:${row.objectId}`, row])
|
|
2343
|
+
);
|
|
2344
|
+
function reconcileStripeSnapshot(shadow, snapshots, sources) {
|
|
2345
|
+
const stripe = projectNetworkCommercialEvents(facts(snapshots), sources);
|
|
2346
|
+
const shadowRows = index(shadow);
|
|
2347
|
+
const stripeRows = index(stripe);
|
|
2348
|
+
const exceptions2 = [];
|
|
2349
|
+
const complete = Object.values(snapshots).every((snapshot) => snapshot.complete);
|
|
2350
|
+
if (!complete) exceptions2.push({ kind: "stripe_snapshot_truncated", detail: {} });
|
|
2351
|
+
for (const [key, row] of shadowRows) {
|
|
2352
|
+
const other = stripeRows.get(key);
|
|
2353
|
+
if (!other) {
|
|
2354
|
+
exceptions2.push({ kind: "missing_from_stripe", sourceKey: row.sourceKey, objectKind: row.objectKind, providerId: row.objectId, detail: {} });
|
|
2355
|
+
} else if (JSON.stringify(selected(row)) !== JSON.stringify(selected(other))) {
|
|
2356
|
+
exceptions2.push({ kind: "object_mismatch", sourceKey: row.sourceKey, objectKind: row.objectKind, providerId: row.objectId, detail: { shadow: selected(row), stripe: selected(other) } });
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
for (const [key, row] of stripeRows) {
|
|
2360
|
+
if (!shadowRows.has(key)) exceptions2.push({ kind: "missing_from_shadow", sourceKey: row.sourceKey, objectKind: row.objectKind, providerId: row.objectId, detail: {} });
|
|
2361
|
+
}
|
|
2362
|
+
if (JSON.stringify(shadow.totals) !== JSON.stringify(stripe.totals)) {
|
|
2363
|
+
exceptions2.push({ kind: "totals_mismatch", detail: { shadow: shadow.totals, stripe: stripe.totals } });
|
|
2364
|
+
}
|
|
2365
|
+
return {
|
|
2366
|
+
ok: exceptions2.length === 0,
|
|
2367
|
+
complete,
|
|
2368
|
+
ignoredObjects: stripe.exceptions.filter((item) => item.kind === "wrong_partition" || item.kind === "unmatched_partition").length,
|
|
2369
|
+
exceptions: exceptions2,
|
|
2370
|
+
stripeTotals: stripe.totals
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// src/network-commercial-parity.ts
|
|
2375
|
+
var DIGEST = /^[0-9a-f]{64}$/;
|
|
2376
|
+
var MAX_RECORDS = 500;
|
|
2377
|
+
var text2 = (value, max = 255) => typeof value === "string" && value.length > 0 && value.length <= max ? value : null;
|
|
2378
|
+
function parityMessage(partition, kind, value) {
|
|
2379
|
+
return [
|
|
2380
|
+
"chapter-commercial-parity",
|
|
2381
|
+
"v1",
|
|
2382
|
+
partition.appId,
|
|
2383
|
+
partition.chapterId,
|
|
2384
|
+
partition.environment,
|
|
2385
|
+
partition.dataEnvironment,
|
|
2386
|
+
partition.runtime,
|
|
2387
|
+
kind,
|
|
2388
|
+
value
|
|
2389
|
+
].join("");
|
|
2390
|
+
}
|
|
2391
|
+
async function parityKey(secret) {
|
|
2392
|
+
const encoder4 = new TextEncoder();
|
|
2393
|
+
return crypto.subtle.importKey(
|
|
2394
|
+
"raw",
|
|
2395
|
+
encoder4.encode(secret),
|
|
2396
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
2397
|
+
false,
|
|
2398
|
+
["sign"]
|
|
2399
|
+
);
|
|
2400
|
+
}
|
|
2401
|
+
async function digestWithKey(key, partition, kind, value) {
|
|
2402
|
+
const encoder4 = new TextEncoder();
|
|
2403
|
+
const signed = await crypto.subtle.sign("HMAC", key, encoder4.encode(parityMessage(partition, kind, value)));
|
|
2404
|
+
return [...new Uint8Array(signed)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2405
|
+
}
|
|
2406
|
+
async function commercialParityDigest(secret, partition, kind, value) {
|
|
2407
|
+
return digestWithKey(await parityKey(secret), partition, kind, value);
|
|
2408
|
+
}
|
|
2409
|
+
async function buildChapterCommercialParitySnapshot(input) {
|
|
2410
|
+
const requestedMax = input.maxRecords ?? MAX_RECORDS;
|
|
2411
|
+
const maxRecords = Number.isSafeInteger(requestedMax) ? Math.max(0, Math.min(requestedMax, MAX_RECORDS)) : MAX_RECORDS;
|
|
2412
|
+
const eligible = input.rows.filter((row) => text2(row.id) && (text2(row.stripeCustomerId) || text2(row.stripeSubscriptionId)));
|
|
2413
|
+
const selected2 = eligible.slice(0, maxRecords);
|
|
2414
|
+
const key = await parityKey(input.secret);
|
|
2415
|
+
const records = await Promise.all(selected2.map(async (row) => {
|
|
2416
|
+
const applicationId = text2(row.id);
|
|
2417
|
+
const customerId = text2(row.stripeCustomerId);
|
|
2418
|
+
const subscriptionId = text2(row.stripeSubscriptionId);
|
|
2419
|
+
return {
|
|
2420
|
+
applicationDigest: await digestWithKey(key, input.partition, "application", applicationId),
|
|
2421
|
+
customerDigest: customerId ? await digestWithKey(key, input.partition, "customer", customerId) : null,
|
|
2422
|
+
subscriptionDigest: subscriptionId ? await digestWithKey(key, input.partition, "subscription", subscriptionId) : null,
|
|
2423
|
+
status: text2(row.status, 64),
|
|
2424
|
+
canceled: row.canceled === true
|
|
2425
|
+
};
|
|
2426
|
+
}));
|
|
2427
|
+
records.sort((left, right) => left.applicationDigest.localeCompare(right.applicationDigest));
|
|
2428
|
+
const truncated = input.scanTruncated || eligible.length > maxRecords;
|
|
2429
|
+
return {
|
|
2430
|
+
version: 1,
|
|
2431
|
+
site: { id: input.siteId },
|
|
2432
|
+
partition: input.partition,
|
|
2433
|
+
generatedAt: input.now ?? Date.now(),
|
|
2434
|
+
complete: !truncated,
|
|
2435
|
+
truncated,
|
|
2436
|
+
counts: { scanned: input.scanned, eligible: eligible.length, returned: records.length },
|
|
2437
|
+
records
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
function recordKey(record4) {
|
|
2441
|
+
return `${record4.applicationDigest}:${record4.customerDigest ?? "-"}:${record4.subscriptionDigest ?? "-"}`;
|
|
2442
|
+
}
|
|
2443
|
+
function validSnapshot(snapshot, source) {
|
|
2444
|
+
const partition = snapshot?.partition;
|
|
2445
|
+
const records = snapshot?.records;
|
|
2446
|
+
if (snapshot?.version !== 1 || snapshot.site?.id !== source.chapterId || !partition || !Array.isArray(records) || records.length > MAX_RECORDS || partition.appId !== source.appId || partition.chapterId !== source.chapterId || partition.environment !== source.environment || partition.dataEnvironment !== source.dataEnvironment || partition.runtime !== source.runtime || typeof snapshot.complete !== "boolean" || typeof snapshot.truncated !== "boolean" || !snapshot.counts) return false;
|
|
2447
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2448
|
+
for (const record4 of records) {
|
|
2449
|
+
if (!DIGEST.test(record4.applicationDigest) || record4.customerDigest !== null && !DIGEST.test(record4.customerDigest) || record4.subscriptionDigest !== null && !DIGEST.test(record4.subscriptionDigest) || record4.status !== null && text2(record4.status, 64) === null || typeof record4.canceled !== "boolean") return false;
|
|
2450
|
+
const key = recordKey(record4);
|
|
2451
|
+
if (seen.has(key)) return false;
|
|
2452
|
+
seen.add(key);
|
|
2453
|
+
}
|
|
2454
|
+
return Number.isSafeInteger(snapshot.counts.scanned) && Number.isSafeInteger(snapshot.counts.eligible) && Number.isSafeInteger(snapshot.counts.returned) && snapshot.counts.returned === records.length && snapshot.counts.scanned >= snapshot.counts.eligible && snapshot.counts.eligible >= snapshot.counts.returned;
|
|
2455
|
+
}
|
|
2456
|
+
async function reconcileCommercialParitySnapshot(input) {
|
|
2457
|
+
if (!validSnapshot(input.snapshot, input.source)) throw new Error("invalid chapter commercial parity snapshot");
|
|
2458
|
+
const sourceKey = networkCommercialSourceKey(input.source);
|
|
2459
|
+
const key = await parityKey(input.secret);
|
|
2460
|
+
const shadow = await Promise.all(input.projection.subscriptions.filter((row) => row.sourceKey === sourceKey && text2(row.applicationId) && text2(row.objectId)).map(async (row) => {
|
|
2461
|
+
const customerId = text2(row.customerId);
|
|
2462
|
+
const subscriptionId = text2(row.subscriptionId) ?? text2(row.objectId);
|
|
2463
|
+
return {
|
|
2464
|
+
applicationDigest: await digestWithKey(key, input.snapshot.partition, "application", row.applicationId),
|
|
2465
|
+
customerDigest: customerId ? await digestWithKey(key, input.snapshot.partition, "customer", customerId) : null,
|
|
2466
|
+
subscriptionDigest: subscriptionId ? await digestWithKey(key, input.snapshot.partition, "subscription", subscriptionId) : null,
|
|
2467
|
+
status: null,
|
|
2468
|
+
canceled: false,
|
|
2469
|
+
stripeStatus: text2(row.status, 64),
|
|
2470
|
+
stripeCanceled: row.status === "canceled"
|
|
2471
|
+
};
|
|
2472
|
+
}));
|
|
2473
|
+
const chapterByKey = new Map(input.snapshot.records.map((record4) => [recordKey(record4), record4]));
|
|
2474
|
+
const shadowByKey = new Map(shadow.map((record4) => [recordKey(record4), record4]));
|
|
2475
|
+
const exceptions2 = [];
|
|
2476
|
+
const records = [];
|
|
2477
|
+
for (const [key2, chapter] of chapterByKey) {
|
|
2478
|
+
const stripe = shadowByKey.get(key2);
|
|
2479
|
+
if (!stripe) {
|
|
2480
|
+
exceptions2.push({ kind: "chapter_only", applicationDigest: chapter.applicationDigest, detail: { chapterStatus: chapter.status } });
|
|
2481
|
+
continue;
|
|
2482
|
+
}
|
|
2483
|
+
const compared = { ...chapter, stripeStatus: stripe.stripeStatus, stripeCanceled: stripe.stripeCanceled };
|
|
2484
|
+
records.push(compared);
|
|
2485
|
+
if (chapter.canceled !== stripe.stripeCanceled) {
|
|
2486
|
+
exceptions2.push({
|
|
2487
|
+
kind: "cancellation_mismatch",
|
|
2488
|
+
applicationDigest: chapter.applicationDigest,
|
|
2489
|
+
detail: { chapterCanceled: chapter.canceled, stripeCanceled: stripe.stripeCanceled }
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
for (const [key2, stripe] of shadowByKey) {
|
|
2494
|
+
if (!chapterByKey.has(key2)) {
|
|
2495
|
+
exceptions2.push({ kind: "shadow_only", applicationDigest: stripe.applicationDigest, detail: { stripeStatus: stripe.stripeStatus } });
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
if (!input.snapshot.complete || input.snapshot.truncated) {
|
|
2499
|
+
exceptions2.push({
|
|
2500
|
+
kind: "chapter_truncated",
|
|
2501
|
+
detail: { scanned: input.snapshot.counts.scanned, eligible: input.snapshot.counts.eligible, returned: input.snapshot.counts.returned }
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
if (chapterByKey.size !== shadowByKey.size) {
|
|
2505
|
+
exceptions2.push({ kind: "count_mismatch", detail: { chapter: chapterByKey.size, shadow: shadowByKey.size } });
|
|
2506
|
+
}
|
|
2507
|
+
records.sort((left, right) => left.applicationDigest.localeCompare(right.applicationDigest));
|
|
2508
|
+
return {
|
|
2509
|
+
ok: input.snapshot.complete && exceptions2.length === 0,
|
|
2510
|
+
complete: input.snapshot.complete,
|
|
2511
|
+
counts: { chapter: chapterByKey.size, shadow: shadowByKey.size, matched: records.length },
|
|
2512
|
+
records,
|
|
2513
|
+
exceptions: exceptions2
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
|
|
1980
2517
|
// src/leader.ts
|
|
1981
2518
|
import { composeCrmConfigs } from "@odla-ai/crm";
|
|
1982
2519
|
function leaderCrmConfig(options = {}) {
|
|
@@ -2145,14 +2682,14 @@ import { createCrmIntegration } from "@odla-ai/crm";
|
|
|
2145
2682
|
|
|
2146
2683
|
// src/tiers.ts
|
|
2147
2684
|
function buildTierSeeds(groupId, configured = [], now = 0) {
|
|
2148
|
-
return configured.map((tier,
|
|
2685
|
+
return configured.map((tier, index2) => ({
|
|
2149
2686
|
id: tier.id,
|
|
2150
2687
|
groupId,
|
|
2151
2688
|
name: tier.name,
|
|
2152
2689
|
priceCents: tier.priceCents,
|
|
2153
2690
|
...tier.stripePriceId ? { stripePriceId: tier.stripePriceId } : {},
|
|
2154
2691
|
...tier.blurb ? { blurb: tier.blurb } : {},
|
|
2155
|
-
sortOrder: tier.sortOrder ??
|
|
2692
|
+
sortOrder: tier.sortOrder ?? index2,
|
|
2156
2693
|
active: tier.active !== false,
|
|
2157
2694
|
createdAt: now
|
|
2158
2695
|
}));
|
|
@@ -2225,13 +2762,26 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
2225
2762
|
const isLeader = (chapter.config.network?.targets?.length ?? 0) > 0;
|
|
2226
2763
|
const runbooks = isLeader ? {
|
|
2227
2764
|
role: "leader",
|
|
2228
|
-
slugs: ["chapter-leader", "chapter-network", "crm"],
|
|
2765
|
+
slugs: ["chapter-leader", "chapter-network", "crm", ...chapter.networkCommercialReadModel ? ["stripe-network-shadow"] : []],
|
|
2229
2766
|
guidance: `${chapter.name} is a LEADER hub: it holds the CRM and shares records with its chapters. Read it with \`odla-ai runbook get chapter-leader\`.`
|
|
2230
2767
|
} : {
|
|
2231
2768
|
role: "follower",
|
|
2232
2769
|
slugs: ["chapter-follower", "chapter-network"],
|
|
2233
2770
|
guidance: `${chapter.name} is a FOLLOWER site: it receives people and records from a leader hub. Read it with \`odla-ai runbook get chapter-follower\`.`
|
|
2234
2771
|
};
|
|
2772
|
+
const secrets = [];
|
|
2773
|
+
if (chapter.account !== "none") secrets.push({
|
|
2774
|
+
// Released Chapter workers request this effective name. odla-db maps it to
|
|
2775
|
+
// the canonical reserved `$clerk_secret` value written by the CLI.
|
|
2776
|
+
name: "clerk_secret_key",
|
|
2777
|
+
description: "Clerk backend key (set with odla-ai secrets set-clerk-key) required by the configured Chapter account model.",
|
|
2778
|
+
required: true
|
|
2779
|
+
});
|
|
2780
|
+
if (chapter.networkCommercialReadModel) secrets.push({
|
|
2781
|
+
name: "network_commercial_stripe_webhook_secret",
|
|
2782
|
+
description: "Signing secret for the read-only network commercial Stripe webhook endpoint.",
|
|
2783
|
+
required: true
|
|
2784
|
+
});
|
|
2235
2785
|
return {
|
|
2236
2786
|
id: "chapter",
|
|
2237
2787
|
title: `Chapter: ${chapter.name}`,
|
|
@@ -2242,14 +2792,7 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
2242
2792
|
},
|
|
2243
2793
|
rules: { ...crmDesc.rules, ...chapter.rules },
|
|
2244
2794
|
seeds,
|
|
2245
|
-
secrets
|
|
2246
|
-
// Released Chapter workers request this effective name. odla-db maps
|
|
2247
|
-
// it to the canonical reserved `$clerk_secret` value written by
|
|
2248
|
-
// `odla-ai secrets set-clerk-key`.
|
|
2249
|
-
name: "clerk_secret_key",
|
|
2250
|
-
description: "Clerk backend key (set with odla-ai secrets set-clerk-key) required by the configured Chapter account model.",
|
|
2251
|
-
required: true
|
|
2252
|
-
}],
|
|
2795
|
+
secrets,
|
|
2253
2796
|
probes: [
|
|
2254
2797
|
...crmDesc.probes ?? [],
|
|
2255
2798
|
// The snapshot route is deliberately private. A credential-free smoke
|
|
@@ -2260,6 +2803,7 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
2260
2803
|
path: "/api/network/shared-notes",
|
|
2261
2804
|
expectedStatus: 401
|
|
2262
2805
|
}] : [],
|
|
2806
|
+
...chapter.network.readers.some((reader) => reader.commercialParity) ? [{ path: "/api/network/commercial-parity", expectedStatus: 401 }] : [],
|
|
2263
2807
|
// Formation metadata is public only when the host explicitly enables
|
|
2264
2808
|
// the bounded intake contract.
|
|
2265
2809
|
...chapter.formation.enabled ? [{ path: "/api/formation/config", expectedStatus: 200 }] : [],
|
|
@@ -2272,7 +2816,11 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
2272
2816
|
{ path: "/api/admin/builder-library", expectedStatus: 401 },
|
|
2273
2817
|
{ path: "/api/admin/builder-library/manifest", expectedStatus: 401 }
|
|
2274
2818
|
] : [],
|
|
2275
|
-
...chapter.networkPersonRegistry ? [{ path: "/api/admin/network-person-registry", expectedStatus: 401 }] : []
|
|
2819
|
+
...chapter.networkPersonRegistry ? [{ path: "/api/admin/network-person-registry", expectedStatus: 401 }] : [],
|
|
2820
|
+
...chapter.networkCommercialReadModel ? [
|
|
2821
|
+
{ path: "/api/network-commercial/stripe-webhook", expectedStatus: 405 },
|
|
2822
|
+
{ path: "/api/admin/network-commercial-read-model", expectedStatus: 401 }
|
|
2823
|
+
] : []
|
|
2276
2824
|
],
|
|
2277
2825
|
runbooks
|
|
2278
2826
|
};
|
|
@@ -2280,7 +2828,7 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
2280
2828
|
|
|
2281
2829
|
// src/member-content-validation-helpers.ts
|
|
2282
2830
|
var ID = /^[a-z][a-z0-9-]{0,79}$/;
|
|
2283
|
-
var
|
|
2831
|
+
var SLUG8 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2284
2832
|
var ROOT_KEYS = /* @__PURE__ */ new Set(["schemaVersion", "revision", "generatedAt", "source", "target", "pages", "upsellOverrides"]);
|
|
2285
2833
|
var SOURCE_KEYS = /* @__PURE__ */ new Set(["type", "appId"]);
|
|
2286
2834
|
var TARGET_KEYS = /* @__PURE__ */ new Set(["chapterId", "environment"]);
|
|
@@ -2312,21 +2860,21 @@ function textValue(value, path, errors, max = MAX_TEXT, required = false) {
|
|
|
2312
2860
|
return valueText;
|
|
2313
2861
|
}
|
|
2314
2862
|
function offerValue(value, path, errors, tiers2, audience) {
|
|
2315
|
-
const
|
|
2316
|
-
if (!
|
|
2863
|
+
const raw2 = record(value);
|
|
2864
|
+
if (!raw2) {
|
|
2317
2865
|
errors.push(`${path} must be an object`);
|
|
2318
2866
|
return void 0;
|
|
2319
2867
|
}
|
|
2320
|
-
unknownKeys(
|
|
2321
|
-
const tierId = textValue(
|
|
2868
|
+
unknownKeys(raw2, OFFER_KEYS, path, errors);
|
|
2869
|
+
const tierId = textValue(raw2.tierId, `${path}.tierId`, errors, 80, true) ?? "";
|
|
2322
2870
|
if (tierId && !ID.test(tierId)) errors.push(`${path}.tierId must be a lowercase kebab-case id`);
|
|
2323
2871
|
if (tiers2.size && !tiers2.has(tierId)) errors.push(`${path}.tierId must reference a declared tier`);
|
|
2324
2872
|
if (audience?.kind === "tiers" && tierId && !audience.tierIds.includes(tierId)) {
|
|
2325
2873
|
errors.push(`${path}.tierId must grant access to this page`);
|
|
2326
2874
|
}
|
|
2327
|
-
const label = textValue(
|
|
2328
|
-
const bodyMarkdown = textValue(
|
|
2329
|
-
const ctaLabel = textValue(
|
|
2875
|
+
const label = textValue(raw2.label, `${path}.label`, errors);
|
|
2876
|
+
const bodyMarkdown = textValue(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN);
|
|
2877
|
+
const ctaLabel = textValue(raw2.ctaLabel, `${path}.ctaLabel`, errors);
|
|
2330
2878
|
return {
|
|
2331
2879
|
tierId,
|
|
2332
2880
|
...label ? { label } : {},
|
|
@@ -2378,37 +2926,37 @@ function validateMemberContentDocument(input, options = {}) {
|
|
|
2378
2926
|
const ids = /* @__PURE__ */ new Set();
|
|
2379
2927
|
const slugs = /* @__PURE__ */ new Set();
|
|
2380
2928
|
const pages = [];
|
|
2381
|
-
for (const [
|
|
2382
|
-
const path = `document.pages[${
|
|
2383
|
-
const
|
|
2384
|
-
if (!
|
|
2929
|
+
for (const [index2, value] of rawPages.slice(0, MAX_PAGES).entries()) {
|
|
2930
|
+
const path = `document.pages[${index2}]`;
|
|
2931
|
+
const raw2 = record(value);
|
|
2932
|
+
if (!raw2) {
|
|
2385
2933
|
errors.push(`${path} must be an object`);
|
|
2386
2934
|
continue;
|
|
2387
2935
|
}
|
|
2388
|
-
unknownKeys(
|
|
2389
|
-
const
|
|
2390
|
-
const slug = textValue(
|
|
2391
|
-
const title = textValue(
|
|
2392
|
-
if (
|
|
2393
|
-
if (slug && !
|
|
2394
|
-
if (ids.has(
|
|
2936
|
+
unknownKeys(raw2, PAGE_KEYS, path, errors);
|
|
2937
|
+
const id6 = textValue(raw2.id, `${path}.id`, errors, 80, true) ?? "";
|
|
2938
|
+
const slug = textValue(raw2.slug, `${path}.slug`, errors, 120, true) ?? "";
|
|
2939
|
+
const title = textValue(raw2.title, `${path}.title`, errors, MAX_TEXT, true) ?? "";
|
|
2940
|
+
if (id6 && !ID.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
2941
|
+
if (slug && !SLUG8.test(slug)) errors.push(`${path}.slug must be a lowercase URL slug`);
|
|
2942
|
+
if (ids.has(id6) || knownParentIds.has(id6)) errors.push(`${path}.id duplicates page "${id6}"`);
|
|
2395
2943
|
if (slugs.has(slug) || (options.parentPages ?? []).some((page) => page.slug === slug)) errors.push(`${path}.slug duplicates "${slug}"`);
|
|
2396
|
-
ids.add(
|
|
2944
|
+
ids.add(id6);
|
|
2397
2945
|
slugs.add(slug);
|
|
2398
|
-
const navRaw = record(
|
|
2399
|
-
let navigation = { section: "General", order:
|
|
2946
|
+
const navRaw = record(raw2.navigation);
|
|
2947
|
+
let navigation = { section: "General", order: index2 };
|
|
2400
2948
|
if (!navRaw) errors.push(`${path}.navigation must be an object`);
|
|
2401
2949
|
else {
|
|
2402
2950
|
unknownKeys(navRaw, NAV_KEYS, `${path}.navigation`, errors);
|
|
2403
2951
|
const section = textValue(navRaw.section, `${path}.navigation.section`, errors, 120, true) ?? "";
|
|
2404
|
-
const
|
|
2405
|
-
if (!Number.isInteger(
|
|
2952
|
+
const order2 = navRaw.order;
|
|
2953
|
+
if (!Number.isInteger(order2) || Number(order2) < 0 || Number(order2) > 1e6) {
|
|
2406
2954
|
errors.push(`${path}.navigation.order must be an integer from 0 to 1,000,000`);
|
|
2407
2955
|
}
|
|
2408
2956
|
const parentId = textValue(navRaw.parentId, `${path}.navigation.parentId`, errors, 80);
|
|
2409
|
-
navigation = { section, order: Number.isInteger(
|
|
2957
|
+
navigation = { section, order: Number.isInteger(order2) ? Number(order2) : index2, ...parentId ? { parentId } : {} };
|
|
2410
2958
|
}
|
|
2411
|
-
const audienceRaw = record(
|
|
2959
|
+
const audienceRaw = record(raw2.audience);
|
|
2412
2960
|
let audience = { kind: "all-members" };
|
|
2413
2961
|
if (!audienceRaw) errors.push(`${path}.audience must be an object`);
|
|
2414
2962
|
else {
|
|
@@ -2430,12 +2978,12 @@ function validateMemberContentDocument(input, options = {}) {
|
|
|
2430
2978
|
audience = { kind: "tiers", tierIds: normalized };
|
|
2431
2979
|
} else errors.push(`${path}.audience.kind must be all-members or tiers`);
|
|
2432
2980
|
}
|
|
2433
|
-
const summary = textValue(
|
|
2434
|
-
const teaserMarkdown = textValue(
|
|
2435
|
-
const bodyMarkdown = textValue(
|
|
2981
|
+
const summary = textValue(raw2.summary, `${path}.summary`, errors, 2e3);
|
|
2982
|
+
const teaserMarkdown = textValue(raw2.teaserMarkdown, `${path}.teaserMarkdown`, errors, MAX_MARKDOWN);
|
|
2983
|
+
const bodyMarkdown = textValue(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN, true) ?? "";
|
|
2436
2984
|
let upsell;
|
|
2437
|
-
if (
|
|
2438
|
-
const upsellRaw = record(
|
|
2985
|
+
if (raw2.upsell !== void 0) {
|
|
2986
|
+
const upsellRaw = record(raw2.upsell);
|
|
2439
2987
|
if (!upsellRaw) errors.push(`${path}.upsell must be an object`);
|
|
2440
2988
|
else {
|
|
2441
2989
|
unknownKeys(upsellRaw, UPSELL_KEYS, `${path}.upsell`, errors);
|
|
@@ -2449,13 +2997,13 @@ function validateMemberContentDocument(input, options = {}) {
|
|
|
2449
2997
|
else if (owner === "chapter") upsell = { owner, ...offer ? { offer } : {} };
|
|
2450
2998
|
}
|
|
2451
2999
|
}
|
|
2452
|
-
pages.push({ id:
|
|
3000
|
+
pages.push({ id: id6, slug, title, ...summary ? { summary } : {}, navigation, audience, ...teaserMarkdown ? { teaserMarkdown } : {}, bodyMarkdown, ...upsell ? { upsell } : {} });
|
|
2453
3001
|
}
|
|
2454
3002
|
const allIds = /* @__PURE__ */ new Set([...knownParentIds, ...ids]);
|
|
2455
|
-
for (const [
|
|
3003
|
+
for (const [index2, page] of pages.entries()) {
|
|
2456
3004
|
const parentId = page.navigation.parentId;
|
|
2457
|
-
if (parentId && !allIds.has(parentId)) errors.push(`document.pages[${
|
|
2458
|
-
if (parentId === page.id) errors.push(`document.pages[${
|
|
3005
|
+
if (parentId && !allIds.has(parentId)) errors.push(`document.pages[${index2}].navigation.parentId references unknown page "${parentId}"`);
|
|
3006
|
+
if (parentId === page.id) errors.push(`document.pages[${index2}].navigation.parentId cannot reference itself`);
|
|
2459
3007
|
}
|
|
2460
3008
|
for (const page of pages) {
|
|
2461
3009
|
const seen = /* @__PURE__ */ new Set([page.id]);
|
|
@@ -2476,21 +3024,21 @@ function validateMemberContentDocument(input, options = {}) {
|
|
|
2476
3024
|
const values = Array.isArray(root.upsellOverrides) ? root.upsellOverrides : [];
|
|
2477
3025
|
const seen = /* @__PURE__ */ new Set();
|
|
2478
3026
|
upsellOverrides = [];
|
|
2479
|
-
for (const [
|
|
2480
|
-
const path = `document.upsellOverrides[${
|
|
2481
|
-
const
|
|
2482
|
-
if (!
|
|
3027
|
+
for (const [index2, value] of values.entries()) {
|
|
3028
|
+
const path = `document.upsellOverrides[${index2}]`;
|
|
3029
|
+
const raw2 = record(value);
|
|
3030
|
+
if (!raw2) {
|
|
2483
3031
|
errors.push(`${path} must be an object`);
|
|
2484
3032
|
continue;
|
|
2485
3033
|
}
|
|
2486
|
-
unknownKeys(
|
|
2487
|
-
const pageId = textValue(
|
|
3034
|
+
unknownKeys(raw2, OVERRIDE_KEYS, path, errors);
|
|
3035
|
+
const pageId = textValue(raw2.pageId, `${path}.pageId`, errors, 80, true) ?? "";
|
|
2488
3036
|
if (seen.has(pageId)) errors.push(`${path}.pageId duplicates "${pageId}"`);
|
|
2489
3037
|
seen.add(pageId);
|
|
2490
3038
|
const parentPage = (options.parentPages ?? []).find((page) => page.id === pageId);
|
|
2491
3039
|
if (!parentPage) errors.push(`${path}.pageId must reference a parent page`);
|
|
2492
3040
|
else if (parentPage.upsell?.owner !== "chapter") errors.push(`${path}.pageId does not delegate upsell authority to the chapter`);
|
|
2493
|
-
const offer = offerValue(
|
|
3041
|
+
const offer = offerValue(raw2.offer, `${path}.offer`, errors, tierIds, parentPage?.audience);
|
|
2494
3042
|
if (offer) upsellOverrides.push({ pageId, offer });
|
|
2495
3043
|
}
|
|
2496
3044
|
}
|
|
@@ -2615,7 +3163,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
2615
3163
|
function unknownKeys2(value, allowed2, path, errors) {
|
|
2616
3164
|
for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
|
|
2617
3165
|
}
|
|
2618
|
-
function
|
|
3166
|
+
function text3(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
2619
3167
|
if (value === void 0 && !required) return void 0;
|
|
2620
3168
|
if (typeof value !== "string" || required && !value.trim()) {
|
|
2621
3169
|
errors.push(`${path} must be a non-empty string`);
|
|
@@ -2625,23 +3173,23 @@ function text(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
|
2625
3173
|
if (result.length > max) errors.push(`${path} must be at most ${max} characters`);
|
|
2626
3174
|
return result;
|
|
2627
3175
|
}
|
|
2628
|
-
function
|
|
3176
|
+
function integer2(value, path, errors, minimum = 1) {
|
|
2629
3177
|
if (!Number.isSafeInteger(value) || Number(value) < minimum) errors.push(`${path} must be an integer >= ${minimum}`);
|
|
2630
3178
|
return Number(value);
|
|
2631
3179
|
}
|
|
2632
3180
|
function scopeValue(value, path, errors) {
|
|
2633
|
-
const
|
|
2634
|
-
if (!
|
|
3181
|
+
const raw2 = record2(value);
|
|
3182
|
+
if (!raw2) {
|
|
2635
3183
|
errors.push(`${path} must be an object`);
|
|
2636
3184
|
return null;
|
|
2637
3185
|
}
|
|
2638
|
-
unknownKeys2(
|
|
2639
|
-
if (
|
|
2640
|
-
if (
|
|
2641
|
-
return { kind:
|
|
3186
|
+
unknownKeys2(raw2, SCOPE_KEYS, path, errors);
|
|
3187
|
+
if (raw2.kind === "global" || raw2.kind === "built_not_found") {
|
|
3188
|
+
if (raw2.chapterId !== void 0) errors.push(`${path}.chapterId is allowed only for chapter scope`);
|
|
3189
|
+
return { kind: raw2.kind };
|
|
2642
3190
|
}
|
|
2643
|
-
if (
|
|
2644
|
-
const chapterId =
|
|
3191
|
+
if (raw2.kind === "chapter") {
|
|
3192
|
+
const chapterId = text3(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
|
|
2645
3193
|
if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
|
|
2646
3194
|
return { kind: "chapter", chapterId };
|
|
2647
3195
|
}
|
|
@@ -2649,28 +3197,28 @@ function scopeValue(value, path, errors) {
|
|
|
2649
3197
|
return null;
|
|
2650
3198
|
}
|
|
2651
3199
|
function resourceValue(value, path, errors) {
|
|
2652
|
-
const
|
|
2653
|
-
if (!
|
|
3200
|
+
const raw2 = record2(value);
|
|
3201
|
+
if (!raw2) {
|
|
2654
3202
|
errors.push(`${path} must be an object`);
|
|
2655
3203
|
return null;
|
|
2656
3204
|
}
|
|
2657
|
-
unknownKeys2(
|
|
2658
|
-
const
|
|
2659
|
-
if (
|
|
2660
|
-
const kind =
|
|
3205
|
+
unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
|
|
3206
|
+
const id6 = text3(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
3207
|
+
if (id6 && !ID2.test(id6)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
3208
|
+
const kind = text3(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
2661
3209
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
2662
|
-
const title =
|
|
2663
|
-
const summary =
|
|
2664
|
-
const status =
|
|
3210
|
+
const title = text3(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
3211
|
+
const summary = text3(raw2.summary, `${path}.summary`, errors, 1e3, false);
|
|
3212
|
+
const status = text3(raw2.status, `${path}.status`, errors) ?? "";
|
|
2665
3213
|
if (!STATUSES.has(status)) errors.push(`${path}.status must be draft, published, or archived`);
|
|
2666
|
-
const revision =
|
|
2667
|
-
const scope = scopeValue(
|
|
2668
|
-
const tags = Array.isArray(
|
|
2669
|
-
if (!Array.isArray(
|
|
3214
|
+
const revision = integer2(raw2.revision, `${path}.revision`, errors);
|
|
3215
|
+
const scope = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
|
|
3216
|
+
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text3(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
|
|
3217
|
+
if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
|
|
2670
3218
|
if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
|
|
2671
3219
|
if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
|
|
2672
|
-
const bodyMarkdown =
|
|
2673
|
-
const url =
|
|
3220
|
+
const bodyMarkdown = text3(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
|
|
3221
|
+
const url = text3(raw2.url, `${path}.url`, errors, 2048, false);
|
|
2674
3222
|
if (url) {
|
|
2675
3223
|
try {
|
|
2676
3224
|
if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
|
|
@@ -2678,13 +3226,13 @@ function resourceValue(value, path, errors) {
|
|
|
2678
3226
|
errors.push(`${path}.url must be a valid https URL`);
|
|
2679
3227
|
}
|
|
2680
3228
|
}
|
|
2681
|
-
const asset =
|
|
2682
|
-
if (
|
|
3229
|
+
const asset = raw2.brandAsset === void 0 ? null : record2(raw2.brandAsset);
|
|
3230
|
+
if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
|
|
2683
3231
|
if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
|
|
2684
3232
|
const brandAsset = asset ? {
|
|
2685
|
-
bookId:
|
|
2686
|
-
assetId:
|
|
2687
|
-
...asset.filename === void 0 ? {} : { filename:
|
|
3233
|
+
bookId: text3(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
|
|
3234
|
+
assetId: text3(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
|
|
3235
|
+
...asset.filename === void 0 ? {} : { filename: text3(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
|
|
2688
3236
|
} : void 0;
|
|
2689
3237
|
if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
|
|
2690
3238
|
if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
|
|
@@ -2692,59 +3240,59 @@ function resourceValue(value, path, errors) {
|
|
|
2692
3240
|
if (!["document", "note"].includes(kind) && bodyMarkdown) errors.push(`${path}.bodyMarkdown is not allowed for ${kind}`);
|
|
2693
3241
|
if (!["link", "inspiration"].includes(kind) && url) errors.push(`${path}.url is not allowed for ${kind}`);
|
|
2694
3242
|
if (!["image", "file"].includes(kind) && brandAsset) errors.push(`${path}.brandAsset is not allowed for ${kind}`);
|
|
2695
|
-
const provenanceRaw = record2(
|
|
3243
|
+
const provenanceRaw = record2(raw2.provenance);
|
|
2696
3244
|
if (!provenanceRaw) errors.push(`${path}.provenance must be an object`);
|
|
2697
3245
|
if (provenanceRaw) unknownKeys2(provenanceRaw, PROVENANCE_KEYS, `${path}.provenance`, errors);
|
|
2698
3246
|
const promotedRaw = provenanceRaw?.promotedFrom === void 0 ? null : record2(provenanceRaw.promotedFrom);
|
|
2699
3247
|
if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
|
|
2700
3248
|
if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
|
|
2701
3249
|
const promotedFrom = promotedRaw ? {
|
|
2702
|
-
chapterId:
|
|
2703
|
-
resourceId:
|
|
2704
|
-
resourceRevision:
|
|
2705
|
-
documentRevision:
|
|
2706
|
-
promotedBy:
|
|
2707
|
-
promotedAt:
|
|
3250
|
+
chapterId: text3(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
|
|
3251
|
+
resourceId: text3(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
|
|
3252
|
+
resourceRevision: integer2(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
|
|
3253
|
+
documentRevision: integer2(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
|
|
3254
|
+
promotedBy: text3(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
|
|
3255
|
+
promotedAt: integer2(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
|
|
2708
3256
|
} : void 0;
|
|
2709
3257
|
const provenance = {
|
|
2710
|
-
createdBy:
|
|
2711
|
-
createdAt:
|
|
2712
|
-
updatedBy:
|
|
2713
|
-
updatedAt:
|
|
3258
|
+
createdBy: text3(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
|
|
3259
|
+
createdAt: integer2(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
|
|
3260
|
+
updatedBy: text3(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
|
|
3261
|
+
updatedAt: integer2(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
2714
3262
|
...promotedFrom ? { promotedFrom } : {}
|
|
2715
3263
|
};
|
|
2716
|
-
return { id:
|
|
3264
|
+
return { id: id6, kind, title, ...summary ? { summary } : {}, tags, scope, status, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
|
|
2717
3265
|
}
|
|
2718
3266
|
function validateBuilderLibraryDocument(value, options = {}) {
|
|
2719
3267
|
const errors = [];
|
|
2720
|
-
const
|
|
2721
|
-
if (!
|
|
2722
|
-
unknownKeys2(
|
|
2723
|
-
if (
|
|
2724
|
-
const revision =
|
|
2725
|
-
const generatedAt =
|
|
2726
|
-
const sourceRaw = record2(
|
|
3268
|
+
const raw2 = record2(value);
|
|
3269
|
+
if (!raw2) return { ok: false, errors: ["document must be an object"] };
|
|
3270
|
+
unknownKeys2(raw2, ROOT_KEYS2, "document", errors);
|
|
3271
|
+
if (raw2.schemaVersion !== BUILDER_LIBRARY_SCHEMA_VERSION) errors.push("document.schemaVersion must be 1");
|
|
3272
|
+
const revision = integer2(raw2.revision, "document.revision", errors);
|
|
3273
|
+
const generatedAt = integer2(raw2.generatedAt, "document.generatedAt", errors, 0);
|
|
3274
|
+
const sourceRaw = record2(raw2.source);
|
|
2727
3275
|
if (!sourceRaw) errors.push("document.source must be an object");
|
|
2728
3276
|
if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
|
|
2729
3277
|
const sourceType = sourceRaw?.type;
|
|
2730
3278
|
if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
|
|
2731
|
-
const sourceAppId =
|
|
3279
|
+
const sourceAppId = text3(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
|
|
2732
3280
|
if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
2733
3281
|
if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
|
|
2734
3282
|
if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
|
|
2735
|
-
const targetRaw =
|
|
2736
|
-
if (
|
|
3283
|
+
const targetRaw = raw2.target === void 0 ? null : record2(raw2.target);
|
|
3284
|
+
if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
|
|
2737
3285
|
if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
|
|
2738
3286
|
const target = targetRaw ? {
|
|
2739
|
-
appId:
|
|
2740
|
-
environment:
|
|
3287
|
+
appId: text3(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
|
|
3288
|
+
environment: text3(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
|
|
2741
3289
|
} : void 0;
|
|
2742
3290
|
if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
|
|
2743
|
-
if (!Array.isArray(
|
|
2744
|
-
const inputs = Array.isArray(
|
|
3291
|
+
if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
|
|
3292
|
+
const inputs = Array.isArray(raw2.resources) ? raw2.resources : [];
|
|
2745
3293
|
if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
|
|
2746
|
-
const resources = inputs.map((item,
|
|
2747
|
-
const ids = resources.map(({ id:
|
|
3294
|
+
const resources = inputs.map((item, index2) => resourceValue(item, `document.resources[${index2}]`, errors)).filter(Boolean);
|
|
3295
|
+
const ids = resources.map(({ id: id6 }) => id6);
|
|
2748
3296
|
if (new Set(ids).size !== ids.length) errors.push("document.resources ids must be unique");
|
|
2749
3297
|
for (const resource of resources) {
|
|
2750
3298
|
if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
|
|
@@ -2861,10 +3409,10 @@ function planDelivery(input) {
|
|
|
2861
3409
|
const transport = !isProd && !redirect ? "log-only" : input.cloudflareReady ? "cloudflare" : "log-only";
|
|
2862
3410
|
const to = redirect ? input.group.debugEmail : input.to;
|
|
2863
3411
|
const subject = (redirect ? "[dev] " : "") + render(tpl.subject, vars);
|
|
2864
|
-
const
|
|
3412
|
+
const text6 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
2865
3413
|
|
|
2866
3414
|
` + render(tpl.text, vars) : render(tpl.text, vars);
|
|
2867
|
-
return { deliver: true, transport, to, subject, text:
|
|
3415
|
+
return { deliver: true, transport, to, subject, text: text6, redirected: redirect };
|
|
2868
3416
|
}
|
|
2869
3417
|
|
|
2870
3418
|
// src/notify.ts
|
|
@@ -2899,9 +3447,9 @@ async function sendTemplated(deps, input) {
|
|
|
2899
3447
|
error = e instanceof Error ? e.message : String(e);
|
|
2900
3448
|
}
|
|
2901
3449
|
}
|
|
2902
|
-
const
|
|
3450
|
+
const id6 = deps.newId();
|
|
2903
3451
|
const row = {
|
|
2904
|
-
id:
|
|
3452
|
+
id: id6,
|
|
2905
3453
|
groupId: input.group.id,
|
|
2906
3454
|
to: decision.to,
|
|
2907
3455
|
template: input.template,
|
|
@@ -2915,7 +3463,7 @@ async function sendTemplated(deps, input) {
|
|
|
2915
3463
|
...messageId ? { messageId } : {},
|
|
2916
3464
|
...error ? { error } : {}
|
|
2917
3465
|
};
|
|
2918
|
-
await deps.db.transact([{ t: "update", ns: "emailLog", id:
|
|
3466
|
+
await deps.db.transact([{ t: "update", ns: "emailLog", id: id6, attrs: row }], error ? void 0 : { mutationId: `email:${input.dedupeKey}` });
|
|
2919
3467
|
return error ? { sent: false, reason: error } : { sent: true };
|
|
2920
3468
|
}
|
|
2921
3469
|
function emailGroupFrom(row) {
|
|
@@ -3101,29 +3649,29 @@ function validateAdmissionGrantDocument(input) {
|
|
|
3101
3649
|
const grants = [];
|
|
3102
3650
|
const seenIds = /* @__PURE__ */ new Set();
|
|
3103
3651
|
const seenEmails = /* @__PURE__ */ new Set();
|
|
3104
|
-
for (const [
|
|
3105
|
-
const grant =
|
|
3106
|
-
const
|
|
3652
|
+
for (const [index2, raw2] of rawGrants.slice(0, MAX_GRANTS).entries()) {
|
|
3653
|
+
const grant = raw2 && typeof raw2 === "object" ? raw2 : {};
|
|
3654
|
+
const id6 = typeof grant.id === "string" ? grant.id : "";
|
|
3107
3655
|
const email = typeof grant.email === "string" ? grant.email.trim() : "";
|
|
3108
3656
|
const normalized = normalizeAdmissionEmail(email);
|
|
3109
3657
|
const decision = grant.decision;
|
|
3110
3658
|
const note = typeof grant.note === "string" ? grant.note.trim() : void 0;
|
|
3111
3659
|
const expiresAt = grant.expiresAt === void 0 ? void 0 : Number(grant.expiresAt);
|
|
3112
|
-
if (!ID3.test(
|
|
3113
|
-
if (seenIds.has(
|
|
3114
|
-
if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${
|
|
3660
|
+
if (!ID3.test(id6)) errors.push(`document.grants[${index2}].id must be an identifier`);
|
|
3661
|
+
if (seenIds.has(id6)) errors.push(`document.grants repeats id "${id6}"`);
|
|
3662
|
+
if (!isAdmissionEmail(normalized)) errors.push(`document.grants[${index2}].email must be valid`);
|
|
3115
3663
|
if (seenEmails.has(normalized)) errors.push(`document.grants repeats email "${normalized}"`);
|
|
3116
3664
|
if (decision !== "waive_interview" && decision !== "require_interview") {
|
|
3117
|
-
errors.push(`document.grants[${
|
|
3665
|
+
errors.push(`document.grants[${index2}].decision is invalid`);
|
|
3118
3666
|
}
|
|
3119
|
-
if (note && note.length > MAX_NOTE) errors.push(`document.grants[${
|
|
3667
|
+
if (note && note.length > MAX_NOTE) errors.push(`document.grants[${index2}].note is too long`);
|
|
3120
3668
|
if (expiresAt !== void 0 && (!Number.isFinite(expiresAt) || expiresAt <= 0)) {
|
|
3121
|
-
errors.push(`document.grants[${
|
|
3669
|
+
errors.push(`document.grants[${index2}].expiresAt must be epoch milliseconds`);
|
|
3122
3670
|
}
|
|
3123
|
-
seenIds.add(
|
|
3671
|
+
seenIds.add(id6);
|
|
3124
3672
|
seenEmails.add(normalized);
|
|
3125
3673
|
grants.push({
|
|
3126
|
-
id:
|
|
3674
|
+
id: id6,
|
|
3127
3675
|
email,
|
|
3128
3676
|
decision,
|
|
3129
3677
|
...note ? { note } : {},
|
|
@@ -3194,8 +3742,8 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
3194
3742
|
const incomingIds = new Set(document.grants.map((grant) => `admission:parent:${document.source.appId}:${grant.id}`));
|
|
3195
3743
|
const ops = [];
|
|
3196
3744
|
for (const row of current) {
|
|
3197
|
-
const
|
|
3198
|
-
if (
|
|
3745
|
+
const id6 = String(row.id ?? "");
|
|
3746
|
+
if (id6 && !incomingIds.has(id6)) ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
|
|
3199
3747
|
active: false,
|
|
3200
3748
|
revokedAt: receivedAt,
|
|
3201
3749
|
revokedBy: document.source.actor,
|
|
@@ -3204,10 +3752,10 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
3204
3752
|
} });
|
|
3205
3753
|
}
|
|
3206
3754
|
for (const grant of document.grants) {
|
|
3207
|
-
const
|
|
3208
|
-
const existing = current.find((row) => row.id ===
|
|
3209
|
-
ops.push({ t: "update", ns: "admissionGrants", id:
|
|
3210
|
-
id:
|
|
3755
|
+
const id6 = `admission:parent:${document.source.appId}:${grant.id}`;
|
|
3756
|
+
const existing = current.find((row) => row.id === id6);
|
|
3757
|
+
ops.push({ t: "update", ns: "admissionGrants", id: id6, attrs: {
|
|
3758
|
+
id: id6,
|
|
3211
3759
|
sourceGrantId: grant.id,
|
|
3212
3760
|
sourceScope: "parent",
|
|
3213
3761
|
sourceId: document.source.appId,
|
|
@@ -3228,7 +3776,7 @@ async function materializeAdmissionGrants(db, envelope, previous, receivedAt) {
|
|
|
3228
3776
|
...!grant.note && existing?.note !== void 0 ? ["note"] : [],
|
|
3229
3777
|
...grant.expiresAt === void 0 && existing?.expiresAt !== void 0 ? ["expiresAt"] : []
|
|
3230
3778
|
];
|
|
3231
|
-
if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id:
|
|
3779
|
+
if (retract.length) ops.push({ t: "retract", ns: "admissionGrants", id: id6, attrs: retract });
|
|
3232
3780
|
}
|
|
3233
3781
|
if (previous?.active) ops.push({ t: "update", ns: "admissionGrantRevisions", id: previous.id, attrs: { active: false } });
|
|
3234
3782
|
const revisionId = `admission-revision:${document.source.appId}:${document.target.environment}:${document.revision}`;
|
|
@@ -3270,7 +3818,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
|
|
|
3270
3818
|
var PRICE = /^price_[A-Za-z0-9]{12,}$/;
|
|
3271
3819
|
var CURRENCY = /^[a-z]{3}$/;
|
|
3272
3820
|
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
3273
|
-
var
|
|
3821
|
+
var text4 = (value, path, errors, max) => {
|
|
3274
3822
|
if (typeof value !== "string" || !value.trim()) {
|
|
3275
3823
|
errors.push(`${path} is required`);
|
|
3276
3824
|
return "";
|
|
@@ -3289,32 +3837,32 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
3289
3837
|
if (rawOffers.length > MAX_GIFT_OFFERS) errors.push(`document.giftOffers.offers supports at most ${MAX_GIFT_OFFERS} entries`);
|
|
3290
3838
|
const ids = /* @__PURE__ */ new Set();
|
|
3291
3839
|
const offers = [];
|
|
3292
|
-
for (const [
|
|
3293
|
-
const path = `document.giftOffers.offers[${
|
|
3294
|
-
const item = record3(
|
|
3840
|
+
for (const [index2, raw2] of rawOffers.slice(0, MAX_GIFT_OFFERS).entries()) {
|
|
3841
|
+
const path = `document.giftOffers.offers[${index2}]`;
|
|
3842
|
+
const item = record3(raw2);
|
|
3295
3843
|
if (!item) {
|
|
3296
3844
|
errors.push(`${path} must be an object`);
|
|
3297
3845
|
continue;
|
|
3298
3846
|
}
|
|
3299
3847
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
3300
3848
|
for (const key of Object.keys(item)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
|
|
3301
|
-
const
|
|
3302
|
-
if (
|
|
3303
|
-
if (ids.has(
|
|
3304
|
-
ids.add(
|
|
3849
|
+
const id6 = text4(item.id, `${path}.id`, errors, 80);
|
|
3850
|
+
if (id6 && !ID4.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
3851
|
+
if (ids.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
|
|
3852
|
+
ids.add(id6);
|
|
3305
3853
|
const targetRaw = record3(item.target);
|
|
3306
3854
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
3307
3855
|
if (targetRaw?.type === "tier") {
|
|
3308
|
-
const tierId =
|
|
3856
|
+
const tierId = text4(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
|
|
3309
3857
|
if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
|
|
3310
3858
|
target = { type: "tier", tierId };
|
|
3311
3859
|
} else if (targetRaw?.type === "entitlement") {
|
|
3312
|
-
const entitlementKey =
|
|
3860
|
+
const entitlementKey = text4(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
|
|
3313
3861
|
if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
|
|
3314
3862
|
target = { type: "entitlement", entitlementKey };
|
|
3315
3863
|
} else errors.push(`${path}.target.type must be tier or entitlement`);
|
|
3316
3864
|
if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
|
|
3317
|
-
const stripePriceId =
|
|
3865
|
+
const stripePriceId = text4(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
|
|
3318
3866
|
if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
3319
3867
|
if (!Number.isSafeInteger(item.priceCents) || Number(item.priceCents) < 1 || Number(item.priceCents) > 1e8) errors.push(`${path}.priceCents must be an integer from 1 to 100,000,000`);
|
|
3320
3868
|
const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
|
|
@@ -3327,7 +3875,7 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
3327
3875
|
if (activeUntil !== void 0 && (!Number.isSafeInteger(activeUntil) || activeUntil < 1)) errors.push(`${path}.activeUntil must be epoch milliseconds`);
|
|
3328
3876
|
if (activeFrom !== void 0 && activeUntil !== void 0 && activeUntil <= activeFrom) errors.push(`${path}.activeUntil must follow activeFrom`);
|
|
3329
3877
|
offers.push({
|
|
3330
|
-
id:
|
|
3878
|
+
id: id6,
|
|
3331
3879
|
target,
|
|
3332
3880
|
billingMode: item.billingMode,
|
|
3333
3881
|
stripePriceId,
|
|
@@ -3336,9 +3884,9 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
3336
3884
|
redemptionLifetimeHours: lifetime,
|
|
3337
3885
|
...activeFrom !== void 0 ? { activeFrom } : {},
|
|
3338
3886
|
...activeUntil !== void 0 ? { activeUntil } : {},
|
|
3339
|
-
title:
|
|
3340
|
-
description:
|
|
3341
|
-
ctaLabel:
|
|
3887
|
+
title: text4(item.title, `${path}.title`, errors, 160),
|
|
3888
|
+
description: text4(item.description, `${path}.description`, errors, 2e3),
|
|
3889
|
+
ctaLabel: text4(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
|
|
3342
3890
|
});
|
|
3343
3891
|
}
|
|
3344
3892
|
return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
|
|
@@ -3383,8 +3931,8 @@ function networkSourceTag(type, hubRecordId, sourceId) {
|
|
|
3383
3931
|
const typeKey = type.toLowerCase();
|
|
3384
3932
|
const readable = /^[a-z0-9_-]+$/.test(hubRecordId);
|
|
3385
3933
|
const prefix = sourceId ? `network:${sourceId.toLowerCase()}:${typeKey}` : `network:${typeKey}`;
|
|
3386
|
-
const
|
|
3387
|
-
if (readable &&
|
|
3934
|
+
const raw2 = `${prefix}:${hubRecordId}`;
|
|
3935
|
+
if (readable && raw2.length <= 64) return raw2;
|
|
3388
3936
|
return `network:${typeKey.slice(0, 16)}:${shortHash(`${sourceId ?? ""}\0${type}\0${hubRecordId}`)}`;
|
|
3389
3937
|
}
|
|
3390
3938
|
function normalizeSharedRecord(record4, fallbackSourceId = "legacy-source") {
|
|
@@ -3654,14 +4202,14 @@ function periodSeries(points, now, count, periodMs) {
|
|
|
3654
4202
|
for (const point of points) {
|
|
3655
4203
|
if (!Number.isFinite(point.t) || !Number.isFinite(point.v)) continue;
|
|
3656
4204
|
if (point.t >= start && point.t <= now) {
|
|
3657
|
-
const
|
|
3658
|
-
current[
|
|
4205
|
+
const index2 = Math.min(count - 1, Math.floor((point.t - start) / periodMs));
|
|
4206
|
+
current[index2] = (current[index2] ?? 0) + point.v;
|
|
3659
4207
|
} else if (point.t >= previousStart && point.t < start) {
|
|
3660
|
-
const
|
|
3661
|
-
previous[
|
|
4208
|
+
const index2 = Math.min(count - 1, Math.floor((point.t - previousStart) / periodMs));
|
|
4209
|
+
previous[index2] = (previous[index2] ?? 0) + point.v;
|
|
3662
4210
|
}
|
|
3663
4211
|
}
|
|
3664
|
-
const labels = current.map((_,
|
|
4212
|
+
const labels = current.map((_, index2) => new Date(start + index2 * periodMs).toISOString().slice(5, 10));
|
|
3665
4213
|
return { labels, current, previous };
|
|
3666
4214
|
}
|
|
3667
4215
|
function dashboardMetricData(points, now) {
|
|
@@ -3745,9 +4293,9 @@ async function updateClerkUserMetadataByEmail(secretKey, email, input, fetchImpl
|
|
|
3745
4293
|
const found = await fetchImpl(`https://api.clerk.com/v1/users?email_address=${encodeURIComponent(email)}&limit=1`, { headers: auth });
|
|
3746
4294
|
if (!found.ok) return false;
|
|
3747
4295
|
const users = await found.json().catch(() => null);
|
|
3748
|
-
const
|
|
3749
|
-
if (!
|
|
3750
|
-
return updateClerkUserMetadata(secretKey,
|
|
4296
|
+
const id6 = Array.isArray(users) && typeof users[0]?.id === "string" ? users[0].id : void 0;
|
|
4297
|
+
if (!id6) return false;
|
|
4298
|
+
return updateClerkUserMetadata(secretKey, id6, input, fetchImpl);
|
|
3751
4299
|
}
|
|
3752
4300
|
async function createClerkUser(secretKey, input, fetchImpl = fetch) {
|
|
3753
4301
|
const { path, body } = clerkUserRequest(input);
|
|
@@ -3810,8 +4358,8 @@ async function clerkGetUserByEmail(secretKey, email, fetchImpl = fetch) {
|
|
|
3810
4358
|
const user = Array.isArray(data) ? data[0] : void 0;
|
|
3811
4359
|
return user ? toRecord(user) : null;
|
|
3812
4360
|
}
|
|
3813
|
-
async function clerkGetUser(secretKey,
|
|
3814
|
-
const data = await clerkGet(`/v1/users/${encodeURIComponent(
|
|
4361
|
+
async function clerkGetUser(secretKey, id6, fetchImpl = fetch) {
|
|
4362
|
+
const data = await clerkGet(`/v1/users/${encodeURIComponent(id6)}`, secretKey, fetchImpl).catch(() => null);
|
|
3815
4363
|
return data ? toRecord(data) : null;
|
|
3816
4364
|
}
|
|
3817
4365
|
async function clerkListUsers(secretKey, fetchImpl = fetch) {
|
|
@@ -3827,8 +4375,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch) {
|
|
|
3827
4375
|
}
|
|
3828
4376
|
return out;
|
|
3829
4377
|
}
|
|
3830
|
-
async function clerkSetRole(secretKey,
|
|
3831
|
-
const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(
|
|
4378
|
+
async function clerkSetRole(secretKey, id6, role, fetchImpl = fetch) {
|
|
4379
|
+
const res = await fetchImpl(`${CLERK_API}/v1/users/${encodeURIComponent(id6)}/metadata`, {
|
|
3832
4380
|
method: "PATCH",
|
|
3833
4381
|
headers: { authorization: `Bearer ${secretKey}`, "content-type": "application/json" },
|
|
3834
4382
|
body: JSON.stringify({ public_metadata: { role } })
|
|
@@ -4054,8 +4602,8 @@ function chapterBrandFromTokens(compiled, base = {}) {
|
|
|
4054
4602
|
function isReconcilable(meeting, now) {
|
|
4055
4603
|
return meeting.status === "scheduled" && Boolean(meeting.googleEventId) && (meeting.startAt ?? 0) > now - 36e5;
|
|
4056
4604
|
}
|
|
4057
|
-
function reconcileMeetings(meetings2,
|
|
4058
|
-
const byEvent = new Map(
|
|
4605
|
+
function reconcileMeetings(meetings2, events2, now) {
|
|
4606
|
+
const byEvent = new Map(events2.map((e) => [e.eventId, e]));
|
|
4059
4607
|
const decisions = [];
|
|
4060
4608
|
for (const m of meetings2) {
|
|
4061
4609
|
if (!isReconcilable(m, now) || !m.googleEventId) continue;
|
|
@@ -4265,10 +4813,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
4265
4813
|
errors.push(`${path} must be a string`);
|
|
4266
4814
|
return void 0;
|
|
4267
4815
|
}
|
|
4268
|
-
const
|
|
4269
|
-
if (required && !
|
|
4270
|
-
if (
|
|
4271
|
-
return
|
|
4816
|
+
const text6 = value.trim();
|
|
4817
|
+
if (required && !text6) errors.push(`${path} is required`);
|
|
4818
|
+
if (text6.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
4819
|
+
return text6;
|
|
4272
4820
|
}
|
|
4273
4821
|
function validateMembershipPageDocument(input, options = {}) {
|
|
4274
4822
|
const errors = [];
|
|
@@ -4286,9 +4834,9 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
4286
4834
|
const authority = new Map((options.tiers ?? []).map((tier) => [tier.id, tier]));
|
|
4287
4835
|
const seen = /* @__PURE__ */ new Set();
|
|
4288
4836
|
const tiers2 = [];
|
|
4289
|
-
for (const [
|
|
4290
|
-
const path = `document.tiers[${
|
|
4291
|
-
const item = objectRecord(
|
|
4837
|
+
for (const [index2, raw2] of rawTiers.slice(0, MAX_TIERS).entries()) {
|
|
4838
|
+
const path = `document.tiers[${index2}]`;
|
|
4839
|
+
const item = objectRecord(raw2);
|
|
4292
4840
|
if (!item) {
|
|
4293
4841
|
errors.push(`${path} must be an object`);
|
|
4294
4842
|
continue;
|
|
@@ -4317,8 +4865,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
4317
4865
|
else {
|
|
4318
4866
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
4319
4867
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
4320
|
-
const
|
|
4321
|
-
return
|
|
4868
|
+
const text6 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
4869
|
+
return text6 ? [text6] : [];
|
|
4322
4870
|
});
|
|
4323
4871
|
}
|
|
4324
4872
|
}
|
|
@@ -4394,7 +4942,7 @@ function membershipPageDocument(input, options = {}) {
|
|
|
4394
4942
|
}
|
|
4395
4943
|
|
|
4396
4944
|
// src/signup-control-validate.ts
|
|
4397
|
-
var
|
|
4945
|
+
var SLUG9 = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
4398
4946
|
var ENVIRONMENT = /^[a-z][a-z0-9_-]{0,62}$/;
|
|
4399
4947
|
var TIER = /^[a-z][a-z0-9-]{0,79}$/;
|
|
4400
4948
|
var PRICE2 = /^price_[A-Za-z0-9]{12,}$/;
|
|
@@ -4417,7 +4965,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
4417
4965
|
function unknownKeys3(value, allowed2, path, errors) {
|
|
4418
4966
|
for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
|
|
4419
4967
|
}
|
|
4420
|
-
function
|
|
4968
|
+
function text5(value, path, errors, max = 65536) {
|
|
4421
4969
|
if (typeof value !== "string" || !value.trim()) {
|
|
4422
4970
|
errors.push(`${path} is required`);
|
|
4423
4971
|
return "";
|
|
@@ -4432,19 +4980,19 @@ function tiersOf(value, errors) {
|
|
|
4432
4980
|
}
|
|
4433
4981
|
if (value.length > 20) errors.push("document.tiers supports at most 20 entries");
|
|
4434
4982
|
const seen = /* @__PURE__ */ new Set();
|
|
4435
|
-
return value.slice(0, 20).flatMap((
|
|
4436
|
-
const path = `document.tiers[${
|
|
4437
|
-
const item = recordOf(
|
|
4983
|
+
return value.slice(0, 20).flatMap((raw2, index2) => {
|
|
4984
|
+
const path = `document.tiers[${index2}]`;
|
|
4985
|
+
const item = recordOf(raw2);
|
|
4438
4986
|
if (!item) {
|
|
4439
4987
|
errors.push(`${path} must be an object`);
|
|
4440
4988
|
return [];
|
|
4441
4989
|
}
|
|
4442
4990
|
unknownKeys3(item, TIER_KEYS2, path, errors);
|
|
4443
|
-
const
|
|
4444
|
-
if (
|
|
4445
|
-
if (seen.has(
|
|
4446
|
-
seen.add(
|
|
4447
|
-
const name =
|
|
4991
|
+
const id6 = text5(item.id, `${path}.id`, errors, 80);
|
|
4992
|
+
if (id6 && !TIER.test(id6)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
4993
|
+
if (seen.has(id6)) errors.push(`${path}.id duplicates "${id6}"`);
|
|
4994
|
+
seen.add(id6);
|
|
4995
|
+
const name = text5(item.name, `${path}.name`, errors, 160);
|
|
4448
4996
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
4449
4997
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
4450
4998
|
if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
|
|
@@ -4456,9 +5004,9 @@ function tiersOf(value, errors) {
|
|
|
4456
5004
|
if (item.available === true && Number(item.priceCents) > 0 && !item.stripePriceId) {
|
|
4457
5005
|
errors.push(`${path}.stripePriceId is required for an available paid tier`);
|
|
4458
5006
|
}
|
|
4459
|
-
const blurb = item.blurb === void 0 ? void 0 :
|
|
5007
|
+
const blurb = item.blurb === void 0 ? void 0 : text5(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
4460
5008
|
return [{
|
|
4461
|
-
id:
|
|
5009
|
+
id: id6,
|
|
4462
5010
|
name,
|
|
4463
5011
|
available: item.available === true,
|
|
4464
5012
|
sortOrder: Number(item.sortOrder) || 0,
|
|
@@ -4512,7 +5060,7 @@ function policyOf(value, errors) {
|
|
|
4512
5060
|
const result = {};
|
|
4513
5061
|
for (const key of allowed2) {
|
|
4514
5062
|
if (input[key] === void 0 && key !== "disclaimerText" && key !== "refundPolicyText") continue;
|
|
4515
|
-
result[key] =
|
|
5063
|
+
result[key] = text5(input[key], `document.policy.${key}`, errors);
|
|
4516
5064
|
}
|
|
4517
5065
|
return result;
|
|
4518
5066
|
}
|
|
@@ -4550,18 +5098,18 @@ function validateSignupControlDocument(input) {
|
|
|
4550
5098
|
if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
|
|
4551
5099
|
const source = recordOf(root.source);
|
|
4552
5100
|
const target = recordOf(root.target);
|
|
4553
|
-
const appId =
|
|
4554
|
-
const chapterId =
|
|
4555
|
-
const environment =
|
|
4556
|
-
if (appId && !
|
|
4557
|
-
if (chapterId && !
|
|
5101
|
+
const appId = text5(source?.appId, "document.source.appId", errors, 63);
|
|
5102
|
+
const chapterId = text5(target?.chapterId, "document.target.chapterId", errors, 63);
|
|
5103
|
+
const environment = text5(target?.environment, "document.target.environment", errors, 63);
|
|
5104
|
+
if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
5105
|
+
if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
|
|
4558
5106
|
if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
|
|
4559
5107
|
if (target?.stripeMode !== "test" && target?.stripeMode !== "live") errors.push("document.target.stripeMode must be test or live");
|
|
4560
5108
|
const tiers2 = tiersOf(root.tiers, errors);
|
|
4561
5109
|
const available = tiers2.filter((tier) => tier.available).sort((a, b) => a.sortOrder - b.sortOrder);
|
|
4562
5110
|
const page = validateMembershipPageDocument(root.membershipPage, { tiers: available });
|
|
4563
5111
|
if (!page.ok) errors.push(...page.errors);
|
|
4564
|
-
else if (page.value.tiers.some((tier,
|
|
5112
|
+
else if (page.value.tiers.some((tier, index2) => tier.tierId !== available[index2]?.id)) {
|
|
4565
5113
|
errors.push("document.membershipPage.tiers must follow the available tier display order");
|
|
4566
5114
|
}
|
|
4567
5115
|
const application = applicationOf(root.application, errors);
|
|
@@ -4596,7 +5144,7 @@ function validateSignupControlDocument(input) {
|
|
|
4596
5144
|
var SIGNUP_CONTROL_THEME_MARKER = "<!--odla:signup-control-theme-->";
|
|
4597
5145
|
var SIGNUP_CONTROL_THEME_STYLE_ID = "odla-signup-control-theme";
|
|
4598
5146
|
var TOKEN_NAME = /^--ui-[a-z0-9][a-z0-9-]{0,94}$/;
|
|
4599
|
-
var
|
|
5147
|
+
var DIGEST2 = /^sha256:[0-9a-f]{64}$/;
|
|
4600
5148
|
var UNSAFE_VALUE = /[\u0000-\u001f\u007f\\<>{};]|\/\*|\*\/|(?:^|[^a-z])(?:url|expression)\s*\(|@import/i;
|
|
4601
5149
|
var MAX_TOKENS = 256;
|
|
4602
5150
|
var MAX_VALUE_LENGTH = 1e3;
|
|
@@ -4608,14 +5156,14 @@ function validatedTokens(input) {
|
|
|
4608
5156
|
}
|
|
4609
5157
|
const tokens = {};
|
|
4610
5158
|
let serializedLength = 0;
|
|
4611
|
-
for (const [name,
|
|
5159
|
+
for (const [name, raw2] of entries.sort(([left], [right]) => left.localeCompare(right))) {
|
|
4612
5160
|
if (!TOKEN_NAME.test(name)) {
|
|
4613
5161
|
throw new TypeError(`signup-control theme token "${name}" must be an allowlisted --ui-* property`);
|
|
4614
5162
|
}
|
|
4615
|
-
if (typeof
|
|
5163
|
+
if (typeof raw2 !== "string" && typeof raw2 !== "number" || typeof raw2 === "number" && !Number.isFinite(raw2)) {
|
|
4616
5164
|
throw new TypeError(`signup-control theme token "${name}" must be a finite string or number`);
|
|
4617
5165
|
}
|
|
4618
|
-
const value = String(
|
|
5166
|
+
const value = String(raw2).trim();
|
|
4619
5167
|
if (!value || value.length > MAX_VALUE_LENGTH || UNSAFE_VALUE.test(value)) {
|
|
4620
5168
|
throw new TypeError(`signup-control theme token "${name}" has an unsafe or oversized value`);
|
|
4621
5169
|
}
|
|
@@ -4640,7 +5188,7 @@ function stateFor(revision, options) {
|
|
|
4640
5188
|
metadata: { source: "bootstrap", revision: null, digest: null, cacheKey: "signup-control:bootstrap" }
|
|
4641
5189
|
};
|
|
4642
5190
|
}
|
|
4643
|
-
if (!Number.isSafeInteger(revision.revision) || revision.revision < 1 || !
|
|
5191
|
+
if (!Number.isSafeInteger(revision.revision) || revision.revision < 1 || !DIGEST2.test(revision.digest)) {
|
|
4644
5192
|
throw new TypeError("signup-control theme revision metadata is invalid");
|
|
4645
5193
|
}
|
|
4646
5194
|
return {
|
|
@@ -4722,16 +5270,16 @@ function markdownSection(rawHeading) {
|
|
|
4722
5270
|
}
|
|
4723
5271
|
function markdownBenefits(lines, path, errors) {
|
|
4724
5272
|
const benefits = [];
|
|
4725
|
-
for (const [
|
|
5273
|
+
for (const [index2, line] of lines.entries()) {
|
|
4726
5274
|
if (!line.trim()) continue;
|
|
4727
5275
|
const match = line.match(MARKDOWN_BENEFIT);
|
|
4728
5276
|
if (!match) {
|
|
4729
|
-
errors.push(`${path} line ${
|
|
5277
|
+
errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
|
|
4730
5278
|
continue;
|
|
4731
5279
|
}
|
|
4732
|
-
const
|
|
4733
|
-
if (
|
|
4734
|
-
if (
|
|
5280
|
+
const text6 = (match[1] ?? "").trim();
|
|
5281
|
+
if (text6.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
|
|
5282
|
+
if (text6) benefits.push(text6);
|
|
4735
5283
|
}
|
|
4736
5284
|
if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
|
|
4737
5285
|
return benefits.slice(0, MAX_BENEFITS2);
|
|
@@ -4746,7 +5294,7 @@ function applyMembershipTierMarkdown(presentation, source) {
|
|
|
4746
5294
|
const sections = /* @__PURE__ */ new Map();
|
|
4747
5295
|
const prelude = [];
|
|
4748
5296
|
let current = null;
|
|
4749
|
-
for (const [
|
|
5297
|
+
for (const [index2, line] of source.replace(/\r\n?/g, "\n").split("\n").entries()) {
|
|
4750
5298
|
const heading = line.match(MARKDOWN_SECTION);
|
|
4751
5299
|
if (heading) {
|
|
4752
5300
|
const headingText = heading[1] ?? "";
|
|
@@ -4766,7 +5314,7 @@ function applyMembershipTierMarkdown(presentation, source) {
|
|
|
4766
5314
|
continue;
|
|
4767
5315
|
}
|
|
4768
5316
|
if (MARKDOWN_HEADING.test(line)) {
|
|
4769
|
-
errors.push(`membership Markdown line ${
|
|
5317
|
+
errors.push(`membership Markdown line ${index2 + 1} uses an unsupported heading`);
|
|
4770
5318
|
continue;
|
|
4771
5319
|
}
|
|
4772
5320
|
if (current) sections.get(current)?.lines.push(line);
|
|
@@ -4833,7 +5381,7 @@ var exclusions = (chapterId, paidTierIds) => [
|
|
|
4833
5381
|
id: chapterId,
|
|
4834
5382
|
reason: "provider_identity",
|
|
4835
5383
|
paths: [
|
|
4836
|
-
...paidTierIds.map((
|
|
5384
|
+
...paidTierIds.map((id6) => `tiers[${id6}].stripePriceId`),
|
|
4837
5385
|
"groups.stripePublishableKey",
|
|
4838
5386
|
"applications.stripeCustomerId",
|
|
4839
5387
|
"applications.stripeSubscriptionId",
|
|
@@ -4875,9 +5423,9 @@ async function exportChapterPromotion(input) {
|
|
|
4875
5423
|
sourceIds: [`signup-control:${document.revision}`, document.source.appId, document.target.chapterId].sort(),
|
|
4876
5424
|
receiptDigests: [input.digest]
|
|
4877
5425
|
};
|
|
4878
|
-
const artifact = (kind,
|
|
5426
|
+
const artifact = (kind, id6, content) => ({
|
|
4879
5427
|
kind,
|
|
4880
|
-
id:
|
|
5428
|
+
id: id6,
|
|
4881
5429
|
sourceRevision: revision,
|
|
4882
5430
|
provenance: promotionProvenance,
|
|
4883
5431
|
content: canonicalPromotionValue(content)
|
|
@@ -4916,7 +5464,7 @@ async function exportChapterPromotion(input) {
|
|
|
4916
5464
|
}
|
|
4917
5465
|
|
|
4918
5466
|
// src/member-system-authority.ts
|
|
4919
|
-
var rows2 = (group) => group.fields.map((
|
|
5467
|
+
var rows2 = (group) => group.fields.map((id6) => ({ ...group, id: id6 }));
|
|
4920
5468
|
var MEMBER_SYSTEM_AUTHORITY_MATRIX = [
|
|
4921
5469
|
...rows2({
|
|
4922
5470
|
scope: "network",
|
|
@@ -5301,10 +5849,10 @@ function classifyMemberStripeEvent(identity, target, hasProvenLegacyLocalLink) {
|
|
|
5301
5849
|
if (supplied.length === keys.length) return "apply";
|
|
5302
5850
|
return hasProvenLegacyLocalLink ? "apply_legacy" : "quarantine_unmatched";
|
|
5303
5851
|
}
|
|
5304
|
-
function planMemberSystemEventReplay(
|
|
5852
|
+
function planMemberSystemEventReplay(events2) {
|
|
5305
5853
|
const seen = /* @__PURE__ */ new Map();
|
|
5306
5854
|
const plan = { accepted: [], replayed: [], collisions: [] };
|
|
5307
|
-
for (const event of
|
|
5855
|
+
for (const event of events2) {
|
|
5308
5856
|
const digest = seen.get(event.id);
|
|
5309
5857
|
if (digest === void 0) {
|
|
5310
5858
|
seen.set(event.id, event.digest);
|
|
@@ -5398,6 +5946,7 @@ export {
|
|
|
5398
5946
|
brandTokens,
|
|
5399
5947
|
bucketSeries,
|
|
5400
5948
|
buildBuilderLibraryManifest,
|
|
5949
|
+
buildChapterCommercialParitySnapshot,
|
|
5401
5950
|
buildGroupSeed,
|
|
5402
5951
|
buildTierSeeds,
|
|
5403
5952
|
builderLibraryDigest,
|
|
@@ -5423,6 +5972,7 @@ export {
|
|
|
5423
5972
|
clerkListUsers,
|
|
5424
5973
|
clerkSetRole,
|
|
5425
5974
|
clerkUserRequest,
|
|
5975
|
+
commercialParityDigest,
|
|
5426
5976
|
composeMemberContent,
|
|
5427
5977
|
createAdmissionGrantEnvelope,
|
|
5428
5978
|
createBuilderLibraryEnvelope,
|
|
@@ -5459,6 +6009,7 @@ export {
|
|
|
5459
6009
|
joinConfig,
|
|
5460
6010
|
joinConfigTiers,
|
|
5461
6011
|
leaderCrmConfig,
|
|
6012
|
+
listStripeObjects,
|
|
5462
6013
|
loadLatestAdmissionRevision,
|
|
5463
6014
|
loadOfferableTiers,
|
|
5464
6015
|
loadTiers,
|
|
@@ -5473,9 +6024,11 @@ export {
|
|
|
5473
6024
|
memberSystemRollbackPlan,
|
|
5474
6025
|
membershipPageDocument,
|
|
5475
6026
|
membershipTierPresentationFromMarkdown,
|
|
6027
|
+
networkCommercialSourceKey,
|
|
5476
6028
|
networkSourceTag,
|
|
5477
6029
|
normalizeAdmissionEmail,
|
|
5478
6030
|
normalizeChapterEmail,
|
|
6031
|
+
normalizeNetworkCommercialStripeEvent,
|
|
5479
6032
|
normalizeSharedRecord,
|
|
5480
6033
|
normalizeWebhookEvent,
|
|
5481
6034
|
offerableTiers,
|
|
@@ -5488,10 +6041,13 @@ export {
|
|
|
5488
6041
|
projectBuilderLibraryDelivery,
|
|
5489
6042
|
projectMemberContent,
|
|
5490
6043
|
projectMemberSelf,
|
|
6044
|
+
projectNetworkCommercialEvents,
|
|
5491
6045
|
projectSharedRecord,
|
|
5492
6046
|
promoteBuilderLibraryResource,
|
|
5493
6047
|
publicGiftOffers,
|
|
6048
|
+
reconcileCommercialParitySnapshot,
|
|
5494
6049
|
reconcileMeetings,
|
|
6050
|
+
reconcileStripeSnapshot,
|
|
5495
6051
|
refundedPatch,
|
|
5496
6052
|
render,
|
|
5497
6053
|
renderSignupControlThemeHead,
|
|
@@ -5505,6 +6061,7 @@ export {
|
|
|
5505
6061
|
resolveJoinContinuation,
|
|
5506
6062
|
resolveLeaderFormation,
|
|
5507
6063
|
resolveMemberContent,
|
|
6064
|
+
resolveNetworkCommercialReadModel,
|
|
5508
6065
|
resolveNetworkPersonRegistry,
|
|
5509
6066
|
resolvePipeline,
|
|
5510
6067
|
resolveScheduling,
|