@rebasepro/common 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e

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/README.md CHANGED
@@ -69,9 +69,9 @@ Rebase has been meticulously crafted to make it incredibly easy for developers
69
69
  to build a CMS/admin tool while offering an excellent data editing experience
70
70
  and a user-friendly interface for marketers and content managers.
71
71
 
72
- ### 🏓 Exceptional Spreadsheet View
72
+ ### 🏓 Exceptional Table View
73
73
 
74
- We've developed a highly efficient windowed **spreadsheet view** for
74
+ We've developed a highly efficient windowed **table view** for
75
75
  collections, allowing inline editing for most common fields, as well as popup
76
76
  views for other cases and your custom field implementations.
77
77
 
@@ -3,9 +3,11 @@ export declare class CollectionRegistry {
3
3
  private collectionsByTableName;
4
4
  private collectionsBySlug;
5
5
  private rootCollections;
6
+ private cachedCollectionsList;
6
7
  private rawCollectionsByTableName;
7
8
  private rawCollectionsBySlug;
8
9
  private rawRootCollections;
10
+ private cachedRawCollectionsList;
9
11
  private lastRawInputSnapshot;
10
12
  constructor(collections?: EntityCollection[]);
11
13
  reset(): void;
@@ -21,6 +23,12 @@ export declare class CollectionRegistry {
21
23
  register(collection: EntityCollection, rawCollection?: EntityCollection): void;
22
24
  private _registerRecursively;
23
25
  normalizeCollection(collection: EntityCollection): EntityCollection;
26
+ /**
27
+ * Extract Relation[] from properties that have inline relation config (i.e. `target` is set).
28
+ * This allows developers to define relations directly on properties without a separate
29
+ * `relations[]` entry on the collection.
30
+ */
31
+ private extractRelationsFromProperties;
24
32
  private normalizeProperties;
25
33
  private normalizeProperty;
26
34
  get(path: string): EntityCollection | undefined;
package/dist/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { EntityReference, EntityRelation, isFirebaseCollection, isPostgresCollection } from "@rebasepro/types";
1
+ import { EntityReference, EntityRelation, getDataSourceCapabilities } from "@rebasepro/types";
2
2
  import { mergeDeep, getIn as getIn$1, isDefaultFieldConfigId, randomString, toSnakeCase, generateForeignKeyName, removeFunctions } from "@rebasepro/utils";
3
3
  import jsonLogic from "json-logic-js";
4
4
  import { deepEqual } from "fast-equals";
@@ -108,7 +108,7 @@ function normalizeToEntityRelation(value) {
108
108
  if (value instanceof EntityRelation) return value;
109
109
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
110
110
  const obj = value;
111
- const isRelationLike = obj.__type === "relation" || typeof obj.isEntityRelation === "function" && obj.isEntityRelation();
111
+ const isRelationLike = obj.__type === "relation" || obj.__type === "reference" || typeof obj.isEntityRelation === "function" && obj.isEntityRelation() || typeof obj.isEntityReference === "function" && obj.isEntityReference();
112
112
  if (!isRelationLike) return null;
113
113
  return new EntityRelation(obj.id, obj.path, obj.data);
114
114
  }
@@ -166,6 +166,21 @@ function traverseValueProperty(inputValue, property, operation) {
166
166
  }
167
167
  return value;
168
168
  }
169
+ function createRelationRef(id, path) {
170
+ return {
171
+ id,
172
+ path,
173
+ __type: "relation"
174
+ };
175
+ }
176
+ function createRelationRefWithData(id, path, data) {
177
+ return {
178
+ id,
179
+ path,
180
+ __type: "relation",
181
+ data
182
+ };
183
+ }
169
184
  function sortProperties(properties, propertiesOrder) {
170
185
  try {
171
186
  const propertiesKeys = Object.keys(properties);
@@ -307,22 +322,24 @@ function resolveProperty(props) {
307
322
  let resultProperty;
308
323
  if (isPropertyBuilder(property)) {
309
324
  const path = rest.path;
310
- if (!path) throw Error("Trying to resolve a property builder without specifying the entity path");
311
- const usedPropertyValue = rest.propertyKey ? getIn$1(rest.values, rest.propertyKey) : void 0;
312
- const dynamicProps = property.dynamicProps?.({
313
- ...rest,
314
- path,
315
- propertyValue: usedPropertyValue,
316
- values: rest.values ?? {},
317
- previousValues: rest.previousValues ?? rest.values ?? {}
318
- });
319
- resultProperty = mergeDeep(property, dynamicProps ?? {});
325
+ if (!path) {
326
+ resultProperty = property;
327
+ } else {
328
+ const usedPropertyValue = rest.propertyKey ? getIn$1(rest.values, rest.propertyKey) : void 0;
329
+ const dynamicProps = property.dynamicProps?.({
330
+ ...rest,
331
+ path,
332
+ propertyValue: usedPropertyValue,
333
+ values: rest.values ?? {},
334
+ previousValues: rest.previousValues ?? rest.values ?? {}
335
+ });
336
+ resultProperty = mergeDeep(property, dynamicProps ?? {});
337
+ }
320
338
  } else {
321
339
  resultProperty = property;
322
340
  }
323
- if (resultProperty.dynamicProps) {
341
+ if (resultProperty?.dynamicProps && rest.path) {
324
342
  const path = rest.path;
325
- if (!path) throw Error("Trying to resolve dynamicProps without specifying the entity path");
326
343
  const usedPropertyValue = rest.propertyKey ? getIn$1(rest.values, rest.propertyKey) : void 0;
327
344
  const dynamicPropsResult = resultProperty.dynamicProps({
328
345
  ...rest,
@@ -383,10 +400,14 @@ function resolveProperty(props) {
383
400
  }
384
401
  return resolvedProperty;
385
402
  }
386
- function resolveRelationProperty(property, relations) {
387
- const relation = relations.find((rel) => rel.relationName === property.relationName);
403
+ function resolveRelationProperty(property, relations, propertyKey) {
404
+ if (property.relation) {
405
+ return property;
406
+ }
407
+ const name = property.relationName || propertyKey;
408
+ const relation = name ? relations.find((rel) => rel.relationName === name) : void 0;
388
409
  if (!relation) {
389
- throw Error(`Relation ${property.relationName} not found`);
410
+ throw Error(`Relation ${name ?? "(unnamed)"} not found`);
390
411
  }
391
412
  return {
392
413
  ...property,
@@ -518,15 +539,35 @@ function getSubcollections(collection) {
518
539
  if (collection.childCollections) {
519
540
  return collection.childCollections() ?? [];
520
541
  }
521
- if (isFirebaseCollection(collection) && collection.subcollections) {
542
+ if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
522
543
  return collection.subcollections() ?? [];
523
544
  }
524
- if (isPostgresCollection(collection) && collection.relations) {
545
+ if (getDataSourceCapabilities(collection.driver).supportsRelations && collection.relations) {
525
546
  const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
526
547
  return manyRelations.map((r) => {
527
548
  const target = r.target();
528
- return r.overrides ? mergeDeep(target, r.overrides) : target;
529
- });
549
+ if (!target) return void 0;
550
+ const relationKey = r.relationName || target.slug;
551
+ let customName;
552
+ if (collection.properties) {
553
+ const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
554
+ if (prop && prop[1].name) {
555
+ customName = prop[1].name;
556
+ }
557
+ }
558
+ const baseOverrides = {
559
+ slug: relationKey
560
+ };
561
+ if (customName) {
562
+ baseOverrides.name = customName;
563
+ baseOverrides.singularName = customName;
564
+ }
565
+ const targetWithOverrides = {
566
+ ...target,
567
+ ...baseOverrides
568
+ };
569
+ return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
570
+ }).filter((c) => Boolean(c));
530
571
  }
531
572
  return [];
532
573
  }
@@ -594,8 +635,8 @@ function evaluateAST(sqlString, auth, entity) {
594
635
  const userRoles = auth.user?.roles || [];
595
636
  return requiredRoles.every((r) => userRoles.includes(r));
596
637
  }
597
- const pattern1 = new RegExp(`^\\{?([a-zA-Z0-9_]+)\\}?\\s*=\\s*(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))`);
598
- const pattern2 = new RegExp(`^(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))\\s*=\\s*\\{?([a-zA-Z0-9_]+)\\}?`);
638
+ const pattern1 = new RegExp("^\\{?([a-zA-Z0-9_]+)\\}?\\s*=\\s*(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))");
639
+ const pattern2 = new RegExp("^(?:current_setting\\s*\\(\\s*'app\\.user_id'\\s*\\)|auth\\.uid\\(\\))\\s*=\\s*\\{?([a-zA-Z0-9_]+)\\}?");
599
640
  const match1 = cleanedSQL.match(pattern1);
600
641
  if (match1 && match1[1]) {
601
642
  return entity.values[match1[1]] === auth.user?.uid;
@@ -628,7 +669,7 @@ function evaluateRule(rule, auth, entity) {
628
669
  return true;
629
670
  }
630
671
  function checkOperation(collection, authController, entity, targetOperation) {
631
- const securityRules = isPostgresCollection(collection) ? collection.securityRules : void 0;
672
+ const securityRules = getDataSourceCapabilities(collection.driver).supportsRLS ? collection.securityRules : void 0;
632
673
  if (!securityRules || securityRules.length === 0) {
633
674
  return true;
634
675
  }
@@ -1157,7 +1198,7 @@ function sanitizeRelation(relation, sourceCollection) {
1157
1198
  if (!newRelation.foreignKeyOnTarget) {
1158
1199
  let foundForeignKey = false;
1159
1200
  try {
1160
- const targetRelations = targetCollection.relations || [];
1201
+ const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
1161
1202
  for (const targetRel of targetRelations) {
1162
1203
  if (targetRel.direction === "owning" && targetRel.cardinality === "one" && targetRel.localKey) {
1163
1204
  try {
@@ -1183,7 +1224,7 @@ function sanitizeRelation(relation, sourceCollection) {
1183
1224
  let isManyToManyInverse = false;
1184
1225
  if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) {
1185
1226
  try {
1186
- const targetRelations = targetCollection.relations || [];
1227
+ const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? targetCollection.relations || [] : [];
1187
1228
  for (const targetRel of targetRelations) {
1188
1229
  if (targetRel.cardinality === "many" && (targetRel.direction === "owning" || !targetRel.direction) && targetRel.relationName === newRelation.inverseRelationName) {
1189
1230
  isManyToManyInverse = true;
@@ -1217,14 +1258,21 @@ function sanitizeRelation(relation, sourceCollection) {
1217
1258
  }
1218
1259
  return newRelation;
1219
1260
  }
1261
+ const _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
1220
1262
  function resolveCollectionRelations(collection) {
1263
+ const cached = _resolvedRelationsCache.get(collection);
1264
+ if (cached) return cached;
1265
+ if (!getDataSourceCapabilities(collection.driver).supportsRelations) return {};
1266
+ const relCollection = collection;
1221
1267
  const relations = {};
1222
- if (collection.relations) {
1223
- collection.relations.forEach((relation) => {
1268
+ const registeredRelationNames = /* @__PURE__ */ new Set();
1269
+ if (relCollection.relations) {
1270
+ relCollection.relations.forEach((relation) => {
1224
1271
  const normalizedRelation = sanitizeRelation(relation, collection);
1225
1272
  const relationKey = normalizedRelation.relationName;
1226
1273
  if (relationKey) {
1227
1274
  relations[relationKey] = normalizedRelation;
1275
+ registeredRelationNames.add(relationKey);
1228
1276
  }
1229
1277
  });
1230
1278
  }
@@ -1236,15 +1284,17 @@ function resolveCollectionRelations(collection) {
1236
1284
  sourceCollection: collection
1237
1285
  });
1238
1286
  if (relation) {
1239
- if (!relations[propKey]) {
1240
- if (!relation.relationName) {
1241
- relation.relationName = propKey;
1242
- }
1243
- relations[propKey] = sanitizeRelation(relation, collection);
1287
+ if (relations[propKey]) return;
1288
+ if (!relation.relationName) {
1289
+ relation.relationName = propKey;
1244
1290
  }
1291
+ const normalizedRelation = sanitizeRelation(relation, collection);
1292
+ relations[propKey] = normalizedRelation;
1293
+ registeredRelationNames.add(normalizedRelation.relationName ?? propKey);
1245
1294
  }
1246
1295
  });
1247
1296
  }
1297
+ _resolvedRelationsCache.set(collection, relations);
1248
1298
  return relations;
1249
1299
  }
1250
1300
  function resolvePropertyRelation({
@@ -1253,7 +1303,24 @@ function resolvePropertyRelation({
1253
1303
  sourceCollection
1254
1304
  }) {
1255
1305
  if (property.type !== "relation") return void 0;
1256
- const relation = (sourceCollection.relations ?? []).find((rel) => rel.relationName === property.relationName);
1306
+ const relProp = property;
1307
+ if (relProp.target) {
1308
+ return {
1309
+ relationName: relProp.relationName || propertyKey,
1310
+ target: relProp.target,
1311
+ cardinality: relProp.cardinality || "one",
1312
+ direction: relProp.direction || "owning",
1313
+ inverseRelationName: relProp.inverseRelationName,
1314
+ localKey: relProp.localKey,
1315
+ foreignKeyOnTarget: relProp.foreignKeyOnTarget,
1316
+ through: relProp.through,
1317
+ joinPath: relProp.joinPath,
1318
+ onUpdate: relProp.onUpdate,
1319
+ onDelete: relProp.onDelete,
1320
+ overrides: relProp.overrides
1321
+ };
1322
+ }
1323
+ const relation = (sourceCollection.relations ?? []).find((rel) => rel.relationName === relProp.relationName);
1257
1324
  if (!relation) {
1258
1325
  console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1259
1326
  return void 0;
@@ -1261,7 +1328,7 @@ function resolvePropertyRelation({
1261
1328
  return relation;
1262
1329
  }
1263
1330
  function getTableName(collection) {
1264
- if (isPostgresCollection(collection)) {
1331
+ if (getDataSourceCapabilities(collection.driver).supportsRelations) {
1265
1332
  return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1266
1333
  }
1267
1334
  return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
@@ -1277,6 +1344,14 @@ function getEnumVarName(tableName, propName) {
1277
1344
  function getColumnName(fullColumn) {
1278
1345
  return fullColumn.includes(".") ? fullColumn.split(".").pop() : fullColumn;
1279
1346
  }
1347
+ function findRelation(resolvedRelations, key) {
1348
+ if (resolvedRelations[key]) return resolvedRelations[key];
1349
+ const slugKey = key.replace(/_/g, "-");
1350
+ if (slugKey !== key && resolvedRelations[slugKey]) return resolvedRelations[slugKey];
1351
+ const snakeKey = key.replace(/-/g, "_");
1352
+ if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
1353
+ return void 0;
1354
+ }
1280
1355
  function getIn(obj, path) {
1281
1356
  if (!obj || !path) return void 0;
1282
1357
  return path.split(".").reduce((acc, part) => acc && acc[part], obj);
@@ -1372,7 +1447,7 @@ function applyPropertyConditions(property, context) {
1372
1447
  conditions
1373
1448
  } = property;
1374
1449
  if (!conditions) return property;
1375
- let result = {
1450
+ const result = {
1376
1451
  ...property
1377
1452
  };
1378
1453
  if (conditions.disabled) {
@@ -1482,10 +1557,12 @@ class CollectionRegistry {
1482
1557
  collectionsByTableName = /* @__PURE__ */ new Map();
1483
1558
  collectionsBySlug = /* @__PURE__ */ new Map();
1484
1559
  rootCollections = [];
1560
+ cachedCollectionsList = null;
1485
1561
  // Raw configuration layer (used by Collection Editor AST generator)
1486
1562
  rawCollectionsByTableName = /* @__PURE__ */ new Map();
1487
1563
  rawCollectionsBySlug = /* @__PURE__ */ new Map();
1488
1564
  rawRootCollections = [];
1565
+ cachedRawCollectionsList = null;
1489
1566
  // Snapshot of raw input for idempotency check — compared BEFORE normalization
1490
1567
  // to avoid the issue where normalization creates new objects that always fail equality.
1491
1568
  lastRawInputSnapshot = null;
@@ -1498,9 +1575,11 @@ class CollectionRegistry {
1498
1575
  this.collectionsByTableName.clear();
1499
1576
  this.collectionsBySlug.clear();
1500
1577
  this.rootCollections = [];
1578
+ this.cachedCollectionsList = null;
1501
1579
  this.rawCollectionsByTableName.clear();
1502
1580
  this.rawCollectionsBySlug.clear();
1503
1581
  this.rawRootCollections = [];
1582
+ this.cachedRawCollectionsList = null;
1504
1583
  }
1505
1584
  /**
1506
1585
  * Registers a collection and its subcollections recursively.
@@ -1533,12 +1612,14 @@ class CollectionRegistry {
1533
1612
  this.rawCollectionsBySlug.set(raw.slug, raw);
1534
1613
  }
1535
1614
  });
1536
- normalizedCollections.forEach((c, index) => {
1615
+ normalizedCollections.forEach((c) => {
1537
1616
  const subcollections = getSubcollections(c);
1538
- const rawSubcollections = getSubcollections(collections[index]);
1539
- if (subcollections && rawSubcollections) {
1540
- subcollections.forEach((subCollection, subIndex) => {
1541
- this._registerRecursively(this.normalizeCollection(subCollection), cloneDeep(rawSubcollections[subIndex]));
1617
+ if (subcollections && subcollections.length > 0) {
1618
+ subcollections.forEach((subCollection) => {
1619
+ if (!subCollection) return;
1620
+ this._registerRecursively(this.normalizeCollection({
1621
+ ...subCollection
1622
+ }), cloneDeep(subCollection));
1542
1623
  });
1543
1624
  }
1544
1625
  });
@@ -1564,41 +1645,100 @@ class CollectionRegistry {
1564
1645
  if (rawCollection.slug) {
1565
1646
  this.rawCollectionsBySlug.set(rawCollection.slug, rawCollection);
1566
1647
  }
1567
- const subcollections = getSubcollections(collection);
1568
- const rawSubcollections = getSubcollections(rawCollection);
1569
- if (subcollections && rawSubcollections) {
1570
- subcollections.forEach((subCollection, index) => {
1571
- this._registerRecursively(this.normalizeCollection(subCollection), cloneDeep(rawSubcollections[index]));
1648
+ const subcollections = getSubcollections(normalizedCollection);
1649
+ if (subcollections && subcollections.length > 0) {
1650
+ subcollections.forEach((subCollection) => {
1651
+ if (!subCollection) return;
1652
+ this._registerRecursively(this.normalizeCollection({
1653
+ ...subCollection
1654
+ }), cloneDeep(subCollection));
1572
1655
  });
1573
1656
  }
1574
1657
  }
1575
1658
  normalizeCollection(collection) {
1576
- const relations = isPostgresCollection(collection) ? collection.relations ?? [] : [];
1577
- const properties = this.normalizeProperties(collection.properties, relations);
1578
- collection.properties = properties;
1579
- if (!collection.childCollections) {
1580
- if (isFirebaseCollection(collection) && collection.subcollections) {
1581
- collection.childCollections = collection.subcollections;
1582
- } else if (isPostgresCollection(collection) && collection.relations) {
1583
- const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
1659
+ const result = {
1660
+ ...collection
1661
+ };
1662
+ const extractedRelations = this.extractRelationsFromProperties(result.properties);
1663
+ const relResult = result;
1664
+ const manualRelations = getDataSourceCapabilities(result.driver).supportsRelations ? relResult.relations ?? [] : [];
1665
+ const mergedRelationsRaw = [...extractedRelations];
1666
+ for (const manual of manualRelations) {
1667
+ const name = manual.relationName;
1668
+ if (!name || !mergedRelationsRaw.find((r) => r.relationName === name)) {
1669
+ mergedRelationsRaw.push(manual);
1670
+ }
1671
+ }
1672
+ let mergedRelations = mergedRelationsRaw;
1673
+ if (getDataSourceCapabilities(result.driver).supportsRelations) {
1674
+ mergedRelations = mergedRelationsRaw.map((r) => {
1675
+ try {
1676
+ return sanitizeRelation(r, result);
1677
+ } catch {
1678
+ return r;
1679
+ }
1680
+ });
1681
+ relResult.relations = mergedRelations;
1682
+ }
1683
+ const properties = this.normalizeProperties(result.properties, mergedRelations);
1684
+ result.properties = properties;
1685
+ if (!result.childCollections) {
1686
+ if (getDataSourceCapabilities(result.driver).supportsSubcollections && result.subcollections) {
1687
+ result.childCollections = result.subcollections;
1688
+ } else if (getDataSourceCapabilities(result.driver).supportsRelations && relResult.relations) {
1689
+ const manyRelations = relResult.relations.filter((r) => r.cardinality === "many");
1584
1690
  if (manyRelations.length > 0) {
1585
- collection.childCollections = () => manyRelations.map((r) => {
1691
+ result.childCollections = () => manyRelations.map((r) => {
1586
1692
  const target = r.target();
1587
1693
  return r.overrides ? mergeDeep(target, r.overrides) : target;
1588
1694
  });
1589
1695
  }
1590
1696
  }
1591
1697
  }
1592
- return collection;
1698
+ return result;
1699
+ }
1700
+ /**
1701
+ * Extract Relation[] from properties that have inline relation config (i.e. `target` is set).
1702
+ * This allows developers to define relations directly on properties without a separate
1703
+ * `relations[]` entry on the collection.
1704
+ */
1705
+ extractRelationsFromProperties(properties) {
1706
+ const relations = [];
1707
+ for (const [key, property] of Object.entries(properties)) {
1708
+ if (property.type === "relation") {
1709
+ const relProp = property;
1710
+ const target = relProp.target ?? relProp.relation?.target;
1711
+ if (target) {
1712
+ const relationName = relProp.relationName ?? relProp.relation?.relationName ?? key;
1713
+ relations.push({
1714
+ relationName,
1715
+ target,
1716
+ cardinality: relProp.cardinality ?? relProp.relation?.cardinality ?? "one",
1717
+ direction: relProp.direction ?? relProp.relation?.direction ?? "owning",
1718
+ inverseRelationName: relProp.inverseRelationName ?? relProp.relation?.inverseRelationName,
1719
+ localKey: relProp.localKey ?? relProp.relation?.localKey,
1720
+ foreignKeyOnTarget: relProp.foreignKeyOnTarget ?? relProp.relation?.foreignKeyOnTarget,
1721
+ through: relProp.through ?? relProp.relation?.through,
1722
+ joinPath: relProp.joinPath ?? relProp.relation?.joinPath,
1723
+ onUpdate: relProp.onUpdate ?? relProp.relation?.onUpdate,
1724
+ onDelete: relProp.onDelete ?? relProp.relation?.onDelete,
1725
+ overrides: relProp.overrides ?? relProp.relation?.overrides
1726
+ });
1727
+ }
1728
+ } else if (property.type === "map" && property.properties) {
1729
+ relations.push(...this.extractRelationsFromProperties(property.properties));
1730
+ }
1731
+ }
1732
+ return relations;
1593
1733
  }
1594
1734
  normalizeProperties(properties, relations) {
1595
1735
  const newProperties = {};
1596
1736
  for (const key in properties) {
1597
- newProperties[key] = this.normalizeProperty(properties[key], relations);
1737
+ newProperties[key] = this.normalizeProperty(key, properties[key], relations);
1598
1738
  }
1599
1739
  return newProperties;
1600
1740
  }
1601
- normalizeProperty(property, relations) {
1741
+ normalizeProperty(key, property, relations) {
1602
1742
  const newProperty = {
1603
1743
  ...property
1604
1744
  };
@@ -1608,9 +1748,9 @@ class CollectionRegistry {
1608
1748
  const arrayProp = newProperty;
1609
1749
  if (arrayProp.of) {
1610
1750
  if (Array.isArray(arrayProp.of)) {
1611
- arrayProp.of = arrayProp.of.map((p) => this.normalizeProperty(p, relations));
1751
+ arrayProp.of = arrayProp.of.map((p, i) => this.normalizeProperty(`${key}[${i}]`, p, relations));
1612
1752
  } else {
1613
- arrayProp.of = this.normalizeProperty(arrayProp.of, relations);
1753
+ arrayProp.of = this.normalizeProperty(`${key}.of`, arrayProp.of, relations);
1614
1754
  }
1615
1755
  } else if (arrayProp.oneOf && arrayProp.oneOf.properties) {
1616
1756
  arrayProp.oneOf.properties = this.normalizeProperties(arrayProp.oneOf.properties, relations);
@@ -1622,11 +1762,12 @@ class CollectionRegistry {
1622
1762
  }
1623
1763
  } else if (newProperty.type === "relation") {
1624
1764
  const relationProperty = newProperty;
1625
- const relation = relations.find((r) => r.relationName === relationProperty.relationName);
1765
+ const name = relationProperty.relationName || key;
1766
+ const relation = relations.find((r) => r.relationName === name);
1626
1767
  if (relation) {
1627
1768
  relationProperty.relation = relation;
1628
1769
  } else {
1629
- console.warn(`Could not find relation for property with relationName: ${relationProperty.relationName}`);
1770
+ console.warn(`Could not find relation for property '${key}' with relationName: ${name}`);
1630
1771
  }
1631
1772
  }
1632
1773
  return newProperty;
@@ -1634,6 +1775,11 @@ class CollectionRegistry {
1634
1775
  get(path) {
1635
1776
  const bySlug = this.collectionsBySlug.get(path);
1636
1777
  if (bySlug) return bySlug;
1778
+ if (path.includes("-")) {
1779
+ const normalized = path.replace(/-/g, "_");
1780
+ const byNormalized = this.collectionsBySlug.get(normalized);
1781
+ if (byNormalized) return byNormalized;
1782
+ }
1637
1783
  return this.collectionsByTableName.get(path);
1638
1784
  }
1639
1785
  /**
@@ -1643,6 +1789,11 @@ class CollectionRegistry {
1643
1789
  getRaw(path) {
1644
1790
  const bySlug = this.rawCollectionsBySlug.get(path);
1645
1791
  if (bySlug) return bySlug;
1792
+ if (path.includes("-")) {
1793
+ const normalized = path.replace(/-/g, "_");
1794
+ const byNormalized = this.rawCollectionsBySlug.get(normalized);
1795
+ if (byNormalized) return byNormalized;
1796
+ }
1646
1797
  return this.rawCollectionsByTableName.get(path);
1647
1798
  }
1648
1799
  /**
@@ -1664,11 +1815,11 @@ class CollectionRegistry {
1664
1815
  }
1665
1816
  for (let i = 2; i < pathSegments.length; i += 2) {
1666
1817
  const relationKey = pathSegments[i];
1667
- if (!isPostgresCollection(currentCollection)) {
1668
- throw new Error(`Relation path navigation requires a PostgreSQL collection, but '${currentCollection.slug}' uses driver '${currentCollection.driver}'`);
1818
+ if (!getDataSourceCapabilities(currentCollection.driver).supportsRelations) {
1819
+ throw new Error(`Relation path navigation requires a collection that supports relations, but '${currentCollection.slug}' uses driver '${currentCollection.driver}'`);
1669
1820
  }
1670
1821
  const resolvedRelations = resolveCollectionRelations(currentCollection);
1671
- const relation = resolvedRelations[relationKey];
1822
+ const relation = findRelation(resolvedRelations, relationKey);
1672
1823
  if (!relation) {
1673
1824
  throw new Error(`Relation '${relationKey}' not found in collection '${currentCollection.slug}'`);
1674
1825
  }
@@ -1678,10 +1829,16 @@ class CollectionRegistry {
1678
1829
  return currentCollection;
1679
1830
  }
1680
1831
  getCollections() {
1681
- return Array.from(this.collectionsByTableName.values());
1832
+ if (!this.cachedCollectionsList) {
1833
+ this.cachedCollectionsList = Array.from(this.collectionsByTableName.values());
1834
+ }
1835
+ return this.cachedCollectionsList;
1682
1836
  }
1683
1837
  getRawCollections() {
1684
- return Array.from(this.rawCollectionsByTableName.values());
1838
+ if (!this.cachedRawCollectionsList) {
1839
+ this.cachedRawCollectionsList = Array.from(this.rawCollectionsByTableName.values());
1840
+ }
1841
+ return this.cachedRawCollectionsList;
1685
1842
  }
1686
1843
  /**
1687
1844
  * Resolves a multi-segment path like "products/123/locales" and returns
@@ -1737,24 +1894,62 @@ function convertWhereToFilter(where) {
1737
1894
  "lte": "<=",
1738
1895
  "in": "in",
1739
1896
  "nin": "not-in",
1897
+ "not-in": "not-in",
1740
1898
  "cs": "array-contains",
1741
- "csa": "array-contains-any"
1899
+ "csa": "array-contains-any",
1900
+ "==": "==",
1901
+ "!=": "!=",
1902
+ ">": ">",
1903
+ ">=": ">=",
1904
+ "<": "<",
1905
+ "<=": "<=",
1906
+ "array-contains": "array-contains",
1907
+ "array-contains-any": "array-contains-any"
1742
1908
  };
1743
1909
  const filter = {};
1744
1910
  for (const [field, rawValue] of Object.entries(where)) {
1745
- const dotIndex = rawValue.indexOf(".");
1746
- if (dotIndex === -1) continue;
1747
- const op = rawValue.substring(0, dotIndex);
1748
- let value = rawValue.substring(dotIndex + 1);
1749
- if (typeof value === "string" && value.startsWith("(") && value.endsWith(")")) {
1750
- value = value.slice(1, -1).split(",").map((v) => v.trim());
1911
+ if (rawValue === null) {
1912
+ filter[field] = ["==", null];
1913
+ continue;
1914
+ }
1915
+ if (typeof rawValue === "boolean") {
1916
+ filter[field] = ["==", rawValue];
1917
+ continue;
1751
1918
  }
1752
- if (typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "") {
1753
- value = Number(value);
1919
+ if (typeof rawValue === "number") {
1920
+ filter[field] = ["==", rawValue];
1921
+ continue;
1754
1922
  }
1755
- const mappedOp = operatorMap[op];
1756
- if (mappedOp) {
1757
- filter[field] = [mappedOp, value];
1923
+ if (Array.isArray(rawValue) && rawValue.length === 2) {
1924
+ const [rawOp, val] = rawValue;
1925
+ const mappedOp = operatorMap[rawOp] ?? "==";
1926
+ filter[field] = [mappedOp, val];
1927
+ continue;
1928
+ }
1929
+ if (typeof rawValue === "string") {
1930
+ const dotIndex = rawValue.indexOf(".");
1931
+ if (dotIndex === -1) {
1932
+ filter[field] = ["==", rawValue];
1933
+ continue;
1934
+ }
1935
+ const op = rawValue.substring(0, dotIndex);
1936
+ let value = rawValue.substring(dotIndex + 1);
1937
+ if (typeof value === "string" && value.startsWith("(") && value.endsWith(")")) {
1938
+ value = value.slice(1, -1).split(",").map((v) => v.trim());
1939
+ }
1940
+ if (value === "null") {
1941
+ value = null;
1942
+ } else if (value === "true") {
1943
+ value = true;
1944
+ } else if (value === "false") {
1945
+ value = false;
1946
+ } else if (typeof value === "string" && !isNaN(Number(value)) && value.trim() !== "") {
1947
+ value = Number(value);
1948
+ }
1949
+ const mappedOp = operatorMap[op];
1950
+ if (mappedOp) {
1951
+ filter[field] = [mappedOp, value];
1952
+ }
1758
1953
  }
1759
1954
  }
1760
1955
  return Object.keys(filter).length > 0 ? filter : void 0;
@@ -1773,7 +1968,7 @@ function createDriverAccessor(driver, slug) {
1773
1968
  const entities = await driver.fetchCollection({
1774
1969
  path: slug,
1775
1970
  limit: params?.limit,
1776
- startAfter: params?.offset,
1971
+ offset: params?.offset,
1777
1972
  filter: convertWhereToFilter(params?.where),
1778
1973
  orderBy: orderParsed?.[0],
1779
1974
  order: orderParsed?.[1],
@@ -1835,7 +2030,7 @@ function createDriverAccessor(driver, slug) {
1835
2030
  return driver.listenCollection({
1836
2031
  path: slug,
1837
2032
  limit: params?.limit,
1838
- startAfter: params?.offset,
2033
+ offset: params?.offset,
1839
2034
  filter: convertWhereToFilter(params?.where),
1840
2035
  orderBy: orderParsed?.[0],
1841
2036
  order: orderParsed?.[1],
@@ -1882,7 +2077,8 @@ function buildRebaseData(driver) {
1882
2077
  if (prop === "collection") return getAccessor;
1883
2078
  if (typeof prop === "symbol") return void 0;
1884
2079
  if (prop === "then" || prop === "toJSON" || prop === "$$typeof") return void 0;
1885
- return getAccessor(prop);
2080
+ const slug = toSnakeCase(prop);
2081
+ return getAccessor(slug);
1886
2082
  }
1887
2083
  });
1888
2084
  }
@@ -1908,8 +2104,11 @@ export {
1908
2104
  canDeleteEntity,
1909
2105
  canEditEntity,
1910
2106
  canReadCollection,
2107
+ createRelationRef,
2108
+ createRelationRefWithData,
1911
2109
  enumToObjectEntries,
1912
2110
  evaluateCondition,
2111
+ findRelation,
1913
2112
  fullPathToCollectionSegments,
1914
2113
  getArrayResolvedProperties,
1915
2114
  getCollectionBySlugWithin,