@odla-ai/chapter 0.26.1 → 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 +51 -3
- 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-Vof2eTMt.d.ts → copy-context-efEMF_aD.d.ts} +33 -5
- package/dist/index.cjs +90 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -11
- package/dist/index.d.ts +84 -11
- package/dist/index.js +90 -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 +424 -16
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +35 -7
- package/dist/worker/index.d.ts +35 -7
- package/dist/worker/index.js +426 -14
- 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,10 +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_]*$/;
|
|
1000
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
|
+
}
|
|
1001
1023
|
function resolveNetwork(config, crm) {
|
|
1002
1024
|
const seen = /* @__PURE__ */ new Set();
|
|
1003
1025
|
const targets = [];
|
|
@@ -1049,24 +1071,81 @@ function resolveNetwork(config, crm) {
|
|
|
1049
1071
|
`defineChapter.network.targets.${target.id}.binding: must be an uppercase Worker binding identifier`
|
|
1050
1072
|
);
|
|
1051
1073
|
}
|
|
1074
|
+
const sharedNotes = target.sharedNotes ?? [];
|
|
1075
|
+
validateSharedNotes({
|
|
1076
|
+
path: `defineChapter.network.targets.${target.id}.sharedNotes`,
|
|
1077
|
+
values: sharedNotes,
|
|
1078
|
+
crm,
|
|
1079
|
+
fields
|
|
1080
|
+
});
|
|
1052
1081
|
targets.push({
|
|
1053
1082
|
id: target.id,
|
|
1054
1083
|
name: target.name?.trim() || target.id,
|
|
1055
1084
|
url: url.origin,
|
|
1056
1085
|
...binding ? { binding } : {},
|
|
1057
1086
|
secretName,
|
|
1058
|
-
...fields ? { fields } : {}
|
|
1087
|
+
...fields ? { fields } : {},
|
|
1088
|
+
sharedNotes
|
|
1059
1089
|
});
|
|
1060
1090
|
}
|
|
1061
|
-
|
|
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
|
|
1134
|
+
});
|
|
1135
|
+
readers.push({ id: reader.id, fields: reader.fields, sharedNotes });
|
|
1136
|
+
}
|
|
1137
|
+
return { targets, readers };
|
|
1062
1138
|
}
|
|
1139
|
+
|
|
1140
|
+
// src/config.ts
|
|
1141
|
+
var SLUG2 = /^[a-z0-9][a-z0-9-]{1,62}$/;
|
|
1063
1142
|
function isResolvedCrm(x) {
|
|
1064
1143
|
return !!x && typeof x === "object" && "prepare" in x && typeof x.prepare === "function";
|
|
1065
1144
|
}
|
|
1066
1145
|
function defineChapter(config) {
|
|
1067
1146
|
if (!config || typeof config !== "object") throw new Error("defineChapter: a config object is required");
|
|
1068
1147
|
const { id: id2, name } = config;
|
|
1069
|
-
if (typeof id2 !== "string" || !
|
|
1148
|
+
if (typeof id2 !== "string" || !SLUG2.test(id2)) {
|
|
1070
1149
|
throw new Error(`defineChapter.id: must be a lowercase slug [a-z0-9-] (2-63 chars) \u2014 got ${JSON.stringify(id2)}`);
|
|
1071
1150
|
}
|
|
1072
1151
|
if (typeof name !== "string" || name.trim() === "") throw new Error("defineChapter.name: a non-empty name is required");
|
|
@@ -1091,7 +1170,7 @@ function defineChapter(config) {
|
|
|
1091
1170
|
const copy = resolveChapterCopy(config.copy);
|
|
1092
1171
|
const network = resolveNetwork(config, crm);
|
|
1093
1172
|
const formation = resolveLeaderFormation(config.formation, crm);
|
|
1094
|
-
const { schema, rules } = chapterDb(mode, auth);
|
|
1173
|
+
const { schema, rules } = chapterDb(mode, auth, network.targets.length > 0);
|
|
1095
1174
|
const services = config.services ?? ["db", "calendar", "o11y"];
|
|
1096
1175
|
const account = config.account ?? "none";
|
|
1097
1176
|
if (account !== "invite" && account !== "create" && account !== "none") {
|
|
@@ -1347,6 +1426,11 @@ function createChapterIntegration(chapter, options = {}) {
|
|
|
1347
1426
|
// The snapshot route is deliberately private. A credential-free smoke
|
|
1348
1427
|
// request must prove that the route is mounted and closed.
|
|
1349
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
|
+
}] : [],
|
|
1350
1434
|
// Formation metadata is public only when the host explicitly enables
|
|
1351
1435
|
// the bounded intake contract.
|
|
1352
1436
|
...chapter.formation.enabled ? [{ path: "/api/formation/config", expectedStatus: 200 }] : []
|