@odla-ai/chapter 0.39.0 → 0.39.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 +49 -0
- package/dist/{chunk-SI3JHSEX.js → chunk-4K2Q5JBC.js} +660 -553
- package/dist/chunk-4K2Q5JBC.js.map +1 -0
- package/dist/{copy-context-Pbs3keLx.d.ts → copy-context-Buz_27Ed.d.ts} +26 -0
- package/dist/index.cjs +457 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +196 -2
- package/dist/index.d.ts +196 -2
- package/dist/index.js +457 -130
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +106 -3
- package/dist/ui/admin/index.js +5 -1
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +5 -1
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/worker/index.cjs +587 -108
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +127 -1
- package/dist/worker/index.d.ts +127 -1
- package/dist/worker/index.js +583 -104
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/runbooks/greenfield.md +15 -0
- package/dist/chunk-SI3JHSEX.js.map +0 -1
package/dist/worker/index.js
CHANGED
|
@@ -516,9 +516,9 @@ function canonicalValue(value) {
|
|
|
516
516
|
}
|
|
517
517
|
if (Array.isArray(value)) return `[${value.map(canonicalValue).join(",")}]`;
|
|
518
518
|
if (typeof value !== "object") throw new TypeError("signup control contains an unsupported value");
|
|
519
|
-
const
|
|
520
|
-
const entries = Object.keys(
|
|
521
|
-
const item =
|
|
519
|
+
const record4 = value;
|
|
520
|
+
const entries = Object.keys(record4).sort().flatMap((key) => {
|
|
521
|
+
const item = record4[key];
|
|
522
522
|
return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue(item)}`];
|
|
523
523
|
});
|
|
524
524
|
return `{${entries.join(",")}}`;
|
|
@@ -718,8 +718,8 @@ var APPLICATION_LABELS = {
|
|
|
718
718
|
state: "State"
|
|
719
719
|
};
|
|
720
720
|
function humanize(id) {
|
|
721
|
-
const
|
|
722
|
-
return
|
|
721
|
+
const text4 = id.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
722
|
+
return text4 ? `${text4[0].toUpperCase()}${text4.slice(1)}` : id;
|
|
723
723
|
}
|
|
724
724
|
function applicationDefs(chapter) {
|
|
725
725
|
try {
|
|
@@ -910,8 +910,8 @@ async function clerkListUsers(secretKey, fetchImpl = fetch) {
|
|
|
910
910
|
const data = await clerkGet(`/v1/users?limit=${PAGE}&offset=${offset}`, secretKey, fetchImpl);
|
|
911
911
|
const page = Array.isArray(data) ? data : [];
|
|
912
912
|
for (const u of page) {
|
|
913
|
-
const
|
|
914
|
-
if (
|
|
913
|
+
const record4 = toRecord(u);
|
|
914
|
+
if (record4) out.push(record4);
|
|
915
915
|
}
|
|
916
916
|
if (page.length < PAGE) break;
|
|
917
917
|
}
|
|
@@ -973,34 +973,34 @@ function networkSourceTag(type, hubRecordId, sourceId) {
|
|
|
973
973
|
if (readable && raw.length <= 64) return raw;
|
|
974
974
|
return `network:${typeKey.slice(0, 16)}:${shortHash(`${sourceId ?? ""}\0${type}\0${hubRecordId}`)}`;
|
|
975
975
|
}
|
|
976
|
-
function normalizeSharedRecord(
|
|
977
|
-
if ("input" in
|
|
976
|
+
function normalizeSharedRecord(record4, fallbackSourceId = "legacy-source") {
|
|
977
|
+
if ("input" in record4 && record4.version === 2) {
|
|
978
978
|
return {
|
|
979
979
|
version: 2,
|
|
980
|
-
sourceId:
|
|
981
|
-
sourceRecordId:
|
|
982
|
-
type:
|
|
983
|
-
input:
|
|
980
|
+
sourceId: record4.source.siteId,
|
|
981
|
+
sourceRecordId: record4.source.recordId,
|
|
982
|
+
type: record4.type,
|
|
983
|
+
input: record4.input
|
|
984
984
|
};
|
|
985
985
|
}
|
|
986
|
-
if ("input" in
|
|
986
|
+
if ("input" in record4) {
|
|
987
987
|
return {
|
|
988
988
|
version: 1,
|
|
989
989
|
sourceId: fallbackSourceId,
|
|
990
|
-
sourceRecordId:
|
|
991
|
-
type:
|
|
992
|
-
input:
|
|
990
|
+
sourceRecordId: record4.hubRecordId,
|
|
991
|
+
type: record4.type,
|
|
992
|
+
input: record4.input
|
|
993
993
|
};
|
|
994
994
|
}
|
|
995
|
-
if ("type" in
|
|
996
|
-
const input = { name:
|
|
995
|
+
if ("type" in record4 && record4.type === "company") {
|
|
996
|
+
const input = { name: record4.name };
|
|
997
997
|
for (const key of ["domain", "industry", "location", "linkedin", "notes"]) {
|
|
998
|
-
if (
|
|
998
|
+
if (record4[key]) input[key] = record4[key];
|
|
999
999
|
}
|
|
1000
1000
|
return {
|
|
1001
1001
|
version: 1,
|
|
1002
1002
|
sourceId: fallbackSourceId,
|
|
1003
|
-
sourceRecordId:
|
|
1003
|
+
sourceRecordId: record4.hubRecordId,
|
|
1004
1004
|
type: "company",
|
|
1005
1005
|
input
|
|
1006
1006
|
};
|
|
@@ -1008,28 +1008,28 @@ function normalizeSharedRecord(record3, fallbackSourceId = "legacy-source") {
|
|
|
1008
1008
|
return {
|
|
1009
1009
|
version: 1,
|
|
1010
1010
|
sourceId: fallbackSourceId,
|
|
1011
|
-
sourceRecordId:
|
|
1011
|
+
sourceRecordId: record4.hubRecordId,
|
|
1012
1012
|
type: "person",
|
|
1013
|
-
input: sharedPersonInput(
|
|
1013
|
+
input: sharedPersonInput(record4)
|
|
1014
1014
|
};
|
|
1015
1015
|
}
|
|
1016
|
-
function sharedRecordFromCrm(crm,
|
|
1017
|
-
if (target.fields && !target.fields[
|
|
1018
|
-
throw new Error(`${target.name} does not accept "${
|
|
1016
|
+
function sharedRecordFromCrm(crm, record4, target, sourceId) {
|
|
1017
|
+
if (target.fields && !target.fields[record4.type]) {
|
|
1018
|
+
throw new Error(`${target.name} does not accept "${record4.type}" records`);
|
|
1019
1019
|
}
|
|
1020
|
-
const def = crm.type(
|
|
1021
|
-
const fields = target.fields?.[
|
|
1020
|
+
const def = crm.type(record4.type);
|
|
1021
|
+
const fields = target.fields?.[record4.type] ?? DEFAULT_SHARE_FIELDS[record4.type];
|
|
1022
1022
|
if (!fields) {
|
|
1023
|
-
throw new Error(`${target.name} requires an explicit field allowlist for "${
|
|
1023
|
+
throw new Error(`${target.name} requires an explicit field allowlist for "${record4.type}" records`);
|
|
1024
1024
|
}
|
|
1025
1025
|
const nameField = def.nameField ?? "name";
|
|
1026
1026
|
const input = {};
|
|
1027
1027
|
for (const field of /* @__PURE__ */ new Set([nameField, ...fields])) {
|
|
1028
|
-
const value =
|
|
1028
|
+
const value = record4.fields?.[field];
|
|
1029
1029
|
if (value !== void 0) input[field] = value;
|
|
1030
1030
|
}
|
|
1031
|
-
if (input[nameField] === void 0) input[nameField] =
|
|
1032
|
-
return sourceId ? { version: 2, source: { siteId: sourceId, recordId:
|
|
1031
|
+
if (input[nameField] === void 0) input[nameField] = record4.name;
|
|
1032
|
+
return sourceId ? { version: 2, source: { siteId: sourceId, recordId: record4.id }, type: record4.type, input } : { version: 1, type: record4.type, hubRecordId: record4.id, input };
|
|
1033
1033
|
}
|
|
1034
1034
|
async function upsertPerson(deps, opts) {
|
|
1035
1035
|
const email = opts.email.toLowerCase();
|
|
@@ -1043,11 +1043,11 @@ async function upsertPerson(deps, opts) {
|
|
|
1043
1043
|
const created = await createRecord(crmDeps3, { type: "person", input: opts.input, mutationId: opts.mutationId });
|
|
1044
1044
|
return { recordId: created.id };
|
|
1045
1045
|
}
|
|
1046
|
-
async function findSharedRecord(deps,
|
|
1046
|
+
async function findSharedRecord(deps, record4, tag) {
|
|
1047
1047
|
const structured = await getRecordByOrigin(
|
|
1048
1048
|
{ crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId },
|
|
1049
|
-
|
|
1050
|
-
|
|
1049
|
+
record4.sourceId,
|
|
1050
|
+
record4.sourceRecordId
|
|
1051
1051
|
);
|
|
1052
1052
|
if (structured) return structured;
|
|
1053
1053
|
const mapped = await deps.db.query({ crm_tag: { $: { where: { tag }, limit: 1 } } });
|
|
@@ -1056,53 +1056,53 @@ async function findSharedRecord(deps, record3, tag) {
|
|
|
1056
1056
|
const found = await deps.db.query({ crm_record: { $: { where: { id: mappedId }, limit: 1 } } });
|
|
1057
1057
|
if (found.crm_record?.[0]) return found.crm_record[0];
|
|
1058
1058
|
}
|
|
1059
|
-
const def = deps.crm.type(
|
|
1059
|
+
const def = deps.crm.type(record4.type);
|
|
1060
1060
|
const emailField = def.emailField;
|
|
1061
|
-
if (emailField && typeof
|
|
1062
|
-
const primaryEmail =
|
|
1061
|
+
if (emailField && typeof record4.input[emailField] === "string") {
|
|
1062
|
+
const primaryEmail = record4.input[emailField].toLowerCase();
|
|
1063
1063
|
const found = await deps.db.query({
|
|
1064
|
-
crm_record: { $: { where: { type:
|
|
1064
|
+
crm_record: { $: { where: { type: record4.type, primaryEmail }, limit: 1 } }
|
|
1065
1065
|
});
|
|
1066
1066
|
if (found.crm_record?.[0]) return found.crm_record[0];
|
|
1067
1067
|
}
|
|
1068
|
-
const domain =
|
|
1068
|
+
const domain = record4.input.domain;
|
|
1069
1069
|
const domainSlot = def.fields.domain?.slot;
|
|
1070
1070
|
if (typeof domain === "string" && domainSlot) {
|
|
1071
1071
|
const found = await deps.db.query({
|
|
1072
|
-
crm_record: { $: { where: { type:
|
|
1072
|
+
crm_record: { $: { where: { type: record4.type, [domainSlot]: domain }, limit: 1 } }
|
|
1073
1073
|
});
|
|
1074
1074
|
if (found.crm_record?.[0]) return found.crm_record[0];
|
|
1075
1075
|
}
|
|
1076
1076
|
const nameField = def.nameField ?? "name";
|
|
1077
|
-
const name =
|
|
1078
|
-
if (
|
|
1077
|
+
const name = record4.input[nameField];
|
|
1078
|
+
if (record4.type === "company" && typeof name === "string" && name.trim()) {
|
|
1079
1079
|
const found = await deps.db.query({
|
|
1080
|
-
crm_record: { $: { where: { type:
|
|
1080
|
+
crm_record: { $: { where: { type: record4.type, name: name.trim() }, limit: 1 } }
|
|
1081
1081
|
});
|
|
1082
1082
|
if (found.crm_record?.[0]) return found.crm_record[0];
|
|
1083
1083
|
}
|
|
1084
1084
|
return void 0;
|
|
1085
1085
|
}
|
|
1086
1086
|
async function projectSharedRecord(deps, shared, options = {}) {
|
|
1087
|
-
const
|
|
1088
|
-
if (!
|
|
1087
|
+
const record4 = normalizeSharedRecord(shared, options.sourceId);
|
|
1088
|
+
if (!record4.type.trim() || !record4.sourceId.trim() || !record4.sourceRecordId.trim()) {
|
|
1089
1089
|
throw new Error("type, source site id, and source record id must be non-empty");
|
|
1090
1090
|
}
|
|
1091
|
-
if (options.sourceId && options.sourceId !==
|
|
1091
|
+
if (options.sourceId && options.sourceId !== record4.sourceId) {
|
|
1092
1092
|
throw new Error("signed sender does not match payload source");
|
|
1093
1093
|
}
|
|
1094
|
-
const tag = networkSourceTag(
|
|
1094
|
+
const tag = networkSourceTag(record4.type, record4.sourceRecordId, record4.version === 2 ? record4.sourceId : void 0);
|
|
1095
1095
|
const crmDeps3 = { crm: deps.crm, db: deps.db, now: deps.now, newId: deps.newId };
|
|
1096
|
-
const existing = await findSharedRecord(deps,
|
|
1096
|
+
const existing = await findSharedRecord(deps, record4, tag);
|
|
1097
1097
|
let recordId;
|
|
1098
1098
|
if (existing && typeof existing.id === "string") {
|
|
1099
|
-
await updateRecord(crmDeps3, { id: existing.id, input:
|
|
1099
|
+
await updateRecord(crmDeps3, { id: existing.id, input: record4.input });
|
|
1100
1100
|
recordId = existing.id;
|
|
1101
1101
|
} else {
|
|
1102
|
-
recordId = `network_${shortHash(`${
|
|
1102
|
+
recordId = `network_${shortHash(`${record4.sourceId}\0${record4.type}\0${record4.sourceRecordId}`)}`;
|
|
1103
1103
|
await createRecord({ ...crmDeps3, newId: () => recordId }, {
|
|
1104
|
-
type:
|
|
1105
|
-
input:
|
|
1104
|
+
type: record4.type,
|
|
1105
|
+
input: record4.input,
|
|
1106
1106
|
mutationId: `share-create:${tag}`
|
|
1107
1107
|
});
|
|
1108
1108
|
}
|
|
@@ -1112,10 +1112,10 @@ async function projectSharedRecord(deps, shared, options = {}) {
|
|
|
1112
1112
|
);
|
|
1113
1113
|
await upsertRecordOrigin(crmDeps3, {
|
|
1114
1114
|
recordId,
|
|
1115
|
-
sourceId:
|
|
1116
|
-
sourceRecordId:
|
|
1115
|
+
sourceId: record4.sourceId,
|
|
1116
|
+
sourceRecordId: record4.sourceRecordId,
|
|
1117
1117
|
...options.sourceUrl ? { sourceUrl: options.sourceUrl } : {},
|
|
1118
|
-
payloadVersion:
|
|
1118
|
+
payloadVersion: record4.version
|
|
1119
1119
|
});
|
|
1120
1120
|
return { recordId };
|
|
1121
1121
|
}
|
|
@@ -1254,10 +1254,10 @@ function planDelivery(input) {
|
|
|
1254
1254
|
const transport = !isProd && !redirect ? "log-only" : input.cloudflareReady ? "cloudflare" : "log-only";
|
|
1255
1255
|
const to = redirect ? input.group.debugEmail : input.to;
|
|
1256
1256
|
const subject = (redirect ? "[dev] " : "") + render(tpl.subject, vars);
|
|
1257
|
-
const
|
|
1257
|
+
const text4 = redirect ? `(dev redirect; original recipient: ${input.to})
|
|
1258
1258
|
|
|
1259
1259
|
` + render(tpl.text, vars) : render(tpl.text, vars);
|
|
1260
|
-
return { deliver: true, transport, to, subject, text:
|
|
1260
|
+
return { deliver: true, transport, to, subject, text: text4, redirected: redirect };
|
|
1261
1261
|
}
|
|
1262
1262
|
|
|
1263
1263
|
// src/notify.ts
|
|
@@ -2040,8 +2040,8 @@ var handleNetworkShared = async (req, url, env, ctx) => {
|
|
|
2040
2040
|
payload,
|
|
2041
2041
|
sourceId ? { sourceId } : {}
|
|
2042
2042
|
);
|
|
2043
|
-
const
|
|
2044
|
-
return json({ recordId, type:
|
|
2043
|
+
const record4 = normalizeSharedRecord(payload, sourceId);
|
|
2044
|
+
return json({ recordId, type: record4.type });
|
|
2045
2045
|
} catch (error) {
|
|
2046
2046
|
return json({
|
|
2047
2047
|
error: error instanceof Error ? error.message : "invalid shared record"
|
|
@@ -2095,10 +2095,10 @@ function boundedString(value, path, errors, max, required = false) {
|
|
|
2095
2095
|
errors.push(`${path} must be a string`);
|
|
2096
2096
|
return void 0;
|
|
2097
2097
|
}
|
|
2098
|
-
const
|
|
2099
|
-
if (required && !
|
|
2100
|
-
if (
|
|
2101
|
-
return
|
|
2098
|
+
const text4 = value.trim();
|
|
2099
|
+
if (required && !text4) errors.push(`${path} is required`);
|
|
2100
|
+
if (text4.length > max) errors.push(`${path} must be at most ${max.toLocaleString("en-US")} characters`);
|
|
2101
|
+
return text4;
|
|
2102
2102
|
}
|
|
2103
2103
|
function validateMembershipPageDocument(input, options = {}) {
|
|
2104
2104
|
const errors = [];
|
|
@@ -2147,8 +2147,8 @@ function validateMembershipPageDocument(input, options = {}) {
|
|
|
2147
2147
|
else {
|
|
2148
2148
|
if (item.benefits.length > MAX_BENEFITS) errors.push(`${path}.benefits supports at most ${MAX_BENEFITS} entries`);
|
|
2149
2149
|
benefits = item.benefits.slice(0, MAX_BENEFITS).flatMap((benefit, benefitIndex) => {
|
|
2150
|
-
const
|
|
2151
|
-
return
|
|
2150
|
+
const text4 = boundedString(benefit, `${path}.benefits[${benefitIndex}]`, errors, 2e3, true);
|
|
2151
|
+
return text4 ? [text4] : [];
|
|
2152
2152
|
});
|
|
2153
2153
|
}
|
|
2154
2154
|
}
|
|
@@ -2478,9 +2478,9 @@ function canonicalValue2(value) {
|
|
|
2478
2478
|
}
|
|
2479
2479
|
if (Array.isArray(value)) return `[${value.map(canonicalValue2).join(",")}]`;
|
|
2480
2480
|
if (typeof value !== "object") throw new TypeError("member content contains an unsupported value");
|
|
2481
|
-
const
|
|
2482
|
-
const entries = Object.keys(
|
|
2483
|
-
const item =
|
|
2481
|
+
const record4 = value;
|
|
2482
|
+
const entries = Object.keys(record4).sort().flatMap((key) => {
|
|
2483
|
+
const item = record4[key];
|
|
2484
2484
|
return item === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue2(item)}`];
|
|
2485
2485
|
});
|
|
2486
2486
|
return `{${entries.join(",")}}`;
|
|
@@ -2910,9 +2910,9 @@ var handleMemberContentControl = async (req, url, env, ctx) => {
|
|
|
2910
2910
|
tiers
|
|
2911
2911
|
});
|
|
2912
2912
|
if (!checked.ok) return reject2("invalid_document", 400, checked.errors);
|
|
2913
|
-
const
|
|
2914
|
-
if (
|
|
2915
|
-
const localCheck = validateMemberContentDocument(
|
|
2913
|
+
const local3 = await loadLocalMemberContent(db, ctx.chapter.id, env.ODLA_ENV);
|
|
2914
|
+
if (local3) {
|
|
2915
|
+
const localCheck = validateMemberContentDocument(local3.document, {
|
|
2916
2916
|
sourceType: "chapter",
|
|
2917
2917
|
sourceAppId: ctx.chapter.id,
|
|
2918
2918
|
tiers,
|
|
@@ -3936,8 +3936,8 @@ async function startSubscription(req, env, ctx) {
|
|
|
3936
3936
|
const secretKey = await getVaultSecret(db, "stripe_secret_key");
|
|
3937
3937
|
if (!group || !secretKey) return json({ error: "payments not configured" }, 503);
|
|
3938
3938
|
if (typeof app.giftOfferId === "string") {
|
|
3939
|
-
const
|
|
3940
|
-
const offer = findGiftOffer(
|
|
3939
|
+
const control3 = await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV);
|
|
3940
|
+
const offer = findGiftOffer(control3?.document.giftOffers, app.giftOfferId, Date.now());
|
|
3941
3941
|
if (!offer || typeof group.stripePublishableKey !== "string" || !group.stripePublishableKey) {
|
|
3942
3942
|
return json({ error: "gift offer is unavailable" }, 409);
|
|
3943
3943
|
}
|
|
@@ -4102,14 +4102,14 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4102
4102
|
if (req.method === "GET" && url.pathname === "/api/gifts") {
|
|
4103
4103
|
const user = await userFor(req, env, ctx);
|
|
4104
4104
|
if (!user) return json({ error: "unauthorized" }, 401);
|
|
4105
|
-
const
|
|
4105
|
+
const control3 = await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV);
|
|
4106
4106
|
const found = await db.query({ giftClaims: { $: { where: {
|
|
4107
4107
|
purchaserEmailNormalized: normalizeChapterEmail(user.email),
|
|
4108
4108
|
chapterId: ctx.chapter.id
|
|
4109
4109
|
}, limit: 200 } } });
|
|
4110
4110
|
const group = first3((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4111
4111
|
return json({
|
|
4112
|
-
offers: publicGiftOffers(
|
|
4112
|
+
offers: publicGiftOffers(control3?.document.giftOffers, Date.now()),
|
|
4113
4113
|
claims: buyerGiftClaims(rows2(found.giftClaims).sort((a, b) => Number(b.createdAt) - Number(a.createdAt)), Date.now()),
|
|
4114
4114
|
refundPolicyText: typeof group?.refundPolicyText === "string" ? group.refundPolicyText : ""
|
|
4115
4115
|
});
|
|
@@ -4125,8 +4125,8 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4125
4125
|
}
|
|
4126
4126
|
const purchaseId = typeof body.purchaseId === "string" ? body.purchaseId : "";
|
|
4127
4127
|
if (!/^[A-Za-z0-9_-]{16,128}$/.test(purchaseId)) return json({ error: "purchaseId is invalid" }, 400);
|
|
4128
|
-
const
|
|
4129
|
-
const offer = findGiftOffer(
|
|
4128
|
+
const control3 = await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV);
|
|
4129
|
+
const offer = findGiftOffer(control3?.document.giftOffers, typeof body.offerId === "string" ? body.offerId : "", Date.now());
|
|
4130
4130
|
if (!offer) return json({ error: "gift offer is unavailable" }, 409);
|
|
4131
4131
|
const group = first3((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4132
4132
|
const stripeSecret = await getVaultSecret(db, "stripe_secret_key");
|
|
@@ -4958,11 +4958,11 @@ var handleAdminGroupEmail = async (req, url, env, ctx) => {
|
|
|
4958
4958
|
for (const key of EMAIL_TEMPLATE_NAMES) {
|
|
4959
4959
|
const t = templates[key];
|
|
4960
4960
|
const subject = typeof t?.subject === "string" ? t.subject.trim() : "";
|
|
4961
|
-
const
|
|
4962
|
-
if (!subject || !
|
|
4961
|
+
const text4 = typeof t?.text === "string" ? t.text : "";
|
|
4962
|
+
if (!subject || !text4.trim()) return json({ error: `template "${key}" needs a subject and a body` }, 400);
|
|
4963
4963
|
if (/[\r\n]/.test(subject) || subject.length > 200) return json({ error: `template "${key}" subject must be a single line under 200 characters` }, 400);
|
|
4964
|
-
if (
|
|
4965
|
-
clean[key] = { subject, text:
|
|
4964
|
+
if (text4.length > 1e4) return json({ error: `template "${key}" body is too long` }, 400);
|
|
4965
|
+
clean[key] = { subject, text: text4, enabled: t?.enabled !== false };
|
|
4966
4966
|
}
|
|
4967
4967
|
const emailish = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
4968
4968
|
const notificationEmail = str2(body.notificationEmail).trim();
|
|
@@ -5082,7 +5082,7 @@ function fetchNetworkTarget(env, target, destination, init) {
|
|
|
5082
5082
|
// src/worker-network-push.ts
|
|
5083
5083
|
import { recordDeliveryAttempt } from "@odla-ai/crm";
|
|
5084
5084
|
import { signFederatedRequest } from "@odla-ai/db";
|
|
5085
|
-
async function pushNetworkRecord(db, env, ctx, target,
|
|
5085
|
+
async function pushNetworkRecord(db, env, ctx, target, record4) {
|
|
5086
5086
|
const secret = await getVaultSecret(db, target.secretName);
|
|
5087
5087
|
const crmDeps3 = {
|
|
5088
5088
|
crm: ctx.chapter.crm,
|
|
@@ -5093,7 +5093,7 @@ async function pushNetworkRecord(db, env, ctx, target, record3) {
|
|
|
5093
5093
|
if (!secret) {
|
|
5094
5094
|
const error = `vault secret "${target.secretName}" is missing`;
|
|
5095
5095
|
await recordDeliveryAttempt(crmDeps3, {
|
|
5096
|
-
recordId:
|
|
5096
|
+
recordId: record4.id,
|
|
5097
5097
|
targetId: target.id,
|
|
5098
5098
|
status: "failed",
|
|
5099
5099
|
payloadVersion: 2,
|
|
@@ -5103,7 +5103,7 @@ async function pushNetworkRecord(db, env, ctx, target, record3) {
|
|
|
5103
5103
|
}
|
|
5104
5104
|
let payload;
|
|
5105
5105
|
try {
|
|
5106
|
-
payload = sharedRecordFromCrm(ctx.chapter.crm,
|
|
5106
|
+
payload = sharedRecordFromCrm(ctx.chapter.crm, record4, target, ctx.chapter.id);
|
|
5107
5107
|
} catch (err) {
|
|
5108
5108
|
return {
|
|
5109
5109
|
id: target.id,
|
|
@@ -5132,7 +5132,7 @@ async function pushNetworkRecord(db, env, ctx, target, record3) {
|
|
|
5132
5132
|
if (!res.ok) {
|
|
5133
5133
|
const error = responseBody.error ?? "follower rejected the record";
|
|
5134
5134
|
await recordDeliveryAttempt(crmDeps3, {
|
|
5135
|
-
recordId:
|
|
5135
|
+
recordId: record4.id,
|
|
5136
5136
|
targetId: target.id,
|
|
5137
5137
|
status: "failed",
|
|
5138
5138
|
payloadVersion: 2,
|
|
@@ -5147,7 +5147,7 @@ async function pushNetworkRecord(db, env, ctx, target, record3) {
|
|
|
5147
5147
|
};
|
|
5148
5148
|
}
|
|
5149
5149
|
await recordDeliveryAttempt(crmDeps3, {
|
|
5150
|
-
recordId:
|
|
5150
|
+
recordId: record4.id,
|
|
5151
5151
|
targetId: target.id,
|
|
5152
5152
|
status: "delivered",
|
|
5153
5153
|
payloadVersion: 2,
|
|
@@ -5163,7 +5163,7 @@ async function pushNetworkRecord(db, env, ctx, target, record3) {
|
|
|
5163
5163
|
} catch (err) {
|
|
5164
5164
|
const error = err instanceof Error ? err.message : "delivery failed";
|
|
5165
5165
|
await recordDeliveryAttempt(crmDeps3, {
|
|
5166
|
-
recordId:
|
|
5166
|
+
recordId: record4.id,
|
|
5167
5167
|
targetId: target.id,
|
|
5168
5168
|
status: "failed",
|
|
5169
5169
|
payloadVersion: 2,
|
|
@@ -5301,10 +5301,10 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
|
|
|
5301
5301
|
if (requested.size !== body.targetIds.length) return json({ error: "targetIds must contain unique strings" }, 400);
|
|
5302
5302
|
const targets = ctx.chapter.network.targets.filter((target) => requested.has(target.id));
|
|
5303
5303
|
if (targets.length !== requested.size) return json({ error: "one or more targetIds are not configured" }, 400);
|
|
5304
|
-
const
|
|
5305
|
-
if (!
|
|
5304
|
+
const record4 = await getRecord({ crm: ctx.chapter.crm, db: got.db }, body.recordId);
|
|
5305
|
+
if (!record4) return json({ error: "record not found" }, 404);
|
|
5306
5306
|
const results = await Promise.all(
|
|
5307
|
-
targets.map((target) => pushNetworkRecord(got.db, env, ctx, target,
|
|
5307
|
+
targets.map((target) => pushNetworkRecord(got.db, env, ctx, target, record4))
|
|
5308
5308
|
);
|
|
5309
5309
|
return json({ ok: results.every((result) => result.ok), results });
|
|
5310
5310
|
};
|
|
@@ -5329,18 +5329,18 @@ function integer(value, fallback, min, max) {
|
|
|
5329
5329
|
const parsed = Number(value);
|
|
5330
5330
|
return Number.isSafeInteger(parsed) && parsed >= min && parsed <= max ? parsed : null;
|
|
5331
5331
|
}
|
|
5332
|
-
function projectRecord(
|
|
5332
|
+
function projectRecord(record4, allowed2) {
|
|
5333
5333
|
const fields = Object.fromEntries(
|
|
5334
|
-
allowed2.flatMap((name) => Object.prototype.hasOwnProperty.call(
|
|
5334
|
+
allowed2.flatMap((name) => Object.prototype.hasOwnProperty.call(record4.fields, name) ? [[name, record4.fields[name]]] : [])
|
|
5335
5335
|
);
|
|
5336
5336
|
return {
|
|
5337
|
-
id:
|
|
5338
|
-
type:
|
|
5339
|
-
name:
|
|
5340
|
-
...
|
|
5337
|
+
id: record4.id,
|
|
5338
|
+
type: record4.type,
|
|
5339
|
+
name: record4.name,
|
|
5340
|
+
...record4.stage ? { stage: record4.stage } : {},
|
|
5341
5341
|
fields,
|
|
5342
|
-
createdAt:
|
|
5343
|
-
updatedAt:
|
|
5342
|
+
createdAt: record4.createdAt,
|
|
5343
|
+
updatedAt: record4.updatedAt
|
|
5344
5344
|
};
|
|
5345
5345
|
}
|
|
5346
5346
|
var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
@@ -5378,7 +5378,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
5378
5378
|
version: 1,
|
|
5379
5379
|
site: { id: ctx.chapter.id, name: ctx.chapter.name },
|
|
5380
5380
|
type,
|
|
5381
|
-
records: page.records.map((
|
|
5381
|
+
records: page.records.map((record4) => projectRecord(record4, allowed2)),
|
|
5382
5382
|
total: page.total,
|
|
5383
5383
|
limit: page.limit,
|
|
5384
5384
|
offset: page.offset
|
|
@@ -5603,8 +5603,8 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
|
|
|
5603
5603
|
if (!IDENTIFIER3.test(recordType) || !RECORD_ID2.test(recordId) || !reader?.sharedNotes.includes(recordType)) {
|
|
5604
5604
|
return json({ error: "shared notes are not enabled for this reader and record type" }, 403);
|
|
5605
5605
|
}
|
|
5606
|
-
const
|
|
5607
|
-
if (!
|
|
5606
|
+
const record4 = await getRecord2({ crm: ctx.chapter.crm, db }, recordId);
|
|
5607
|
+
if (!record4 || record4.type !== recordType) return json({ error: "record not found" }, 404);
|
|
5608
5608
|
if (req.method === "GET") {
|
|
5609
5609
|
return json({
|
|
5610
5610
|
version: 1,
|
|
@@ -5795,6 +5795,467 @@ var handleFormation = async (req, url, env, ctx) => {
|
|
|
5795
5795
|
}
|
|
5796
5796
|
};
|
|
5797
5797
|
|
|
5798
|
+
// src/worker-routes-builder-library.ts
|
|
5799
|
+
import { verifyFederatedRequest as verifyFederatedRequest8 } from "@odla-ai/db";
|
|
5800
|
+
|
|
5801
|
+
// src/builder-library-validation.ts
|
|
5802
|
+
var ID4 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
5803
|
+
var APP_ID = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
5804
|
+
var MAX_RESOURCES = 200;
|
|
5805
|
+
var MAX_MARKDOWN3 = 65536;
|
|
5806
|
+
var MAX_TEXT2 = 500;
|
|
5807
|
+
var MAX_TAGS = 20;
|
|
5808
|
+
var KINDS = /* @__PURE__ */ new Set(["document", "note", "link", "inspiration", "image", "file"]);
|
|
5809
|
+
var STATUSES = /* @__PURE__ */ new Set(["draft", "published", "archived"]);
|
|
5810
|
+
var ROOT_KEYS4 = /* @__PURE__ */ new Set(["schemaVersion", "revision", "generatedAt", "source", "target", "resources"]);
|
|
5811
|
+
var SOURCE_KEYS2 = /* @__PURE__ */ new Set(["type", "appId"]);
|
|
5812
|
+
var TARGET_KEYS2 = /* @__PURE__ */ new Set(["appId", "environment"]);
|
|
5813
|
+
var RESOURCE_KEYS = /* @__PURE__ */ new Set(["id", "kind", "title", "summary", "tags", "scope", "status", "revision", "bodyMarkdown", "url", "brandAsset", "provenance"]);
|
|
5814
|
+
var SCOPE_KEYS = /* @__PURE__ */ new Set(["kind", "chapterId"]);
|
|
5815
|
+
var ASSET_KEYS = /* @__PURE__ */ new Set(["bookId", "assetId", "filename"]);
|
|
5816
|
+
var PROVENANCE_KEYS = /* @__PURE__ */ new Set(["createdBy", "createdAt", "updatedBy", "updatedAt", "promotedFrom"]);
|
|
5817
|
+
var PROMOTION_KEYS = /* @__PURE__ */ new Set(["chapterId", "resourceId", "resourceRevision", "documentRevision", "promotedBy", "promotedAt"]);
|
|
5818
|
+
var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5819
|
+
function unknownKeys3(value, allowed2, path, errors) {
|
|
5820
|
+
for (const key of Object.keys(value)) if (!allowed2.has(key)) errors.push(`${path}.${key} is not supported`);
|
|
5821
|
+
}
|
|
5822
|
+
function text3(value, path, errors, max = MAX_TEXT2, required = true) {
|
|
5823
|
+
if (value === void 0 && !required) return void 0;
|
|
5824
|
+
if (typeof value !== "string" || required && !value.trim()) {
|
|
5825
|
+
errors.push(`${path} must be a non-empty string`);
|
|
5826
|
+
return void 0;
|
|
5827
|
+
}
|
|
5828
|
+
const result = value.trim();
|
|
5829
|
+
if (result.length > max) errors.push(`${path} must be at most ${max} characters`);
|
|
5830
|
+
return result;
|
|
5831
|
+
}
|
|
5832
|
+
function integer2(value, path, errors, minimum = 1) {
|
|
5833
|
+
if (!Number.isSafeInteger(value) || Number(value) < minimum) errors.push(`${path} must be an integer >= ${minimum}`);
|
|
5834
|
+
return Number(value);
|
|
5835
|
+
}
|
|
5836
|
+
function scopeValue(value, path, errors) {
|
|
5837
|
+
const raw = record3(value);
|
|
5838
|
+
if (!raw) {
|
|
5839
|
+
errors.push(`${path} must be an object`);
|
|
5840
|
+
return null;
|
|
5841
|
+
}
|
|
5842
|
+
unknownKeys3(raw, SCOPE_KEYS, path, errors);
|
|
5843
|
+
if (raw.kind === "global" || raw.kind === "built_not_found") {
|
|
5844
|
+
if (raw.chapterId !== void 0) errors.push(`${path}.chapterId is allowed only for chapter scope`);
|
|
5845
|
+
return { kind: raw.kind };
|
|
5846
|
+
}
|
|
5847
|
+
if (raw.kind === "chapter") {
|
|
5848
|
+
const chapterId = text3(raw.chapterId, `${path}.chapterId`, errors, 63) ?? "";
|
|
5849
|
+
if (chapterId && !APP_ID.test(chapterId)) errors.push(`${path}.chapterId must be a lowercase app slug`);
|
|
5850
|
+
return { kind: "chapter", chapterId };
|
|
5851
|
+
}
|
|
5852
|
+
errors.push(`${path}.kind must be chapter, built_not_found, or global`);
|
|
5853
|
+
return null;
|
|
5854
|
+
}
|
|
5855
|
+
function resourceValue(value, path, errors) {
|
|
5856
|
+
const raw = record3(value);
|
|
5857
|
+
if (!raw) {
|
|
5858
|
+
errors.push(`${path} must be an object`);
|
|
5859
|
+
return null;
|
|
5860
|
+
}
|
|
5861
|
+
unknownKeys3(raw, RESOURCE_KEYS, path, errors);
|
|
5862
|
+
const id = text3(raw.id, `${path}.id`, errors, 80) ?? "";
|
|
5863
|
+
if (id && !ID4.test(id)) errors.push(`${path}.id must be lowercase kebab-case`);
|
|
5864
|
+
const kind = text3(raw.kind, `${path}.kind`, errors) ?? "";
|
|
5865
|
+
if (!KINDS.has(kind)) errors.push(`${path}.kind is unsupported`);
|
|
5866
|
+
const title = text3(raw.title, `${path}.title`, errors, 240) ?? "";
|
|
5867
|
+
const summary = text3(raw.summary, `${path}.summary`, errors, 1e3, false);
|
|
5868
|
+
const status = text3(raw.status, `${path}.status`, errors) ?? "";
|
|
5869
|
+
if (!STATUSES.has(status)) errors.push(`${path}.status must be draft, published, or archived`);
|
|
5870
|
+
const revision = integer2(raw.revision, `${path}.revision`, errors);
|
|
5871
|
+
const scope = scopeValue(raw.scope, `${path}.scope`, errors) ?? { kind: "global" };
|
|
5872
|
+
const tags = Array.isArray(raw.tags) ? raw.tags.map((tag, index) => text3(tag, `${path}.tags[${index}]`, errors, 80) ?? "") : [];
|
|
5873
|
+
if (!Array.isArray(raw.tags)) errors.push(`${path}.tags must be an array`);
|
|
5874
|
+
if (tags.length > MAX_TAGS) errors.push(`${path}.tags may contain at most ${MAX_TAGS} items`);
|
|
5875
|
+
if (new Set(tags).size !== tags.length) errors.push(`${path}.tags must be unique`);
|
|
5876
|
+
const bodyMarkdown = text3(raw.bodyMarkdown, `${path}.bodyMarkdown`, errors, MAX_MARKDOWN3, false);
|
|
5877
|
+
const url = text3(raw.url, `${path}.url`, errors, 2048, false);
|
|
5878
|
+
if (url) {
|
|
5879
|
+
try {
|
|
5880
|
+
if (new URL(url).protocol !== "https:") errors.push(`${path}.url must use https`);
|
|
5881
|
+
} catch {
|
|
5882
|
+
errors.push(`${path}.url must be a valid https URL`);
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
const asset = raw.brandAsset === void 0 ? null : record3(raw.brandAsset);
|
|
5886
|
+
if (raw.brandAsset !== void 0 && !asset) errors.push(`${path}.brandAsset must be an object`);
|
|
5887
|
+
if (asset) unknownKeys3(asset, ASSET_KEYS, `${path}.brandAsset`, errors);
|
|
5888
|
+
const brandAsset = asset ? {
|
|
5889
|
+
bookId: text3(asset.bookId, `${path}.brandAsset.bookId`, errors, 160) ?? "",
|
|
5890
|
+
assetId: text3(asset.assetId, `${path}.brandAsset.assetId`, errors, 160) ?? "",
|
|
5891
|
+
...asset.filename === void 0 ? {} : { filename: text3(asset.filename, `${path}.brandAsset.filename`, errors, 240) ?? "" }
|
|
5892
|
+
} : void 0;
|
|
5893
|
+
if ((kind === "document" || kind === "note") && !bodyMarkdown) errors.push(`${path}.bodyMarkdown is required for ${kind}`);
|
|
5894
|
+
if ((kind === "link" || kind === "inspiration") && !url) errors.push(`${path}.url is required for ${kind}`);
|
|
5895
|
+
if ((kind === "image" || kind === "file") && !brandAsset) errors.push(`${path}.brandAsset is required for ${kind}`);
|
|
5896
|
+
if (!["document", "note"].includes(kind) && bodyMarkdown) errors.push(`${path}.bodyMarkdown is not allowed for ${kind}`);
|
|
5897
|
+
if (!["link", "inspiration"].includes(kind) && url) errors.push(`${path}.url is not allowed for ${kind}`);
|
|
5898
|
+
if (!["image", "file"].includes(kind) && brandAsset) errors.push(`${path}.brandAsset is not allowed for ${kind}`);
|
|
5899
|
+
const provenanceRaw = record3(raw.provenance);
|
|
5900
|
+
if (!provenanceRaw) errors.push(`${path}.provenance must be an object`);
|
|
5901
|
+
if (provenanceRaw) unknownKeys3(provenanceRaw, PROVENANCE_KEYS, `${path}.provenance`, errors);
|
|
5902
|
+
const promotedRaw = provenanceRaw?.promotedFrom === void 0 ? null : record3(provenanceRaw.promotedFrom);
|
|
5903
|
+
if (provenanceRaw?.promotedFrom !== void 0 && !promotedRaw) errors.push(`${path}.provenance.promotedFrom must be an object`);
|
|
5904
|
+
if (promotedRaw) unknownKeys3(promotedRaw, PROMOTION_KEYS, `${path}.provenance.promotedFrom`, errors);
|
|
5905
|
+
const promotedFrom = promotedRaw ? {
|
|
5906
|
+
chapterId: text3(promotedRaw.chapterId, `${path}.provenance.promotedFrom.chapterId`, errors, 63) ?? "",
|
|
5907
|
+
resourceId: text3(promotedRaw.resourceId, `${path}.provenance.promotedFrom.resourceId`, errors, 80) ?? "",
|
|
5908
|
+
resourceRevision: integer2(promotedRaw.resourceRevision, `${path}.provenance.promotedFrom.resourceRevision`, errors),
|
|
5909
|
+
documentRevision: integer2(promotedRaw.documentRevision, `${path}.provenance.promotedFrom.documentRevision`, errors),
|
|
5910
|
+
promotedBy: text3(promotedRaw.promotedBy, `${path}.provenance.promotedFrom.promotedBy`, errors, 320) ?? "",
|
|
5911
|
+
promotedAt: integer2(promotedRaw.promotedAt, `${path}.provenance.promotedFrom.promotedAt`, errors, 0)
|
|
5912
|
+
} : void 0;
|
|
5913
|
+
const provenance = {
|
|
5914
|
+
createdBy: text3(provenanceRaw?.createdBy, `${path}.provenance.createdBy`, errors, 320) ?? "",
|
|
5915
|
+
createdAt: integer2(provenanceRaw?.createdAt, `${path}.provenance.createdAt`, errors, 0),
|
|
5916
|
+
updatedBy: text3(provenanceRaw?.updatedBy, `${path}.provenance.updatedBy`, errors, 320) ?? "",
|
|
5917
|
+
updatedAt: integer2(provenanceRaw?.updatedAt, `${path}.provenance.updatedAt`, errors, 0),
|
|
5918
|
+
...promotedFrom ? { promotedFrom } : {}
|
|
5919
|
+
};
|
|
5920
|
+
return { id, kind, title, ...summary ? { summary } : {}, tags, scope, status, revision, ...bodyMarkdown ? { bodyMarkdown } : {}, ...url ? { url } : {}, ...brandAsset ? { brandAsset } : {}, provenance };
|
|
5921
|
+
}
|
|
5922
|
+
function validateBuilderLibraryDocument(value, options = {}) {
|
|
5923
|
+
const errors = [];
|
|
5924
|
+
const raw = record3(value);
|
|
5925
|
+
if (!raw) return { ok: false, errors: ["document must be an object"] };
|
|
5926
|
+
unknownKeys3(raw, ROOT_KEYS4, "document", errors);
|
|
5927
|
+
if (raw.schemaVersion !== BUILDER_LIBRARY_SCHEMA_VERSION) errors.push("document.schemaVersion must be 1");
|
|
5928
|
+
const revision = integer2(raw.revision, "document.revision", errors);
|
|
5929
|
+
const generatedAt = integer2(raw.generatedAt, "document.generatedAt", errors, 0);
|
|
5930
|
+
const sourceRaw = record3(raw.source);
|
|
5931
|
+
if (!sourceRaw) errors.push("document.source must be an object");
|
|
5932
|
+
if (sourceRaw) unknownKeys3(sourceRaw, SOURCE_KEYS2, "document.source", errors);
|
|
5933
|
+
const sourceType = sourceRaw?.type;
|
|
5934
|
+
if (sourceType !== "chapter" && sourceType !== "built_not_found") errors.push("document.source.type must be chapter or built_not_found");
|
|
5935
|
+
const sourceAppId = text3(sourceRaw?.appId, "document.source.appId", errors, 63) ?? "";
|
|
5936
|
+
if (sourceAppId && !APP_ID.test(sourceAppId)) errors.push("document.source.appId must be a lowercase app slug");
|
|
5937
|
+
if (options.sourceType && sourceType !== options.sourceType) errors.push(`document.source.type must be ${options.sourceType}`);
|
|
5938
|
+
if (options.sourceAppId && sourceAppId !== options.sourceAppId) errors.push(`document.source.appId must be ${options.sourceAppId}`);
|
|
5939
|
+
const targetRaw = raw.target === void 0 ? null : record3(raw.target);
|
|
5940
|
+
if (raw.target !== void 0 && !targetRaw) errors.push("document.target must be an object");
|
|
5941
|
+
if (targetRaw) unknownKeys3(targetRaw, TARGET_KEYS2, "document.target", errors);
|
|
5942
|
+
const target = targetRaw ? {
|
|
5943
|
+
appId: text3(targetRaw.appId, "document.target.appId", errors, 63) ?? "",
|
|
5944
|
+
environment: text3(targetRaw.environment, "document.target.environment", errors, 80) ?? ""
|
|
5945
|
+
} : void 0;
|
|
5946
|
+
if (options.targetAppId && target?.appId !== options.targetAppId) errors.push(`document.target.appId must be ${options.targetAppId}`);
|
|
5947
|
+
if (!Array.isArray(raw.resources)) errors.push("document.resources must be an array");
|
|
5948
|
+
const inputs = Array.isArray(raw.resources) ? raw.resources : [];
|
|
5949
|
+
if (inputs.length > MAX_RESOURCES) errors.push(`document.resources may contain at most ${MAX_RESOURCES} items`);
|
|
5950
|
+
const resources = inputs.map((item, index) => resourceValue(item, `document.resources[${index}]`, errors)).filter(Boolean);
|
|
5951
|
+
const ids = resources.map(({ id }) => id);
|
|
5952
|
+
if (new Set(ids).size !== ids.length) errors.push("document.resources ids must be unique");
|
|
5953
|
+
for (const resource of resources) {
|
|
5954
|
+
if (sourceType === "chapter" && (resource.scope.kind !== "chapter" || resource.scope.chapterId !== sourceAppId)) {
|
|
5955
|
+
errors.push(`document.resources.${resource.id}: Chapter sources may author only their own chapter scope`);
|
|
5956
|
+
}
|
|
5957
|
+
if (sourceType === "built_not_found" && resource.scope.kind === "chapter") {
|
|
5958
|
+
errors.push(`document.resources.${resource.id}: Built Not Found sources may author only built_not_found or global scope`);
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
if (errors.length) return { ok: false, errors };
|
|
5962
|
+
return { ok: true, value: { schemaVersion: 1, revision, generatedAt, source: { type: sourceType, appId: sourceAppId }, ...target ? { target } : {}, resources } };
|
|
5963
|
+
}
|
|
5964
|
+
|
|
5965
|
+
// src/builder-library.ts
|
|
5966
|
+
var BUILDER_LIBRARY_SCHEMA_VERSION = 1;
|
|
5967
|
+
var BUILDER_LIBRARY_ADMIN_PATH = "/api/admin/builder-library";
|
|
5968
|
+
var BUILDER_LIBRARY_MANIFEST_PATH = "/api/admin/builder-library/manifest";
|
|
5969
|
+
var BUILDER_LIBRARY_PROMOTE_PATH = "/api/admin/builder-library/promote";
|
|
5970
|
+
var BUILDER_LIBRARY_CONTROL_PATH = "/api/builder-library-control";
|
|
5971
|
+
function canViewBuilderLibraryResource(resource, viewer) {
|
|
5972
|
+
if (viewer.kind === "built_not_found") return true;
|
|
5973
|
+
return resource.scope.kind === "global" || resource.scope.kind === "chapter" && resource.scope.chapterId === viewer.chapterId;
|
|
5974
|
+
}
|
|
5975
|
+
function buildBuilderLibraryManifest(documents, viewer, generatedAt = Date.now(), limit = 100) {
|
|
5976
|
+
const ordered = documents.flatMap((document) => document.resources.map((resource) => ({ document, resource }))).filter(({ resource }) => resource.status === "published" && canViewBuilderLibraryResource(resource, viewer)).sort((a, b) => a.resource.title.localeCompare(b.resource.title) || a.document.source.appId.localeCompare(b.document.source.appId) || a.resource.id.localeCompare(b.resource.id));
|
|
5977
|
+
return {
|
|
5978
|
+
schemaVersion: BUILDER_LIBRARY_SCHEMA_VERSION,
|
|
5979
|
+
generatedAt,
|
|
5980
|
+
viewer,
|
|
5981
|
+
revisions: documents.map(({ source, revision }) => ({ source, revision })),
|
|
5982
|
+
resources: ordered.slice(0, limit).map(({ resource }) => resource),
|
|
5983
|
+
truncated: ordered.length > limit
|
|
5984
|
+
};
|
|
5985
|
+
}
|
|
5986
|
+
function promoteBuilderLibraryResource(input) {
|
|
5987
|
+
if (input.resource.scope.kind !== "chapter") throw new TypeError("only a Chapter-scoped resource can be promoted");
|
|
5988
|
+
const chapterId = input.resource.scope.chapterId;
|
|
5989
|
+
return {
|
|
5990
|
+
...input.resource,
|
|
5991
|
+
id: input.globalId ?? `${chapterId}-${input.resource.id}`,
|
|
5992
|
+
scope: { kind: "global" },
|
|
5993
|
+
revision: 1,
|
|
5994
|
+
provenance: {
|
|
5995
|
+
createdBy: input.actor,
|
|
5996
|
+
createdAt: input.promotedAt,
|
|
5997
|
+
updatedBy: input.actor,
|
|
5998
|
+
updatedAt: input.promotedAt,
|
|
5999
|
+
promotedFrom: {
|
|
6000
|
+
chapterId,
|
|
6001
|
+
resourceId: input.resource.id,
|
|
6002
|
+
resourceRevision: input.resource.revision,
|
|
6003
|
+
documentRevision: input.chapterDocumentRevision,
|
|
6004
|
+
promotedBy: input.actor,
|
|
6005
|
+
promotedAt: input.promotedAt
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
6008
|
+
};
|
|
6009
|
+
}
|
|
6010
|
+
|
|
6011
|
+
// src/builder-library-canonical.ts
|
|
6012
|
+
var encoder3 = new TextEncoder();
|
|
6013
|
+
function canonicalValue3(value) {
|
|
6014
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") return JSON.stringify(value);
|
|
6015
|
+
if (Array.isArray(value)) return `[${value.map(canonicalValue3).join(",")}]`;
|
|
6016
|
+
if (typeof value !== "object") throw new TypeError("Builder Library contains an unsupported value");
|
|
6017
|
+
const record4 = value;
|
|
6018
|
+
return `{${Object.keys(record4).sort().flatMap((key) => record4[key] === void 0 ? [] : [`${JSON.stringify(key)}:${canonicalValue3(record4[key])}`]).join(",")}}`;
|
|
6019
|
+
}
|
|
6020
|
+
function canonicalBuilderLibrary(value) {
|
|
6021
|
+
return canonicalValue3(value);
|
|
6022
|
+
}
|
|
6023
|
+
async function builderLibraryDigest(document) {
|
|
6024
|
+
const digest = await crypto.subtle.digest("SHA-256", encoder3.encode(canonicalBuilderLibrary(document)));
|
|
6025
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
6026
|
+
}
|
|
6027
|
+
|
|
6028
|
+
// src/builder-library-store.ts
|
|
6029
|
+
var control2 = (row) => row?.document && typeof row.document === "object" ? row : null;
|
|
6030
|
+
var local2 = (row) => row?.document && typeof row.document === "object" ? row : null;
|
|
6031
|
+
async function loadActiveBuilderLibraryControls(db, targetId, environment) {
|
|
6032
|
+
const rows4 = (await db.query({ builderLibraryControls: { $: { where: { targetId, environment, active: true }, limit: 500 } } })).builderLibraryControls;
|
|
6033
|
+
return Array.isArray(rows4) ? rows4.map(control2).filter(Boolean) : [];
|
|
6034
|
+
}
|
|
6035
|
+
async function loadLatestBuilderLibraryControl(db, sourceId, targetId, environment) {
|
|
6036
|
+
const rows4 = (await db.query({ builderLibraryControls: { $: { where: { sourceId, targetId, environment }, order: { revision: "desc" }, limit: 20 } } })).builderLibraryControls;
|
|
6037
|
+
return control2(Array.isArray(rows4) ? [...rows4].sort((a, b) => Number(b.revision) - Number(a.revision))[0] : void 0);
|
|
6038
|
+
}
|
|
6039
|
+
async function loadLocalBuilderLibrary(db, appId, environment) {
|
|
6040
|
+
const id = `builder-library-local:${environment}`;
|
|
6041
|
+
const rows4 = (await db.query({ builderLibraryLocal: { $: { where: { id }, limit: 1 } } })).builderLibraryLocal;
|
|
6042
|
+
return local2(Array.isArray(rows4) ? rows4[0] : void 0);
|
|
6043
|
+
}
|
|
6044
|
+
async function materializeBuilderLibraryControl(db, envelope, previous, receivedAt) {
|
|
6045
|
+
const { document, digest } = envelope;
|
|
6046
|
+
const target = document.target;
|
|
6047
|
+
const id = `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}`;
|
|
6048
|
+
const ops = [];
|
|
6049
|
+
if (previous?.active) ops.push({ t: "update", ns: "builderLibraryControls", id: previous.id, attrs: { active: false } });
|
|
6050
|
+
ops.push({ t: "update", ns: "builderLibraryControls", id, attrs: {
|
|
6051
|
+
id,
|
|
6052
|
+
sourceId: document.source.appId,
|
|
6053
|
+
targetId: target.appId,
|
|
6054
|
+
environment: target.environment,
|
|
6055
|
+
revision: document.revision,
|
|
6056
|
+
digest,
|
|
6057
|
+
document,
|
|
6058
|
+
active: true,
|
|
6059
|
+
receivedAt
|
|
6060
|
+
} });
|
|
6061
|
+
await db.transact(ops, { mutationId: `builder-library-control:${document.source.appId}:${target.environment}:${document.revision}:${digest}` });
|
|
6062
|
+
}
|
|
6063
|
+
async function saveLocalBuilderLibrary(db, document, environment, expectedRevision, updatedAt, updatedBy) {
|
|
6064
|
+
const id = `builder-library-local:${environment}`;
|
|
6065
|
+
const current = await loadLocalBuilderLibrary(db, document.source.appId, environment);
|
|
6066
|
+
if ((current?.revision ?? 0) !== expectedRevision) throw new Error("builder_library_revision_conflict");
|
|
6067
|
+
const row = { id, appId: document.source.appId, environment, revision: document.revision, document, updatedAt, ...updatedBy ? { updatedBy } : {} };
|
|
6068
|
+
await db.transact([{ t: "update", ns: "builderLibraryLocal", id, attrs: row }], {
|
|
6069
|
+
mutationId: `builder-library-local:${environment}:${document.revision}`,
|
|
6070
|
+
guards: current ? [{ ns: "builderLibraryLocal", id, exists: true, equals: { revision: expectedRevision } }] : [{ ns: "builderLibraryLocal", id, exists: false }]
|
|
6071
|
+
});
|
|
6072
|
+
return row;
|
|
6073
|
+
}
|
|
6074
|
+
|
|
6075
|
+
// src/worker-routes-builder-library.ts
|
|
6076
|
+
var BODY_CAP2 = 1e6;
|
|
6077
|
+
async function verifySource(req, db, sources) {
|
|
6078
|
+
for (const source of sources) {
|
|
6079
|
+
const secret = await getVaultSecret(db, source.secretName);
|
|
6080
|
+
if (!secret) continue;
|
|
6081
|
+
const verified = await verifyFederatedRequest8(req.clone(), { secret, senders: [source.sourceId] });
|
|
6082
|
+
if (verified.ok) return { source, body: verified.body };
|
|
6083
|
+
}
|
|
6084
|
+
return null;
|
|
6085
|
+
}
|
|
6086
|
+
var handleBuilderLibraryControl = async (req, url, env, ctx) => {
|
|
6087
|
+
if (req.method !== "POST" || url.pathname !== BUILDER_LIBRARY_CONTROL_PATH) return null;
|
|
6088
|
+
const settings = ctx.chapter.builderLibrary;
|
|
6089
|
+
if (!settings) return json({ error: "not found" }, 404);
|
|
6090
|
+
const db = ctx.makeDb(env);
|
|
6091
|
+
const verified = await verifySource(req, db, settings.sources);
|
|
6092
|
+
if (!verified) return json({ error: "unauthorized" }, 401);
|
|
6093
|
+
let input;
|
|
6094
|
+
try {
|
|
6095
|
+
input = JSON.parse(verified.body);
|
|
6096
|
+
} catch {
|
|
6097
|
+
return json({ ok: false, reason: "invalid_document", errors: ["request body must be JSON"] }, 400);
|
|
6098
|
+
}
|
|
6099
|
+
const digest = typeof input.digest === "string" ? input.digest : "";
|
|
6100
|
+
const sourceType = ctx.chapter.mode === "hub" ? "chapter" : "built_not_found";
|
|
6101
|
+
const checked = validateBuilderLibraryDocument(input.document, {
|
|
6102
|
+
sourceType,
|
|
6103
|
+
sourceAppId: verified.source.sourceId,
|
|
6104
|
+
targetAppId: ctx.chapter.id
|
|
6105
|
+
});
|
|
6106
|
+
if (!checked.ok) return json({ ok: false, reason: "invalid_document", errors: checked.errors }, 400);
|
|
6107
|
+
const document = checked.value;
|
|
6108
|
+
if (ctx.chapter.mode === "chapter" && document.resources.some((resource) => resource.scope.kind !== "global")) {
|
|
6109
|
+
return json({ ok: false, reason: "invalid_document", errors: ["Built Not Found may deliver only global resources to a Chapter"] }, 400);
|
|
6110
|
+
}
|
|
6111
|
+
if (!document.target || document.target.environment !== env.ODLA_ENV) return json({ ok: false, reason: "wrong_environment" }, 409);
|
|
6112
|
+
if (!digest || await builderLibraryDigest(document) !== digest) return json({ ok: false, reason: "invalid_digest" }, 400);
|
|
6113
|
+
const latest = await loadLatestBuilderLibraryControl(db, document.source.appId, ctx.chapter.id, env.ODLA_ENV);
|
|
6114
|
+
if (latest && document.revision < latest.revision) return json({ ok: false, reason: "stale_revision" }, 409);
|
|
6115
|
+
if (latest && document.revision === latest.revision) {
|
|
6116
|
+
if (latest.digest !== digest) return json({ ok: false, reason: "conflicting_revision" }, 409);
|
|
6117
|
+
return json({ ok: true, target: document.target, revision: latest.revision, digest: latest.digest, status: "replayed", acknowledgedAt: latest.receivedAt });
|
|
6118
|
+
}
|
|
6119
|
+
const receivedAt = Date.now();
|
|
6120
|
+
try {
|
|
6121
|
+
await materializeBuilderLibraryControl(db, { document, digest }, latest, receivedAt);
|
|
6122
|
+
} catch {
|
|
6123
|
+
return json({ ok: false, reason: "materialization_failed" }, 503);
|
|
6124
|
+
}
|
|
6125
|
+
return json({ ok: true, target: document.target, revision: document.revision, digest, status: "accepted", acknowledgedAt: receivedAt });
|
|
6126
|
+
};
|
|
6127
|
+
function actorFor(user) {
|
|
6128
|
+
return user.email ?? user.userId;
|
|
6129
|
+
}
|
|
6130
|
+
function withLocalAuthority2(value, mode, appId, revision, now, actor, current) {
|
|
6131
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
6132
|
+
const raw = value;
|
|
6133
|
+
const currentById = new Map((current?.resources ?? []).map((item) => [item.id, item]));
|
|
6134
|
+
const resources = Array.isArray(raw.resources) ? raw.resources.map((input) => {
|
|
6135
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return input;
|
|
6136
|
+
const item = input;
|
|
6137
|
+
const existing = typeof item.id === "string" ? currentById.get(item.id) : void 0;
|
|
6138
|
+
return {
|
|
6139
|
+
...item,
|
|
6140
|
+
revision: (existing?.revision ?? 0) + 1,
|
|
6141
|
+
provenance: {
|
|
6142
|
+
createdBy: existing?.provenance.createdBy ?? actor,
|
|
6143
|
+
createdAt: existing?.provenance.createdAt ?? now,
|
|
6144
|
+
updatedBy: actor,
|
|
6145
|
+
updatedAt: now,
|
|
6146
|
+
...existing?.provenance.promotedFrom ? { promotedFrom: existing.provenance.promotedFrom } : {}
|
|
6147
|
+
}
|
|
6148
|
+
};
|
|
6149
|
+
}) : raw.resources;
|
|
6150
|
+
return {
|
|
6151
|
+
...raw,
|
|
6152
|
+
schemaVersion: 1,
|
|
6153
|
+
revision,
|
|
6154
|
+
generatedAt: now,
|
|
6155
|
+
source: { type: mode === "hub" ? "built_not_found" : "chapter", appId },
|
|
6156
|
+
resources
|
|
6157
|
+
};
|
|
6158
|
+
}
|
|
6159
|
+
var handleAdminBuilderLibrary = async (req, url, env, ctx) => {
|
|
6160
|
+
const paths = /* @__PURE__ */ new Set([BUILDER_LIBRARY_ADMIN_PATH, BUILDER_LIBRARY_MANIFEST_PATH, BUILDER_LIBRARY_PROMOTE_PATH]);
|
|
6161
|
+
if (!paths.has(url.pathname)) return null;
|
|
6162
|
+
if (!ctx.chapter.builderLibrary) return json({ error: "not found" }, 404);
|
|
6163
|
+
const user = await ctx.verifyUser(req, env);
|
|
6164
|
+
if (!user) return json({ error: "unauthorized" }, 401);
|
|
6165
|
+
const db = ctx.makeDb(env);
|
|
6166
|
+
if (!await ctx.isAdmin(db, user)) return json({ error: "forbidden" }, 403);
|
|
6167
|
+
const [local3, controls] = await Promise.all([
|
|
6168
|
+
loadLocalBuilderLibrary(db, ctx.chapter.id, env.ODLA_ENV),
|
|
6169
|
+
loadActiveBuilderLibraryControls(db, ctx.chapter.id, env.ODLA_ENV)
|
|
6170
|
+
]);
|
|
6171
|
+
if (url.pathname === BUILDER_LIBRARY_MANIFEST_PATH) {
|
|
6172
|
+
if (req.method !== "GET") return json({ error: "method not allowed" }, 405);
|
|
6173
|
+
const chapterId = url.searchParams.get("chapterId")?.trim();
|
|
6174
|
+
if (ctx.chapter.mode === "hub" && (!chapterId || !ctx.chapter.builderLibrary.sources.some((source) => source.sourceId === chapterId))) {
|
|
6175
|
+
return json({ error: "chapterId must name a configured Chapter source" }, 400);
|
|
6176
|
+
}
|
|
6177
|
+
const viewer = ctx.chapter.mode === "hub" ? { kind: "chapter", chapterId } : { kind: "chapter", chapterId: ctx.chapter.id };
|
|
6178
|
+
const documents = [
|
|
6179
|
+
...local3 ? [local3.document] : [],
|
|
6180
|
+
...controls.filter((row) => ctx.chapter.mode === "chapter" || row.sourceId === chapterId).map((row) => row.document)
|
|
6181
|
+
];
|
|
6182
|
+
return json(buildBuilderLibraryManifest(documents, viewer));
|
|
6183
|
+
}
|
|
6184
|
+
if (url.pathname === BUILDER_LIBRARY_PROMOTE_PATH) {
|
|
6185
|
+
if (req.method !== "POST") return json({ error: "method not allowed" }, 405);
|
|
6186
|
+
if (ctx.chapter.mode !== "hub") return json({ error: "only Built Not Found can promote Chapter material" }, 403);
|
|
6187
|
+
let input2;
|
|
6188
|
+
try {
|
|
6189
|
+
input2 = await req.json();
|
|
6190
|
+
} catch {
|
|
6191
|
+
return json({ error: "invalid JSON body" }, 400);
|
|
6192
|
+
}
|
|
6193
|
+
const chapterId = typeof input2.chapterId === "string" ? input2.chapterId : "";
|
|
6194
|
+
const resourceId = typeof input2.resourceId === "string" ? input2.resourceId : "";
|
|
6195
|
+
const expectedRevision2 = Number(input2.expectedRevision);
|
|
6196
|
+
const source = controls.find((row) => row.sourceId === chapterId);
|
|
6197
|
+
const resource = source?.document.resources.find((item) => item.id === resourceId);
|
|
6198
|
+
if (!source || !resource) return json({ error: "Chapter resource not found" }, 404);
|
|
6199
|
+
if (!Number.isInteger(expectedRevision2) || expectedRevision2 !== (local3?.revision ?? 0)) return json({ error: "Builder Library changed; reload before promoting" }, 409);
|
|
6200
|
+
const now2 = Date.now();
|
|
6201
|
+
let promoted;
|
|
6202
|
+
try {
|
|
6203
|
+
promoted = promoteBuilderLibraryResource({ resource, chapterDocumentRevision: source.revision, actor: actorFor(user), promotedAt: now2, ...typeof input2.globalId === "string" ? { globalId: input2.globalId } : {} });
|
|
6204
|
+
} catch {
|
|
6205
|
+
return json({ error: "resource cannot be promoted" }, 400);
|
|
6206
|
+
}
|
|
6207
|
+
if (local3?.document.resources.some((item) => item.id === promoted.id)) return json({ error: `global resource id "${promoted.id}" already exists` }, 409);
|
|
6208
|
+
const document = {
|
|
6209
|
+
schemaVersion: 1,
|
|
6210
|
+
revision: expectedRevision2 + 1,
|
|
6211
|
+
generatedAt: now2,
|
|
6212
|
+
source: { type: "built_not_found", appId: ctx.chapter.id },
|
|
6213
|
+
resources: [...local3?.document.resources ?? [], promoted]
|
|
6214
|
+
};
|
|
6215
|
+
const checked2 = validateBuilderLibraryDocument(document, { sourceType: "built_not_found", sourceAppId: ctx.chapter.id });
|
|
6216
|
+
if (!checked2.ok) return json({ error: "promoted resource is invalid", errors: checked2.errors }, 400);
|
|
6217
|
+
try {
|
|
6218
|
+
const saved = await saveLocalBuilderLibrary(db, checked2.value, env.ODLA_ENV, expectedRevision2, now2, actorFor(user));
|
|
6219
|
+
return json({ ok: true, revision: saved.revision, resource: promoted });
|
|
6220
|
+
} catch {
|
|
6221
|
+
return json({ error: "Builder Library changed; reload before promoting" }, 409);
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
if (req.method === "GET") {
|
|
6225
|
+
return json({
|
|
6226
|
+
mode: ctx.chapter.mode,
|
|
6227
|
+
local: local3 ? { revision: local3.revision, document: local3.document } : null,
|
|
6228
|
+
sources: controls.map(({ sourceId, revision, digest, document, receivedAt }) => ({ sourceId, revision, digest, document, receivedAt })),
|
|
6229
|
+
localAuthoring: ctx.chapter.builderLibrary.localAuthoring,
|
|
6230
|
+
visibility: { builtNotFoundSeesChapterSources: ctx.chapter.mode === "hub", chaptersSeeSiblingSources: false }
|
|
6231
|
+
});
|
|
6232
|
+
}
|
|
6233
|
+
if (req.method !== "PUT") return json({ error: "method not allowed" }, 405);
|
|
6234
|
+
if (!ctx.chapter.builderLibrary.localAuthoring) return json({ error: "local authoring is disabled" }, 403);
|
|
6235
|
+
const raw = await req.text();
|
|
6236
|
+
if (raw.length > BODY_CAP2) return json({ error: "request body too large" }, 413);
|
|
6237
|
+
let input;
|
|
6238
|
+
try {
|
|
6239
|
+
input = JSON.parse(raw);
|
|
6240
|
+
} catch {
|
|
6241
|
+
return json({ error: "invalid JSON body" }, 400);
|
|
6242
|
+
}
|
|
6243
|
+
const expectedRevision = Number(input.expectedRevision);
|
|
6244
|
+
if (!Number.isInteger(expectedRevision) || expectedRevision < 0) return json({ error: "expectedRevision must be a non-negative integer" }, 400);
|
|
6245
|
+
const now = Date.now();
|
|
6246
|
+
const actor = actorFor(user);
|
|
6247
|
+
const authoritative = withLocalAuthority2(input.document, ctx.chapter.mode, ctx.chapter.id, expectedRevision + 1, now, actor, local3?.document);
|
|
6248
|
+
const checked = validateBuilderLibraryDocument(authoritative, { sourceType: ctx.chapter.mode === "hub" ? "built_not_found" : "chapter", sourceAppId: ctx.chapter.id });
|
|
6249
|
+
if (!checked.ok) return json({ error: "invalid Builder Library", errors: checked.errors }, 400);
|
|
6250
|
+
try {
|
|
6251
|
+
const saved = await saveLocalBuilderLibrary(db, checked.value, env.ODLA_ENV, expectedRevision, now, actor);
|
|
6252
|
+
return json({ ok: true, revision: saved.revision, document: saved.document });
|
|
6253
|
+
} catch (error) {
|
|
6254
|
+
if (error instanceof Error && error.message === "builder_library_revision_conflict") return json({ error: "Builder Library changed; reload before saving" }, 409);
|
|
6255
|
+
return json({ error: "Builder Library could not be saved" }, 503);
|
|
6256
|
+
}
|
|
6257
|
+
};
|
|
6258
|
+
|
|
5798
6259
|
// src/signup-control-delivery.ts
|
|
5799
6260
|
import { signFederatedRequest as signFederatedRequest5 } from "@odla-ai/db";
|
|
5800
6261
|
async function deliverSignupControl(options) {
|
|
@@ -5845,12 +6306,27 @@ async function deliverMemberContent(options) {
|
|
|
5845
6306
|
return result;
|
|
5846
6307
|
}
|
|
5847
6308
|
|
|
5848
|
-
// src/
|
|
6309
|
+
// src/builder-library-delivery.ts
|
|
5849
6310
|
import { signFederatedRequest as signFederatedRequest7 } from "@odla-ai/db";
|
|
6311
|
+
async function deliverBuilderLibrary(options) {
|
|
6312
|
+
const destination = new URL(BUILDER_LIBRARY_CONTROL_PATH, options.targetUrl);
|
|
6313
|
+
const body = JSON.stringify(options.envelope);
|
|
6314
|
+
const signed = await signFederatedRequest7({ secret: options.secret, sender: options.sender, method: "POST", url: destination, body });
|
|
6315
|
+
const response = await (options.fetch ?? globalThis.fetch)(destination, { method: "POST", headers: { ...signed, "content-type": "application/json" }, body });
|
|
6316
|
+
const result = await response.json();
|
|
6317
|
+
if (!response.ok || !("ok" in result) || result.ok !== true) {
|
|
6318
|
+
const failure = result;
|
|
6319
|
+
throw new Error(`Builder Library rejected (${response.status}): ${failure.reason ?? failure.error ?? "unknown"}`);
|
|
6320
|
+
}
|
|
6321
|
+
return result;
|
|
6322
|
+
}
|
|
6323
|
+
|
|
6324
|
+
// src/admission-grants-delivery.ts
|
|
6325
|
+
import { signFederatedRequest as signFederatedRequest8 } from "@odla-ai/db";
|
|
5850
6326
|
async function deliverAdmissionGrants(options) {
|
|
5851
6327
|
const destination = new URL(ADMISSION_GRANTS_PATH, options.targetUrl);
|
|
5852
6328
|
const body = JSON.stringify(options.envelope);
|
|
5853
|
-
const signed = await
|
|
6329
|
+
const signed = await signFederatedRequest8({
|
|
5854
6330
|
secret: options.secret,
|
|
5855
6331
|
sender: options.sender,
|
|
5856
6332
|
method: "POST",
|
|
@@ -6010,6 +6486,7 @@ var BUILTIN_ROUTES = [
|
|
|
6010
6486
|
handleNetworkShared,
|
|
6011
6487
|
handleSignupControl,
|
|
6012
6488
|
handleMemberContentControl,
|
|
6489
|
+
handleBuilderLibraryControl,
|
|
6013
6490
|
handleAdmissionGrants,
|
|
6014
6491
|
handleNetworkSnapshot,
|
|
6015
6492
|
handleNetworkRecords,
|
|
@@ -6033,6 +6510,7 @@ var BUILTIN_ROUTES = [
|
|
|
6033
6510
|
handleAdminDashboard,
|
|
6034
6511
|
handleAdminBilling,
|
|
6035
6512
|
handleAdminMemberContent,
|
|
6513
|
+
handleAdminBuilderLibrary,
|
|
6036
6514
|
// Lifecycle actions
|
|
6037
6515
|
handleAdminMeetingReschedule,
|
|
6038
6516
|
handleAdminMeetingCancel,
|
|
@@ -6074,6 +6552,7 @@ export {
|
|
|
6074
6552
|
chapterWorker,
|
|
6075
6553
|
createWorkerContext,
|
|
6076
6554
|
deliverAdmissionGrants,
|
|
6555
|
+
deliverBuilderLibrary,
|
|
6077
6556
|
deliverMemberContent,
|
|
6078
6557
|
deliverSignupControl,
|
|
6079
6558
|
renderActiveSignupControlHtml,
|