@rebasepro/server-postgresql 0.2.1 → 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.
@@ -0,0 +1,51 @@
1
+ import { FindResponse, CollectionAccessor, QueryBuilderInterface, FilterOperator } from "@rebasepro/types";
2
+ export declare class QueryBuilder<M extends Record<string, unknown> = Record<string, unknown>> implements QueryBuilderInterface<M> {
3
+ private collection;
4
+ private params;
5
+ constructor(collection: CollectionAccessor<M>);
6
+ /**
7
+ * Add a filter condition to your query.
8
+ * @example
9
+ * client.collection('users').where('age', '>=', 18).find()
10
+ */
11
+ where(column: keyof M & string, operator: FilterOperator, value: unknown): this;
12
+ /**
13
+ * Order the results by a specific column.
14
+ * @example
15
+ * client.collection('users').orderBy('createdAt', 'desc').find()
16
+ */
17
+ orderBy(column: keyof M & string, ascending?: "asc" | "desc"): this;
18
+ /**
19
+ * Limit the number of results returned.
20
+ */
21
+ limit(count: number): this;
22
+ /**
23
+ * Skip the first N results.
24
+ */
25
+ offset(count: number): this;
26
+ /**
27
+ * Set a free-text search string if supported by the backend.
28
+ */
29
+ search(searchString: string): this;
30
+ /**
31
+ * Include related entities in the response.
32
+ * Relations will be populated with full entity data instead of just IDs.
33
+ *
34
+ * @param relations - Relation names to include, or "*" for all.
35
+ * @example
36
+ * // Include specific relations
37
+ * client.data.posts.include("tags", "author").find()
38
+ *
39
+ * // Include all relations
40
+ * client.data.posts.include("*").find()
41
+ */
42
+ include(...relations: string[]): this;
43
+ /**
44
+ * Execute the find query and return the results.
45
+ */
46
+ find(): Promise<FindResponse<M>>;
47
+ /**
48
+ * Listen to realtime updates matching this query.
49
+ */
50
+ listen(onUpdate: (data: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;
51
+ }
@@ -1,3 +1,4 @@
1
1
  export * from "./util";
2
2
  export * from "./collections";
3
3
  export * from "./data/buildRebaseData";
4
+ export * from "./data/query_builder";
package/dist/index.es.js CHANGED
@@ -1158,118 +1158,6 @@ function enumToObjectEntries(enumValues) {
1158
1158
  });
1159
1159
  }
1160
1160
  }
