@odla-ai/chapter 0.46.0 → 0.47.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 +4 -2
- package/dist/{copy-context-GaxckY3T.d.ts → copy-context-20WFI5TI.d.ts} +8 -4
- package/dist/index.cjs +7 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -10
- package/dist/index.d.ts +39 -10
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/ui/admin/index.d.ts +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/member/index.d.ts +2 -2
- package/dist/worker/index.cjs +73 -22
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +8 -4
- package/dist/worker/index.d.ts +8 -4
- package/dist/worker/index.js +73 -22
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/worker/index.d.cts
CHANGED
|
@@ -514,7 +514,7 @@ interface ChapterScheduling {
|
|
|
514
514
|
*
|
|
515
515
|
* - `"claim"` reads a role ladder from a JWT claim (the member-site model:
|
|
516
516
|
* `provisional → member → admin`). Admin is the last (highest) rung; the
|
|
517
|
-
*
|
|
517
|
+
* Studio-managed super-admin tier sits above it.
|
|
518
518
|
* - `"table"` gates on the deny-all `admins` allowlist (the hub model):
|
|
519
519
|
* a binary "email is an admin or isn't", set only in odla Studio.
|
|
520
520
|
*
|
|
@@ -526,10 +526,13 @@ interface ChapterAuth {
|
|
|
526
526
|
/** Role ladder low→high, for `source: "claim"`. The last entry is admin.
|
|
527
527
|
* Default `["provisional", "member", "admin"]`. */
|
|
528
528
|
ladder?: readonly string[];
|
|
529
|
-
/**
|
|
530
|
-
* create/modify admins
|
|
531
|
-
* odla Studio. Default: `true` for `"claim"`, `false` for `"table"`. */
|
|
529
|
+
/** Enable the Studio-managed super-admin tier — the only tier that may
|
|
530
|
+
* create/modify admins. Default: `true` for `"claim"`, `false` for `"table"`. */
|
|
532
531
|
superAdmins?: boolean;
|
|
532
|
+
/** Authority behind the super-admin tier. `"table"` preserves the legacy
|
|
533
|
+
* app allowlist; `"odla"` reads protected `$users.role`. Default `"table"`
|
|
534
|
+
* for backward compatibility. */
|
|
535
|
+
superAdminSource?: "table" | "odla";
|
|
533
536
|
}
|
|
534
537
|
/** The fully-resolved auth policy (defaults applied) carried on the {@link Chapter}. */
|
|
535
538
|
interface ResolvedAuth {
|
|
@@ -539,6 +542,7 @@ interface ResolvedAuth {
|
|
|
539
542
|
/** The highest ladder rung (last entry) — the "admin" gate. */
|
|
540
543
|
adminRole: string;
|
|
541
544
|
superAdmins: boolean;
|
|
545
|
+
superAdminSource: "none" | "table" | "odla";
|
|
542
546
|
}
|
|
543
547
|
/** The application status pipeline. Which statuses exist, and the subsets a site
|
|
544
548
|
* allows a call to be booked from / an application approved from. Defaults to
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -514,7 +514,7 @@ interface ChapterScheduling {
|
|
|
514
514
|
*
|
|
515
515
|
* - `"claim"` reads a role ladder from a JWT claim (the member-site model:
|
|
516
516
|
* `provisional → member → admin`). Admin is the last (highest) rung; the
|
|
517
|
-
*
|
|
517
|
+
* Studio-managed super-admin tier sits above it.
|
|
518
518
|
* - `"table"` gates on the deny-all `admins` allowlist (the hub model):
|
|
519
519
|
* a binary "email is an admin or isn't", set only in odla Studio.
|
|
520
520
|
*
|
|
@@ -526,10 +526,13 @@ interface ChapterAuth {
|
|
|
526
526
|
/** Role ladder low→high, for `source: "claim"`. The last entry is admin.
|
|
527
527
|
* Default `["provisional", "member", "admin"]`. */
|
|
528
528
|
ladder?: readonly string[];
|
|
529
|
-
/**
|
|
530
|
-
* create/modify admins
|
|
531
|
-
* odla Studio. Default: `true` for `"claim"`, `false` for `"table"`. */
|
|
529
|
+
/** Enable the Studio-managed super-admin tier — the only tier that may
|
|
530
|
+
* create/modify admins. Default: `true` for `"claim"`, `false` for `"table"`. */
|
|
532
531
|
superAdmins?: boolean;
|
|
532
|
+
/** Authority behind the super-admin tier. `"table"` preserves the legacy
|
|
533
|
+
* app allowlist; `"odla"` reads protected `$users.role`. Default `"table"`
|
|
534
|
+
* for backward compatibility. */
|
|
535
|
+
superAdminSource?: "table" | "odla";
|
|
533
536
|
}
|
|
534
537
|
/** The fully-resolved auth policy (defaults applied) carried on the {@link Chapter}. */
|
|
535
538
|
interface ResolvedAuth {
|
|
@@ -539,6 +542,7 @@ interface ResolvedAuth {
|
|
|
539
542
|
/** The highest ladder rung (last entry) — the "admin" gate. */
|
|
540
543
|
adminRole: string;
|
|
541
544
|
superAdmins: boolean;
|
|
545
|
+
superAdminSource: "none" | "table" | "odla";
|
|
542
546
|
}
|
|
543
547
|
/** The application status pipeline. Which statuses exist, and the subsets a site
|
|
544
548
|
* allows a call to be booked from / an application approved from. Defaults to
|
package/dist/worker/index.js
CHANGED
|
@@ -80,12 +80,16 @@ function createWorkerContext(options) {
|
|
|
80
80
|
}
|
|
81
81
|
async function isAdminEmail(db, email) {
|
|
82
82
|
if (!email) return false;
|
|
83
|
-
const { admins } = await db.query({ admins: { $: { where: { email: email.toLowerCase() }, limit: 1 } } });
|
|
83
|
+
const { admins } = await db.query({ admins: { $: { where: { email: email.trim().toLowerCase() }, limit: 1 } } });
|
|
84
84
|
return Array.isArray(admins) && admins.length > 0;
|
|
85
85
|
}
|
|
86
86
|
async function isSuperAdminEmail(db, email) {
|
|
87
87
|
if (!auth.superAdmins || !email) return false;
|
|
88
|
-
|
|
88
|
+
if (auth.superAdminSource === "odla") {
|
|
89
|
+
const { $users } = await db.query({ $users: { $: { where: { email: email.trim().toLowerCase(), role: "super_admin" }, limit: 1 } } });
|
|
90
|
+
return Array.isArray($users) && $users.length > 0;
|
|
91
|
+
}
|
|
92
|
+
const { superAdmins } = await db.query({ superAdmins: { $: { where: { email: email.trim().toLowerCase() }, limit: 1 } } });
|
|
89
93
|
return Array.isArray(superAdmins) && superAdmins.length > 0;
|
|
90
94
|
}
|
|
91
95
|
async function roleFor(db, u) {
|
|
@@ -6091,9 +6095,39 @@ var IDENTIFIER = /^[a-z][a-zA-Z0-9_]*$/;
|
|
|
6091
6095
|
var MAX_PAGE = 50;
|
|
6092
6096
|
var MAX_OFFSET = 1e4;
|
|
6093
6097
|
var MAX_SEARCH = 200;
|
|
6098
|
+
var MAX_STAGE = 100;
|
|
6094
6099
|
var MAX_BODY = 32768;
|
|
6095
6100
|
var RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9:_-]{0,191}$/;
|
|
6096
6101
|
var MUTATION_ID2 = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
|
|
6102
|
+
var NETWORK_RECORD_SORTS = [
|
|
6103
|
+
{ id: "updated_desc", label: "Recently updated" },
|
|
6104
|
+
{ id: "updated_asc", label: "Least recently updated" },
|
|
6105
|
+
{ id: "name_asc", label: "Name A\u2013Z" },
|
|
6106
|
+
{ id: "name_desc", label: "Name Z\u2013A" }
|
|
6107
|
+
];
|
|
6108
|
+
var NETWORK_RECORD_SORT_COLUMNS = {
|
|
6109
|
+
updated_desc: { field: "updatedAt", dir: "desc" },
|
|
6110
|
+
updated_asc: { field: "updatedAt", dir: "asc" },
|
|
6111
|
+
name_asc: { field: "name", dir: "asc" },
|
|
6112
|
+
name_desc: { field: "name", dir: "desc" }
|
|
6113
|
+
};
|
|
6114
|
+
function networkRecordWindow(params) {
|
|
6115
|
+
const limit = integer(params.get("limit"), 25, 1, MAX_PAGE);
|
|
6116
|
+
const offset = integer(params.get("offset"), 0, 0, MAX_OFFSET);
|
|
6117
|
+
const search = (params.get("search") ?? "").trim();
|
|
6118
|
+
const stage = (params.get("stage") ?? "").trim();
|
|
6119
|
+
const requestedSort = (params.get("sort") ?? "updated_desc").trim();
|
|
6120
|
+
const sortId = NETWORK_RECORD_SORTS.some((candidate) => candidate.id === requestedSort) ? requestedSort : null;
|
|
6121
|
+
if (limit == null || offset == null || search.length > MAX_SEARCH || stage.length > 0 && (stage.length > MAX_STAGE || /[\u0000-\u001f\u007f]/.test(stage)) || !sortId) return null;
|
|
6122
|
+
return {
|
|
6123
|
+
limit,
|
|
6124
|
+
offset,
|
|
6125
|
+
...search ? { search } : {},
|
|
6126
|
+
...stage ? { stage } : {},
|
|
6127
|
+
sortId,
|
|
6128
|
+
sort: NETWORK_RECORD_SORT_COLUMNS[sortId]
|
|
6129
|
+
};
|
|
6130
|
+
}
|
|
6097
6131
|
function bodyTooLarge(body) {
|
|
6098
6132
|
return new TextEncoder().encode(body).byteLength > MAX_BODY;
|
|
6099
6133
|
}
|
|
@@ -6296,20 +6330,22 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
6296
6330
|
const type = url.searchParams.get("type") ?? "";
|
|
6297
6331
|
const allowed2 = reader.fields[type];
|
|
6298
6332
|
if (!IDENTIFIER.test(type) || !allowed2) return json({ error: "record type is not shared with this reader" }, 403);
|
|
6299
|
-
const
|
|
6300
|
-
|
|
6301
|
-
const
|
|
6302
|
-
|
|
6303
|
-
|
|
6333
|
+
const window = networkRecordWindow(url.searchParams);
|
|
6334
|
+
if (!window) return json({ error: "invalid record query parameters" }, 400);
|
|
6335
|
+
const typeDef = ctx.chapter.crm.type(type);
|
|
6336
|
+
const stages = typeDef.pipeline?.stages ?? [];
|
|
6337
|
+
if (window.stage && !stages.some((stage) => stage.id === window.stage)) {
|
|
6338
|
+
return json({ error: "stage is not defined for this record type" }, 400);
|
|
6304
6339
|
}
|
|
6305
6340
|
const page = await listRecords(
|
|
6306
6341
|
{ crm: ctx.chapter.crm, db },
|
|
6307
6342
|
{
|
|
6308
6343
|
type,
|
|
6309
|
-
limit,
|
|
6310
|
-
offset,
|
|
6311
|
-
...search ? { search } : {},
|
|
6312
|
-
|
|
6344
|
+
limit: window.limit,
|
|
6345
|
+
offset: window.offset,
|
|
6346
|
+
...window.search ? { search: window.search } : {},
|
|
6347
|
+
...window.stage ? { stage: [window.stage] } : {},
|
|
6348
|
+
sort: window.sort
|
|
6313
6349
|
}
|
|
6314
6350
|
);
|
|
6315
6351
|
const response = {
|
|
@@ -6319,7 +6355,15 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
|
|
|
6319
6355
|
records: page.records.map((record4) => readerRecord(record4, reader, ctx.chapter.crm)),
|
|
6320
6356
|
total: page.total,
|
|
6321
6357
|
limit: page.limit,
|
|
6322
|
-
offset: page.offset
|
|
6358
|
+
offset: page.offset,
|
|
6359
|
+
filters: {
|
|
6360
|
+
stages: stages.map((stage) => ({ id: stage.id, label: stage.label ?? stage.id })),
|
|
6361
|
+
sorts: [...NETWORK_RECORD_SORTS]
|
|
6362
|
+
},
|
|
6363
|
+
appliedQuery: {
|
|
6364
|
+
...window.stage ? { stage: window.stage } : {},
|
|
6365
|
+
sort: window.sortId
|
|
6366
|
+
}
|
|
6323
6367
|
};
|
|
6324
6368
|
return json(response);
|
|
6325
6369
|
};
|
|
@@ -6352,7 +6396,8 @@ async function readTargetRecords(db, env, ctx, target, query) {
|
|
|
6352
6396
|
const upstream = body && "error" in body && typeof body.error === "string" ? body.error : void 0;
|
|
6353
6397
|
return {
|
|
6354
6398
|
ok: false,
|
|
6355
|
-
error: upstream ?? (response.ok ? "invalid follower record response" : `follower returned ${response.status}`)
|
|
6399
|
+
error: upstream ?? (response.ok ? "invalid follower record response" : `follower returned ${response.status}`),
|
|
6400
|
+
...response.status === 400 ? { status: 400 } : {}
|
|
6356
6401
|
};
|
|
6357
6402
|
}
|
|
6358
6403
|
return {
|
|
@@ -6443,16 +6488,22 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
|
|
|
6443
6488
|
const type = url.searchParams.get("type") ?? "";
|
|
6444
6489
|
const target = targetFor2(ctx, targetId);
|
|
6445
6490
|
if (!target || !IDENTIFIER.test(type)) return json({ error: "configured targetId and record type are required" }, 400);
|
|
6446
|
-
const
|
|
6447
|
-
|
|
6448
|
-
const
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6491
|
+
const window = networkRecordWindow(url.searchParams);
|
|
6492
|
+
if (!window) return json({ error: "invalid record query parameters" }, 400);
|
|
6493
|
+
const query = new URLSearchParams({
|
|
6494
|
+
type,
|
|
6495
|
+
limit: String(window.limit),
|
|
6496
|
+
offset: String(window.offset),
|
|
6497
|
+
sort: window.sortId
|
|
6498
|
+
});
|
|
6499
|
+
if (window.search) query.set("search", window.search);
|
|
6500
|
+
if (window.stage) query.set("stage", window.stage);
|
|
6454
6501
|
const result = await readTargetRecords(got.db, env, ctx, target, query);
|
|
6455
|
-
|
|
6502
|
+
if (!result.ok) return json({ error: result.error }, result.status ?? 502);
|
|
6503
|
+
if ((window.stage || url.searchParams.has("sort")) && (!result.page.appliedQuery || result.page.appliedQuery.sort !== window.sortId || result.page.appliedQuery.stage !== window.stage)) {
|
|
6504
|
+
return json({ error: "follower did not apply the requested record query" }, 502);
|
|
6505
|
+
}
|
|
6506
|
+
return json(result.page);
|
|
6456
6507
|
};
|
|
6457
6508
|
|
|
6458
6509
|
// src/worker-routes-network-notes.ts
|