@dyrected/core 2.14.0 → 2.16.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/dist/server.js CHANGED
@@ -2308,6 +2308,12 @@ var init_chunker = __esm({
2308
2308
  import { createGoogleGenerativeAI } from "@ai-sdk/google";
2309
2309
  import { createOpenAI } from "@ai-sdk/openai";
2310
2310
  import { embed, embedMany } from "ai";
2311
+ function hasEmbeddingApiKey(config) {
2312
+ const ai = config?.ai;
2313
+ return Boolean(
2314
+ ai?.apiKey || process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY
2315
+ );
2316
+ }
2311
2317
  function getEmbeddingModel(config) {
2312
2318
  const ai = config?.ai;
2313
2319
  const provider = ai?.provider;
@@ -2647,6 +2653,9 @@ var init_rag_service = __esm({
2647
2653
  if (!col || !this.isCollectionRAGEnabled(config, col)) {
2648
2654
  return { indexed: 0, skipped: 0 };
2649
2655
  }
2656
+ if (!hasEmbeddingApiKey(config)) {
2657
+ return { indexed: 0, skipped: 0 };
2658
+ }
2650
2659
  const docId = String(doc.id || doc._id || "");
2651
2660
  if (!docId) return { indexed: 0, skipped: 0 };
2652
2661
  const ragConfig = col.ai?.rag;
@@ -6397,6 +6406,134 @@ function isPlainObject2(value) {
6397
6406
  return typeof value === "object" && value !== null && !Array.isArray(value);
6398
6407
  }
6399
6408
 
6409
+ // src/utils/id.ts
6410
+ import { randomBytes as randomBytes2, randomUUID } from "crypto";
6411
+ var NANOID_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-";
6412
+ var BASE62_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
6413
+ var CROCKFORD_BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
6414
+ var COMMON_SLUG_PREFIXES = {
6415
+ users: "usr",
6416
+ members: "mem",
6417
+ accounts: "acc",
6418
+ applications: "app",
6419
+ customers: "cus",
6420
+ products: "prd",
6421
+ orders: "ord",
6422
+ reports: "rpt",
6423
+ sessions: "ses",
6424
+ transactions: "txn",
6425
+ wallets: "wal",
6426
+ media: "med",
6427
+ files: "fil",
6428
+ posts: "post",
6429
+ pages: "page",
6430
+ tags: "tag",
6431
+ categories: "cat",
6432
+ comments: "com"
6433
+ };
6434
+ function nanoid(size = 21, alphabet = NANOID_ALPHABET) {
6435
+ const mask = (2 << 31 - Math.clz32(alphabet.length - 1 | 1)) - 1;
6436
+ const step = Math.ceil(1.6 * mask * size / alphabet.length);
6437
+ let id = "";
6438
+ while (true) {
6439
+ const bytes = randomBytes2(step);
6440
+ for (let i = 0; i < step; i++) {
6441
+ const byte = bytes[i] & mask;
6442
+ if (alphabet[byte]) {
6443
+ id += alphabet[byte];
6444
+ if (id.length === size) return id;
6445
+ }
6446
+ }
6447
+ }
6448
+ }
6449
+ function encodeUlidTime(now2, len = 10) {
6450
+ let str = "";
6451
+ let time = Math.floor(now2);
6452
+ for (let i = len - 1; i >= 0; i--) {
6453
+ const mod = time % 32;
6454
+ str = CROCKFORD_BASE32[mod] + str;
6455
+ time = (time - mod) / 32;
6456
+ }
6457
+ return str;
6458
+ }
6459
+ function encodeUlidRandom(bytes) {
6460
+ let str = "";
6461
+ let bitBuffer = 0;
6462
+ let bitsInBuffer = 0;
6463
+ for (let i = 0; i < bytes.length; i++) {
6464
+ bitBuffer = bitBuffer << 8 | bytes[i];
6465
+ bitsInBuffer += 8;
6466
+ while (bitsInBuffer >= 5) {
6467
+ bitsInBuffer -= 5;
6468
+ const index = bitBuffer >> bitsInBuffer & 31;
6469
+ str += CROCKFORD_BASE32[index];
6470
+ }
6471
+ }
6472
+ return str;
6473
+ }
6474
+ function ulid(seedTime = Date.now()) {
6475
+ const timePart = encodeUlidTime(seedTime);
6476
+ const randPart = encodeUlidRandom(randomBytes2(10));
6477
+ return timePart + randPart;
6478
+ }
6479
+ function uuid() {
6480
+ return randomUUID();
6481
+ }
6482
+ function resolvePrefix(slug, explicitPrefix) {
6483
+ if (explicitPrefix && typeof explicitPrefix === "string" && explicitPrefix.trim().length > 0) {
6484
+ const cleaned = explicitPrefix.trim().toLowerCase().replace(/[^a-z0-9_]/g, "");
6485
+ if (cleaned.length > 0) return cleaned;
6486
+ }
6487
+ if (!slug || typeof slug !== "string") return "col";
6488
+ const s = slug.trim().toLowerCase();
6489
+ if (COMMON_SLUG_PREFIXES[s]) return COMMON_SLUG_PREFIXES[s];
6490
+ const pieces = s.split(/[-_]/).filter(Boolean);
6491
+ if (pieces.length > 1 && pieces[0].length >= 2) {
6492
+ const first2 = pieces[0].replace(/[^a-z0-9]/g, "");
6493
+ if (first2.length >= 2) return first2.slice(0, 4);
6494
+ }
6495
+ const cleanWord = s.replace(/[^a-z0-9]/g, "");
6496
+ if (cleanWord.length <= 4) return cleanWord || "col";
6497
+ return cleanWord.slice(0, 3) || "col";
6498
+ }
6499
+ function prefixedId(prefix = "col", size = 16) {
6500
+ const cleanPrefix = (prefix || "col").toLowerCase().replace(/[^a-z0-9_]/g, "") || "col";
6501
+ return `${cleanPrefix}_${nanoid(size, BASE62_ALPHABET)}`;
6502
+ }
6503
+ function generateId(options) {
6504
+ if (typeof options === "string") {
6505
+ return prefixedId(resolvePrefix(options));
6506
+ }
6507
+ const opts = options || {};
6508
+ const type = opts.type || "prefixed-nanoid";
6509
+ switch (type) {
6510
+ case "uuid":
6511
+ return uuid();
6512
+ case "ulid":
6513
+ return ulid();
6514
+ case "nanoid":
6515
+ return nanoid(opts.size || 21);
6516
+ case "prefixed-nanoid":
6517
+ default:
6518
+ return prefixedId(opts.prefix || "col", opts.size || 16);
6519
+ }
6520
+ }
6521
+ function generateDocumentId(collectionOrSlug) {
6522
+ if (!collectionOrSlug) {
6523
+ return prefixedId("col");
6524
+ }
6525
+ if (typeof collectionOrSlug === "string") {
6526
+ const prefix2 = resolvePrefix(collectionOrSlug);
6527
+ return prefixedId(prefix2);
6528
+ }
6529
+ if (typeof collectionOrSlug.idGenerator === "function") {
6530
+ return collectionOrSlug.idGenerator(collectionOrSlug.slug);
6531
+ }
6532
+ const type = collectionOrSlug.idType || "prefixed-nanoid";
6533
+ const prefix = resolvePrefix(collectionOrSlug.slug, collectionOrSlug.idPrefix);
6534
+ return generateId({ prefix, type });
6535
+ }
6536
+
6400
6537
  // src/workflows.ts
6401
6538
  var WORKFLOW_HISTORY_COLLECTION = "__workflow_history";
6402
6539
  var LIFECYCLE_EVENTS_COLLECTION = "__lifecycle_events";
@@ -7365,6 +7502,9 @@ var CollectionController = class {
7365
7502
  surface: "collection.beforeChange",
7366
7503
  path: `collection:${this.collection.slug}.hooks.beforeChange`
7367
7504
  });
7505
+ if (!data.id) {
7506
+ data = { ...data, id: generateDocumentId(this.collection) };
7507
+ }
7368
7508
  const doc = this.collection.workflow ? (await createWorkflowDocument({
7369
7509
  config,
7370
7510
  collection: this.collection,
@@ -7515,6 +7655,9 @@ var CollectionController = class {
7515
7655
  surface: "collection.beforeChange",
7516
7656
  path: `collection:${this.collection.slug}.hooks.beforeChange`
7517
7657
  });
7658
+ if (!data.id) {
7659
+ data = { ...data, id: generateDocumentId(this.collection) };
7660
+ }
7518
7661
  const doc = await db.create({
7519
7662
  collection: this.collection.slug,
7520
7663
  data
@@ -7755,13 +7898,13 @@ var CollectionController = class {
7755
7898
  const config = c.get("config");
7756
7899
  const db = config.db;
7757
7900
  if (!db) return c.json({ message: "Database not configured" }, 500);
7758
- if (!this.collection.views?.length) {
7759
- return c.json({ error: true, message: `Collection ${this.collection.slug} has no views` }, 404);
7901
+ if (!this.collection.views?.length && !this.collection.actions?.length) {
7902
+ return c.json({ error: true, message: `Collection ${this.collection.slug} has no views or actions` }, 404);
7760
7903
  }
7761
7904
  const actionName = c.req.param("action");
7762
7905
  const viewSlugParam = c.req.param("viewSlug");
7763
7906
  let action;
7764
- for (const view2 of this.collection.views) {
7907
+ for (const view2 of this.collection.views ?? []) {
7765
7908
  if (viewSlugParam && view2.slug !== viewSlugParam) continue;
7766
7909
  const match = view2.actions?.find((candidate) => candidate.name === actionName);
7767
7910
  if (match) {
@@ -7769,6 +7912,9 @@ var CollectionController = class {
7769
7912
  break;
7770
7913
  }
7771
7914
  }
7915
+ if (!action && !viewSlugParam) {
7916
+ action = this.collection.actions?.find((candidate) => candidate.name === actionName);
7917
+ }
7772
7918
  if (!action) {
7773
7919
  return c.json(
7774
7920
  { error: true, message: `Action "${actionName}" was not found${viewSlugParam ? ` in view "${viewSlugParam}"` : ""}` },
@@ -7777,7 +7923,8 @@ var CollectionController = class {
7777
7923
  }
7778
7924
  const body = await c.req.json().catch(() => ({}));
7779
7925
  const requestedIds = Array.isArray(body?.ids) ? body.ids.filter((value) => typeof value === "string") : typeof body?.id === "string" ? [body.id] : [];
7780
- if (!requestedIds.length) {
7926
+ const isHeaderAction = action.type === "header";
7927
+ if (!requestedIds.length && !isHeaderAction) {
7781
7928
  return c.json(
7782
7929
  { error: true, message: "Provide an `id` or an `ids` array of documents to act on." },
7783
7930
  400
@@ -7786,7 +7933,7 @@ var CollectionController = class {
7786
7933
  const input = body?.input ?? {};
7787
7934
  const user = c.get("user");
7788
7935
  const accessArgs = { user, req: this.toHookRequestContext(c) };
7789
- const view = viewSlugParam ? this.collection.views.find((candidate) => candidate.slug === viewSlugParam) : void 0;
7936
+ const view = viewSlugParam ? this.collection.views?.find((candidate) => candidate.slug === viewSlugParam) : void 0;
7790
7937
  if (view?.access) {
7791
7938
  const allowed = await resolveBooleanAccess(config, view.access.update ?? view.access.read ?? true, {
7792
7939
  ...accessArgs,
@@ -7805,6 +7952,30 @@ var CollectionController = class {
7805
7952
  return c.json({ error: true, message: `Access denied: action "${action.name}"` }, 403);
7806
7953
  }
7807
7954
  }
7955
+ if (isHeaderAction && !requestedIds.length) {
7956
+ if (typeof action.handler === "function") {
7957
+ try {
7958
+ const handlerResult = await action.handler({
7959
+ doc: null,
7960
+ docs: [],
7961
+ user: user ?? null,
7962
+ input,
7963
+ collection: {
7964
+ slug: this.collection.slug,
7965
+ label: this.collection.labels?.singular ?? this.collection.slug
7966
+ }
7967
+ });
7968
+ return c.json(handlerResult ?? { success: true });
7969
+ } catch (error) {
7970
+ const status = typeof error.statusCode === "number" ? error.statusCode : 500;
7971
+ return c.json(
7972
+ { error: true, message: error instanceof Error ? error.message : String(error) },
7973
+ status
7974
+ );
7975
+ }
7976
+ }
7977
+ return c.json({ success: true });
7978
+ }
7808
7979
  const results = [];
7809
7980
  for (const id of requestedIds) {
7810
7981
  const targetDoc = await db.findOne({ collection: this.collection.slug, id });
@@ -7824,7 +7995,8 @@ var CollectionController = class {
7824
7995
  });
7825
7996
  resolvedData = handlerResult ?? {};
7826
7997
  } catch (error) {
7827
- results.push({ id, ok: false, status: 500, error: error instanceof Error ? error.message : String(error) });
7998
+ const status = typeof error.statusCode === "number" ? error.statusCode : 500;
7999
+ results.push({ id, ok: false, status, error: error instanceof Error ? error.message : String(error) });
7828
8000
  continue;
7829
8001
  }
7830
8002
  } else {
@@ -7847,7 +8019,8 @@ var CollectionController = class {
7847
8019
  if (requestedIds.length === 1) {
7848
8020
  const single = results[0];
7849
8021
  if (!single.ok) {
7850
- return c.json(single.error ?? { error: true, message: "Action failed" }, single.status ?? 500);
8022
+ const body2 = typeof single.error === "string" ? { error: true, message: single.error } : single.error ?? { error: true, message: "Action failed" };
8023
+ return c.json(body2, single.status ?? 500);
7851
8024
  }
7852
8025
  return c.json(single.doc);
7853
8026
  }
@@ -9211,7 +9384,7 @@ var MediaFolderController = class {
9211
9384
  };
9212
9385
 
9213
9386
  // src/controllers/auth.controller.ts
9214
- import { randomBytes as randomBytes2 } from "crypto";
9387
+ import { randomBytes as randomBytes3 } from "crypto";
9215
9388
 
9216
9389
  // src/auth/lockout.ts
9217
9390
  var DEFAULT_MAX_LOGIN_ATTEMPTS = 5;
@@ -9522,7 +9695,7 @@ var AuthController = class {
9522
9695
  const data = {
9523
9696
  ...safeExtraFields,
9524
9697
  email,
9525
- password: await hashPassword(randomBytes2(32).toString("hex"))
9698
+ password: await hashPassword(randomBytes3(32).toString("hex"))
9526
9699
  };
9527
9700
  if (this.hasField("status")) {
9528
9701
  data.status = "pending";
@@ -10125,7 +10298,7 @@ var AuthController = class {
10125
10298
  };
10126
10299
 
10127
10300
  // src/controllers/admin-auth.controller.ts
10128
- import { randomBytes as randomBytes3 } from "crypto";
10301
+ import { randomBytes as randomBytes4 } from "crypto";
10129
10302
  import { TextEncoder as TextEncoder2 } from "util";
10130
10303
  import {
10131
10304
  SignJWT as SignJWT2,
@@ -10340,7 +10513,7 @@ var AdminAuthController = class {
10340
10513
  access.roles,
10341
10514
  access.data
10342
10515
  ),
10343
- password: await hashPassword(randomBytes3(32).toString("hex"))
10516
+ password: await hashPassword(randomBytes4(32).toString("hex"))
10344
10517
  }
10345
10518
  });
10346
10519
  } else if (user) {
@@ -10427,7 +10600,7 @@ var AdminAuthController = class {
10427
10600
  }
10428
10601
  async buildOidcStartUrl(provider, returnTo, siteId, origin) {
10429
10602
  const discovery = await this.getOidcDiscovery(provider);
10430
- const nonce = randomBytes3(16).toString("hex");
10603
+ const nonce = randomBytes4(16).toString("hex");
10431
10604
  const state = await this.signState({
10432
10605
  providerId: provider.id,
10433
10606
  returnTo,
@@ -10988,7 +11161,7 @@ function formatAIErrorMessage(error) {
10988
11161
  }
10989
11162
  return msg;
10990
11163
  }
10991
- function generateId() {
11164
+ function generateId2() {
10992
11165
  return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
10993
11166
  }
10994
11167
  function buildDyrectedSystemPrompt(context) {
@@ -11134,7 +11307,7 @@ var AIAgent = class {
11134
11307
  }
11135
11308
  async createThread(title, id) {
11136
11309
  const thread = {
11137
- id: id || generateId(),
11310
+ id: id || generateId2(),
11138
11311
  projectId: this.projectId,
11139
11312
  userId: this.userId,
11140
11313
  title: title || "New Conversation",
@@ -11201,7 +11374,7 @@ var AIAgent = class {
11201
11374
  }
11202
11375
  async persistUserMessage(threadId, content) {
11203
11376
  const message = {
11204
- id: generateId(),
11377
+ id: generateId2(),
11205
11378
  threadId,
11206
11379
  role: "user",
11207
11380
  content,
@@ -11217,7 +11390,7 @@ var AIAgent = class {
11217
11390
  }
11218
11391
  async persistAssistantMessage(threadId, content, metadata, parts) {
11219
11392
  const message = {
11220
- id: generateId(),
11393
+ id: generateId2(),
11221
11394
  threadId,
11222
11395
  role: "assistant",
11223
11396
  content,
@@ -14297,6 +14470,8 @@ function registerRoutes(app, config) {
14297
14470
  app.post(`${path}/aggregate`, (c) => controller.aggregate(c));
14298
14471
  if (collection.views?.length) {
14299
14472
  app.post(`${path}/views/:viewSlug/actions/:action`, optionalAuth(config), (c) => controller.runViewAction(c));
14473
+ }
14474
+ if (collection.views?.length || collection.actions?.length) {
14300
14475
  app.post(`${path}/actions/:action`, optionalAuth(config), (c) => controller.runViewAction(c));
14301
14476
  }
14302
14477
  if (collection.audit) {
@@ -14777,11 +14952,31 @@ function normalizeConfig(config) {
14777
14952
  }
14778
14953
  const updatedFieldNames = new Set(fields.map((f) => f.name));
14779
14954
  const fieldsToInject = SYSTEM_FIELDS.filter((f) => !updatedFieldNames.has(f.name));
14955
+ const indexedFieldNames = /* @__PURE__ */ new Set();
14956
+ if (col.indexes) {
14957
+ for (const idx of col.indexes) {
14958
+ if (Array.isArray(idx.fields)) {
14959
+ for (const f of idx.fields) {
14960
+ indexedFieldNames.add(f);
14961
+ }
14962
+ }
14963
+ }
14964
+ }
14965
+ const allFields = [...fields, ...fieldsToInject].map((field) => {
14966
+ if (field.unique || field.name && indexedFieldNames.has(field.name)) {
14967
+ return { ...field, promoted: true };
14968
+ }
14969
+ return field;
14970
+ });
14780
14971
  const workflow = col.workflow || (col.drafts ? simplePublishingWorkflow() : void 0);
14972
+ const idPrefix = col.idPrefix || resolvePrefix(col.slug);
14973
+ const idType = col.idType || "prefixed-nanoid";
14781
14974
  return {
14782
14975
  ...col,
14976
+ idPrefix,
14977
+ idType,
14783
14978
  workflow,
14784
- fields: [...fields, ...fieldsToInject]
14979
+ fields: allFields
14785
14980
  };
14786
14981
  });
14787
14982
  const hasAuditCollection = normalizedCollections.some((col) => col.slug === AUDIT_COLLECTION_SLUG);
@@ -15074,6 +15269,8 @@ async function createDyrectedApp(rawConfig) {
15074
15269
  if (err instanceof HTTPException6) {
15075
15270
  return c.json({ error: true, message: err.message }, err.status);
15076
15271
  }
15272
+ const statusCode = err.statusCode;
15273
+ const status = typeof statusCode === "number" && Number.isInteger(statusCode) && statusCode >= 400 && statusCode <= 599 ? statusCode : 500;
15077
15274
  const logger = getRequestLogger(c, "core");
15078
15275
  logger.error({
15079
15276
  err,
@@ -15086,9 +15283,10 @@ async function createDyrectedApp(rawConfig) {
15086
15283
  method: c.req.method
15087
15284
  });
15088
15285
  return c.json({
15286
+ error: true,
15089
15287
  message: err.message || "Internal Server Error",
15090
15288
  stack: process.env.NODE_ENV === "development" ? err.stack : void 0
15091
- }, 500);
15289
+ }, status);
15092
15290
  });
15093
15291
  registerRoutes(app, config);
15094
15292
  return app;
@@ -15143,6 +15341,7 @@ export {
15143
15341
  getLockedUntilMs,
15144
15342
  getObservabilityRuntime,
15145
15343
  getRequestLogger,
15344
+ hasEmbeddingApiKey,
15146
15345
  hashContent,
15147
15346
  hashPassword,
15148
15347
  isAuthSessionActive,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/core",
3
- "version": "2.14.0",
3
+ "version": "2.16.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",