1161
- function getSubcollections(collection) {
1162
- if (collection.childCollections) {
1163
- return collection.childCollections() ?? [];
1164
- }
1165
- if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
1166
- return collection.subcollections() ?? [];
1167
- }
1168
- if (getDataSourceCapabilities(collection.driver).supportsRelations && collection.relations) {
1169
- const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
1170
- return manyRelations.map((r) => {
1171
- const target = r.target();
1172
- if (!target) return void 0;
1173
- const relationKey = r.relationName || target.slug;
1174
- let customName;
1175
- if (collection.properties) {
1176
- const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
1177
- if (prop && prop[1].name) {
1178
- customName = prop[1].name;
1179
- }
1180
- }
1181
- const baseOverrides = {
1182
- slug: relationKey
1183
- };
1184
- if (customName) {
1185
- baseOverrides.name = customName;
1186
- baseOverrides.singularName = customName;
1187
- }
1188
- const targetWithOverrides = {
1189
- ...target,
1190
- ...baseOverrides
1191
- };
1192
- return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
1193
- }).filter((c) => Boolean(c));
1194
- }
1195
- return [];
1196
- }
1197
- function hasPropertyCallbacks(properties, callbackName) {
1198
- if (!properties) return false;
1199
- for (const property of Object.values(properties)) {
1200
- if (property.callbacks?.[callbackName]) return true;
1201
- if (property.type === "map" && property.properties) {
1202
- if (hasPropertyCallbacks(property.properties, callbackName)) return true;
1203
- } else if (property.type === "array" && property.of) {
1204
- const ofs = Array.isArray(property.of) ? property.of : [property.of];
1205
- for (const of of ofs) {
1206
- if (of.callbacks?.[callbackName]) return true;
1207
- if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
1208
- }
1209
- }
1210
- }
1211
- return false;
1212
- }
1213
- async function processProperties(properties, values, previousValues, propsContext, callbackName) {
1214
- if (!values || typeof values !== "object") return values;
1215
- const result = {
1216
- ...values
1217
- };
1218
- for (const [key, property] of Object.entries(properties)) {
1219
- if (result[key] === void 0) continue;
1220
- let currentValue = result[key];
1221
- const previousValue = previousValues?.[key];
1222
- if (property.type === "array" && Array.isArray(currentValue)) {
1223
- if (property.of && !Array.isArray(property.of)) {
1224
- currentValue = await Promise.all(currentValue.map(async (item, index) => {
1225
- const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
1226
- const singlePropData = {
1227
- "_tmp": property.of
1228
- };
1229
- const res = await processProperties(singlePropData, {
1230
- "_tmp": item
1231
- }, {
1232
- "_tmp": prevItem
1233
- }, propsContext, callbackName);
1234
- return res["_tmp"];
1235
- }));
1236
- }
1237
- } else if (property.type === "map" && property.properties && typeof currentValue === "object") {
1238
- currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
1239
- }
1240
- if (property.callbacks?.[callbackName]) {
1241
- const cbRes = await Promise.resolve(property.callbacks[callbackName]({
1242
- ...propsContext,
1243
- value: currentValue,
1244
- previousValue
1245
- }));
1246
- if (cbRes !== void 0) {
1247
- currentValue = cbRes;
1248
- }
1249
- }
1250
- result[key] = currentValue;
1251
- }
1252
- return result;
1253
- }
1254
- const buildPropertyCallbacks = (properties) => {
1255
- if (!properties) return void 0;
1256
- const propertyCallbacks = {};
1257
- if (hasPropertyCallbacks(properties, "afterRead")) {
1258
- propertyCallbacks.afterRead = async (props) => {
1259
- const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
1260
- return {
1261
- ...props.entity,
1262
- values: processedValues
1263
- };
1264
- };
1265
- }
1266
- if (hasPropertyCallbacks(properties, "beforeSave")) {
1267
- propertyCallbacks.beforeSave = async (props) => {
1268
- return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
1269
- };
1270
- }
1271
- return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1272
- };
1273
1161
  function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1274
1162
  if (!relation.target) {
1275
1163
  throw new Error("Relation is missing a `target` collection.");
@@ -1301,6 +1189,8 @@ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1301
1189
  } else {
1302
1190
  targetCollection = evaluated;
1303
1191
  }
1192
+ } else if (rawTarget && typeof rawTarget === "object") {
1193
+ targetCollection = rawTarget;
1304
1194
  }
