@dyrected/core 2.5.64 → 2.6.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.cjs CHANGED
@@ -177,7 +177,7 @@ var PopulationService = class {
177
177
  }
178
178
  const populatedDoc = { ...data };
179
179
  for (const field of fields) {
180
- if (field.type === "join" && field.collection && field.on && currentDepth === 0) {
180
+ if (field.type === "join" && field.collection && field.on && currentDepth < maxDepth) {
181
181
  const targetCollection = this.collections.find(
182
182
  (c) => c.slug === field.collection
183
183
  );
@@ -196,7 +196,7 @@ var PopulationService = class {
196
196
  (doc) => DefaultsService.apply(targetCollection.fields, doc)
197
197
  ),
198
198
  fields: targetCollection.fields,
199
- currentDepth: 1,
199
+ currentDepth: currentDepth + 1,
200
200
  maxDepth
201
201
  });
202
202
  populatedDoc[field.name] = {
@@ -1387,6 +1387,191 @@ function humanizeProviderName(id, type) {
1387
1387
  return cleaned.replace(/\b\w/g, (char) => char.toUpperCase());
1388
1388
  }
1389
1389
 
1390
+ // src/utils/collection-search.ts
1391
+ var DEFAULT_SEARCHABLE_NAMES = /* @__PURE__ */ new Set([
1392
+ "title",
1393
+ "name",
1394
+ "label",
1395
+ "slug",
1396
+ "email",
1397
+ "caption",
1398
+ "description"
1399
+ ]);
1400
+ var MAX_RELATION_MATCHES = 50;
1401
+ var MAX_RELATION_TITLE_DEPTH = 8;
1402
+ var TITLE_FALLBACK_NAMES = ["title", "name", "label", "heading", "email", "subject"];
1403
+ function isScalarSearchField(field) {
1404
+ return field.type === "text" || field.type === "textarea" || field.type === "richText" || field.type === "email";
1405
+ }
1406
+ function findFieldByName(collection, fieldName) {
1407
+ if (!fieldName) return void 0;
1408
+ return collection.fields.find((field) => field.name === fieldName);
1409
+ }
1410
+ function resolveCollectionTitleField(collection) {
1411
+ const configured = findFieldByName(collection, collection.admin?.useAsTitle);
1412
+ if (configured) return configured;
1413
+ for (const name of TITLE_FALLBACK_NAMES) {
1414
+ const field = findFieldByName(collection, name);
1415
+ if (field) return field;
1416
+ }
1417
+ return collection.fields.find((field) => !!field.name && field.type !== "join" && field.type !== "row");
1418
+ }
1419
+ function getCollectionSearchableFields(collection) {
1420
+ const fieldByName = new Map(
1421
+ collection.fields.filter((field) => !!field.name).map((field) => [field.name, field])
1422
+ );
1423
+ const configured = collection.admin?.searchableFields?.map((fieldName) => fieldByName.get(fieldName)).filter((field) => !!field);
1424
+ if (configured && configured.length > 0) {
1425
+ return configured;
1426
+ }
1427
+ const results = [];
1428
+ const pushUnique = (field) => {
1429
+ if (!field?.name) return;
1430
+ if (results.some((item) => item.name === field.name)) return;
1431
+ results.push(field);
1432
+ };
1433
+ pushUnique(findFieldByName(collection, collection.admin?.useAsTitle));
1434
+ for (const field of collection.fields) {
1435
+ if (!field.name) continue;
1436
+ if (isScalarSearchField(field) && DEFAULT_SEARCHABLE_NAMES.has(field.name)) {
1437
+ pushUnique(field);
1438
+ }
1439
+ }
1440
+ return results;
1441
+ }
1442
+ function buildScalarSearchClause(fieldName, search) {
1443
+ return { [fieldName]: { contains: search } };
1444
+ }
1445
+ function getFallbackTitleFields(collection) {
1446
+ const fields = [];
1447
+ for (const name of TITLE_FALLBACK_NAMES) {
1448
+ const field = findFieldByName(collection, name);
1449
+ if (!field?.name || !isScalarSearchField(field)) continue;
1450
+ if (fields.some((item) => item.name === field.name)) continue;
1451
+ fields.push(field);
1452
+ }
1453
+ return fields;
1454
+ }
1455
+ async function findMatchingDocumentIdsByWhere(args) {
1456
+ const { collection, where, db } = args;
1457
+ if (!where) return [];
1458
+ const matches = await db.find({
1459
+ collection: collection.slug,
1460
+ where,
1461
+ limit: MAX_RELATION_MATCHES
1462
+ });
1463
+ return matches.docs.map((doc) => doc?.id).filter((id) => typeof id === "string");
1464
+ }
1465
+ function buildMultiRelationshipClause(fieldName, ids) {
1466
+ const matches = ids.map((id) => ({
1467
+ OR: [
1468
+ { [fieldName]: { contains: `"${id}"` } },
1469
+ { [fieldName]: { contains: id } }
1470
+ ]
1471
+ }));
1472
+ if (matches.length === 0) return void 0;
1473
+ return matches.length === 1 ? matches[0] : { OR: matches };
1474
+ }
1475
+ function buildRelationshipMatchClause(field, ids) {
1476
+ if (!field.name || ids.length === 0) return void 0;
1477
+ if (field.hasMany) {
1478
+ return buildMultiRelationshipClause(field.name, ids);
1479
+ }
1480
+ return { [field.name]: { in: ids } };
1481
+ }
1482
+ async function findMatchingDocumentIdsByTitle(args) {
1483
+ const { collection, search, db, collections, depth = 0 } = args;
1484
+ if (depth >= MAX_RELATION_TITLE_DEPTH) {
1485
+ return [];
1486
+ }
1487
+ const ids = /* @__PURE__ */ new Set();
1488
+ const titleField = resolveCollectionTitleField(collection);
1489
+ if (titleField?.name && isScalarSearchField(titleField)) {
1490
+ for (const id of await findMatchingDocumentIdsByWhere({
1491
+ collection,
1492
+ where: buildScalarSearchClause(titleField.name, search),
1493
+ db
1494
+ })) {
1495
+ ids.add(id);
1496
+ }
1497
+ }
1498
+ if (titleField?.type === "relationship" && titleField.relationTo) {
1499
+ for (const field of getFallbackTitleFields(collection)) {
1500
+ for (const id of await findMatchingDocumentIdsByWhere({
1501
+ collection,
1502
+ where: buildScalarSearchClause(field.name, search),
1503
+ db
1504
+ })) {
1505
+ ids.add(id);
1506
+ }
1507
+ }
1508
+ const relatedCollection = collections.find(
1509
+ (item) => item.slug === titleField.relationTo
1510
+ );
1511
+ if (relatedCollection) {
1512
+ const relatedIds = await findMatchingDocumentIdsByTitle({
1513
+ collection: relatedCollection,
1514
+ search,
1515
+ db,
1516
+ collections,
1517
+ depth: depth + 1
1518
+ });
1519
+ const relationClause = buildRelationshipMatchClause(titleField, relatedIds);
1520
+ for (const id of await findMatchingDocumentIdsByWhere({
1521
+ collection,
1522
+ where: relationClause,
1523
+ db
1524
+ })) {
1525
+ ids.add(id);
1526
+ }
1527
+ }
1528
+ }
1529
+ return Array.from(ids).slice(0, MAX_RELATION_MATCHES);
1530
+ }
1531
+ async function buildRelationshipSearchClause(args) {
1532
+ const { field, search, db, collections } = args;
1533
+ if (!field.name || field.type !== "relationship" || !field.relationTo) {
1534
+ return void 0;
1535
+ }
1536
+ const relatedCollection = collections.find(
1537
+ (collection) => collection.slug === field.relationTo
1538
+ );
1539
+ if (!relatedCollection) return void 0;
1540
+ const ids = await findMatchingDocumentIdsByTitle({
1541
+ collection: relatedCollection,
1542
+ search,
1543
+ db,
1544
+ collections
1545
+ });
1546
+ return buildRelationshipMatchClause(field, ids);
1547
+ }
1548
+ async function buildCollectionSearchWhere(args) {
1549
+ const { collection, search, db, collections } = args;
1550
+ const trimmedSearch = search?.trim();
1551
+ if (!trimmedSearch) return void 0;
1552
+ const searchableFields = getCollectionSearchableFields(collection);
1553
+ if (searchableFields.length === 0) return void 0;
1554
+ const clauses = [];
1555
+ for (const field of searchableFields) {
1556
+ if (!field.name) continue;
1557
+ if (isScalarSearchField(field)) {
1558
+ clauses.push(buildScalarSearchClause(field.name, trimmedSearch));
1559
+ continue;
1560
+ }
1561
+ if (field.type === "relationship") {
1562
+ const clause = await buildRelationshipSearchClause({
1563
+ field,
1564
+ search: trimmedSearch,
1565
+ db,
1566
+ collections
1567
+ });
1568
+ if (clause) clauses.push(clause);
1569
+ }
1570
+ }
1571
+ if (clauses.length === 0) return void 0;
1572
+ return clauses.length === 1 ? clauses[0] : { OR: clauses };
1573
+ }
1574
+
1390
1575
  // src/utils/access-control.ts
1391
1576
  function toHookRequestContext(req) {
1392
1577
  return {
@@ -1899,6 +2084,7 @@ var CollectionController = class {
1899
2084
  const limit2 = Number(c.req.query("limit")) || 10;
1900
2085
  const page2 = Number(c.req.query("page")) || 1;
1901
2086
  const sort2 = c.req.query("sort") || void 0;
2087
+ const search2 = c.req.query("search") || void 0;
1902
2088
  let where2 = void 0;
1903
2089
  const whereRaw2 = c.req.query("where");
1904
2090
  if (whereRaw2) {
@@ -1907,6 +2093,15 @@ var CollectionController = class {
1907
2093
  } catch {
1908
2094
  }
1909
2095
  }
2096
+ const searchWhere2 = await buildCollectionSearchWhere({
2097
+ collection: this.collection,
2098
+ search: search2,
2099
+ db: createReadonlyDb(db),
2100
+ collections: config.collections
2101
+ });
2102
+ if (searchWhere2) {
2103
+ where2 = mergeWhereConstraint(where2, searchWhere2);
2104
+ }
1910
2105
  const paginatedResult = await provider.members.list({
1911
2106
  limit: limit2,
1912
2107
  page: page2,
@@ -1941,6 +2136,7 @@ var CollectionController = class {
1941
2136
  const page = Number(c.req.query("page")) || 1;
1942
2137
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
1943
2138
  const sort = c.req.query("sort") || void 0;
2139
+ const search = c.req.query("search") || void 0;
1944
2140
  const user = c.get("user");
1945
2141
  let where = void 0;
1946
2142
  const whereRaw = c.req.query("where");
@@ -1961,6 +2157,15 @@ var CollectionController = class {
1961
2157
  }
1962
2158
  }
1963
2159
  }
2160
+ const searchWhere = await buildCollectionSearchWhere({
2161
+ collection: this.collection,
2162
+ search,
2163
+ db: readonlyDb,
2164
+ collections: config.collections
2165
+ });
2166
+ if (searchWhere) {
2167
+ where = mergeWhereConstraint(where, searchWhere);
2168
+ }
1964
2169
  const beforeReadResult = await runCollectionHooks(
1965
2170
  this.collection.hooks?.beforeRead,
1966
2171
  {
@@ -5312,6 +5517,12 @@ function generateOpenApi(config) {
5312
5517
  schema: { type: "string" },
5313
5518
  description: "JSON filter"
5314
5519
  },
5520
+ {
5521
+ name: "search",
5522
+ in: "query",
5523
+ schema: { type: "string" },
5524
+ description: "Free-text search across configured searchable fields"
5525
+ },
5315
5526
  {
5316
5527
  name: "sort",
5317
5528
  in: "query",
@@ -5508,13 +5719,6 @@ function generateOpenApi(config) {
5508
5719
  }
5509
5720
  };
5510
5721
  }
5511
- spec.paths[`${path}/seed`] = {
5512
- post: {
5513
- tags: [collectionTag],
5514
- summary: `Seed initial ${labels.plural}`,
5515
- responses: { 200: { description: "Seeded documents" } }
5516
- }
5517
- };
5518
5722
  if (collection.upload) {
5519
5723
  spec.paths[`${path}/media`] = {
5520
5724
  get: {
@@ -5816,13 +6020,6 @@ function generateOpenApi(config) {
5816
6020
  }
5817
6021
  }
5818
6022
  };
5819
- spec.paths[`${path}/seed`] = {
5820
- post: {
5821
- tags: [globalTag],
5822
- summary: `Seed ${global.label || slug}`,
5823
- responses: { 200: { description: "Seeded global" } }
5824
- }
5825
- };
5826
6023
  }
5827
6024
  if (config.storage) {
5828
6025
  spec.paths["/api/media/{filename}"] = {
@@ -6582,7 +6779,6 @@ function registerRoutes(app, config) {
6582
6779
  app.get(`${path}/:id`, (c) => controller.findOne(c));
6583
6780
  app.patch(`${path}/:id`, (c) => controller.update(c));
6584
6781
  app.delete(`${path}/:id`, (c) => controller.delete(c));
6585
- app.post(`${path}/seed`, (c) => controller.seed(c));
6586
6782
  if (collection.auth) {
6587
6783
  app.post(
6588
6784
  `${path}/:id/change-password`,
@@ -6608,7 +6804,6 @@ function registerRoutes(app, config) {
6608
6804
  const controller = new GlobalController(global);
6609
6805
  app.get(path, (c) => controller.get(c));
6610
6806
  app.patch(path, (c) => controller.update(c));
6611
- app.post(`${path}/seed`, (c) => controller.seed(c));
6612
6807
  }
6613
6808
  if (!process.env.DYRECTED_JWT_SECRET) {
6614
6809
  getConfigLogger(config, "router").warn({
@@ -6757,7 +6952,6 @@ function registerRoutes(app, config) {
6757
6952
  return controller.deleteMany(c);
6758
6953
  if (method === "DELETE") return controller.delete(c);
6759
6954
  if (method === "POST" && id === "media") return controller.create(c);
6760
- if (method === "POST" && id === "seed") return controller.seed(c);
6761
6955
  } else {
6762
6956
  if (method === "GET") return controller.find(c);
6763
6957
  if (method === "POST") return controller.create(c);
@@ -6783,9 +6977,7 @@ function registerRoutes(app, config) {
6783
6977
  if (global) {
6784
6978
  const controller = new GlobalController(global);
6785
6979
  const method = c.req.method;
6786
- if (id) {
6787
- if (method === "POST" && id === "seed") return controller.seed(c);
6788
- } else {
6980
+ if (!id) {
6789
6981
  if (method === "GET") return controller.get(c);
6790
6982
  if (method === "PATCH") return controller.update(c);
6791
6983
  }
package/dist/server.d.cts CHANGED
@@ -2,7 +2,7 @@ import * as hono_types from 'hono/types';
2
2
  import * as hono from 'hono';
3
3
  import { Hono, Context } from 'hono';
4
4
  import { Logger } from 'pino';
5
- import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-Dv5XACR4.cjs';
5
+ import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-DbK2Tv6i.cjs';
6
6
  import { JWTPayload } from 'jose';
7
7
  import { Tracer, Counter, Attributes, Histogram, Span } from '@opentelemetry/api';
8
8
  import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
package/dist/server.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as hono_types from 'hono/types';
2
2
  import * as hono from 'hono';
3
3
  import { Hono, Context } from 'hono';
4
4
  import { Logger } from 'pino';
5
- import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-Dv5XACR4.js';
5
+ import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-DbK2Tv6i.js';
6
6
  import { JWTPayload } from 'jose';
7
7
  import { Tracer, Counter, Attributes, Histogram, Span } from '@opentelemetry/api';
8
8
  import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
package/dist/server.js CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  touchAuthSession,
37
37
  transitionWorkflow,
38
38
  verifyCollectionToken
39
- } from "./chunk-TEGRS6J7.js";
39
+ } from "./chunk-K3ASMUKV.js";
40
40
 
41
41
  // src/app.ts
42
42
  import { Hono } from "hono";
@@ -110,7 +110,7 @@ var PopulationService = class {
110
110
  }
111
111
  const populatedDoc = { ...data };
112
112
  for (const field of fields) {
113
- if (field.type === "join" && field.collection && field.on && currentDepth === 0) {
113
+ if (field.type === "join" && field.collection && field.on && currentDepth < maxDepth) {
114
114
  const targetCollection = this.collections.find(
115
115
  (c) => c.slug === field.collection
116
116
  );
@@ -129,7 +129,7 @@ var PopulationService = class {
129
129
  (doc) => DefaultsService.apply(targetCollection.fields, doc)
130
130
  ),
131
131
  fields: targetCollection.fields,
132
- currentDepth: 1,
132
+ currentDepth: currentDepth + 1,
133
133
  maxDepth
134
134
  });
135
135
  populatedDoc[field.name] = {
@@ -487,6 +487,191 @@ async function resolveAccess(config, access, args) {
487
487
  return false;
488
488
  }
489
489
 
490
+ // src/utils/collection-search.ts
491
+ var DEFAULT_SEARCHABLE_NAMES = /* @__PURE__ */ new Set([
492
+ "title",
493
+ "name",
494
+ "label",
495
+ "slug",
496
+ "email",
497
+ "caption",
498
+ "description"
499
+ ]);
500
+ var MAX_RELATION_MATCHES = 50;
501
+ var MAX_RELATION_TITLE_DEPTH = 8;
502
+ var TITLE_FALLBACK_NAMES = ["title", "name", "label", "heading", "email", "subject"];
503
+ function isScalarSearchField(field) {
504
+ return field.type === "text" || field.type === "textarea" || field.type === "richText" || field.type === "email";
505
+ }
506
+ function findFieldByName(collection, fieldName) {
507
+ if (!fieldName) return void 0;
508
+ return collection.fields.find((field) => field.name === fieldName);
509
+ }
510
+ function resolveCollectionTitleField(collection) {
511
+ const configured = findFieldByName(collection, collection.admin?.useAsTitle);
512
+ if (configured) return configured;
513
+ for (const name of TITLE_FALLBACK_NAMES) {
514
+ const field = findFieldByName(collection, name);
515
+ if (field) return field;
516
+ }
517
+ return collection.fields.find((field) => !!field.name && field.type !== "join" && field.type !== "row");
518
+ }
519
+ function getCollectionSearchableFields(collection) {
520
+ const fieldByName = new Map(
521
+ collection.fields.filter((field) => !!field.name).map((field) => [field.name, field])
522
+ );
523
+ const configured = collection.admin?.searchableFields?.map((fieldName) => fieldByName.get(fieldName)).filter((field) => !!field);
524
+ if (configured && configured.length > 0) {
525
+ return configured;
526
+ }
527
+ const results = [];
528
+ const pushUnique = (field) => {
529
+ if (!field?.name) return;
530
+ if (results.some((item) => item.name === field.name)) return;
531
+ results.push(field);
532
+ };
533
+ pushUnique(findFieldByName(collection, collection.admin?.useAsTitle));
534
+ for (const field of collection.fields) {
535
+ if (!field.name) continue;
536
+ if (isScalarSearchField(field) && DEFAULT_SEARCHABLE_NAMES.has(field.name)) {
537
+ pushUnique(field);
538
+ }
539
+ }
540
+ return results;
541
+ }
542
+ function buildScalarSearchClause(fieldName, search) {
543
+ return { [fieldName]: { contains: search } };
544
+ }
545
+ function getFallbackTitleFields(collection) {
546
+ const fields = [];
547
+ for (const name of TITLE_FALLBACK_NAMES) {
548
+ const field = findFieldByName(collection, name);
549
+ if (!field?.name || !isScalarSearchField(field)) continue;
550
+ if (fields.some((item) => item.name === field.name)) continue;
551
+ fields.push(field);
552
+ }
553
+ return fields;
554
+ }
555
+ async function findMatchingDocumentIdsByWhere(args) {
556
+ const { collection, where, db } = args;
557
+ if (!where) return [];
558
+ const matches = await db.find({
559
+ collection: collection.slug,
560
+ where,
561
+ limit: MAX_RELATION_MATCHES
562
+ });
563
+ return matches.docs.map((doc) => doc?.id).filter((id) => typeof id === "string");
564
+ }
565
+ function buildMultiRelationshipClause(fieldName, ids) {
566
+ const matches = ids.map((id) => ({
567
+ OR: [
568
+ { [fieldName]: { contains: `"${id}"` } },
569
+ { [fieldName]: { contains: id } }
570
+ ]
571
+ }));
572
+ if (matches.length === 0) return void 0;
573
+ return matches.length === 1 ? matches[0] : { OR: matches };
574
+ }
575
+ function buildRelationshipMatchClause(field, ids) {
576
+ if (!field.name || ids.length === 0) return void 0;
577
+ if (field.hasMany) {
578
+ return buildMultiRelationshipClause(field.name, ids);
579
+ }
580
+ return { [field.name]: { in: ids } };
581
+ }
582
+ async function findMatchingDocumentIdsByTitle(args) {
583
+ const { collection, search, db, collections, depth = 0 } = args;
584
+ if (depth >= MAX_RELATION_TITLE_DEPTH) {
585
+ return [];
586
+ }
587
+ const ids = /* @__PURE__ */ new Set();
588
+ const titleField = resolveCollectionTitleField(collection);
589
+ if (titleField?.name && isScalarSearchField(titleField)) {
590
+ for (const id of await findMatchingDocumentIdsByWhere({
591
+ collection,
592
+ where: buildScalarSearchClause(titleField.name, search),
593
+ db
594
+ })) {
595
+ ids.add(id);
596
+ }
597
+ }
598
+ if (titleField?.type === "relationship" && titleField.relationTo) {
599
+ for (const field of getFallbackTitleFields(collection)) {
600
+ for (const id of await findMatchingDocumentIdsByWhere({
601
+ collection,
602
+ where: buildScalarSearchClause(field.name, search),
603
+ db
604
+ })) {
605
+ ids.add(id);
606
+ }
607
+ }
608
+ const relatedCollection = collections.find(
609
+ (item) => item.slug === titleField.relationTo
610
+ );
611
+ if (relatedCollection) {
612
+ const relatedIds = await findMatchingDocumentIdsByTitle({
613
+ collection: relatedCollection,
614
+ search,
615
+ db,
616
+ collections,
617
+ depth: depth + 1
618
+ });
619
+ const relationClause = buildRelationshipMatchClause(titleField, relatedIds);
620
+ for (const id of await findMatchingDocumentIdsByWhere({
621
+ collection,
622
+ where: relationClause,
623
+ db
624
+ })) {
625
+ ids.add(id);
626
+ }
627
+ }
628
+ }
629
+ return Array.from(ids).slice(0, MAX_RELATION_MATCHES);
630
+ }
631
+ async function buildRelationshipSearchClause(args) {
632
+ const { field, search, db, collections } = args;
633
+ if (!field.name || field.type !== "relationship" || !field.relationTo) {
634
+ return void 0;
635
+ }
636
+ const relatedCollection = collections.find(
637
+ (collection) => collection.slug === field.relationTo
638
+ );
639
+ if (!relatedCollection) return void 0;
640
+ const ids = await findMatchingDocumentIdsByTitle({
641
+ collection: relatedCollection,
642
+ search,
643
+ db,
644
+ collections
645
+ });
646
+ return buildRelationshipMatchClause(field, ids);
647
+ }
648
+ async function buildCollectionSearchWhere(args) {
649
+ const { collection, search, db, collections } = args;
650
+ const trimmedSearch = search?.trim();
651
+ if (!trimmedSearch) return void 0;
652
+ const searchableFields = getCollectionSearchableFields(collection);
653
+ if (searchableFields.length === 0) return void 0;
654
+ const clauses = [];
655
+ for (const field of searchableFields) {
656
+ if (!field.name) continue;
657
+ if (isScalarSearchField(field)) {
658
+ clauses.push(buildScalarSearchClause(field.name, trimmedSearch));
659
+ continue;
660
+ }
661
+ if (field.type === "relationship") {
662
+ const clause = await buildRelationshipSearchClause({
663
+ field,
664
+ search: trimmedSearch,
665
+ db,
666
+ collections
667
+ });
668
+ if (clause) clauses.push(clause);
669
+ }
670
+ }
671
+ if (clauses.length === 0) return void 0;
672
+ return clauses.length === 1 ? clauses[0] : { OR: clauses };
673
+ }
674
+
490
675
  // src/utils/access-control.ts
491
676
  function toHookRequestContext(req) {
492
677
  return {
@@ -711,6 +896,7 @@ var CollectionController = class {
711
896
  const limit2 = Number(c.req.query("limit")) || 10;
712
897
  const page2 = Number(c.req.query("page")) || 1;
713
898
  const sort2 = c.req.query("sort") || void 0;
899
+ const search2 = c.req.query("search") || void 0;
714
900
  let where2 = void 0;
715
901
  const whereRaw2 = c.req.query("where");
716
902
  if (whereRaw2) {
@@ -719,6 +905,15 @@ var CollectionController = class {
719
905
  } catch {
720
906
  }
721
907
  }
908
+ const searchWhere2 = await buildCollectionSearchWhere({
909
+ collection: this.collection,
910
+ search: search2,
911
+ db: createReadonlyDb(db),
912
+ collections: config.collections
913
+ });
914
+ if (searchWhere2) {
915
+ where2 = mergeWhereConstraint(where2, searchWhere2);
916
+ }
722
917
  const paginatedResult = await provider.members.list({
723
918
  limit: limit2,
724
919
  page: page2,
@@ -753,6 +948,7 @@ var CollectionController = class {
753
948
  const page = Number(c.req.query("page")) || 1;
754
949
  const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
755
950
  const sort = c.req.query("sort") || void 0;
951
+ const search = c.req.query("search") || void 0;
756
952
  const user = c.get("user");
757
953
  let where = void 0;
758
954
  const whereRaw = c.req.query("where");
@@ -773,6 +969,15 @@ var CollectionController = class {
773
969
  }
774
970
  }
775
971
  }
972
+ const searchWhere = await buildCollectionSearchWhere({
973
+ collection: this.collection,
974
+ search,
975
+ db: readonlyDb,
976
+ collections: config.collections
977
+ });
978
+ if (searchWhere) {
979
+ where = mergeWhereConstraint(where, searchWhere);
980
+ }
776
981
  const beforeReadResult = await runCollectionHooks(
777
982
  this.collection.hooks?.beforeRead,
778
983
  {
@@ -4321,7 +4526,6 @@ function registerRoutes(app, config) {
4321
4526
  app.get(`${path}/:id`, (c) => controller.findOne(c));
4322
4527
  app.patch(`${path}/:id`, (c) => controller.update(c));
4323
4528
  app.delete(`${path}/:id`, (c) => controller.delete(c));
4324
- app.post(`${path}/seed`, (c) => controller.seed(c));
4325
4529
  if (collection.auth) {
4326
4530
  app.post(
4327
4531
  `${path}/:id/change-password`,
@@ -4347,7 +4551,6 @@ function registerRoutes(app, config) {
4347
4551
  const controller = new GlobalController(global);
4348
4552
  app.get(path, (c) => controller.get(c));
4349
4553
  app.patch(path, (c) => controller.update(c));
4350
- app.post(`${path}/seed`, (c) => controller.seed(c));
4351
4554
  }
4352
4555
  if (!process.env.DYRECTED_JWT_SECRET) {
4353
4556
  getConfigLogger(config, "router").warn({
@@ -4496,7 +4699,6 @@ function registerRoutes(app, config) {
4496
4699
  return controller.deleteMany(c);
4497
4700
  if (method === "DELETE") return controller.delete(c);
4498
4701
  if (method === "POST" && id === "media") return controller.create(c);
4499
- if (method === "POST" && id === "seed") return controller.seed(c);
4500
4702
  } else {
4501
4703
  if (method === "GET") return controller.find(c);
4502
4704
  if (method === "POST") return controller.create(c);
@@ -4522,9 +4724,7 @@ function registerRoutes(app, config) {
4522
4724
  if (global) {
4523
4725
  const controller = new GlobalController(global);
4524
4726
  const method = c.req.method;
4525
- if (id) {
4526
- if (method === "POST" && id === "seed") return controller.seed(c);
4527
- } else {
4727
+ if (!id) {
4528
4728
  if (method === "GET") return controller.get(c);
4529
4729
  if (method === "PATCH") return controller.update(c);
4530
4730
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyrected/core",
3
- "version": "2.5.64",
3
+ "version": "2.6.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",