@odla-ai/chapter 0.47.0 → 0.47.2

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.
@@ -6087,7 +6087,13 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
6087
6087
  };
6088
6088
 
6089
6089
  // src/worker-routes-network-records-follower.ts
6090
- import { CRM_NS, CrmInputError, getRecord as getRecord4, listRecords, setStage as setStage2, updateRecord as updateRecord3 } from "@odla-ai/crm";
6090
+ import {
6091
+ CRM_NS as CRM_NS2,
6092
+ CrmInputError,
6093
+ getRecord as getRecord4,
6094
+ setStage as setStage2,
6095
+ updateRecord as updateRecord3
6096
+ } from "@odla-ai/crm";
6091
6097
  import { verifyFederatedRequest as verifyFederatedRequest6 } from "@odla-ai/db";
6092
6098
 
6093
6099
  // src/worker-routes-network-records-common.ts
@@ -6095,9 +6101,39 @@ var IDENTIFIER = /^[a-z][a-zA-Z0-9_]*$/;
6095
6101
  var MAX_PAGE = 50;
6096
6102
  var MAX_OFFSET = 1e4;
6097
6103
  var MAX_SEARCH = 200;
6104
+ var MAX_STAGE = 100;
6098
6105
  var MAX_BODY = 32768;
6099
6106
  var RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9:_-]{0,191}$/;
6100
6107
  var MUTATION_ID2 = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{7,127}$/;
6108
+ var NETWORK_RECORD_SORTS = [
6109
+ { id: "updated_desc", label: "Recently updated" },
6110
+ { id: "updated_asc", label: "Least recently updated" },
6111
+ { id: "name_asc", label: "Name A\u2013Z" },
6112
+ { id: "name_desc", label: "Name Z\u2013A" }
6113
+ ];
6114
+ var NETWORK_RECORD_SORT_COLUMNS = {
6115
+ updated_desc: { field: "updatedAt", dir: "desc" },
6116
+ updated_asc: { field: "updatedAt", dir: "asc" },
6117
+ name_asc: { field: "name", dir: "asc" },
6118
+ name_desc: { field: "name", dir: "desc" }
6119
+ };
6120
+ function networkRecordWindow(params) {
6121
+ const limit = integer(params.get("limit"), 25, 1, MAX_PAGE);
6122
+ const offset = integer(params.get("offset"), 0, 0, MAX_OFFSET);
6123
+ const search = (params.get("search") ?? "").trim();
6124
+ const stage = (params.get("stage") ?? "").trim();
6125
+ const requestedSort = (params.get("sort") ?? "updated_desc").trim();
6126
+ const sortId = NETWORK_RECORD_SORTS.some((candidate) => candidate.id === requestedSort) ? requestedSort : null;
6127
+ if (limit == null || offset == null || search.length > MAX_SEARCH || stage.length > 0 && (stage.length > MAX_STAGE || /[\u0000-\u001f\u007f]/.test(stage)) || !sortId) return null;
6128
+ return {
6129
+ limit,
6130
+ offset,
6131
+ ...search ? { search } : {},
6132
+ ...stage ? { stage } : {},
6133
+ sortId,
6134
+ sort: NETWORK_RECORD_SORT_COLUMNS[sortId]
6135
+ };
6136
+ }
6101
6137
  function bodyTooLarge(body) {
6102
6138
  return new TextEncoder().encode(body).byteLength > MAX_BODY;
6103
6139
  }
@@ -6152,6 +6188,45 @@ function restrictedRecord(record4, target) {
6152
6188
  return { ...record4, ...edit ? { edit } : { edit: void 0 } };
6153
6189
  }
6154
6190
 
