@odla-ai/chapter 0.59.0 → 0.59.1
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 +14 -2
- package/dist/{chunk-WFTWT3V7.js → chunk-P6X3W2XP.js} +11 -5
- package/dist/{chunk-WFTWT3V7.js.map → chunk-P6X3W2XP.js.map} +1 -1
- package/dist/{chunk-Y2H4VURI.js → chunk-SDGVPIXF.js} +3 -2
- package/dist/chunk-SDGVPIXF.js.map +1 -0
- package/dist/index.cjs +161 -95
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +161 -95
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +4 -0
- package/dist/ui/admin/index.js +1 -1
- package/dist/ui/index.js +2 -2
- package/dist/ui/member/index.d.ts +1 -1
- package/dist/ui/member/index.js +1 -1
- package/dist/worker/index.cjs +264 -181
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +122 -14
- package/dist/worker/index.d.ts +122 -14
- package/dist/worker/index.js +264 -181
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-Y2H4VURI.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1714,7 +1714,7 @@ declare function readMembershipAccess(db: ChapterDb, input: MembershipQuoteTarge
|
|
|
1714
1714
|
declare function verifyMembershipAccessReceipt(value: unknown, input: MembershipQuoteTarget, now: number): Promise<MembershipAccessReceipt>;
|
|
1715
1715
|
|
|
1716
1716
|
/** Where the named recipient stands, from the purchaser's side, folded from the seat's lifecycle status. */
|
|
1717
|
-
type NamedSeatRecipientState = "payment_pending" | "invited" | "accepted" | "joined" | "declined" | "refunded" | "canceled";
|
|
1717
|
+
type NamedSeatRecipientState = "payment_pending" | "invited" | "accepted" | "joined" | "declined" | "refunded" | "canceled" | "disputed";
|
|
1718
1718
|
|
|
1719
1719
|
/** The purchaser's one named seat as the members page shows it: who was named, and whether they accepted or joined. */
|
|
1720
1720
|
interface NamedSeatOfferSeat {
|
package/dist/index.d.ts
CHANGED
|
@@ -1714,7 +1714,7 @@ declare function readMembershipAccess(db: ChapterDb, input: MembershipQuoteTarge
|
|
|
1714
1714
|
declare function verifyMembershipAccessReceipt(value: unknown, input: MembershipQuoteTarget, now: number): Promise<MembershipAccessReceipt>;
|
|
1715
1715
|
|
|
1716
1716
|
/** Where the named recipient stands, from the purchaser's side, folded from the seat's lifecycle status. */
|
|
1717
|
-
type NamedSeatRecipientState = "payment_pending" | "invited" | "accepted" | "joined" | "declined" | "refunded" | "canceled";
|
|
1717
|
+
type NamedSeatRecipientState = "payment_pending" | "invited" | "accepted" | "joined" | "declined" | "refunded" | "canceled" | "disputed";
|
|
1718
1718
|
|
|
1719
1719
|
/** The purchaser's one named seat as the members page shows it: who was named, and whether they accepted or joined. */
|
|
1720
1720
|
interface NamedSeatOfferSeat {
|
package/dist/index.js
CHANGED
|
@@ -1714,8 +1714,8 @@ var APPLICATION_LABELS = {
|
|
|
1714
1714
|
state: "State"
|
|
1715
1715
|
};
|
|
1716
1716
|
function humanize(id11) {
|
|
1717
|
-
const
|
|
1718
|
-
return
|
|
1717
|
+
const text8 = id11.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
1718
|
+
return text8 ? `${text8[0].toUpperCase()}${text8.slice(1)}` : id11;
|
|
1719
1719
|
}
|
|
1720
1720
|
function applicationDefs(chapter) {
|
|
1721
1721
|
try {
|
|
@@ -4382,6 +4382,9 @@ function namedSeatRecipientState(seat) {
|
|
|
4382
4382
|
return seat.refundReason === "declined" ? "declined" : "refunded";
|
|
4383
4383
|
case "canceled":
|
|
4384
4384
|
return "canceled";
|
|
4385
|
+
// A card-network dispute on the seat's own payment pauses the recipient until it is won or re-charged.
|
|
4386
|
+
case "payment_disputed":
|
|
4387
|
+
return "disputed";
|
|
4385
4388
|
default:
|
|
4386
4389
|
return "payment_pending";
|
|
4387
4390
|
}
|
|
@@ -4577,12 +4580,87 @@ async function reconcileMembershipRenewal(db, input, now) {
|
|
|
4577
4580
|
|
|
4578
4581
|
// src/membership-dispute-authority.ts
|
|
4579
4582
|
import { stripeRequest as stripeRequest4 } from "@odla-ai/stripe";
|
|
4583
|
+
|
|
4584
|
+
// src/membership-dispute-transition.ts
|
|
4580
4585
|
var MEMBERSHIP_DISPUTED_STATUS = "payment_disputed";
|
|
4581
4586
|
var DISPUTE_WON_STATUSES = ["won", "warning_closed"];
|
|
4582
4587
|
var CLOSED_STATUSES = [...DISPUTE_WON_STATUSES, "lost"];
|
|
4583
|
-
|
|
4588
|
+
function disputeTransition(row2, fact, eventId, now, terminal) {
|
|
4589
|
+
const status2 = String(fact.status ?? "unknown"), closed = CLOSED_STATUSES.includes(status2);
|
|
4590
|
+
const sameDispute = row2.disputeId === fact.objectId;
|
|
4591
|
+
if (sameDispute && Number(row2.disputeClosedAt ?? 0) > 0 && !closed) return null;
|
|
4592
|
+
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
4593
|
+
const suspended = row2.status === MEMBERSHIP_DISPUTED_STATUS, done = terminal.includes(String(row2.status));
|
|
4594
|
+
const restored = String(row2.disputePriorStatus ?? (row2.journeyApprovedAt ? "active" : "paid_pending_approval"));
|
|
4595
|
+
const next = done ? String(row2.status) : won ? suspended ? restored : String(row2.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
4596
|
+
return {
|
|
4597
|
+
status: next,
|
|
4598
|
+
disputeId: fact.objectId,
|
|
4599
|
+
disputeStatus: status2,
|
|
4600
|
+
disputeEventId: eventId,
|
|
4601
|
+
disputeClosedAt: closed ? now : 0,
|
|
4602
|
+
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
4603
|
+
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
4604
|
+
...sameDispute ? {} : { disputedAt: now },
|
|
4605
|
+
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(row2.status) } : {},
|
|
4606
|
+
revision: Number(row2.revision) + 1,
|
|
4607
|
+
updatedAt: now
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
// src/membership-seat-dispute.ts
|
|
4584
4612
|
var text3 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4585
|
-
var
|
|
4613
|
+
var TERMINAL = ["payment_pending", "payment_processing", "payment_canceling", "refund_pending", "refunded", "canceled"];
|
|
4614
|
+
var namedSeatDisputeStanding = (seat, applied) => ({ seatId: String(seat.id), status: String(seat.status), ...text3(seat.disputeStatus) ? { disputeStatus: text3(seat.disputeStatus) } : {}, applied });
|
|
4615
|
+
function disputedChargeCoversSeat(charge, seat) {
|
|
4616
|
+
return charge.seatId === seat.id && charge.customerId === seat.providerCustomerId && charge.paymentIntentId !== void 0 && (charge.paymentIntentId === seat.providerPaymentIntentId || charge.paymentIntentId === seat.rechargePaymentIntentId);
|
|
4617
|
+
}
|
|
4618
|
+
async function reconcileNamedSeatDispute(db, input, now, charge, evidence) {
|
|
4619
|
+
if (!charge.seatId) membershipFail("membership_dispute_scope_mismatch");
|
|
4620
|
+
const membership = async (applied, seat) => {
|
|
4621
|
+
const id11 = await networkMembershipId(input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4622
|
+
requireMembershipScope(member, input);
|
|
4623
|
+
return {
|
|
4624
|
+
membershipId: id11,
|
|
4625
|
+
status: String(member.status),
|
|
4626
|
+
termEndsAt: Number(member.termEndsAt),
|
|
4627
|
+
graceEndsAt: Number(member.graceEndsAt ?? 0),
|
|
4628
|
+
autoRenew: member.autoRenew === true,
|
|
4629
|
+
applied,
|
|
4630
|
+
...text3(member.disputeStatus) ? { disputeStatus: text3(member.disputeStatus) } : {},
|
|
4631
|
+
...seat ? { seat: namedSeatDisputeStanding(seat, applied) } : {}
|
|
4632
|
+
};
|
|
4633
|
+
};
|
|
4634
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4635
|
+
const state = await loadMembershipAuthorityState(db, input), seat = await membershipLoad(db, "networkSeat", charge.seatId);
|
|
4636
|
+
if (!seat) return membership(false);
|
|
4637
|
+
requireMembershipScope(seat, input);
|
|
4638
|
+
if (charge.customerId !== seat.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4639
|
+
if (!disputedChargeCoversSeat(charge, seat) || seat.disputeEventId === input.eventId) return membership(seat.disputeEventId === input.eventId, seat);
|
|
4640
|
+
const attrs = disputeTransition(seat, evidence.fact, input.eventId, now, TERMINAL);
|
|
4641
|
+
if (!attrs) return membership(false, seat);
|
|
4642
|
+
try {
|
|
4643
|
+
await db.transact([membershipUpdate("networkSeat", String(seat.id), attrs)], { mutationId: `seat-dispute:${seat.id}:${input.eventId}`, guards: [
|
|
4644
|
+
{ ns: "networkSeat", id: String(seat.id), exists: true, equals: { revision: seat.revision } },
|
|
4645
|
+
{ ns: "membershipAuthorityState", id: String(state.id), exists: true, equals: { mode: "authoritative", revision: state.revision } },
|
|
4646
|
+
{ ns: "networkCommercialEvents", id: String(evidence.row.id), exists: true, equals: { digest: evidence.fact.digest } },
|
|
4647
|
+
evidence.guard
|
|
4648
|
+
] });
|
|
4649
|
+
} catch (error) {
|
|
4650
|
+
if (error.code !== "transact_guard_failed") throw error;
|
|
4651
|
+
continue;
|
|
4652
|
+
}
|
|
4653
|
+
const saved = await membershipLoad(db, "networkSeat", String(seat.id));
|
|
4654
|
+
requireMembershipScope(saved, input);
|
|
4655
|
+
return membership(true, saved);
|
|
4656
|
+
}
|
|
4657
|
+
return membershipFail("membership_dispute_retry_required");
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4660
|
+
// src/membership-dispute-authority.ts
|
|
4661
|
+
var TERMINAL2 = ["refunded", "refund_pending", "canceled", "declined", "restarting", "superseded"];
|
|
4662
|
+
var text4 = (value) => typeof value === "string" && value ? value : void 0;
|
|
4663
|
+
var refOf = (value) => typeof value === "string" ? text4(value) : text4(value?.id);
|
|
4586
4664
|
async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
4587
4665
|
if (!/^ch_[A-Za-z0-9]+$/.test(chargeId)) membershipFail("invalid_dispute_charge");
|
|
4588
4666
|
const config = { secretKey, ...fetch2 ? { fetch: fetch2 } : {} };
|
|
@@ -4596,7 +4674,7 @@ async function readDisputedCharge(secretKey, chargeId, fetch2) {
|
|
|
4596
4674
|
if (!invoice.ok || invoice.body.id !== invoiceId) membershipFail("membership_dispute_provider_unavailable");
|
|
4597
4675
|
subscriptionId = refOf(invoice.body.subscription) ?? refOf(invoice.body.parent?.subscription_details?.subscription);
|
|
4598
4676
|
}
|
|
4599
|
-
const seatId =
|
|
4677
|
+
const seatId = text4(charge.body.metadata?.seatId);
|
|
4600
4678
|
return {
|
|
4601
4679
|
chargeId,
|
|
4602
4680
|
customerId,
|
|
@@ -4622,37 +4700,23 @@ async function reconcileMembershipDispute(db, input, now, secretKey, resolved) {
|
|
|
4622
4700
|
termEndsAt: Number(value.termEndsAt),
|
|
4623
4701
|
graceEndsAt: Number(value.graceEndsAt ?? 0),
|
|
4624
4702
|
autoRenew: value.autoRenew === true,
|
|
4625
|
-
...
|
|
4703
|
+
...text4(value.disputeStatus) ? { disputeStatus: text4(value.disputeStatus) } : {},
|
|
4626
4704
|
applied
|
|
4627
4705
|
});
|
|
4628
4706
|
const known = await membershipLoad(db, "networkMembership", id11);
|
|
4629
4707
|
requireMembershipScope(known, input);
|
|
4630
4708
|
if (known.disputeEventId === input.eventId) return receipt(known);
|
|
4709
|
+
const seatKnown = known.namedSeatId ? await membershipLoad(db, "networkSeat", String(known.namedSeatId)) : void 0;
|
|
4710
|
+
if (seatKnown?.disputeEventId === input.eventId) return { ...receipt(known, true), seat: namedSeatDisputeStanding(seatKnown, true) };
|
|
4631
4711
|
const charge = resolved && resolved.chargeId === fact.chargeId ? resolved : await readDisputedCharge(secretKey, fact.chargeId);
|
|
4712
|
+
if (charge.customerId !== known.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4713
|
+
if (charge.seatId) return reconcileNamedSeatDispute(db, input, now, charge, { row: row2, fact, guard: evidenceGuard });
|
|
4632
4714
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
4633
4715
|
const state = await loadMembershipAuthorityState(db, input), member = await membershipLoad(db, "networkMembership", id11);
|
|
4634
4716
|
requireMembershipScope(member, input);
|
|
4635
|
-
if (charge.customerId !== member.providerCustomerId) membershipFail("membership_dispute_scope_mismatch");
|
|
4636
4717
|
if (!disputedChargeCoversMembership(charge, member) || member.disputeEventId === input.eventId) return receipt(member);
|
|
4637
|
-
const
|
|
4638
|
-
|
|
4639
|
-
if (sameDispute && Number(member.disputeClosedAt ?? 0) > 0 && !closed) return receipt(member, false);
|
|
4640
|
-
const won = DISPUTE_WON_STATUSES.includes(status2);
|
|
4641
|
-
const suspended = member.status === MEMBERSHIP_DISPUTED_STATUS, terminal = TERMINAL.includes(String(member.status));
|
|
4642
|
-
const next = terminal ? String(member.status) : won ? suspended ? String(member.disputePriorStatus ?? (member.journeyApprovedAt ? "active" : "paid_pending_approval")) : String(member.status) : MEMBERSHIP_DISPUTED_STATUS;
|
|
4643
|
-
const attrs = {
|
|
4644
|
-
status: next,
|
|
4645
|
-
disputeId: fact.objectId,
|
|
4646
|
-
disputeStatus: status2,
|
|
4647
|
-
disputeEventId: input.eventId,
|
|
4648
|
-
disputeClosedAt: closed ? now : 0,
|
|
4649
|
-
...fact.disputeReason ? { disputeReason: fact.disputeReason } : {},
|
|
4650
|
-
...fact.amountCents !== void 0 ? { disputeAmountCents: fact.amountCents } : {},
|
|
4651
|
-
...sameDispute ? {} : { disputedAt: now },
|
|
4652
|
-
...next === MEMBERSHIP_DISPUTED_STATUS && !suspended ? { disputePriorStatus: String(member.status) } : {},
|
|
4653
|
-
revision: Number(member.revision) + 1,
|
|
4654
|
-
updatedAt: now
|
|
4655
|
-
};
|
|
4718
|
+
const attrs = disputeTransition(member, fact, input.eventId, now, TERMINAL2);
|
|
4719
|
+
if (!attrs) return receipt(member, false);
|
|
4656
4720
|
try {
|
|
4657
4721
|
await db.transact([membershipUpdate("networkMembership", id11, attrs)], { mutationId: `membership-dispute:${id11}:${input.eventId}`, guards: [
|
|
4658
4722
|
{ ns: "networkMembership", id: id11, exists: true, equals: { revision: member.revision } },
|
|
@@ -6056,7 +6120,7 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
6056
6120
|
};
|
|
6057
6121
|
signal.addEventListener("abort", cancel, { once: true });
|
|
6058
6122
|
let size = 0;
|
|
6059
|
-
let
|
|
6123
|
+
let text8 = "";
|
|
6060
6124
|
const decoder = new TextDecoder();
|
|
6061
6125
|
try {
|
|
6062
6126
|
while (true) {
|
|
@@ -6066,10 +6130,10 @@ async function readBoundedJson(response, signal, maxBytes, label) {
|
|
|
6066
6130
|
if (done) break;
|
|
6067
6131
|
size += value.byteLength;
|
|
6068
6132
|
if (size > maxBytes) throw new TypeError(`${label} exceeds limit`);
|
|
6069
|
-
|
|
6133
|
+
text8 += decoder.decode(value, { stream: true });
|
|
6070
6134
|
}
|
|
6071
6135
|
try {
|
|
6072
|
-
return JSON.parse(
|
|
6136
|
+
return JSON.parse(text8 + decoder.decode());
|
|
6073
6137
|
} catch {
|
|
6074
6138
|
throw new TypeError(`invalid ${label}`);
|
|
6075
6139
|
}
|
|
@@ -6556,6 +6620,19 @@ var id10 = () => attr9("string", false, true, true);
|
|
|
6556
6620
|
var key2 = (optional = false) => attr9("string", optional, false, true);
|
|
6557
6621
|
var scope = () => ({ appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), applicationId: key2(), skuId: key2() });
|
|
6558
6622
|
var times = () => ({ revision: attr9("number"), createdAt: attr9("number"), updatedAt: attr9("number") });
|
|
6623
|
+
var dispute = () => ({
|
|
6624
|
+
disputeId: key2(true),
|
|
6625
|
+
disputeStatus: attr9("string", true),
|
|
6626
|
+
disputeReason: attr9("string", true),
|
|
6627
|
+
disputeAmountCents: attr9("number", true),
|
|
6628
|
+
disputedAt: attr9("number", true),
|
|
6629
|
+
disputeClosedAt: attr9("number", true),
|
|
6630
|
+
disputePriorStatus: attr9("string", true),
|
|
6631
|
+
disputeEventId: key2(true),
|
|
6632
|
+
rechargePaymentIntentId: key2(true),
|
|
6633
|
+
rechargedAt: attr9("number", true),
|
|
6634
|
+
rechargeAttempt: attr9("json", true)
|
|
6635
|
+
});
|
|
6559
6636
|
var membershipAuthorityEntities = {
|
|
6560
6637
|
networkMembership: { attrs: {
|
|
6561
6638
|
id: id10(),
|
|
@@ -6596,19 +6673,7 @@ var membershipAuthorityEntities = {
|
|
|
6596
6673
|
...times(),
|
|
6597
6674
|
refundRequestedAt: attr9("number", true),
|
|
6598
6675
|
restartId: key2(true),
|
|
6599
|
-
|
|
6600
|
-
// the status the membership held before it was suspended, and the re-issued charge once an operator collected again.
|
|
6601
|
-
disputeId: key2(true),
|
|
6602
|
-
disputeStatus: attr9("string", true),
|
|
6603
|
-
disputeReason: attr9("string", true),
|
|
6604
|
-
disputeAmountCents: attr9("number", true),
|
|
6605
|
-
disputedAt: attr9("number", true),
|
|
6606
|
-
disputeClosedAt: attr9("number", true),
|
|
6607
|
-
disputePriorStatus: attr9("string", true),
|
|
6608
|
-
disputeEventId: key2(true),
|
|
6609
|
-
rechargePaymentIntentId: key2(true),
|
|
6610
|
-
rechargedAt: attr9("number", true),
|
|
6611
|
-
rechargeAttempt: attr9("json", true)
|
|
6676
|
+
...dispute()
|
|
6612
6677
|
} },
|
|
6613
6678
|
membershipRestart: { attrs: {
|
|
6614
6679
|
id: id10(),
|
|
@@ -6684,7 +6749,8 @@ var membershipAuthorityEntities = {
|
|
|
6684
6749
|
refundedCents: attr9("number", true),
|
|
6685
6750
|
providerRefundIds: attr9("json", true),
|
|
6686
6751
|
regiftedAt: attr9("number", true),
|
|
6687
|
-
formerRecipients: attr9("json", true)
|
|
6752
|
+
formerRecipients: attr9("json", true),
|
|
6753
|
+
...dispute()
|
|
6688
6754
|
} },
|
|
6689
6755
|
networkSeatRecipient: { attrs: { id: id10(), appId: key2(), chapterId: key2(), environment: key2(), runtime: key2(), seatId: key2(), recipientEmailNormalized: key2(), createdAt: attr9("number") } },
|
|
6690
6756
|
membershipEffect: { attrs: {
|
|
@@ -7044,7 +7110,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
7044
7110
|
function unknownKeys2(value, allowed2, path, errors) {
|
|
7045
7111
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
7046
7112
|
}
|
|
7047
|
-
function
|
|
7113
|
+
function text5(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
7048
7114
|
if (value === void 0 && !required) return void 0;
|
|
7049
7115
|
if (typeof value !== "string" || required && !value.trim()) {
|
|
7050
7116
|
errors.push(`${path} must be a non-empty string`);
|
|
@@ -7070,7 +7136,7 @@ function scopeValue(value, path, errors) {
|
|
|
7070
7136
|
return { kind: raw2.kind };
|
|
7071
7137
|
}
|
|
7072
7138
|
if (raw2.kind === "chapter") {
|
|
7073
|
-
const chapterId =
|
|
7139
|
+
const chapterId = text5(raw2.chapterId, `${path}.chapterId`, errors, 63) ?? "";
|
|
7074
7140
|
if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
|
|
7075
7141
|
return { kind: "chapter", chapterId };
|
|
7076
7142
|
}
|
|
@@ -7084,22 +7150,22 @@ function resourceValue(value, path, errors) {
|
|
|
7084
7150
|
return null;
|
|
7085
7151
|
}
|
|
7086
7152
|
unknownKeys2(raw2, RESOURCE_KEYS, path, errors);
|
|
7087
|
-
const id11 =
|
|
7153
|
+
const id11 = text5(raw2.id, `${path}.id`, errors, 80) ?? "";
|
|
7088
7154
|
if (id11 && !ID2.test(id11)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
7089
|
-
const kind =
|
|
7155
|
+
const kind = text5(raw2.kind, `${path}.kind`, errors) ?? "";
|
|
7090
7156
|
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
7091
|
-
const title =
|
|
7092
|
-
const summary =
|
|
7093
|
-
const status2 =
|
|
7157
|
+
const title = text5(raw2.title, `${path}.title`, errors, 240) ?? "";
|
|
7158
|
+
const summary = text5(raw2.summary, `${path}.summary`, errors, 1e3, false);
|
|
7159
|
+
const status2 = text5(raw2.status, `${path}.status`, errors) ?? "";
|
|
7094
7160
|
if (!STATUSES.has(status2)) errors.push(`${path}.status must be draft, published, or archived`);
|
|
7095
7161
|
const revision = integer3(raw2.revision, `${path}.revision`, errors);
|
|
7096
7162
|
const scope2 = scopeValue(raw2.scope, `${path}.scope`, errors) ?? { kind: "global" };
|
|
7097
|
-
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) =>
|
|
7163
|
+
const tags = Array.isArray(raw2.tags) ? raw2.tags.map((tag, index2) => text5(tag, `${path}.tags[${index2}]`, errors, 80) ?? "") : [];
|
|
7098
7164
|
if (!Array.isArray(raw2.tags)) errors.push(`${path}.tags must be an array`);
|
|
7099
7165
|
if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
|
|
7100
7166
|
if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
|
|
7101
|
-
const bodyMarkdown =
|
|
7102
|
-
const url =
|
|
7167
|
+
const bodyMarkdown = text5(raw2.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN2, false);
|
|
7168
|
+
const url = text5(raw2.url, `${path}.url`, errors, 2048, false);
|
|
7103
7169
|
if (url) {
|
|
7104
7170
|
try {
|
|
7105
7171
|
if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
|
|
@@ -7111,9 +7177,9 @@ function resourceValue(value, path, errors) {
|
|
|
7111
7177
|
if (raw2.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
|
|
7112
7178
|
if (asset) unknownKeys2(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
|
|
7113
7179
|
const brandAsset = asset ? {
|
|
7114
|
-
bookId:
|
|
7115
|
-
assetId:
|
|
7116
|
-
...asset.filename === void 0 ? {} : { filename:
|
|
7180
|
+
bookId: text5(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
|
|
7181
|
+
assetId: text5(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
|
|
7182
|
+
...asset.filename === void 0 ? {} : { filename: text5(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
|
|
7117
7183
|
} : void 0;
|
|
7118
7184
|
if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
|
|
7119
7185
|
if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
|
|
@@ -7128,17 +7194,17 @@ function resourceValue(value, path, errors) {
|
|
|
7128
7194
|
if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
|
|
7129
7195
|
if (promotedRaw) unknownKeys2(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
|
|
7130
7196
|
const promotedFrom = promotedRaw ? {
|
|
7131
|
-
chapterId:
|
|
7132
|
-
resourceId:
|
|
7197
|
+
chapterId: text5(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
|
|
7198
|
+
resourceId: text5(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
|
|
7133
7199
|
resourceRevision: integer3(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
|
|
7134
7200
|
documentRevision: integer3(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
|
|
7135
|
-
promotedBy:
|
|
7201
|
+
promotedBy: text5(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
|
|
7136
7202
|
promotedAt: integer3(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
|
|
7137
7203
|
} : void 0;
|
|
7138
7204
|
const provenance = {
|
|
7139
|
-
createdBy:
|
|
7205
|
+
createdBy: text5(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
|
|
7140
7206
|
createdAt: integer3(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
|
|
7141
|
-
updatedBy:
|
|
7207
|
+
updatedBy: text5(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
|
|
7142
7208
|
updatedAt: integer3(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
7143
7209
|
...promotedFrom ? { promotedFrom } : {}
|
|
7144
7210
|
};
|
|
@@ -7157,7 +7223,7 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7157
7223
|
if (sourceRaw) unknownKeys2(sourceRaw, SOURCE_KEYS2, "document.source", errors);
|
|
7158
7224
|
const sourceType = sourceRaw?.type;
|
|
7159
7225
|
if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
|
|
7160
|
-
const sourceAppId =
|
|
7226
|
+
const sourceAppId = text5(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
|
|
7161
7227
|
if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
7162
7228
|
if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
|
|
7163
7229
|
if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
|
|
@@ -7165,8 +7231,8 @@ function validateBuilderLibraryDocument(value, options = {}) {
|
|
|
7165
7231
|
if (raw2.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
|
|
7166
7232
|
if (targetRaw) unknownKeys2(targetRaw, TARGET_KEYS2, "document.target", errors);
|
|
7167
7233
|
const target = targetRaw ? {
|
|
7168
|
-
appId:
|
|
7169
|
-
environment:
|
|
7234
|
+
appId: text5(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
|
|
7235
|
+
environment: text5(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
|
|
7170
7236
|
} : void 0;
|
|
7171
7237
|
if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
|
|
7172
7238
|
if (!Array.isArray(raw2.resources)) errors.push("document.resources must be an array");
|
|
@@ -7261,9 +7327,9 @@ async function createBuilderLibraryEnvelope(document) {
|
|
|
7261
7327
|
}
|
|
7262
7328
|
|
|
7263
7329
|
// src/email-retirement.ts
|
|
7264
|
-
function isRetiredEmailCopy(subject,
|
|
7330
|
+
function isRetiredEmailCopy(subject, text8) {
|
|
7265
7331
|
const subjectCopy = subject.trimStart().replace(/^\[dev\]\s*/i, "");
|
|
7266
|
-
const bodyCopy =
|
|
7332
|
+
const bodyCopy = text8.trimStart().replace(/^\(dev redirect; original recipient: [^\r\n]*\)\r?\n\r?\n/, "");
|
|
7267
7333
|
return /^\(\s*unused\s*:/i.test(subjectCopy) || /^Unused\.\s*Switched off\b/i.test(bodyCopy);
|
|
7268
7334
|
}
|
|
7269
7335
|
|
|
@@ -7341,10 +7407,10 @@ function planDelivery(input) {
|
|
|
7341
7407
|
if (isRetiredEmailCopy(renderedSubject, renderedText)) return { deliver: false, reason: "template-retired" };
|
|
7342
7408
|
if (validateEmailCopy({ subject: renderedSubject, text: renderedText }, { allowedVariables: [] }).length) return { deliver: false, reason: "template-invalid" };
|
|
7343
7409
|
const subject = (redirect ? "[dev] " : "") + renderedSubject;
|
|
7344
|
-
const
|
|
7410
|
+
const text8 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
7345
7411
|
|
|
7346
7412
|
` + renderedText : renderedText;
|
|
7347
|
-
return { deliver: true, transport, to, subject, text:
|
|
7413
|
+
return { deliver: true, transport, to, subject, text: text8, redirected: redirect };
|
|
7348
7414
|
}
|
|
7349
7415
|
|
|
7350
7416
|
// src/email-readiness.ts
|
|
@@ -8000,7 +8066,7 @@ var ID4 = /^[a-z][a-z0-9-]{1,79}$/;
|
|
|
8000
8066
|
var PRICE = /^price_[A-Za-z0-9]{12,}$/;
|
|
8001
8067
|
var CURRENCY = /^[a-z]{3}$/;
|
|
8002
8068
|
var record3 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8003
|
-
var
|
|
8069
|
+
var text6 = (value, path, errors, max) => {
|
|
8004
8070
|
if (typeof value !== "string" || !value.trim()) {
|
|
8005
8071
|
errors.push(`${path} is required`);
|
|
8006
8072
|
return "";
|
|
@@ -8028,23 +8094,23 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
8028
8094
|
}
|
|
8029
8095
|
const allowed2 = /* @__PURE__ */ new Set(["id", "target", "billingMode", "stripePriceId", "priceCents", "currency", "redemptionLifetimeHours", "activeFrom", "activeUntil", "title", "description", "ctaLabel"]);
|
|
8030
8096
|
for (const key3 of Object.keys(item)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
8031
|
-
const id11 =
|
|
8097
|
+
const id11 = text6(item.id, `${path}.id`, errors, 80);
|
|
8032
8098
|
if (id11 && !ID4.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
8033
8099
|
if (ids2.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
8034
8100
|
ids2.add(id11);
|
|
8035
8101
|
const targetRaw = record3(item.target);
|
|
8036
8102
|
let target = { type: "entitlement", entitlementKey: "invalid" };
|
|
8037
8103
|
if (targetRaw?.type === "tier") {
|
|
8038
|
-
const tierId =
|
|
8104
|
+
const tierId = text6(targetRaw.tierId, `${path}.target.tierId`, errors, 80);
|
|
8039
8105
|
if (!tierIds.has(tierId)) errors.push(`${path}.target.tierId must reference a declared tier`);
|
|
8040
8106
|
target = { type: "tier", tierId };
|
|
8041
8107
|
} else if (targetRaw?.type === "entitlement") {
|
|
8042
|
-
const entitlementKey =
|
|
8108
|
+
const entitlementKey = text6(targetRaw.entitlementKey, `${path}.target.entitlementKey`, errors, 80);
|
|
8043
8109
|
if (entitlementKey && !ID4.test(entitlementKey)) errors.push(`${path}.target.entitlementKey must be a lowercase kebab-case id`);
|
|
8044
8110
|
target = { type: "entitlement", entitlementKey };
|
|
8045
8111
|
} else errors.push(`${path}.target.type must be tier or entitlement`);
|
|
8046
8112
|
if (item.billingMode !== "one_time" && item.billingMode !== "subscription") errors.push(`${path}.billingMode must be one_time or subscription`);
|
|
8047
|
-
const stripePriceId =
|
|
8113
|
+
const stripePriceId = text6(item.stripePriceId, `${path}.stripePriceId`, errors, 128);
|
|
8048
8114
|
if (stripePriceId && !PRICE.test(stripePriceId)) errors.push(`${path}.stripePriceId must be a real Stripe Price id`);
|
|
8049
8115
|
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`);
|
|
8050
8116
|
const currency = typeof item.currency === "string" ? item.currency.trim().toLowerCase() : "";
|
|
@@ -8066,9 +8132,9 @@ function validateGiftOfferCatalog(input, tierIds) {
|
|
|
8066
8132
|
redemptionLifetimeHours: lifetime,
|
|
8067
8133
|
...activeFrom !== void 0 ? { activeFrom } : {},
|
|
8068
8134
|
...activeUntil !== void 0 ? { activeUntil } : {},
|
|
8069
|
-
title:
|
|
8070
|
-
description:
|
|
8071
|
-
ctaLabel:
|
|
8135
|
+
title: text6(item.title, `${path}.title`, errors, 160),
|
|
8136
|
+
description: text6(item.description, `${path}.description`, errors, 2e3),
|
|
8137
|
+
ctaLabel: text6(item.ctaLabel, `${path}.ctaLabel`, errors, 120)
|
|
8072
8138
|
});
|
|
8073
8139
|
}
|
|
8074
8140
|
return errors.length ? { ok: false, errors } : { ok: true, value: { schemaVersion: 1, offers } };
|
|
@@ -9156,10 +9222,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
9156
9222
|
errors.push(`${path} must be a string`);
|
|
9157
9223
|
return void 0;
|
|
9158
9224
|
}
|
|
9159
|
-
const
|
|
9160
|
-
if (required && !
|
|
9161
|
-
if (
|
|
9162
|
-
return
|
|
9225
|
+
const text8 = value.trim();
|
|
9226
|
+
if (required && !text8) errors.push(`${path} is required`);
|
|
9227
|
+
if (text8.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
9228
|
+
return text8;
|
|
9163
9229
|
}
|
|
9164
9230
|
function validateMembershipPageDocument(input, options = {}) {
|
|
9165
9231
|
const errors = [];
|
|
@@ -9208,8 +9274,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
9208
9274
|
else {
|
|
9209
9275
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
9210
9276
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
9211
|
-
const
|
|
9212
|
-
return
|
|
9277
|
+
const text8 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
9278
|
+
return text8 ? [text8] : [];
|
|
9213
9279
|
});
|
|
9214
9280
|
}
|
|
9215
9281
|
}
|
|
@@ -9308,7 +9374,7 @@ var recordOf = (value) => value !== null && typeof value === "object" && !Array.
|
|
|
9308
9374
|
function unknownKeys3(value, allowed2, path, errors) {
|
|
9309
9375
|
for (const key3 of Object.keys(value)) if (!allowed2.has(key3)) errors.push(`${path}.${key3} is not supported`);
|
|
9310
9376
|
}
|
|
9311
|
-
function
|
|
9377
|
+
function text7(value, path, errors, max = 65536) {
|
|
9312
9378
|
if (typeof value !== "string" || !value.trim()) {
|
|
9313
9379
|
errors.push(`${path} is required`);
|
|
9314
9380
|
return "";
|
|
@@ -9331,11 +9397,11 @@ function tiersOf(value, errors) {
|
|
|
9331
9397
|
return [];
|
|
9332
9398
|
}
|
|
9333
9399
|
unknownKeys3(item, TIER_KEYS2, path, errors);
|
|
9334
|
-
const id11 =
|
|
9400
|
+
const id11 = text7(item.id, `${path}.id`, errors, 80);
|
|
9335
9401
|
if (id11 && !TIER.test(id11)) errors.push(`${path}.id must be a lowercase kebab-case id`);
|
|
9336
9402
|
if (seen.has(id11)) errors.push(`${path}.id duplicates "${id11}"`);
|
|
9337
9403
|
seen.add(id11);
|
|
9338
|
-
const name =
|
|
9404
|
+
const name = text7(item.name, `${path}.name`, errors, 160);
|
|
9339
9405
|
if (typeof item.available !== "boolean") errors.push(`${path}.available must be boolean`);
|
|
9340
9406
|
if (!Number.isInteger(item.sortOrder) || Number(item.sortOrder) < 0) errors.push(`${path}.sortOrder must be a non-negative integer`);
|
|
9341
9407
|
if (!Number.isInteger(item.priceCents) || Number(item.priceCents) < 0 || Number(item.priceCents) > 1e8) {
|
|
@@ -9353,7 +9419,7 @@ function tiersOf(value, errors) {
|
|
|
9353
9419
|
if (item.interval !== void 0 && item.interval !== "year" && item.interval !== "month") {
|
|
9354
9420
|
errors.push(`${path}.interval must be year or month`);
|
|
9355
9421
|
}
|
|
9356
|
-
const blurb = item.blurb === void 0 ? void 0 :
|
|
9422
|
+
const blurb = item.blurb === void 0 ? void 0 : text7(item.blurb, `${path}.blurb`, errors, 4e3);
|
|
9357
9423
|
return [{
|
|
9358
9424
|
id: id11,
|
|
9359
9425
|
name,
|
|
@@ -9411,7 +9477,7 @@ function policyOf(value, errors, tiers2) {
|
|
|
9411
9477
|
const result = {};
|
|
9412
9478
|
for (const key3 of allowed2) {
|
|
9413
9479
|
if (input[key3] === void 0 && key3 !== "disclaimerText" && key3 !== "refundPolicyText") continue;
|
|
9414
|
-
result[key3] =
|
|
9480
|
+
result[key3] = text7(input[key3], `document.policy.${key3}`, errors);
|
|
9415
9481
|
}
|
|
9416
9482
|
const tierPolicies = {};
|
|
9417
9483
|
if (input.tierPolicies !== void 0) {
|
|
@@ -9426,8 +9492,8 @@ function policyOf(value, errors, tiers2) {
|
|
|
9426
9492
|
}
|
|
9427
9493
|
unknownKeys3(policy2, /* @__PURE__ */ new Set(["refundPolicyText", "trustCopy"]), path, errors);
|
|
9428
9494
|
tierPolicies[id11] = {
|
|
9429
|
-
refundPolicyText:
|
|
9430
|
-
...policy2.trustCopy !== void 0 ? { trustCopy:
|
|
9495
|
+
refundPolicyText: text7(policy2.refundPolicyText, `${path}.refundPolicyText`, errors),
|
|
9496
|
+
...policy2.trustCopy !== void 0 ? { trustCopy: text7(policy2.trustCopy, `${path}.trustCopy`, errors) } : {}
|
|
9431
9497
|
};
|
|
9432
9498
|
}
|
|
9433
9499
|
}
|
|
@@ -9467,9 +9533,9 @@ function validateSignupControlDocument(input) {
|
|
|
9467
9533
|
if (!Number.isSafeInteger(root.generatedAt) || Number(root.generatedAt) < 1) errors.push("document.generatedAt must be epoch milliseconds");
|
|
9468
9534
|
const source = recordOf(root.source);
|
|
9469
9535
|
const target = recordOf(root.target);
|
|
9470
|
-
const appId =
|
|
9471
|
-
const chapterId =
|
|
9472
|
-
const environment =
|
|
9536
|
+
const appId = text7(source?.appId, "document.source.appId", errors, 63);
|
|
9537
|
+
const chapterId = text7(target?.chapterId, "document.target.chapterId", errors, 63);
|
|
9538
|
+
const environment = text7(target?.environment, "document.target.environment", errors, 63);
|
|
9473
9539
|
if (appId && !SLUG9.test(appId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
9474
9540
|
if (chapterId && !SLUG9.test(chapterId)) errors.push("document.target.chapterId must be a lowercase app slug");
|
|
9475
9541
|
if (environment && !ENVIRONMENT.test(environment)) errors.push("document.target.environment is invalid");
|
|
@@ -9646,9 +9712,9 @@ function markdownBenefits(lines, path, errors) {
|
|
|
9646
9712
|
errors.push(`${path} line ${index2 + 1} must be a Markdown bullet`);
|
|
9647
9713
|
continue;
|
|
9648
9714
|
}
|
|
9649
|
-
const
|
|
9650
|
-
if (
|
|
9651
|
-
if (
|
|
9715
|
+
const text8 = (match[1] ?? "").trim();
|
|
9716
|
+
if (text8.length > 2e3) errors.push(`${path} line ${index2 + 1} must be at most 2,000 characters`);
|
|
9717
|
+
if (text8) benefits.push(text8);
|
|
9652
9718
|
}
|
|
9653
9719
|
if (benefits.length > MAX_BENEFITS2) errors.push(`${path} supports at most ${MAX_BENEFITS2} entries`);
|
|
9654
9720
|
return benefits.slice(0, MAX_BENEFITS2);
|