1305
1195
  if (!targetCollection) {
1306
1196
  throw new Error("Relation is missing a valid `target` collection.");
@@ -1420,11 +1310,14 @@ function resolveCollectionRelations(collection) {
1420
1310
  const registeredRelationNames = /* @__PURE__ */ new Set();
1421
1311
  if (relCollection.relations) {
1422
1312
  relCollection.relations.forEach((relation) => {
1423
- const normalizedRelation = sanitizeRelation(relation, collection);
1424
- const relationKey = normalizedRelation.relationName;
1425
- if (relationKey) {
1426
- relations2[relationKey] = normalizedRelation;
1427
- registeredRelationNames.add(relationKey);
1313
+ try {
1314
+ const normalizedRelation = sanitizeRelation(relation, collection);
1315
+ const relationKey = normalizedRelation.relationName;
1316
+ if (relationKey) {
1317
+ relations2[relationKey] = normalizedRelation;
1318
+ registeredRelationNames.add(relationKey);
1319
+ }
1320
+ } catch (e) {
1428
1321
  }
1429
1322
  });
1430
1323
  }
@@ -1472,12 +1365,8 @@ function resolvePropertyRelation({
1472
1365
  overrides: relProp.overrides
1473
1366
  };
1474
1367
  }
1475
- const relation = (sourceCollection.relations ?? []).find((rel) => rel.relationName === relProp.relationName);
1476
- if (!relation) {
1477
- console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1478
- return void 0;
1479
- }
1480
- return relation;
1368
+ console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1369
+ return void 0;
1481
1370
  }
1482
1371
  function getTableName(collection) {
1483
1372
  if (getDataSourceCapabilities(collection.driver).supportsRelations) {
@@ -1504,6 +1393,119 @@ function findRelation(resolvedRelations, key) {
1504
1393
  if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
1505
1394
  return void 0;
1506
1395
  }
1396
+ function getSubcollections(collection) {
1397
+ if (collection.childCollections) {
1398
+ return collection.childCollections() ?? [];
1399
+ }
1400
+ if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
1401
+ return collection.subcollections() ?? [];
1402
+ }
1403
+ if (getDataSourceCapabilities(collection.driver).supportsRelations) {
1404
+ const resolvedRelations = resolveCollectionRelations(collection);
1405
+ const manyRelations = Object.values(resolvedRelations).filter((r) => r.cardinality === "many");
1406
+ return manyRelations.map((r) => {
1407
+ const target = r.target();
1408
+ if (!target) return void 0;
1409
+ const relationKey = r.relationName || target.slug;
1410
+ let customName;
1411
+ if (collection.properties) {
1412
+ const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
1413
+ if (prop && prop[1].name) {
1414
+ customName = prop[1].name;
1415
+ }
1416
+ }
1417
+ const baseOverrides = {
1418
+ slug: relationKey
1419
+ };
1420
+ if (customName) {
1421
+ baseOverrides.name = customName;
1422
+ baseOverrides.singularName = customName;
1423
+ }
1424
+ const targetWithOverrides = {
1425
+ ...target,
1426
+ ...baseOverrides
1427
+ };
1428
+ return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
1429
+ }).filter((c) => Boolean(c));
1430
+ }
1431
+ return [];
1432
+ }
1433
+ function hasPropertyCallbacks(properties, callbackName) {
1434
+ if (!properties) return false;
1435
+ for (const property of Object.values(properties)) {
1436
+ if (property.callbacks?.[callbackName]) return true;
1437
+ if (property.type === "map" && property.properties) {
1438
+ if (hasPropertyCallbacks(property.properties, callbackName)) return true;
1439
+ } else if (property.type === "array" && property.of) {
1440
+ const ofs = Array.isArray(property.of) ? property.of : [property.of];
1441
+ for (const of of ofs) {
1442
+ if (of.callbacks?.[callbackName]) return true;
1443
+ if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
1444
+ }
1445
+ }
1446
+ }
1447
+ return false;
1448
+ }
1449
+ async function processProperties(properties, values, previousValues, propsContext, callbackName) {
1450
+ if (!values || typeof values !== "object") return values;
1451
+ const result = {
1452
+ ...values
1453
+ };
1454
+ for (const [key, property] of Object.entries(properties)) {
1455
+ if (result[key] === void 0) continue;
1456
+ let currentValue = result[key];
1457
+ const previousValue = previousValues?.[key];
1458
+ if (property.type === "array" && Array.isArray(currentValue)) {
1459
+ if (property.of && !Array.isArray(property.of)) {
1460
+ currentValue = await Promise.all(currentValue.map(async (item, index) => {
1461
+ const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
1462
+ const singlePropData = {
1463
+ "_tmp": property.of
1464
+ };
1465
+ const res = await processProperties(singlePropData, {
1466
+ "_tmp": item
1467
+ }, {
1468
+ "_tmp": prevItem
1469
+ }, propsContext, callbackName);
1470
+ return res["_tmp"];
1471
+ }));
1472
+ }
1473
+ } else if (property.type === "map" && property.properties && typeof currentValue === "object") {
1474
+ currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
1475
+ }
1476
+ if (property.callbacks?.[callbackName]) {
1477
+ const cbRes = await Promise.resolve(property.callbacks[callbackName]({
1478
+ ...propsContext,
1479
+ value: currentValue,
1480
+ previousValue
1481
+ }));
1482
+ if (cbRes !== void 0) {
1483
+ currentValue = cbRes;
1484
+ }
1485
+ }
1486
+ result[key] = currentValue;
1487
+ }
1488
+ return result;
1489
+ }
1490
+ const buildPropertyCallbacks = (properties) => {
1491
+ if (!properties) return void 0;
1492
+ const propertyCallbacks = {};
1493
+ if (hasPropertyCallbacks(properties, "afterRead")) {
1494
+ propertyCallbacks.afterRead = async (props) => {
1495
+ const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
1496
+ return {
1497
+ ...props.entity,
1498
+ values: processedValues
1499
+ };
1500
+ };
1501
+ }
1502
+ if (hasPropertyCallbacks(properties, "beforeSave")) {
1503
+ propertyCallbacks.beforeSave = async (props) => {
1504
+ return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
1505
+ };
1506
+ }
1507
+ return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
1508
+ };
1507
1509
  var logic = { exports: {} };
1508
1510
  (function(module, exports) {
1509
1511
  (function(root, factory) {
@@ -2411,8 +2413,18 @@ class CollectionRegistry {
2411
2413
  const mergedRelationsRaw = [...extractedRelations];
2412
2414
  for (const manual of manualRelations) {
2413
2415
  const name = manual.relationName;
2414
- if (!name || !mergedRelationsRaw.find((r) => r.relationName === name)) {
2416
+ if (!name) {
2415
2417
  mergedRelationsRaw.push(manual);
2418
+ } else {
2419
+ const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
2420
+ if (existingIndex === -1) {
2421
+ mergedRelationsRaw.push(manual);
2422
+ } else {
2423
+ mergedRelationsRaw[existingIndex] = {
2424
+ ...manual,
2425
+ ...mergedRelationsRaw[existingIndex]
2426
+ };
2427
+ }
2416
2428
  }
2417
2429
  }
2418
2430
  let mergedRelations = mergedRelationsRaw;
@@ -2632,6 +2644,118 @@ class CollectionRegistry {
2632
2644
  };
2633
2645
  }
2634
2646
  }
2647
+ function mapOperator(op) {
2648
+ switch (op) {
2649
+ case "==":
2650
+ return "eq";
2651
+ case "!=":
2652
+ return "neq";
2653
+ case ">":
2654
+ return "gt";
2655
+ case ">=":
2656
+ return "gte";
2657
+ case "<":
2658
+ return "lt";
2659
+ case "<=":
2660
+ return "lte";
2661
+ case "array-contains":
2662
+ return "cs";
2663
+ case "array-contains-any":
2664
+ return "csa";
2665
+ case "not-in":
2666
+ return "nin";
2667
+ default:
2668
+ return op;
2669
+ }
2670
+ }
2671
+ class QueryBuilder {
2672
+ constructor(collection) {
2673
+ this.collection = collection;
2674
+ }
2675
+ params = {
2676
+ where: {}
2677
+ };
2678
+ /**
2679
+ * Add a filter condition to your query.
2680
+ * @example
2681
+ * client.collection('users').where('age', '>=', 18).find()
2682
+ */
2683
+ where(column, operator, value) {
2684
+ if (!this.params.where) {
2685
+ this.params.where = {};
2686
+ }
2687
+ const mappedOp = mapOperator(operator);
2688
+ let formattedValue = value;
2689
+ if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2690
+ formattedValue = `(${value.join(",")})`;
2691
+ } else if (value === null) {
2692
+ formattedValue = "null";
2693
+ }
2694
+ this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2695
+ return this;
2696
+ }
2697
+ /**
2698
+ * Order the results by a specific column.
2699
+ * @example
2700
+ * client.collection('users').orderBy('createdAt', 'desc').find()
2701
+ */
2702
+ orderBy(column, ascending = "asc") {
2703
+ this.params.orderBy = `${column}:${ascending}`;
2704
+ return this;
2705
+ }
2706
+ /**
2707
+ * Limit the number of results returned.
2708
+ */
2709
+ limit(count2) {
2710
+ this.params.limit = count2;
2711
+ return this;
2712
+ }
2713
+ /**
2714
+ * Skip the first N results.
2715
+ */
2716
+ offset(count2) {
2717
+ this.params.offset = count2;
2718
+ return this;
2719
+ }
2720
+ /**
2721
+ * Set a free-text search string if supported by the backend.
2722
+ */
2723
+ search(searchString) {
2724
+ this.params.searchString = searchString;
2725
+ return this;
2726
+ }
2727
+ /**
2728
+ * Include related entities in the response.
2729
+ * Relations will be populated with full entity data instead of just IDs.
2730
+ *
2731
+ * @param relations - Relation names to include, or "*" for all.
2732
+ * @example
2733
+ * // Include specific relations
2734
+ * client.data.posts.include("tags", "author").find()
2735
+ *
2736
+ * // Include all relations
2737
+ * client.data.posts.include("*").find()
2738
+ */
2739
+ include(...relations2) {
2740
+ this.params.include = relations2;
2741
+ return this;
2742
+ }
2743
+ /**
2744
+ * Execute the find query and return the results.
2745
+ */
2746
+ async find() {
2747
+ return this.collection.find(this.params);
2748
+ }
2749
+ /**
2750
+ * Listen to realtime updates matching this query.
2751
+ */
2752
+ listen(onUpdate, onError) {
2753
+ if (!this.collection.listen) {
2754
+ throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
2755
+ }
2756
+ return this.collection.listen(this.params, onUpdate, onError);
2757
+ }
2758
+ }
2635
2759
  function convertWhereToFilter(where) {
2636
2760
  if (!where) return void 0;
2637
2761
  const operatorMap = {
@@ -2711,7 +2835,7 @@ function parseOrderBy(orderBy) {
2711
2835
  return [field, direction];
2712
2836
  }
2713
2837
  function createDriverAccessor(driver, slug) {
2714
- return {
2838
+ const accessor = {
2715
2839
  async find(params) {
2716
2840
  const orderParsed = parseOrderBy(params?.orderBy);
2717
2841
  const entities = await driver.fetchCollection({
@@ -2805,8 +2929,28 @@ function createDriverAccessor(driver, slug) {
2805
2929
  onUpdate: (entity) => onUpdate(entity ?? void 0),
2806
2930
  onError
2807
2931
  });
2808
- } : void 0
2932
+ } : void 0,
2933
+ // Fluent Query Builder
2934
+ where(column, operator, value) {
2935
+ return new QueryBuilder(accessor).where(column, operator, value);
2936
+ },
2937
+ orderBy(column, ascending) {
2938
+ return new QueryBuilder(accessor).orderBy(column, ascending);
2939
+ },
2940
+ limit(count2) {
2941
+ return new QueryBuilder(accessor).limit(count2);
2942
+ },
2943
+ offset(count2) {
2944
+ return new QueryBuilder(accessor).offset(count2);
2945
+ },
2946
+ search(searchString) {
2947
+ return new QueryBuilder(accessor).search(searchString);
2948
+ },
2949
+ include(...relations2) {
2950
+ return new QueryBuilder(accessor).include(...relations2);
2951
+ }
2809
2952
  };
2953
+ return accessor;
2810
2954
  }
2811
2955
  function buildRebaseData(driver) {
2812
2956
  const cache = /* @__PURE__ */ new Map();
@@ -6514,7 +6658,7 @@ class PostgresBackendDriver {
6514
6658
  callbacks: void 0,
6515
6659
  propertyCallbacks: void 0
6516
6660
  };
6517
- const registryCollection = this.registry.getCollectionByPath(path2);
6661
+ const registryCollection = this.registry?.getCollectionByPath(path2);
6518
6662
  const resolvedCollection = registryCollection ? {
6519
6663
  ...collection,
6520
6664
  ...registryCollection
@@ -6562,7 +6706,8 @@ class PostgresBackendDriver {
6562
6706
  user: this.user,
6563
6707
  driver: this,
6564
6708
  data: this.data,
6565
- client: this.client
6709
+ client: this.client,
6710
+ storageSource: this.client?.storage
6566
6711
  };
6567
6712
  return Promise.all(entities.map(async (entity) => {
6568
6713
  let fetched = entity;
@@ -6657,7 +6802,8 @@ class PostgresBackendDriver {
6657
6802
  user: this.user,
6658
6803
  driver: this,
6659
6804
  data: this.data,
6660
- client: this.client
6805
+ client: this.client,
6806
+ storageSource: this.client?.storage
6661
6807
  };
6662
6808
  if (callbacks?.afterRead) {
6663
6809
  entity = await callbacks.afterRead({
@@ -6727,7 +6873,8 @@ class PostgresBackendDriver {
6727
6873
  user: this.user,
6728
6874
  driver: this,
6729
6875
  data: this.data,
6730
- client: this.client
6876
+ client: this.client,
6877
+ storageSource: this.client?.storage
6731
6878
  };
6732
6879
  let previousValuesForHistory;
6733
6880
  if (status === "existing" && entityId) {
@@ -6876,7 +7023,8 @@ class PostgresBackendDriver {
6876
7023
  user: this.user,
6877
7024
  driver: this,
6878
7025
  data: this.data,
6879
- client: this.client
7026
+ client: this.client,
7027
+ storageSource: this.client?.storage
6880
7028
  };
6881
7029
  if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
6882
7030
  let preventDefault = false;
@@ -8155,6 +8303,7 @@ const defaultUsersCollection = {
8155
8303
  singularName: "User",
8156
8304
  slug: "users",
8157
8305
  table: "users",
8306
+ schema: "rebase",
8158
8307
  icon: "Users",
8159
8308
  group: "Settings",
8160
8309
  properties: {