@odla-ai/chapter 0.3.0 → 0.4.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/dist/index.cjs +230 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +261 -1
- package/dist/index.d.ts +261 -1
- package/dist/index.js +230 -3
- package/dist/index.js.map +1 -1
- package/dist/ui/index.d.ts +121 -1
- package/dist/ui/index.js +503 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/worker/index.cjs +759 -146
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +7 -4
- package/dist/worker/index.d.ts +7 -4
- package/dist/worker/index.js +758 -147
- package/dist/worker/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -20,31 +20,57 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
BOOKABLE_STATUSES: () => BOOKABLE_STATUSES,
|
|
24
|
+
SCHEDULING_DEFAULTS: () => SCHEDULING_DEFAULTS,
|
|
25
|
+
applicationBookingUpdate: () => applicationBookingUpdate,
|
|
26
|
+
applicationSummary: () => applicationSummary,
|
|
27
|
+
bookingDecision: () => bookingDecision,
|
|
23
28
|
buildGroupSeed: () => buildGroupSeed,
|
|
24
29
|
canApprove: () => canApprove,
|
|
25
30
|
canBook: () => canBook,
|
|
31
|
+
canBookFrom: () => canBookFrom,
|
|
26
32
|
canChangeRole: () => canChangeRole,
|
|
27
33
|
canTransition: () => canTransition,
|
|
34
|
+
canceledPatch: () => canceledPatch,
|
|
28
35
|
chapterDb: () => chapterDb,
|
|
29
36
|
createChapterIntegration: () => createChapterIntegration,
|
|
30
37
|
defaultCrm: () => defaultCrm,
|
|
31
38
|
defineChapter: () => defineChapter,
|
|
39
|
+
endForSlot: () => endForSlot,
|
|
40
|
+
findApplicationRef: () => findApplicationRef,
|
|
41
|
+
firstPaymentPatch: () => firstPaymentPatch,
|
|
32
42
|
getVaultSecret: () => getVaultSecret,
|
|
43
|
+
introIdempotencyKey: () => introIdempotencyKey,
|
|
33
44
|
isAdminRole: () => isAdminRole,
|
|
34
45
|
isAlreadySent: () => isAlreadySent,
|
|
46
|
+
isSlotAvailable: () => isSlotAvailable,
|
|
35
47
|
joinConfig: () => joinConfig,
|
|
48
|
+
meetingCreateRow: () => meetingCreateRow,
|
|
49
|
+
meetingRescheduleUpdate: () => meetingRescheduleUpdate,
|
|
50
|
+
memberApplication: () => memberApplication,
|
|
51
|
+
memberSession: () => memberSession,
|
|
52
|
+
normalizeWebhookEvent: () => normalizeWebhookEvent,
|
|
53
|
+
paymentsReady: () => paymentsReady,
|
|
36
54
|
planDelivery: () => planDelivery,
|
|
37
55
|
projectSharedRecord: () => projectSharedRecord,
|
|
56
|
+
refundedPatch: () => refundedPatch,
|
|
38
57
|
render: () => render,
|
|
58
|
+
renderSummary: () => renderSummary,
|
|
39
59
|
renderTemplateBody: () => renderTemplateBody,
|
|
60
|
+
renewalPatch: () => renewalPatch,
|
|
40
61
|
resolveApplication: () => resolveApplication,
|
|
41
62
|
resolveAuth: () => resolveAuth,
|
|
42
63
|
resolvePipeline: () => resolvePipeline,
|
|
64
|
+
resolveScheduling: () => resolveScheduling,
|
|
43
65
|
roleFromClaim: () => roleFromClaim,
|
|
44
66
|
sharedPersonInput: () => sharedPersonInput,
|
|
67
|
+
slotWindow: () => slotWindow,
|
|
45
68
|
stageIndex: () => stageIndex,
|
|
69
|
+
stripeForm: () => stripeForm,
|
|
46
70
|
submitApplication: () => submitApplication,
|
|
47
|
-
|
|
71
|
+
subscriptionIdempotencyKey: () => subscriptionIdempotencyKey,
|
|
72
|
+
verifyStripeSignature: () => verifyStripeSignature,
|
|
73
|
+
webhookMutationId: () => webhookMutationId
|
|
48
74
|
});
|
|
49
75
|
module.exports = __toCommonJS(index_exports);
|
|
50
76
|
|
|
@@ -477,7 +503,7 @@ async function submitApplication(db, chapter, fields, opts) {
|
|
|
477
503
|
);
|
|
478
504
|
return { ok: true, id: id2, duplicate, status: chapter.pipeline.initial };
|
|
479
505
|
}
|
|
480
|
-
function joinConfig(group,
|
|
506
|
+
function joinConfig(group, paymentsReady2) {
|
|
481
507
|
return {
|
|
482
508
|
id: group.id,
|
|
483
509
|
name: group.name,
|
|
@@ -488,7 +514,7 @@ function joinConfig(group, paymentsReady) {
|
|
|
488
514
|
trustCopy: group.trustCopy ?? "",
|
|
489
515
|
commitmentText: group.commitmentText ?? "",
|
|
490
516
|
normsText: group.normsText ?? "",
|
|
491
|
-
paymentsReady
|
|
517
|
+
paymentsReady: paymentsReady2
|
|
492
518
|
};
|
|
493
519
|
}
|
|
494
520
|
|
|
@@ -637,6 +663,70 @@ async function verifyStripeSignature(payload, header, secret, opts = {}) {
|
|
|
637
663
|
const mac = await crypto.subtle.sign("HMAC", key, enc.encode(`${t}.${payload}`));
|
|
638
664
|
return timingSafeEqual(toHex(mac), v1);
|
|
639
665
|
}
|
|
666
|
+
function paymentsReady(group, hasSecretKey) {
|
|
667
|
+
return Boolean(group.stripePublishableKey && group.stripePriceId && hasSecretKey);
|
|
668
|
+
}
|
|
669
|
+
function stripeForm(params) {
|
|
670
|
+
const out = new URLSearchParams();
|
|
671
|
+
for (const [k, v] of Object.entries(params)) {
|
|
672
|
+
if (v === void 0 || v === null) continue;
|
|
673
|
+
if (typeof v === "object") {
|
|
674
|
+
for (const [k2, v2] of Object.entries(v)) {
|
|
675
|
+
if (v2 !== void 0 && v2 !== null) out.append(`${k}[${k2}]`, String(v2));
|
|
676
|
+
}
|
|
677
|
+
} else {
|
|
678
|
+
out.append(k, String(v));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return out.toString();
|
|
682
|
+
}
|
|
683
|
+
function subscriptionIdempotencyKey(applicationId) {
|
|
684
|
+
return `sub:${applicationId}`;
|
|
685
|
+
}
|
|
686
|
+
function webhookMutationId(eventId) {
|
|
687
|
+
return `stripe:${eventId}`;
|
|
688
|
+
}
|
|
689
|
+
function findApplicationRef(obj) {
|
|
690
|
+
const metaOf = (v) => v && typeof v === "object" ? v.metadata ?? {} : {};
|
|
691
|
+
const pick = (m) => typeof m.applicationId === "string" ? m.applicationId : void 0;
|
|
692
|
+
const applicationId = pick(metaOf(obj)) ?? pick(metaOf(obj.subscription_details)) ?? pick(metaOf(obj.parent?.subscription_details));
|
|
693
|
+
const customerId = typeof obj.customer === "string" ? obj.customer : void 0;
|
|
694
|
+
return { ...applicationId ? { applicationId } : {}, ...customerId ? { customerId } : {} };
|
|
695
|
+
}
|
|
696
|
+
function normalizeWebhookEvent(event) {
|
|
697
|
+
const obj = event.data?.object ?? {};
|
|
698
|
+
const ref = findApplicationRef(obj);
|
|
699
|
+
switch (event.type) {
|
|
700
|
+
case "invoice.paid": {
|
|
701
|
+
const lines = obj.lines?.data ?? [];
|
|
702
|
+
const periodEnd = lines[0]?.period?.end;
|
|
703
|
+
const renewalAt = typeof periodEnd === "number" ? periodEnd * 1e3 : void 0;
|
|
704
|
+
const kind = obj.billing_reason === "subscription_create" ? "first_payment" : "renewal";
|
|
705
|
+
return { kind, ...ref, ...renewalAt !== void 0 ? { renewalAt } : {} };
|
|
706
|
+
}
|
|
707
|
+
case "charge.refunded":
|
|
708
|
+
return { kind: "refunded", ...ref };
|
|
709
|
+
case "customer.subscription.deleted":
|
|
710
|
+
return { kind: "canceled", ...ref };
|
|
711
|
+
default:
|
|
712
|
+
return { kind: "ignored", type: event.type };
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
function firstPaymentPatch(currentStatus, renewalAt) {
|
|
716
|
+
return {
|
|
717
|
+
...currentStatus === "submitted" ? { status: "paid_pending_vetting" } : {},
|
|
718
|
+
...renewalAt !== void 0 ? { renewalAt } : {}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function renewalPatch(renewalAt) {
|
|
722
|
+
return { renewalAt };
|
|
723
|
+
}
|
|
724
|
+
function refundedPatch() {
|
|
725
|
+
return { status: "refunded" };
|
|
726
|
+
}
|
|
727
|
+
function canceledPatch() {
|
|
728
|
+
return { canceled: true };
|
|
729
|
+
}
|
|
640
730
|
|
|
641
731
|
// src/network.ts
|
|
642
732
|
var import_crm3 = require("@odla-ai/crm");
|
|
@@ -666,4 +756,141 @@ async function projectSharedRecord(deps, person) {
|
|
|
666
756
|
const created = await (0, import_crm3.createRecord)(crmDeps, { type: "person", input, mutationId: `share:${person.hubRecordId}` });
|
|
667
757
|
return { recordId: created.id };
|
|
668
758
|
}
|
|
759
|
+
|
|
760
|
+
// src/session.ts
|
|
761
|
+
function applicationSummary(app) {
|
|
762
|
+
return {
|
|
763
|
+
id: app.id,
|
|
764
|
+
firstName: app.firstName ?? null,
|
|
765
|
+
lastName: app.lastName ?? null,
|
|
766
|
+
email: app.email ?? null,
|
|
767
|
+
status: app.status,
|
|
768
|
+
createdAt: app.createdAt ?? null,
|
|
769
|
+
meetingLink: app.meetingLink ?? null,
|
|
770
|
+
paid: Boolean(app.stripeSubscriptionId) && app.status !== "refunded",
|
|
771
|
+
renewalAt: app.renewalAt ?? null,
|
|
772
|
+
canceled: app.canceled === true
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function memberApplication(app, meeting, defaultTimezone) {
|
|
776
|
+
const summary = applicationSummary(app);
|
|
777
|
+
let meetingAt = app.meetingAt ?? null;
|
|
778
|
+
let meetUrl = null;
|
|
779
|
+
let timezone = defaultTimezone;
|
|
780
|
+
if (meeting) {
|
|
781
|
+
timezone = meeting.timezone ?? timezone;
|
|
782
|
+
if (meeting.status === "scheduled") {
|
|
783
|
+
meetingAt = meeting.startAt ?? null;
|
|
784
|
+
meetUrl = meeting.meetUrl ?? null;
|
|
785
|
+
} else {
|
|
786
|
+
meetingAt = null;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return { ...summary, meetingAt, meetUrl, timezone };
|
|
790
|
+
}
|
|
791
|
+
function memberSession(user, opts) {
|
|
792
|
+
return {
|
|
793
|
+
userId: user.userId,
|
|
794
|
+
email: user.email ?? null,
|
|
795
|
+
role: user.role,
|
|
796
|
+
superAdmin: opts.superAdmin,
|
|
797
|
+
application: opts.application
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/scheduling.ts
|
|
802
|
+
var SCHEDULING_DEFAULTS = {
|
|
803
|
+
slotMinutes: 45,
|
|
804
|
+
days: [1, 2, 3, 4, 5],
|
|
805
|
+
startHour: 9,
|
|
806
|
+
endHour: 17,
|
|
807
|
+
timezone: "America/Los_Angeles",
|
|
808
|
+
minNoticeHours: 24,
|
|
809
|
+
windowDays: 14,
|
|
810
|
+
summaryTemplate: "Introduction call with {{firstName}} {{lastName}}"
|
|
811
|
+
};
|
|
812
|
+
function isValidTimeZone(tz) {
|
|
813
|
+
try {
|
|
814
|
+
new Intl.DateTimeFormat(void 0, { timeZone: tz });
|
|
815
|
+
return true;
|
|
816
|
+
} catch {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
function resolveScheduling(config) {
|
|
821
|
+
const d = config ?? {};
|
|
822
|
+
const c = {
|
|
823
|
+
slotMinutes: d.slotMinutes ?? SCHEDULING_DEFAULTS.slotMinutes,
|
|
824
|
+
days: d.days ?? SCHEDULING_DEFAULTS.days,
|
|
825
|
+
startHour: d.startHour ?? SCHEDULING_DEFAULTS.startHour,
|
|
826
|
+
endHour: d.endHour ?? SCHEDULING_DEFAULTS.endHour,
|
|
827
|
+
timezone: d.timezone ?? SCHEDULING_DEFAULTS.timezone,
|
|
828
|
+
minNoticeHours: d.minNoticeHours ?? SCHEDULING_DEFAULTS.minNoticeHours,
|
|
829
|
+
windowDays: d.windowDays ?? SCHEDULING_DEFAULTS.windowDays,
|
|
830
|
+
summaryTemplate: d.summaryTemplate ?? SCHEDULING_DEFAULTS.summaryTemplate
|
|
831
|
+
};
|
|
832
|
+
const fail = (msg) => {
|
|
833
|
+
throw new Error(`scheduling: ${msg}`);
|
|
834
|
+
};
|
|
835
|
+
if (!(c.slotMinutes >= 15 && c.slotMinutes <= 240)) fail("slotMinutes must be 15\u2013240");
|
|
836
|
+
if (!(c.windowDays >= 1 && c.windowDays <= 62)) fail("windowDays must be 1\u201362 (FreeBusy caps at 62)");
|
|
837
|
+
if (!(c.minNoticeHours >= 0 && c.minNoticeHours <= 336)) fail("minNoticeHours must be 0\u2013336");
|
|
838
|
+
if (!(c.startHour >= 0 && c.startHour < c.endHour && c.endHour <= 24)) fail("require 0 \u2264 startHour < endHour \u2264 24");
|
|
839
|
+
const days = [...c.days];
|
|
840
|
+
if (!days.length || !days.every((n) => Number.isInteger(n) && n >= 0 && n <= 6)) {
|
|
841
|
+
fail("days must be a non-empty list of weekday integers 0\u20136");
|
|
842
|
+
}
|
|
843
|
+
if (typeof c.timezone !== "string" || !isValidTimeZone(c.timezone)) fail(`invalid IANA timezone "${c.timezone}"`);
|
|
844
|
+
if (typeof c.summaryTemplate !== "string") fail("summaryTemplate must be a string");
|
|
845
|
+
return { ...c, days };
|
|
846
|
+
}
|
|
847
|
+
var BOOKABLE_STATUSES = ["submitted", "paid_pending_vetting", "call_scheduled"];
|
|
848
|
+
function canBookFrom(status) {
|
|
849
|
+
return BOOKABLE_STATUSES.includes(status);
|
|
850
|
+
}
|
|
851
|
+
function slotWindow(now, windowDays) {
|
|
852
|
+
return { from: now, to: now + windowDays * 864e5 };
|
|
853
|
+
}
|
|
854
|
+
function endForSlot(startAt, slotMinutes) {
|
|
855
|
+
return startAt + slotMinutes * 6e4;
|
|
856
|
+
}
|
|
857
|
+
function isSlotAvailable(slots, startAt) {
|
|
858
|
+
return slots.some((s) => s.startAt === startAt);
|
|
859
|
+
}
|
|
860
|
+
function renderSummary(template, app) {
|
|
861
|
+
return template.replace("{{firstName}}", app.firstName ?? "").replace("{{lastName}}", app.lastName ?? "");
|
|
862
|
+
}
|
|
863
|
+
function bookingDecision(existing) {
|
|
864
|
+
const eventId = existing?.googleEventId ?? null;
|
|
865
|
+
return { reschedule: Boolean(eventId), eventId };
|
|
866
|
+
}
|
|
867
|
+
function introIdempotencyKey(applicationId) {
|
|
868
|
+
return `application:${applicationId}:intro`;
|
|
869
|
+
}
|
|
870
|
+
function meetingCreateRow(i) {
|
|
871
|
+
return {
|
|
872
|
+
id: i.meetingId,
|
|
873
|
+
applicationId: i.applicationId,
|
|
874
|
+
groupId: i.groupId,
|
|
875
|
+
startAt: i.startAt,
|
|
876
|
+
endAt: i.endAt,
|
|
877
|
+
timezone: i.timezone,
|
|
878
|
+
status: "scheduled",
|
|
879
|
+
googleEventId: i.googleEventId,
|
|
880
|
+
...i.meetUrl ? { meetUrl: i.meetUrl } : {},
|
|
881
|
+
...i.htmlLink ? { htmlLink: i.htmlLink } : {},
|
|
882
|
+
drift: "none",
|
|
883
|
+
createdAt: i.createdAt
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
function meetingRescheduleUpdate(startAt, endAt) {
|
|
887
|
+
return { startAt, endAt, drift: "none" };
|
|
888
|
+
}
|
|
889
|
+
function applicationBookingUpdate(currentStatus, startAt, htmlLink) {
|
|
890
|
+
return {
|
|
891
|
+
meetingAt: startAt,
|
|
892
|
+
...htmlLink ? { meetingLink: htmlLink } : {},
|
|
893
|
+
...currentStatus !== "call_scheduled" ? { status: "call_scheduled" } : {}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
669
896
|
//# sourceMappingURL=index.cjs.map
|