@odla-ai/chapter 0.47.1 → 0.47.3

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.
@@ -6117,7 +6117,7 @@ var handleAdminNetworkPush = async (req, url, env, ctx) => {
6117
6117
  };
6118
6118
 
6119
6119
  // src/worker-routes-network-records-follower.ts
6120
- var import_crm9 = require("@odla-ai/crm");
6120
+ var import_crm10 = require("@odla-ai/crm");
6121
6121
  var import_db9 = require("@odla-ai/db");
6122
6122
 
6123
6123
  // src/worker-routes-network-records-common.ts
@@ -6212,6 +6212,85 @@ function restrictedRecord(record4, target) {
6212
6212
  return { ...record4, ...edit ? { edit } : { edit: void 0 } };
6213
6213
  }
6214
6214
 
6215
+ // src/worker-routes-network-records-search.ts
6216
+ var import_crm9 = require("@odla-ai/crm");
6217
+ var SEARCH_BATCH_SIZE = 1e3;
6218
+ var MAX_READER_SEARCH_POPULATION = 1e4;
6219
+ var ReaderSearchPopulationError = class extends Error {
6220
+ count;
6221
+ constructor(count) {
6222
+ super(`reader-visible search population exceeds ${MAX_READER_SEARCH_POPULATION} records`);
6223
+ this.name = "ReaderSearchPopulationError";
6224
+ this.count = count;
6225
+ }
6226
+ };
6227
+ function folded(value) {
6228
+ return value.replace(/[øØ]/gu, "o").replace(/[łŁ]/gu, "l").replace(/[đĐðÐ]/gu, "d").replace(/[æÆ]/gu, "ae").replace(/[œŒ]/gu, "oe").replace(/[þÞ]/gu, "th").replace(/ß/gu, "ss").normalize("NFKD").replace(/\p{M}/gu, "").toLocaleLowerCase("en-US").replace(/[^\p{L}\p{N}@._+-]+/gu, " ").trim();
6229
+ }
6230
+ function terms(value) {
6231
+ return folded(value).split(/\s+/u).filter(Boolean);
6232
+ }
6233
+ function matchesReaderIdentity(record4, search, includeEmail) {
6234
+ const query = terms(search);
6235
+ if (!query.length) return true;
6236
+ const name = folded(record4.name);
6237
+ const visible = [name, ...name.split(/\s+/u).filter(Boolean)];
6238
+ if (includeEmail && record4.primaryEmail) visible.push(folded(record4.primaryEmail));
6239
+ return query.every((term) => visible.some((candidate) => candidate.includes(term)));
6240
+ }
6241
+ function baseWhere(type, stage) {
6242
+ return {
6243
+ and: [
6244
+ { type },
6245
+ { archived: false },
6246
+ ...stage ? [{ stage }] : []
6247
+ ]
6248
+ };
6249
+ }
6250
+ async function listVisibleSearchPopulation(db, where, window, count) {
6251
+ if (count > MAX_READER_SEARCH_POPULATION) throw new ReaderSearchPopulationError(count);
6252
+ const offsets = Array.from(
6253
+ { length: Math.ceil(count / SEARCH_BATCH_SIZE) },
6254
+ (_, index2) => index2 * SEARCH_BATCH_SIZE
6255
+ );
6256
+ const pages = await Promise.all(offsets.map((offset) => db.query({
6257
+ [import_crm9.CRM_NS.record]: { $: {
6258
+ where,
6259
+ order: { [window.sort.field]: window.sort.dir },
6260
+ limit: Math.min(SEARCH_BATCH_SIZE, count - offset),
6261
+ offset
6262
+ } }
6263
+ })));
6264
+ return pages.flatMap((page) => (page[import_crm9.CRM_NS.record] ?? []).map(import_crm9.rowToRecord));
6265
+ }
6266
+ async function listReaderRecords(db, type, allowed2, emailField, window) {
6267
+ const where = baseWhere(type, window.stage);
6268
+ const crmDb = db;
6269
+ const aggregate = await crmDb.aggregate(import_crm9.CRM_NS.record, { count: true }, { where });
6270
+ const candidateCount = aggregate.count ?? 0;
6271
+ if (window.search) {
6272
+ const population = await listVisibleSearchPopulation(crmDb, where, window, candidateCount);
6273
+ const includeEmail = Boolean(emailField && allowed2.includes(emailField));
6274
+ const matches = population.filter((record4) => matchesReaderIdentity(record4, window.search, includeEmail));
6275
+ return {
6276
+ records: matches.slice(window.offset, window.offset + window.limit),
6277
+ total: matches.length
6278
+ };
6279
+ }
6280
+ const result = await crmDb.query({
6281
+ [import_crm9.CRM_NS.record]: { $: {
6282
+ where,
6283
+ order: { [window.sort.field]: window.sort.dir },
6284
+ limit: window.limit,
6285
+ offset: window.offset
6286
+ } }
6287
+ });
6288
+ return {
6289
+ records: (result[import_crm9.CRM_NS.record] ?? []).map(import_crm9.rowToRecord),
6290
+ total: candidateCount
6291
+ };
6292
+ }
6293
+
6215
6294
  // src/worker-routes-network-records-follower.ts
