@odla-ai/chapter 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -7
- package/dist/{chunk-QBTFQUDL.js → chunk-IOHVFBXH.js} +2 -2
- package/dist/{chunk-OIWCKF2G.js → chunk-JAWTIROV.js} +2 -1
- package/dist/chunk-JAWTIROV.js.map +1 -0
- package/dist/{chunk-4QZEAWQL.js → chunk-L2SA47IO.js} +434 -79
- package/dist/chunk-L2SA47IO.js.map +1 -0
- package/dist/{copy-context-CLvhJhrF.d.ts → copy-context-efEMF_aD.d.ts} +38 -5
- package/dist/index.cjs +98 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -11
- package/dist/index.d.ts +89 -11
- package/dist/index.js +98 -6
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +12 -3
- package/dist/ui/admin/index.js +6 -2
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +7 -3
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/ui/member/index.js +2 -2
- package/dist/worker/index.cjs +545 -92
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +40 -7
- package/dist/worker/index.d.ts +40 -7
- package/dist/worker/index.js +546 -89
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4QZEAWQL.js.map +0 -1
- package/dist/chunk-OIWCKF2G.js.map +0 -1
- /package/dist/{chunk-QBTFQUDL.js.map → chunk-IOHVFBXH.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -141,6 +141,19 @@ var superAdmins = {
|
|
|
141
141
|
createdAt: attr("number", { indexed: true })
|
|
142
142
|
}
|
|
143
143
|
};
|
|
144
|
+
var networkNotes = {
|
|
145
|
+
attrs: {
|
|
146
|
+
id: id(),
|
|
147
|
+
recordKey: attr("string", { indexed: true }),
|
|
148
|
+
targetId: attr("string", { indexed: true }),
|
|
149
|
+
recordType: attr("string", { indexed: true }),
|
|
150
|
+
recordId: attr("string", { indexed: true }),
|
|
151
|
+
body: attr("string"),
|
|
152
|
+
authorId: attr("string", { indexed: true }),
|
|
153
|
+
authorEmail: attr("string", { optional: true }),
|
|
154
|
+
createdAt: attr("number", { indexed: true })
|
|
155
|
+
}
|
|
156
|
+
};
|
|
144
157
|
var applications = {
|
|
145
158
|
attrs: {
|
|
146
159
|
id: id(),
|
|
@@ -232,7 +245,7 @@ var emailLog = {
|
|
|
232
245
|
sentAt: attr("number", { indexed: true })
|
|
233
246
|
}
|
|
234
247
|
};
|
|
235
|
-
function chapterDb(mode, auth) {
|
|
248
|
+
function chapterDb(mode, auth, includeNetworkNotes = false) {
|
|
236
249
|
const entities = {};
|
|
237
250
|
if (mode === "chapter") {
|
|
238
251
|
entities.applications = applications;
|
|
@@ -242,6 +255,7 @@ function chapterDb(mode, auth) {
|
|
|
242
255
|
}
|
|
243
256
|
if (auth.source === "table") entities.admins = admins;
|
|
244
257
|
if (auth.superAdmins) entities.superAdmins = superAdmins;
|
|
258
|
+
if (includeNetworkNotes) entities.networkNotes = networkNotes;
|
|
245
259
|
const schema = { entities, links: {} };
|
|
246
260
|
const rules = {};
|
|
247
261
|
for (const ns of Object.keys(entities)) {
|
|
@@ -633,6 +647,7 @@ var DEFAULT_ADMIN_COPY = {
|
|
|
633
647
|
overview: "Overview",
|
|
634
648
|
billing: "Billing",
|
|
635
649
|
people: "People",
|
|
650
|
+
network: "Network",
|
|
636
651
|
portfolio: "Portfolio",
|
|
637
652
|
settings: "Settings",
|
|
638
653
|
calendar: "Calendar",
|
|
@@ -994,9 +1009,17 @@ function formationFields(crm, formation) {
|
|
|
994
1009
|
});
|
|
995
1010
|
}
|
|
996
1011
|
|
|
997
|
-
// src/config.ts
|
|
1012
|
+
// src/config-network.ts
|
|
998
1013
|
var SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
999
1014
|
var FIELD = /^[a-z][a-zA-Z0-9_]*$/;
|
|
1015
|
+
var BINDING = /^[A-Z][A-Z0-9_]{0,127}$/;
|
|
1016
|
+
function validateSharedNotes(opts) {
|
|
1017
|
+
const duplicate = opts.values.some((type, index) => opts.values.indexOf(type) !== index);
|
|
1018
|
+
const invalid = opts.values.some((type) => !FIELD.test(type) || !opts.crm.config.types[type] || (opts.fields ? !opts.fields[type] : false));
|
|
1019
|
+
if (duplicate || invalid) {
|
|
1020
|
+
throw new Error(`${opts.path}: must contain unique CRM types allowed by fields`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1000
1023
|
function resolveNetwork(config, crm) {
|
|
1001
1024
|
const seen = /* @__PURE__ */ new Set();
|
|
1002
1025
|
const targets = [];
|
|
@@ -1042,23 +1065,87 @@ function resolveNetwork(config, crm) {
|
|
|
1042
1065
|
if (!/^[a-zA-Z][a-zA-Z0-9_-]{1,127}$/.test(secretName)) {
|
|
1043
1066
|
throw new Error(`defineChapter.network.targets.${target.id}.secretName: must be a vault-key identifier`);
|
|
1044
1067
|
}
|
|
1068
|
+
const binding = target.binding?.trim();
|
|
1069
|
+
if (binding && !BINDING.test(binding)) {
|
|
1070
|
+
throw new Error(
|
|
1071
|
+
`defineChapter.network.targets.${target.id}.binding: must be an uppercase Worker binding identifier`
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
const sharedNotes = target.sharedNotes ?? [];
|
|
1075
|
+
validateSharedNotes({
|
|
1076
|
+
path: `defineChapter.network.targets.${target.id}.sharedNotes`,
|
|
1077
|
+
values: sharedNotes,
|
|
1078
|
+
crm,
|
|
1079
|
+
fields
|
|
1080
|
+
});
|
|
1045
1081
|
targets.push({
|
|
1046
1082
|
id: target.id,
|
|
1047
1083
|
name: target.name?.trim() || target.id,
|
|
1048
1084
|
url: url.origin,
|
|
1085
|
+
...binding ? { binding } : {},
|
|
1049
1086
|
secretName,
|
|
1050
|
-
...fields ? { fields } : {}
|
|
1087
|
+
...fields ? { fields } : {},
|
|
1088
|
+
sharedNotes
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
const readerIds = /* @__PURE__ */ new Set();
|
|
1092
|
+
const readers = [];
|
|
1093
|
+
for (const reader of config.network?.readers ?? []) {
|
|
1094
|
+
if (!SLUG.test(reader.id)) {
|
|
1095
|
+
throw new Error(`defineChapter.network.readers.id: must be a lowercase slug \u2014 got ${JSON.stringify(reader.id)}`);
|
|
1096
|
+
}
|
|
1097
|
+
if (readerIds.has(reader.id)) {
|
|
1098
|
+
throw new Error(`defineChapter.network.readers: duplicate reader "${reader.id}"`);
|
|
1099
|
+
}
|
|
1100
|
+
readerIds.add(reader.id);
|
|
1101
|
+
const entries = Object.entries(reader.fields ?? {});
|
|
1102
|
+
if (entries.length === 0) {
|
|
1103
|
+
throw new Error(`defineChapter.network.readers.${reader.id}.fields: at least one CRM type is required`);
|
|
1104
|
+
}
|
|
1105
|
+
for (const [type, names] of entries) {
|
|
1106
|
+
if (!FIELD.test(type) || names.length === 0 || names.some((name) => !FIELD.test(name))) {
|
|
1107
|
+
throw new Error(
|
|
1108
|
+
`defineChapter.network.readers.${reader.id}.fields.${type}: type and field names must be non-empty CRM identifiers`
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
const def = crm.config.types[type];
|
|
1112
|
+
if (!def) {
|
|
1113
|
+
throw new Error(`defineChapter.network.readers.${reader.id}.fields: unknown local CRM type "${type}"`);
|
|
1114
|
+
}
|
|
1115
|
+
const unknown = names.filter((field) => !def.fields[field]);
|
|
1116
|
+
if (unknown.length > 0) {
|
|
1117
|
+
throw new Error(
|
|
1118
|
+
`defineChapter.network.readers.${reader.id}.fields.${type}: ${unknown.map((field) => `"${field}"`).join(", ")} not declared on the local CRM type`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
const nameField = def.nameField ?? "name";
|
|
1122
|
+
if (!names.includes(nameField)) {
|
|
1123
|
+
throw new Error(
|
|
1124
|
+
`defineChapter.network.readers.${reader.id}.fields.${type}: must include required name field "${nameField}"`
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
const sharedNotes = reader.sharedNotes ?? [];
|
|
1129
|
+
validateSharedNotes({
|
|
1130
|
+
path: `defineChapter.network.readers.${reader.id}.sharedNotes`,
|
|
1131
|
+
values: sharedNotes,
|
|
1132
|
+
crm,
|
|
1133
|
+
fields: reader.fields
|
|
1051
1134
|
});
|
|
1135
|
+
readers.push({ id: reader.id, fields: reader.fields, sharedNotes });
|
|
1052
1136
|
}
|
|
1053
|
-
return { targets };
|
|
1137
|
+
return { targets, readers };
|
|
1054
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
// src/config.ts
|
|
1141
|
+
var SLUG2 = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
1055
1142
|
function isResolvedCrm(x) {
|
|
1056
1143
|
return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
|
|
1057
1144
|
}
|
|
1058
1145
|
function defineChapter(config) {
|
|
1059
1146
|
if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
|
|
1060
1147
|
const { id: id2, name } = config;
|
|
1061
|
-
if (typeof id2 !== "string" || !
|
|
1148
|
+
if (typeof id2 !== "string" || !SLUG2.test(id2)) {
|
|
1062
1149
|
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id2)}`);
|
|
1063
1150
|
}
|
|
1064
1151
|
if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
|
|
@@ -1083,7 +1170,7 @@ function defineChapter(config) {
|
|
|
1083
1170
|
const copy = resolveChapterCopy(config.copy);
|
|
1084
1171
|
const network = resolveNetwork(config, crm);
|
|
1085
1172
|
const formation = resolveLeaderFormation(config.formation, crm);
|
|
1086
|
-
const { schema, rules } = chapterDb(mode, auth);
|
|
1173
|
+
const { schema, rules } = chapterDb(mode, auth, network.targets.length > 0);
|
|
1087
1174
|
const services = config.services ?? ["db", "calendar", "o11y"];
|
|
1088
1175
|
const account = config.account ?? "none";
|
|
1089
1176
|
if (account !== "invite" && account !== "create" && account !== "none") {
|
|
@@ -1339,6 +1426,11 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
1339
1426
|
// The snapshot route is deliberately private. A credential-free smoke
|
|
1340
1427
|
// request must prove that the route is mounted and closed.
|
|
1341
1428
|
{ path: "/api/network/snapshot", expectedStatus: 401 },
|
|
1429
|
+
...chapter.network.readers.length > 0 ? [{ path: "/api/network/records?type=person", expectedStatus: 401 }] : [],
|
|
1430
|
+
...chapter.network.readers.some((reader) => reader.sharedNotes.length > 0) ? [{
|
|
1431
|
+
path: "/api/network/shared-notes?type=person&recordId=probe",
|
|
1432
|
+
expectedStatus: 401
|
|
1433
|
+
}] : [],
|
|
1342
1434
|
// Formation metadata is public only when the host explicitly enables
|
|
1343
1435
|
// the bounded intake contract.
|
|
1344
1436
|
...chapter.formation.enabled ? [{ path: "/api/formation/config", expectedStatus: 200 }] : []
|