6191
+ // src/worker-routes-network-records-search.ts
6192
+ import { CRM_NS, rowToRecord } from "@odla-ai/crm";
6193
+ var MAX_SAFE_LIKE_BYTES = 50;
6194
+ function safePrefix(value) {
6195
+ const prefixed = `${value}%`;
6196
+ return value.includes("%") || value.includes("_") || new TextEncoder().encode(prefixed).byteLength > MAX_SAFE_LIKE_BYTES ? value : { $like: prefixed };
6197
+ }
6198
+ async function listReaderRecords(db, type, allowed2, emailField, window) {
6199
+ const clauses = [
6200
+ { type },
6201
+ { archived: false },
6202
+ ...window.stage ? [{ stage: window.stage }] : []
6203
+ ];
6204
+ if (window.search) {
6205
+ const visible = [{ name: safePrefix(window.search) }];
6206
+ if (emailField && allowed2.includes(emailField)) {
6207
+ visible.push({ primaryEmail: safePrefix(window.search.toLowerCase()) });
6208
+ }
6209
+ clauses.push({ or: visible });
6210
+ }
6211
+ const where = { and: clauses };
6212
+ const crmDb = db;
6213
+ const [result, aggregate] = await Promise.all([
6214
+ crmDb.query({
6215
+ [CRM_NS.record]: { $: {
6216
+ where,
6217
+ order: { [window.sort.field]: window.sort.dir },
6218
+ limit: window.limit,
6219
+ offset: window.offset
6220
+ } }
6221
+ }),
6222
+ crmDb.aggregate(CRM_NS.record, { count: true }, { where })
6223
+ ]);
6224
+ return {
6225
+ records: (result[CRM_NS.record] ?? []).map(rowToRecord),
6226
+ total: aggregate.count ?? 0
6227
+ };
6228
+ }
6229
+
6155
6230
  // src/worker-routes-network-records-follower.ts
6156
6231
  var NETWORK_MUTATION_META = "chapterNetworkMutation";