6216
6295
  var NETWORK_MUTATION_META = "chapterNetworkMutation";
6217
6296
  function mutationFingerprint(input) {
@@ -6231,16 +6310,16 @@ function mutationError(error) {
6231
6310
  if (error?.code === "transact_guard_failed") {
6232
6311
  return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
6233
6312
  }
6234
- if (error instanceof import_crm9.CrmInputError) {
6313
+ if (error instanceof import_crm10.CrmInputError) {
6235
6314
  return json({ error: error.message, code: "invalid_transition_or_fields", ...error.fields ? { fields: error.fields } : {} }, 422);
6236
6315
  }
6237
6316
  return json({ error: "follower mutation failed", code: "mutation_failed" }, 500);
6238
6317
  }
6239
6318
  async function priorMutation(db, id) {
6240
6319
  const result = await db.query({
6241
- [import_crm9.CRM_NS.activity]: { $: { where: { id }, limit: 1 } }
6320
+ [import_crm10.CRM_NS.activity]: { $: { where: { id }, limit: 1 } }
6242
6321
  });
6243
- const row = (result[import_crm9.CRM_NS.activity] ?? [])[0];
6322
+ const row = (result[import_crm10.CRM_NS.activity] ?? [])[0];
6244
6323
  const meta = row?.meta;
6245
6324
  const receipt = meta?.[NETWORK_MUTATION_META];
6246
6325
  return typeof receipt?.fingerprint === "string" ? { fingerprint: receipt.fingerprint } : null;
@@ -6294,7 +6373,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6294
6373
  if (prior.fingerprint !== fingerprint) {
6295
6374
  return json({ error: "mutationId was already used for different input", code: "mutation_conflict" }, 409);
6296
6375
  }
6297
- const replayed = await (0, import_crm9.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
6376
+ const replayed = await (0, import_crm10.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
6298
6377
  if (!replayed || replayed.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
6299
6378
  const result = {
6300
6379
  version: 1,
@@ -6304,7 +6383,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6304
6383
  };
6305
6384
  return json(result);
6306
6385
  }
6307
- const record4 = await (0, import_crm9.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
6386
+ const record4 = await (0, import_crm10.getRecord)({ crm: ctx.chapter.crm, db }, input.recordId);
6308
6387
  if (!record4 || record4.type !== input.type) return json({ error: "record not found", code: "not_found" }, 404);
6309
6388
  if (record4.updatedAt !== input.expectedRevision) {
6310
6389
  return json({ error: "record changed since it was reviewed", code: "stale_revision" }, 409);
@@ -6325,7 +6404,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6325
6404
  };
6326
6405
  try {
6327
6406
  const deps2 = { crm: ctx.chapter.crm, db };
6328
- const updated = hasFields ? await (0, import_crm9.updateRecord)(deps2, {
6407
+ const updated = hasFields ? await (0, import_crm10.updateRecord)(deps2, {
6329
6408
  id: record4.id,
6330
6409
  input: fields,
6331
6410
  mutationId: activityId,
@@ -6336,7 +6415,7 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6336
6415
  authorId: `network:${verified.sender}`,
6337
6416
  meta: auditMeta
6338
6417
  }
6339
- }) : await (0, import_crm9.setStage)(deps2, {
6418
+ }) : await (0, import_crm10.setStage)(deps2, {
6340
6419
  id: record4.id,
6341
6420
  to: input.stage,
6342
6421
  authorId: `network:${verified.sender}`,
@@ -6367,25 +6446,33 @@ var handleNetworkRecords = async (req, url, env, ctx) => {
6367
6446
  if (window.stage && !stages.some((stage) => stage.id === window.stage)) {
6368
6447
  return json({ error: "stage is not defined for this record type" }, 400);
6369
6448
  }
6370
- const page = await (0, import_crm9.listRecords)(
6371
- { crm: ctx.chapter.crm, db },
6372
- {
6449
+ let page;
6450
+ try {
6451
+ page = await listReaderRecords(
6452
+ db,
6373
6453
  type,
6374
- limit: window.limit,
6375
- offset: window.offset,
6376
- ...window.search ? { search: window.search } : {},
6377
- ...window.stage ? { stage: [window.stage] } : {},
6378
- sort: window.sort
6454
+ allowed2,
6455
+ typeDef.emailField,
6456
+ window
6457
+ );
6458
+ } catch (error) {
6459
+ if (error instanceof ReaderSearchPopulationError) {
6460
+ return json({
6461
+ error: "directory search requires a narrower stage filter",
6462
+ code: "search_population_too_large",
6463
+ maximum: 1e4
6464
+ }, 422);
6379
6465
  }
6380
- );
6466
+ throw error;
6467
+ }
6381
6468
  const response = {
6382
6469
  version: 1,
6383
6470
  site: { id: ctx.chapter.id, name: ctx.chapter.name },
6384
6471
  type,
6385
6472
  records: page.records.map((record4) => readerRecord(record4, reader, ctx.chapter.crm)),
6386
6473
  total: page.total,
6387
- limit: page.limit,
6388
- offset: page.offset,
6474
+ limit: window.limit,
6475
+ offset: window.offset,
6389
6476
  filters: {
6390
6477
  stages: stages.map((stage) => ({ id: stage.id, label: stage.label ?? stage.id })),
6391
6478
  sorts: [...NETWORK_RECORD_SORTS]
@@ -6621,7 +6708,7 @@ var handleAdminNetworkNotes = async (req, url, env, ctx) => {
6621
6708
  };
6622
6709
 
6623
6710
  // src/worker-routes-network-shared-notes.ts
6624
- var import_crm10 = require("@odla-ai/crm");
6711
+ var import_crm11 = require("@odla-ai/crm");
6625
6712
  var import_db11 = require("@odla-ai/db");
6626
6713
  var IDENTIFIER3 = /^[a-z][a-zA-Z0-9_]*$/;
6627
6714
  var RECORD_ID3 = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$/;
@@ -6651,7 +6738,7 @@ function sharedNote(activity, sourceId) {
6651
6738
  }
6652
6739
  async function listSharedNotes(db, sourceId, recordId) {
6653
6740
  const result = await db.query({
6654
- [import_crm10.CRM_NS.activity]: {
6741
+ [import_crm11.CRM_NS.activity]: {
6655
6742
  $: {
6656
6743
  where: {
6657
6744
  and: [
@@ -6664,7 +6751,7 @@ async function listSharedNotes(db, sourceId, recordId) {
6664
6751
  }
6665
6752
  }
6666
6753
  });
6667
- return (result[import_crm10.CRM_NS.activity] ?? []).flatMap((activity) => {
6754
+ return (result[import_crm11.CRM_NS.activity] ?? []).flatMap((activity) => {
6668
6755
  const note = sharedNote(activity, sourceId);
6669
6756
  return note ? [note] : [];
6670
6757
  });
@@ -6692,7 +6779,7 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6692
6779
  if (!IDENTIFIER3.test(recordType) || !RECORD_ID3.test(recordId) || !reader?.sharedNotes.includes(recordType)) {
6693
6780
  return json({ error: "shared notes are not enabled for this reader and record type" }, 403);
6694
6781
  }
6695
- const record4 = await (0, import_crm10.getRecord)({ crm: ctx.chapter.crm, db }, recordId);
6782
+ const record4 = await (0, import_crm11.getRecord)({ crm: ctx.chapter.crm, db }, recordId);
6696
6783
  if (!record4 || record4.type !== recordType) return json({ error: "record not found" }, 404);
6697
6784
  if (req.method === "GET") {
6698
6785
  return json({
@@ -6714,7 +6801,7 @@ var handleNetworkSharedNotes = async (req, url, env, ctx) => {
6714
6801
  }
6715
6802
  const createdAt = Date.now();
6716
6803
  const id = `network:${verified.sender}:${mutationId}`;
6717
- const result = await (0, import_crm10.addActivity)(
6804
+ const result = await (0, import_crm11.addActivity)(
6718
6805
  {
6719
6806
  crm: ctx.chapter.crm,
6720
6807
  db,
@@ -8287,7 +8374,7 @@ var handleAdminNetworkCommercialReadModel = async (req, url, env, ctx) => {
8287
8374
  };
8288
8375
 
8289
8376
  // src/worker-routes-formation.ts
8290
- var import_crm11 = require("@odla-ai/crm");
8377
+ var import_crm12 = require("@odla-ai/crm");
8291
8378
  var SUBMISSION = /^[A-Za-z0-9_-]{8,128}$/;
8292
8379
  var handleFormation = async (req, url, env, ctx) => {
8293
8380
  const formation = ctx.chapter.formation;
@@ -8339,7 +8426,7 @@ var handleFormation = async (req, url, env, ctx) => {
8339
8426
  const db = ctx.makeDb(env);
8340
8427
  const id = submissionId ? await applicationIdForSubmission(`formation:${ctx.chapter.id}:${submissionId}`) : crypto.randomUUID();
8341
8428
  try {
8342
- const created = await (0, import_crm11.createRecord)(
8429
+ const created = await (0, import_crm12.createRecord)(
8343
8430
  {
8344
8431
  crm: ctx.chapter.crm,
8345
8432
  db,