@rebasepro/common 0.1.2 → 0.2.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.
package/dist/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EntityReference, EntityRelation, getDataSourceCapabilities } from "@rebasepro/types";
2
- import { mergeDeep, getIn as getIn$1, isDefaultFieldConfigId, randomString, toSnakeCase, generateForeignKeyName, removeFunctions, deepClone } from "@rebasepro/utils";
2
+ import { mergeDeep, toSnakeCase, generateForeignKeyName, getIn as getIn$1, isDefaultFieldConfigId, randomString, removeFunctions, deepClone } from "@rebasepro/utils";
3
3
  import jsonLogic from "json-logic-js";
4
4
  import { deepEqual } from "fast-equals";
5
5
  const DEFAULT_ONE_OF_TYPE = "type";
@@ -59,6 +59,10 @@ function getDefaultValueFortype(type) {
59
59
  return [];
60
60
  } else if (type === "map") {
61
61
  return {};
62
+ } else if (type === "vector") {
63
+ return null;
64
+ } else if (type === "binary") {
65
+ return null;
62
66
  } else {
63
67
  return null;
64
68
  }
@@ -312,6 +316,241 @@ function segmentsToStrippedPath(paths) {
312
316
  function fullPathToCollectionSegments(path) {
313
317
  return path.split("/").filter((e, i) => i % 2 === 0);
314
318
  }
319
+ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
320
+ if (!relation.target) {
321
+ throw new Error("Relation is missing a `target` collection.");
322
+ }
323
+ const rawTarget = relation.target;
324
+ let targetCollection;
325
+ if (typeof rawTarget === "string") {
326
+ if (resolveCollection) {
327
+ targetCollection = resolveCollection(rawTarget);
328
+ }
329
+ if (!targetCollection) {
330
+ targetCollection = {
331
+ slug: rawTarget,
332
+ name: rawTarget
333
+ };
334
+ }
335
+ } else if (typeof rawTarget === "function") {
336
+ const evaluated = rawTarget();
337
+ if (typeof evaluated === "string") {
338
+ if (resolveCollection) {
339
+ targetCollection = resolveCollection(evaluated);
340
+ }
341
+ if (!targetCollection) {
342
+ targetCollection = {
343
+ slug: evaluated,
344
+ name: evaluated
345
+ };
346
+ }
347
+ } else {
348
+ targetCollection = evaluated;
349
+ }
350
+ } else if (rawTarget && typeof rawTarget === "object") {
351
+ targetCollection = rawTarget;
352
+ }
353
+ if (!targetCollection) {
354
+ throw new Error("Relation is missing a valid `target` collection.");
355
+ }
356
+ const newRelation = {
357
+ ...relation
358
+ };
359
+ newRelation.target = () => {
360
+ if (typeof rawTarget === "string") {
361
+ return resolveCollection && resolveCollection(rawTarget) || targetCollection;
362
+ } else if (typeof rawTarget === "function") {
363
+ const evaluated = rawTarget();
364
+ if (typeof evaluated === "string") {
365
+ return resolveCollection && resolveCollection(evaluated) || targetCollection;
366
+ }
367
+ return evaluated;
368
+ }
369
+ return targetCollection;
370
+ };
371
+ if (!newRelation.relationName) {
372
+ newRelation.relationName = toSnakeCase(targetCollection.slug);
373
+ }
374
+ if (!newRelation.direction) {
375
+ if (newRelation.foreignKeyOnTarget) newRelation.direction = "inverse";
376
+ else if (newRelation.through) newRelation.direction = "owning";
377
+ else if (newRelation.cardinality === "many") newRelation.direction = "inverse";
378
+ else newRelation.direction = "owning";
379
+ }
380
+ if (!newRelation.joinPath) {
381
+ const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
382
+ if (newRelation.cardinality === "one" && newRelation.direction === "owning") {
383
+ if (!newRelation.localKey) {
384
+ newRelation.localKey = generateForeignKeyName(newRelation.relationName);
385
+ }
386
+ } else if (newRelation.cardinality === "one" && newRelation.direction === "inverse") {
387
+ if (!newRelation.foreignKeyOnTarget) {
388
+ let foundForeignKey = false;
389
+ try {
390
+ const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
391
+ for (const targetRel of targetRelations) {
392
+ if (targetRel.direction === "owning" && targetRel.cardinality === "one" && targetRel.localKey) {
393
+ try {
394
+ const targetRelTarget = targetRel.target();
395
+ if (targetRelTarget.slug === sourceCollection.slug) {
396
+ newRelation.foreignKeyOnTarget = targetRel.localKey;
397
+ foundForeignKey = true;
398
+ break;
399
+ }
400
+ } catch (e) {
401
+ continue;
402
+ }
403
+ }
404
+ }
405
+ } catch (e) {
406
+ }
407
+ if (!foundForeignKey) {
408
+ const keyPrefix = newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName;
409
+ newRelation.foreignKeyOnTarget = generateForeignKeyName(keyPrefix);
410
+ }
411
+ }
412
+ } else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
413
+ let isManyToManyInverse = false;
414
+ if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) {
415
+ try {
416
+ const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
417
+ for (const targetRel of targetRelations) {
418
+ if (targetRel.cardinality === "many" && (targetRel.direction === "owning" || !targetRel.direction) && targetRel.relationName === newRelation.inverseRelationName) {
419
+ isManyToManyInverse = true;
420
+ break;
421
+ }
422
+ }
423
+ if (!isManyToManyInverse && targetCollection.properties) {
424
+ for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
425
+ if (prop.type !== "relation") continue;
426
+ const relProp = prop;
427
+ const relName = relProp.relationName || propKey;
428
+ if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
429
+ isManyToManyInverse = true;
430
+ break;
431
+ }
432
+ }
433
+ }
434
+ } catch (e) {
435
+ }
436
+ }
437
+ if (!isManyToManyInverse && !newRelation.foreignKeyOnTarget) {
438
+ newRelation.foreignKeyOnTarget = generateForeignKeyName(sourceName);
439
+ }
440
+ } else if (newRelation.cardinality === "many" && newRelation.direction === "owning") {
441
+ const sourceTableName = getTableName(sourceCollection);
442
+ const targetTableName = getTableName(targetCollection);
443
+ newRelation.through = {
444
+ table: newRelation.through?.table ?? [sourceTableName, targetTableName].sort().join("_"),
445
+ sourceColumn: newRelation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
446
+ targetColumn: newRelation.through?.targetColumn ?? generateForeignKeyName(newRelation.relationName)
447
+ };
448
+ }
449
+ }
450
+ if (newRelation.cardinality === "one" && newRelation.direction === "owning" && !newRelation.localKey && !newRelation.joinPath) {
451
+ throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'owning' one-to-one relation requires a 'localKey'. Check the relation config for '${newRelation.relationName}'`);
452
+ }
453
+ if (newRelation.cardinality === "one" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) {
454
+ throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-one relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
455
+ }
456
+ if (newRelation.cardinality === "many" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath && !newRelation.inverseRelationName) {
457
+ throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-many relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
458
+ }
459
+ return newRelation;
460
+ }
461
+ const _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
462
+ function resolveCollectionRelations(collection) {
463
+ const cached = _resolvedRelationsCache.get(collection);
464
+ if (cached) return cached;
465
+ if (!getDataSourceCapabilities(collection.driver).supportsRelations) return {};
466
+ const relCollection = collection;
467
+ const relations = {};
468
+ const registeredRelationNames = /* @__PURE__ */ new Set();
469
+ if (relCollection.relations) {
470
+ relCollection.relations.forEach((relation) => {
471
+ try {
472
+ const normalizedRelation = sanitizeRelation(relation, collection);
473
+ const relationKey = normalizedRelation.relationName;
474
+ if (relationKey) {
475
+ relations[relationKey] = normalizedRelation;
476
+ registeredRelationNames.add(relationKey);
477
+ }
478
+ } catch (e) {
479
+ }
480
+ });
481
+ }
482
+ if (collection.properties) {
483
+ Object.entries(collection.properties).forEach(([propKey, prop]) => {
484
+ const relation = resolvePropertyRelation({
485
+ propertyKey: propKey,
486
+ property: prop,
487
+ sourceCollection: collection
488
+ });
489
+ if (relation) {
490
+ if (relations[propKey]) return;
491
+ if (!relation.relationName) {
492
+ relation.relationName = propKey;
493
+ }
494
+ const normalizedRelation = sanitizeRelation(relation, collection);
495
+ relations[propKey] = normalizedRelation;
496
+ registeredRelationNames.add(normalizedRelation.relationName ?? propKey);
497
+ }
498
+ });
499
+ }
500
+ _resolvedRelationsCache.set(collection, relations);
501
+ return relations;
502
+ }
503
+ function resolvePropertyRelation({
504
+ propertyKey,
505
+ property,
506
+ sourceCollection
507
+ }) {
508
+ if (property.type !== "relation") return void 0;
509
+ const relProp = property;
510
+ if (relProp.target) {
511
+ return {
512
+ relationName: relProp.relationName || propertyKey,
513
+ target: relProp.target,
514
+ cardinality: relProp.cardinality || "one",
515
+ direction: relProp.direction || "owning",
516
+ inverseRelationName: relProp.inverseRelationName,
517
+ localKey: relProp.localKey,
518
+ foreignKeyOnTarget: relProp.foreignKeyOnTarget,
519
+ through: relProp.through,
520
+ joinPath: relProp.joinPath,
521
+ onUpdate: relProp.onUpdate,
522
+ onDelete: relProp.onDelete,
523
+ overrides: relProp.overrides
524
+ };
525
+ }
526
+ console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
527
+ return void 0;
528
+ }
529
+ function getTableName(collection) {
530
+ if (getDataSourceCapabilities(collection.driver).supportsRelations) {
531
+ return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
532
+ }
533
+ return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
534
+ }
535
+ function getTableVarName(tableName) {
536
+ return tableName.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
537
+ }
538
+ function getEnumVarName(tableName, propName) {
539
+ const tableVar = getTableVarName(tableName);
540
+ const propVar = propName.charAt(0).toUpperCase() + propName.slice(1);
541
+ return `${tableVar}${propVar}`;
542
+ }
543
+ function getColumnName(fullColumn) {
544
+ return fullColumn.includes(".") ? fullColumn.split(".").pop() : fullColumn;
545
+ }
546
+ function findRelation(resolvedRelations, key) {
547
+ if (resolvedRelations[key]) return resolvedRelations[key];
548
+ const slugKey = key.replace(/_/g, "-");
549
+ if (slugKey !== key && resolvedRelations[slugKey]) return resolvedRelations[slugKey];
550
+ const snakeKey = key.replace(/-/g, "_");
551
+ if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
552
+ return void 0;
553
+ }
315
554
  function resolveProperty(props) {
316
555
  const {
317
556
  property,
@@ -541,8 +780,9 @@ function getSubcollections(collection) {
541
780
  if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
542
781
  return collection.subcollections() ?? [];
543
782
  }
544
- if (getDataSourceCapabilities(collection.driver).supportsRelations && collection.relations) {
545
- const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
783
+ if (getDataSourceCapabilities(collection.driver).supportsRelations) {
784
+ const resolvedRelations = resolveCollectionRelations(collection);
785
+ const manyRelations = Object.values(resolvedRelations).filter((r) => r.cardinality === "many");
546
786
  return manyRelations.map((r) => {
547
787
  const target = r.target();
548
788
  if (!target) return void 0;
@@ -1170,187 +1410,6 @@ const buildPropertyCallbacks = (properties) => {
1170
1410
  }
1171
1411
  return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1172
1412
  };
1173
- function sanitizeRelation(relation, sourceCollection) {
1174
- if (!relation.target) {
1175
- throw new Error("Relation is missing a `target` collection.");
1176
- }
1177
- const targetCollection = relation.target();
1178
- const newRelation = {
1179
- ...relation
1180
- };
1181
- if (!newRelation.relationName) {
1182
- newRelation.relationName = toSnakeCase(targetCollection.slug);
1183
- }
1184
- if (!newRelation.direction) {
1185
- if (newRelation.foreignKeyOnTarget) newRelation.direction = "inverse";
1186
- else if (newRelation.through) newRelation.direction = "owning";
1187
- else if (newRelation.cardinality === "many") newRelation.direction = "inverse";
1188
- else newRelation.direction = "owning";
1189
- }
1190
- if (!newRelation.joinPath) {
1191
- const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
1192
- if (newRelation.cardinality === "one" && newRelation.direction === "owning") {
1193
- if (!newRelation.localKey) {
1194
- newRelation.localKey = generateForeignKeyName(newRelation.relationName);
1195
- }
1196
- } else if (newRelation.cardinality === "one" && newRelation.direction === "inverse") {
1197
- if (!newRelation.foreignKeyOnTarget) {
1198
- let foundForeignKey = false;
1199
- try {
1200
- const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
1201
- for (const targetRel of targetRelations) {
1202
- if (targetRel.direction === "owning" && targetRel.cardinality === "one" && targetRel.localKey) {
1203
- try {
1204
- const targetRelTarget = targetRel.target();
1205
- if (targetRelTarget.slug === sourceCollection.slug) {
1206
- newRelation.foreignKeyOnTarget = targetRel.localKey;
1207
- foundForeignKey = true;
1208
- break;
1209
- }
1210
- } catch (e) {
1211
- continue;
1212
- }
1213
- }
1214
- }
1215
- } catch (e) {
1216
- }
1217
- if (!foundForeignKey) {
1218
- const keyPrefix = newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName;
1219
- newRelation.foreignKeyOnTarget = generateForeignKeyName(keyPrefix);
1220
- }
1221
- }
1222
- } else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
1223
- let isManyToManyInverse = false;
1224
- if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) {
1225
- try {
1226
- const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
1227
- for (const targetRel of targetRelations) {
1228
- if (targetRel.cardinality === "many" && (targetRel.direction === "owning" || !targetRel.direction) && targetRel.relationName === newRelation.inverseRelationName) {
1229
- isManyToManyInverse = true;
1230
- break;
1231
- }
1232
- }
1233
- } catch (e) {
1234
- }
1235
- }
1236
- if (!isManyToManyInverse && !newRelation.foreignKeyOnTarget) {
1237
- newRelation.foreignKeyOnTarget = generateForeignKeyName(sourceName);
1238
- }
1239
- } else if (newRelation.cardinality === "many" && newRelation.direction === "owning") {
1240
- const sourceTableName = getTableName(sourceCollection);
1241
- const targetTableName = getTableName(targetCollection);
1242
- newRelation.through = {
1243
- table: newRelation.through?.table ?? [sourceTableName, targetTableName].sort().join("_"),
1244
- sourceColumn: newRelation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
1245
- targetColumn: newRelation.through?.targetColumn ?? generateForeignKeyName(newRelation.relationName)
1246
- };
1247
- }
1248
- }
1249
- if (newRelation.cardinality === "one" && newRelation.direction === "owning" && !newRelation.localKey && !newRelation.joinPath) {
1250
- throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'owning' one-to-one relation requires a 'localKey'. Check the relation config for '${newRelation.relationName}'`);
1251
- }
1252
- if (newRelation.cardinality === "one" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) {
1253
- throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-one relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
1254
- }
1255
- if (newRelation.cardinality === "many" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath && !newRelation.inverseRelationName) {
1256
- throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-many relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
1257
- }
1258
- return newRelation;
1259
- }
1260
- const _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
1261
- function resolveCollectionRelations(collection) {
1262
- const cached = _resolvedRelationsCache.get(collection);
1263
- if (cached) return cached;
1264
- if (!getDataSourceCapabilities(collection.driver).supportsRelations) return {};
1265
- const relCollection = collection;
1266
- const relations = {};
1267
- const registeredRelationNames = /* @__PURE__ */ new Set();
1268
- if (relCollection.relations) {
1269
- relCollection.relations.forEach((relation) => {
1270
- const normalizedRelation = sanitizeRelation(relation, collection);
1271
- const relationKey = normalizedRelation.relationName;
1272
- if (relationKey) {
1273
- relations[relationKey] = normalizedRelation;
1274
- registeredRelationNames.add(relationKey);
1275
- }
1276
- });
1277
- }
1278
- if (collection.properties) {
1279
- Object.entries(collection.properties).forEach(([propKey, prop]) => {
1280
- const relation = resolvePropertyRelation({
1281
- propertyKey: propKey,
1282
- property: prop,
1283
- sourceCollection: collection
1284
- });
1285
- if (relation) {
1286
- if (relations[propKey]) return;
1287
- if (!relation.relationName) {
1288
- relation.relationName = propKey;
1289
- }
1290
- const normalizedRelation = sanitizeRelation(relation, collection);
1291
- relations[propKey] = normalizedRelation;
1292
- registeredRelationNames.add(normalizedRelation.relationName ?? propKey);
1293
- }
1294
- });
1295
- }
1296
- _resolvedRelationsCache.set(collection, relations);
1297
- return relations;
1298
- }
1299
- function resolvePropertyRelation({
1300
- propertyKey,
1301
- property,
1302
- sourceCollection
1303
- }) {
1304
- if (property.type !== "relation") return void 0;
1305
- const relProp = property;
1306
- if (relProp.target) {
1307
- return {
1308
- relationName: relProp.relationName || propertyKey,
1309
- target: relProp.target,
1310
- cardinality: relProp.cardinality || "one",
1311
- direction: relProp.direction || "owning",
1312
- inverseRelationName: relProp.inverseRelationName,
1313
- localKey: relProp.localKey,
1314
- foreignKeyOnTarget: relProp.foreignKeyOnTarget,
1315
- through: relProp.through,
1316
- joinPath: relProp.joinPath,
1317
- onUpdate: relProp.onUpdate,
1318
- onDelete: relProp.onDelete,
1319
- overrides: relProp.overrides
1320
- };
1321
- }
1322
- const relation = (sourceCollection.relations ?? []).find((rel) => rel.relationName === relProp.relationName);
1323
- if (!relation) {
1324
- console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1325
- return void 0;
1326
- }
1327
- return relation;
1328
- }
1329
- function getTableName(collection) {
1330
- if (getDataSourceCapabilities(collection.driver).supportsRelations) {
1331
- return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1332
- }
1333
- return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1334
- }
1335
- function getTableVarName(tableName) {
1336
- return tableName.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
1337
- }
1338
- function getEnumVarName(tableName, propName) {
1339
- const tableVar = getTableVarName(tableName);
1340
- const propVar = propName.charAt(0).toUpperCase() + propName.slice(1);
1341
- return `${tableVar}${propVar}`;
1342
- }
1343
- function getColumnName(fullColumn) {
1344
- return fullColumn.includes(".") ? fullColumn.split(".").pop() : fullColumn;
1345
- }
1346
- function findRelation(resolvedRelations, key) {
1347
- if (resolvedRelations[key]) return resolvedRelations[key];
1348
- const slugKey = key.replace(/_/g, "-");
1349
- if (slugKey !== key && resolvedRelations[slugKey]) return resolvedRelations[slugKey];
1350
- const snakeKey = key.replace(/-/g, "_");
1351
- if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
1352
- return void 0;
1353
- }
1354
1413
  function getIn(obj, path) {
1355
1414
  if (!obj || !path) return void 0;
1356
1415
  return path.split(".").reduce((acc, part) => acc && acc[part], obj);
@@ -1597,6 +1656,12 @@ class CollectionRegistry {
1597
1656
  return false;
1598
1657
  }
1599
1658
  this.reset();
1659
+ collections.forEach((c) => {
1660
+ if (c.slug) {
1661
+ this.collectionsBySlug.set(c.slug, c);
1662
+ }
1663
+ this.collectionsByTableName.set(getTableName(c), c);
1664
+ });
1600
1665
  const normalizedCollections = collections.map((c) => this.normalizeCollection({
1601
1666
  ...c
1602
1667
  }));
@@ -1667,15 +1732,25 @@ class CollectionRegistry {
1667
1732
  const mergedRelationsRaw = [...extractedRelations];
1668
1733
  for (const manual of manualRelations) {
1669
1734
  const name = manual.relationName;
1670
- if (!name || !mergedRelationsRaw.find((r) => r.relationName === name)) {
1735
+ if (!name) {
1671
1736
  mergedRelationsRaw.push(manual);
1737
+ } else {
1738
+ const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
1739
+ if (existingIndex === -1) {
1740
+ mergedRelationsRaw.push(manual);
1741
+ } else {
1742
+ mergedRelationsRaw[existingIndex] = {
1743
+ ...manual,
1744
+ ...mergedRelationsRaw[existingIndex]
1745
+ };
1746
+ }
1672
1747
  }
1673
1748
  }
1674
1749
  let mergedRelations = mergedRelationsRaw;
1675
1750
  if (getDataSourceCapabilities(result.driver).supportsRelations) {
1676
1751
  mergedRelations = mergedRelationsRaw.map((r) => {
1677
1752
  try {
1678
- return sanitizeRelation(r, result);
1753
+ return sanitizeRelation(r, result, (slug) => this.get(slug));
1679
1754
  } catch {
1680
1755
  return r;
1681
1756
  }
@@ -1888,6 +1963,118 @@ class CollectionRegistry {
1888
1963
  };
1889
1964
  }
1890
1965
  }
1966
+ function mapOperator(op) {
1967
+ switch (op) {
1968
+ case "==":
1969
+ return "eq";
1970
+ case "!=":
1971
+ return "neq";
1972
+ case ">":
1973
+ return "gt";
1974
+ case ">=":
1975
+ return "gte";
1976
+ case "<":
1977
+ return "lt";
1978
+ case "<=":
1979
+ return "lte";
1980
+ case "array-contains":
1981
+ return "cs";
1982
+ case "array-contains-any":
1983
+ return "csa";
1984
+ case "not-in":
1985
+ return "nin";
1986
+ default:
1987
+ return op;
1988
+ }
1989
+ }
1990
+ class QueryBuilder {
1991
+ constructor(collection) {
1992
+ this.collection = collection;
1993
+ }
1994
+ params = {
1995
+ where: {}
1996
+ };
1997
+ /**
1998
+ * Add a filter condition to your query.
1999
+ * @example
2000
+ * client.collection('users').where('age', '>=', 18).find()
2001
+ */
2002
+ where(column, operator, value) {
2003
+ if (!this.params.where) {
2004
+ this.params.where = {};
2005
+ }
2006
+ const mappedOp = mapOperator(operator);
2007
+ let formattedValue = value;
2008
+ if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2009
+ formattedValue = `(${value.join(",")})`;
2010
+ } else if (value === null) {
2011
+ formattedValue = "null";
2012
+ }
2013
+ this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2014
+ return this;
2015
+ }
2016
+ /**
2017
+ * Order the results by a specific column.
2018
+ * @example
2019
+ * client.collection('users').orderBy('createdAt', 'desc').find()
2020
+ */
2021
+ orderBy(column, ascending = "asc") {
2022
+ this.params.orderBy = `${column}:${ascending}`;
2023
+ return this;
2024
+ }
2025
+ /**
2026
+ * Limit the number of results returned.
2027
+ */
2028
+ limit(count) {
2029
+ this.params.limit = count;
2030
+ return this;
2031
+ }
2032
+ /**
2033
+ * Skip the first N results.
2034
+ */
2035
+ offset(count) {
2036
+ this.params.offset = count;
2037
+ return this;
2038
+ }
2039
+ /**
2040
+ * Set a free-text search string if supported by the backend.
2041
+ */
2042
+ search(searchString) {
2043
+ this.params.searchString = searchString;
2044
+ return this;
2045
+ }
2046
+ /**
2047
+ * Include related entities in the response.
2048
+ * Relations will be populated with full entity data instead of just IDs.
2049
+ *
2050
+ * @param relations - Relation names to include, or "*" for all.
2051
+ * @example
2052
+ * // Include specific relations
2053
+ * client.data.posts.include("tags", "author").find()
2054
+ *
2055
+ * // Include all relations
2056
+ * client.data.posts.include("*").find()
2057
+ */
2058
+ include(...relations) {
2059
+ this.params.include = relations;
2060
+ return this;
2061
+ }
2062
+ /**
2063
+ * Execute the find query and return the results.
2064
+ */
2065
+ async find() {
2066
+ return this.collection.find(this.params);
2067
+ }
2068
+ /**
2069
+ * Listen to realtime updates matching this query.
2070
+ */
2071
+ listen(onUpdate, onError) {
2072
+ if (!this.collection.listen) {
2073
+ throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
2074
+ }
2075
+ return this.collection.listen(this.params, onUpdate, onError);
2076
+ }
2077
+ }
1891
2078
  function convertWhereToFilter(where) {
1892
2079
  if (!where) return void 0;
1893
2080
  const operatorMap = {
@@ -1967,7 +2154,7 @@ function parseOrderBy(orderBy) {
1967
2154
  return [field, direction];
1968
2155
  }
1969
2156
  function createDriverAccessor(driver, slug) {
1970
- return {
2157
+ const accessor = {
1971
2158
  async find(params) {
1972
2159
  const orderParsed = parseOrderBy(params?.orderBy);
1973
2160
  const entities = await driver.fetchCollection({
@@ -2061,8 +2248,28 @@ function createDriverAccessor(driver, slug) {
2061
2248
  onUpdate: (entity) => onUpdate(entity ?? void 0),
2062
2249
  onError
2063
2250
  });
2064
- } : void 0
2251
+ } : void 0,
2252
+ // Fluent Query Builder
2253
+ where(column, operator, value) {
2254
+ return new QueryBuilder(accessor).where(column, operator, value);
2255
+ },
2256
+ orderBy(column, ascending) {
2257
+ return new QueryBuilder(accessor).orderBy(column, ascending);
2258
+ },
2259
+ limit(count) {
2260
+ return new QueryBuilder(accessor).limit(count);
2261
+ },
2262
+ offset(count) {
2263
+ return new QueryBuilder(accessor).offset(count);
2264
+ },
2265
+ search(searchString) {
2266
+ return new QueryBuilder(accessor).search(searchString);
2267
+ },
2268
+ include(...relations) {
2269
+ return new QueryBuilder(accessor).include(...relations);
2270
+ }
2065
2271
  };
2272
+ return accessor;
2066
2273
  }
2067
2274
  function buildRebaseData(driver) {
2068
2275
  const cache = /* @__PURE__ */ new Map();
@@ -2092,6 +2299,7 @@ export {
2092
2299
  CollectionRegistry,
2093
2300
  DEFAULT_ONE_OF_TYPE,
2094
2301
  DEFAULT_ONE_OF_VALUE,
2302
+ QueryBuilder,
2095
2303
  addInitialSlash,
2096
2304
  applyPropertyConditions,
2097
2305
  buildAdditionalFieldDelegate,