6157
6232
  function mutationFingerprint(input) {
@@ -6178,9 +6253,9 @@ function mutationError(error) {
6178
6253
  }
6179
6254
  async function priorMutation(db, id) {
6180
6255
  const result = await db.query({
6181
- [CRM_NS.activity]: { $: { where: { id }, limit: 1 } }
6256
+ [CRM_NS2.activity]: { $: { where: { id }, limit: 1 } }
6182
6257
  });
6183
- const row = (result[CRM_NS.activity] ?? [])[0];
6258
+ const row = (result[CRM_NS2.activity] ?? [])[0];
6184
6259
  const meta = row?.meta;
6185
6260
  const receipt = meta?.[NETWORK_MUTATION_META];
6186
6261
  return typeof receipt?.fingerprint === "string" ? { fingerprint: receipt.fingerprint } : null;
@@ -6300,21 +6375,19 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6300
6375
  const type = url.searchParams.get("type") ?? "";
6301
6376
  const allowed2 = reader.fields[type];
6302
6377
  if (!IDENTIFIER.test(type) || !allowed2) return json({ error: "record type is not shared with this reader" }, 403);
6303
- const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
6304
- const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
6305
- const search = (url.searchParams.get("search") ?? "").trim();
6306
- if (limit == null || offset == null || search.length > MAX_SEARCH) {
6307
- return json({ error: "invalid pagination or search parameters" }, 400);
6308
- }
6309
- const page = await listRecords(
6310
- { crm: ctx.chapter.crm, db },
6311
- {
6312
- type,
6313
- limit,
6314
- offset,
6315
- ...search ? { search } : {},
6316
- sort: { field: "updatedAt", dir: "desc" }
6317
- }
6378
+ const window = networkRecordWindow(url.searchParams);
6379
+ if (!window) return json({ error: "invalid record query parameters" }, 400);
6380
+ const typeDef = ctx.chapter.crm.type(type);
6381
+ const stages = typeDef.pipeline?.stages ?? [];
6382
+ if (window.stage && !stages.some((stage) => stage.id === window.stage)) {
6383
+ return json({ error: "stage is not defined for this record type" }, 400);
6384
+ }
6385
+ const page = await listReaderRecords(
6386
+ db,
6387
+ type,
6388
+ allowed2,
6389
+ typeDef.emailField,
6390
+ window
6318
6391
  );
6319
6392
  const response = {
6320
6393
  version: 1,
@@ -6322,8 +6395,16 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6322
6395
  type,
6323
6396
  records: page.records.map((record4) => readerRecord(record4, reader, ctx.chapter.crm)),
6324
6397
  total: page.total,
6325
- limit: page.limit,
6326
- offset: page.offset
6398
+ limit: window.limit,
6399
+ offset: window.offset,
6400
+ filters: {
6401
+ stages: stages.map((stage) => ({ id: stage.id, label: stage.label ?? stage.id })),
6402
+ sorts: [...NETWORK_RECORD_SORTS]
6403
+ },
6404
+ appliedQuery: {
6405
+ ...window.stage ? { stage: window.stage } : {},
6406
+ sort: window.sortId
6407
+ }
6327
6408
  };
6328
6409
  return json(response);
6329
6410
  };
@@ -6356,7 +6437,8 @@ async function readTargetRecords(db, env, ctx, target, query) {
6356
6437
  const upstream = body && "error" in body && typeof body.error === "string" ? body.error : void 0;
6357
6438
  return {
6358
6439
  ok: false,
6359
- error: upstream ?? (response.ok ? "invalid follower record response" : `follower returned ${response.status}`)
6440
+ error: upstream ?? (response.ok ? "invalid follower record response" : `follower returned ${response.status}`),
6441
+ ...response.status === 400 ? { status: 400 } : {}
6360
6442
  };
6361
6443
  }
6362
6444
  return {
@@ -6447,16 +6529,22 @@ var handleAdminNetworkRecords = async (req, url, env, ctx) => {
6447
6529
  const type = url.searchParams.get("type") ?? "";
6448
6530
  const target = targetFor2(ctx, targetId);
6449
6531
  if (!target || !IDENTIFIER.test(type)) return json({ error: "configured targetId and record type are required" }, 400);
6450
- const limit = integer(url.searchParams.get("limit"), 25, 1, MAX_PAGE);
6451
- const offset = integer(url.searchParams.get("offset"), 0, 0, MAX_OFFSET);
6452
- const search = (url.searchParams.get("search") ?? "").trim();
6453
- if (limit == null || offset == null || search.length > MAX_SEARCH) {
6454
- return json({ error: "invalid pagination or search parameters" }, 400);
6455
- }
6456
- const query = new URLSearchParams({ type, limit: String(limit), offset: String(offset) });
6457
- if (search) query.set("search", search);
6532
+ const window = networkRecordWindow(url.searchParams);
6533
+ if (!window) return json({ error: "invalid record query parameters" }, 400);
6534
+ const query = new URLSearchParams({
6535
+ type,
6536
+ limit: String(window.limit),
6537
+ offset: String(window.offset),
6538
+ sort: window.sortId
6539
+ });
6540
+ if (window.search) query.set("search", window.search);
6541
+ if (window.stage) query.set("stage", window.stage);
6458
6542
  const result = await readTargetRecords(got.db, env, ctx, target, query);
6459
- return result.ok ? json(result.page) : json({ error: result.error }, 502);
6543
+ if (!result.ok) return json({ error: result.error }, result.status ?? 502);
6544
+ if ((window.stage || url.searchParams.has("sort")) && (!result.page.appliedQuery || result.page.appliedQuery.sort !== window.sortId || result.page.appliedQuery.stage !== window.stage)) {
6545
+ return json({ error: "follower did not apply the requested record query" }, 502);
6546
+ }
6547
+ return json(result.page);
6460
6548
  };
6461
6549
 
6462
6550
  // src/worker-routes-network-notes.ts
@@ -6546,7 +6634,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
6546
6634
  // src/worker-routes-network-shared-notes.ts
6547
6635
  import {
6548
6636
  addActivity,
6549
- CRM_NS as CRM_NS2,
6637
+ CRM_NS as CRM_NS3,
6550
6638
  getRecord as getRecord5
6551
6639
  } from "@odla-ai/crm";
6552
6640
  import { signFederatedRequest as signFederatedRequest5, verifyFederatedRequest as verifyFederatedRequest7 } from "@odla-ai/db";
@@ -6578,7 +6666,7 @@ function sharedNote(activity, sourceId) {
6578
6666
  }
6579
6667
  async function listSharedNotes(db, sourceId, recordId) {
6580
6668
  const result = await db.query({
6581
- [CRM_NS2.activity]: {
6669
+ [CRM_NS3.activity]: {
6582
6670
  $: {
6583
6671
  where: {
6584
6672
  and: [
@@ -6591,7 +6679,7 @@ async function listSharedNotes(db, sourceId, recordId) {
6591
6679
  }
6592
6680
  }
6593
6681
  });
6594
- return (result[CRM_NS2.activity] ?? []).flatMap((activity) => {
6682
+ return (result[CRM_NS3.activity] ?? []).flatMap((activity) => {
6595
6683
  const note = sharedNote(activity, sourceId);
6596
6684
  return note ? [note] : [];
6597
6685
  });