@taruvi/refine-providers 1.3.2 → 1.3.4-beta.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/README.md CHANGED
@@ -108,6 +108,8 @@ mutate({ resource: "posts", id: 1 });
108
108
 
109
109
  ### Filtering
110
110
 
111
+ List requests send **JSON `filters`** (`Database.filters(tree)` in the SDK): `convertRefineFiltersToBackendTree` maps Refine `CrudFilter[]` to the platform tree (nested `and` / `or` preserved). Bracket query keys from `convertRefineFilters` remain for helpers / Storage flatten mode.
112
+
111
113
  Full support for Refine filter operators:
112
114
 
113
115
  ```tsx
@@ -116,25 +118,41 @@ const { data } = useList({
116
118
  filters: [
117
119
  { field: "status", operator: "eq", value: "published" },
118
120
  { field: "views", operator: "gte", value: 100 },
119
- { field: "title", operator: "contains", value: "refine" },
121
+ { field: "title", operator: "containss", value: "refine" },
120
122
  { field: "category", operator: "in", value: ["tech", "news"] },
121
123
  ],
122
124
  });
123
125
  ```
124
126
 
125
- **Supported Operators:**
127
+ **Refine → backend operator mapping (important):**
128
+
129
+ | Refine operator | Backend / JSON leaf operator | Meaning on platform |
130
+ |-----------------|------------------------------|------------------------|
131
+ | `containss` | `contains` | Case-insensitive substring (ILIKE-style) |
132
+ | `contains` | `containss` | Case-sensitive substring |
133
+ | `startswith`, `endswith` | same name | Case-insensitive |
134
+ | `startswiths`, `endswiths` | same name | Case-sensitive |
135
+ | `icontains` / `nicontains` | pass-through | Aliases; platform normalizes `icontains` → `contains` |
136
+
137
+ Other operators (`eq`, `in`, `between`, array/range ops, etc.) follow `REFINE_OPERATOR_MAP` in [`src/utils.ts`](src/utils.ts).
138
+
139
+ **More operators:**
126
140
 
127
141
  | Operator | Description | Example |
128
142
  |----------|-------------|---------|
129
143
  | `eq` | Equal | `status = "active"` |
130
144
  | `ne` | Not equal | `status != "deleted"` |
131
145
  | `lt`, `gt`, `lte`, `gte` | Comparison | `age >= 18` |
132
- | `contains`, `ncontains` | Contains (case-sensitive) | `title contains "hello"` |
133
- | `containss`, `ncontainss` | Contains (case-insensitive) | `title icontains "hello"` |
134
- | `startswith`, `endswith` | String matching | `email endswith "@gmail.com"` |
135
- | `in`, `nin` | Array membership | `status in ["active", "pending"]` |
136
- | `null`, `nnull` | Null checks | `deleted_at is null` |
137
- | `between`, `nbetween` | Range | `price between [10, 100]` |
146
+ | `containss`, `ncontainss` | Case-insensitive contains / not | Prefer for “search” UX |
147
+ | `contains`, `ncontains` | Case-sensitive contains / not | |
148
+ | `startswith`, `endswith` | Case-insensitive prefix/suffix | |
149
+ | `startswiths`, `endswiths` | Case-sensitive prefix/suffix | |
150
+ | `in`, `nin`, `ina`, `nina` | Array membership | |
151
+ | `null`, `nnull` | Null checks | |
152
+ | `between`, `nbetween` | Range | |
153
+ | `acontains`, … `aelement`, `naelement` | PostgreSQL array ops | |
154
+ | `rcontains`, … `rstrictright` | PostgreSQL range ops | |
155
+ | `like`, `ilike`, `search` | Pattern / FTS | |
138
156
 
139
157
  ### Sorting & Pagination
140
158
 
package/dist/index.cjs CHANGED
@@ -11,7 +11,7 @@ var DataLoader__default = /*#__PURE__*/_interopDefault(DataLoader);
11
11
 
12
12
  // package.json
13
13
  var package_default = {
14
- version: "1.3.2"};
14
+ version: "1.3.4-beta.0"};
15
15
 
16
16
  // src/utils.ts
17
17
  var REFINE_OPERATOR_MAP = {
@@ -24,65 +24,231 @@ var REFINE_OPERATOR_MAP = {
24
24
  gt: "gt",
25
25
  lte: "lte",
26
26
  gte: "gte",
27
- // String operations (case-sensitive)
28
- contains: "contains",
29
- ncontains: "ncontains",
30
- startswith: "startswith",
31
- nstartswith: "nstartswith",
32
- endswith: "endswith",
33
- nendswith: "nendswith",
34
- // String operations (case-insensitive)
35
- containss: "icontains",
36
- ncontainss: "nicontains",
37
- startswiths: "istartswith",
38
- nstartswiths: "nistartswith",
39
- endswiths: "iendswith",
40
- nendswiths: "niendswith",
41
- // Array operations
27
+ // String operators mapped to backend-safe case-sensitive LIKE variants.
28
+ contains: "containss",
29
+ ncontains: "ncontainss",
30
+ containss: "containss",
31
+ ncontainss: "ncontainss",
32
+ // String operators mapped to backend-safe case-sensitive prefix/suffix LIKE.
33
+ startswith: "startswiths",
34
+ nstartswith: "nstartswiths",
35
+ endswith: "endswiths",
36
+ nendswith: "nendswiths",
37
+ startswiths: "startswiths",
38
+ nstartswiths: "nstartswiths",
39
+ endswiths: "endswiths",
40
+ nendswiths: "nendswiths",
41
+ // Aliases mapped to backend-safe case-sensitive LIKE variants.
42
+ icontains: "containss",
43
+ nicontains: "ncontainss",
44
+ // Array / membership
42
45
  in: "in",
43
46
  nin: "nin",
47
+ ina: "ina",
48
+ nina: "nina",
44
49
  // Null checks
45
50
  null: "null",
46
51
  nnull: "nnull",
47
52
  // Range
48
53
  between: "between",
49
- nbetween: "nbetween"
54
+ nbetween: "nbetween",
55
+ // PostgreSQL array ops (pass-through suffix names)
56
+ acontains: "acontains",
57
+ nacontains: "nacontains",
58
+ acontainedby: "acontainedby",
59
+ nacontainedby: "nacontainedby",
60
+ aoverlap: "aoverlap",
61
+ naoverlap: "naoverlap",
62
+ aelement: "aelement",
63
+ naelement: "naelement",
64
+ // PostgreSQL range column ops
65
+ rcontains: "rcontains",
66
+ rcontainedby: "rcontainedby",
67
+ roverlaps: "roverlaps",
68
+ radjacent: "radjacent",
69
+ rstrictleft: "rstrictleft",
70
+ rstrictright: "rstrictright",
71
+ // Raw pattern / field search
72
+ like: "like",
73
+ ilike: "ilike",
74
+ search: "search"
50
75
  };
51
- function convertRefineFilters(filters) {
76
+ var COMMA_VALUE_OPERATORS = /* @__PURE__ */ new Set([
77
+ "in",
78
+ "nin",
79
+ "ina",
80
+ "nina",
81
+ "between",
82
+ "nbetween",
83
+ "acontains",
84
+ "nacontains",
85
+ "acontainedby",
86
+ "nacontainedby",
87
+ "aoverlap",
88
+ "naoverlap",
89
+ "aelement",
90
+ "naelement",
91
+ "rcontains",
92
+ "rcontainedby",
93
+ "roverlaps",
94
+ "radjacent",
95
+ "rstrictleft",
96
+ "rstrictright"
97
+ ]);
98
+ function isLogicalFilter(filter) {
99
+ return typeof filter === "object" && filter !== null && "operator" in filter && (filter.operator === "and" || filter.operator === "or") && "value" in filter && Array.isArray(filter.value);
100
+ }
101
+ function isFieldFilter(filter) {
102
+ return typeof filter === "object" && filter !== null && "field" in filter && "operator" in filter && typeof filter.field === "string" && typeof filter.operator === "string";
103
+ }
104
+ function refineOperatorToBackendKey(operator) {
105
+ const suffix = REFINE_OPERATOR_MAP[operator];
106
+ if (suffix === void 0) return operator;
107
+ return suffix === "" ? "eq" : suffix;
108
+ }
109
+ function formatRefineLeafValue(operator, value) {
110
+ if (COMMA_VALUE_OPERATORS.has(operator)) {
111
+ return Array.isArray(value) ? value.join(",") : String(value);
112
+ }
113
+ if (operator === "null" || operator === "nnull") {
114
+ return "true";
115
+ }
116
+ return String(value);
117
+ }
118
+ function encodeLeafFlat(field, operator, value, params) {
119
+ if (value === void 0 || value === null && operator !== "null") {
120
+ return;
121
+ }
122
+ const suffix = REFINE_OPERATOR_MAP[operator];
123
+ if (suffix === void 0) {
124
+ console.warn(`Unknown Refine operator: ${operator}`);
125
+ return;
126
+ }
127
+ const paramKey = suffix ? `${field}__${suffix}` : String(field);
128
+ params[paramKey] = formatRefineLeafValue(operator, value);
129
+ }
130
+ function encodeCrudFilterBracket(filter, path, out) {
131
+ if (isLogicalFilter(filter)) {
132
+ const op = filter.operator;
133
+ out[`${path}[operator]`] = op;
134
+ const children = filter.value;
135
+ children.forEach((child, i) => {
136
+ encodeCrudFilterBracket(child, `${path}[value][${i}]`, out);
137
+ });
138
+ return;
139
+ }
140
+ if (!isFieldFilter(filter)) {
141
+ return;
142
+ }
143
+ const leaf = filter;
144
+ const { field, operator, value } = leaf;
145
+ if (value === void 0 || value === null && operator !== "null") {
146
+ return;
147
+ }
148
+ const suffix = REFINE_OPERATOR_MAP[operator];
149
+ if (suffix === void 0) {
150
+ console.warn(`Unknown Refine operator: ${operator}`);
151
+ return;
152
+ }
153
+ out[`${path}[field]`] = field;
154
+ out[`${path}[operator]`] = refineOperatorToBackendKey(operator);
155
+ out[`${path}[value]`] = formatRefineLeafValue(operator, value);
156
+ }
157
+ function isLogicalBackendNode(n) {
158
+ return !("field" in n) && (n.operator === "and" || n.operator === "or");
159
+ }
160
+ function jsonLeafValue(operator, value) {
161
+ if (operator === "null" || operator === "nnull") return true;
162
+ return value;
163
+ }
164
+ function crudFilterToBackendNodeOrNull(filter) {
165
+ if (isLogicalFilter(filter)) {
166
+ const children = filter.value.map(crudFilterToBackendNodeOrNull).filter((n) => n !== null);
167
+ if (children.length === 0) return null;
168
+ return { operator: filter.operator, value: children };
169
+ }
170
+ if (isFieldFilter(filter)) {
171
+ const leaf = filter;
172
+ if (leaf.value === void 0 || leaf.value === null && leaf.operator !== "null") {
173
+ return null;
174
+ }
175
+ if (REFINE_OPERATOR_MAP[leaf.operator] === void 0) {
176
+ console.warn(`Unknown Refine operator: ${leaf.operator}`);
177
+ return null;
178
+ }
179
+ const backendOp = refineOperatorToBackendKey(leaf.operator);
180
+ return {
181
+ field: leaf.field,
182
+ operator: backendOp,
183
+ value: jsonLeafValue(leaf.operator, leaf.value)
184
+ };
185
+ }
186
+ return null;
187
+ }
188
+ function convertRefineFiltersToBackendTree(filters) {
189
+ if (!filters?.length) return null;
190
+ const nodes = filters.map(crudFilterToBackendNodeOrNull).filter((n) => n !== null);
191
+ if (nodes.length === 0) return null;
192
+ if (nodes.length === 1 && isLogicalBackendNode(nodes[0])) {
193
+ return [nodes[0]];
194
+ }
195
+ return [{ operator: "and", value: nodes }];
196
+ }
197
+ function convertRefineFiltersFlattened(filters) {
52
198
  if (!filters || filters.length === 0) return {};
53
199
  const params = {};
54
200
  for (const filter of filters) {
55
- if ("operator" in filter && (filter.operator === "and" || filter.operator === "or")) {
201
+ if (isLogicalFilter(filter)) {
56
202
  if (filter.value && Array.isArray(filter.value)) {
57
- const nested = convertRefineFilters(filter.value);
203
+ const nested = convertRefineFiltersFlattened(
204
+ filter.value
205
+ );
58
206
  Object.assign(params, nested);
59
207
  }
60
208
  continue;
61
209
  }
62
- if ("field" in filter && filter.field && filter.operator) {
63
- const { field, operator, value } = filter;
64
- if (value === void 0 || value === null && operator !== "null") {
65
- continue;
66
- }
67
- const suffix = REFINE_OPERATOR_MAP[operator];
68
- if (suffix === void 0) {
69
- console.warn(`Unknown Refine operator: ${operator}`);
70
- continue;
71
- }
72
- const paramKey = suffix ? `${field}__${suffix}` : String(field);
73
- if (operator === "in" || operator === "nin") {
74
- params[paramKey] = Array.isArray(value) ? value.join(",") : String(value);
75
- } else if (operator === "between" || operator === "nbetween") {
76
- params[paramKey] = Array.isArray(value) ? value.join(",") : String(value);
77
- } else if (operator === "null" || operator === "nnull") {
78
- params[paramKey] = "true";
79
- } else {
80
- params[paramKey] = String(value);
81
- }
210
+ if (isFieldFilter(filter)) {
211
+ const leaf = filter;
212
+ encodeLeafFlat(leaf.field, leaf.operator, leaf.value, params);
82
213
  }
83
214
  }
84
215
  return params;
85
216
  }
217
+ function convertRefineFilters(filters, options) {
218
+ if (!filters || filters.length === 0) return {};
219
+ if (options?.logicalEncoding === "flatten") {
220
+ return convertRefineFiltersFlattened(filters);
221
+ }
222
+ const hasLogical = filters.some(isLogicalFilter);
223
+ if (!hasLogical) {
224
+ const params = {};
225
+ for (const filter of filters) {
226
+ if (isFieldFilter(filter)) {
227
+ const leaf = filter;
228
+ encodeLeafFlat(leaf.field, leaf.operator, leaf.value, params);
229
+ }
230
+ }
231
+ return params;
232
+ }
233
+ if (filters.length === 1 && isLogicalFilter(filters[0])) {
234
+ const out2 = {};
235
+ encodeCrudFilterBracket(filters[0], "filters[0]", out2);
236
+ return out2;
237
+ }
238
+ const out = {};
239
+ out["filters[0][operator]"] = "and";
240
+ filters.forEach((f, i) => {
241
+ encodeCrudFilterBracket(f, `filters[0][value][${i}]`, out);
242
+ });
243
+ return out;
244
+ }
245
+ function applyRefineQueryParamsToDatabase(query, params) {
246
+ let result = query;
247
+ for (const [key, val] of Object.entries(params)) {
248
+ result = result.filters(key, "eq", val);
249
+ }
250
+ return result;
251
+ }
86
252
  function convertRefineSorters(sorters) {
87
253
  if (!sorters || sorters.length === 0) return void 0;
88
254
  return sorters.map((sort) => sort.order === "desc" ? `-${sort.field}` : sort.field).join(",");
@@ -138,16 +304,7 @@ function formatHaving(having) {
138
304
  continue;
139
305
  }
140
306
  const paramKey = suffix ? `${field}__${suffix}` : String(field);
141
- let paramValue;
142
- if (operator === "in" || operator === "nin") {
143
- paramValue = Array.isArray(value) ? value.join(",") : String(value);
144
- } else if (operator === "between" || operator === "nbetween") {
145
- paramValue = Array.isArray(value) ? value.join(",") : String(value);
146
- } else if (operator === "null" || operator === "nnull") {
147
- paramValue = "true";
148
- } else {
149
- paramValue = String(value);
150
- }
307
+ const paramValue = formatRefineLeafValue(operator, value);
151
308
  params.push(`${paramKey}=${paramValue}`);
152
309
  }
153
310
  }
@@ -181,43 +338,13 @@ function applyAggregations(query, meta) {
181
338
  }
182
339
  function applyFilters(query, filters) {
183
340
  if (!filters || filters.length === 0) return query;
184
- let result = query;
185
- for (const filter of filters) {
186
- if ("operator" in filter && (filter.operator === "and" || filter.operator === "or")) {
187
- if (filter.value && Array.isArray(filter.value)) {
188
- result = applyFilters(result, filter.value);
189
- }
190
- continue;
191
- }
192
- if ("field" in filter && filter.field && filter.operator) {
193
- const { field, operator, value } = filter;
194
- if (value === void 0 || value === null && operator !== "null") continue;
195
- const suffix = REFINE_OPERATOR_MAP[operator];
196
- if (suffix === void 0) {
197
- console.warn(`Unknown Refine operator: ${operator}`);
198
- continue;
199
- }
200
- const paramKey = suffix ? `${field}__${suffix}` : String(field);
201
- let paramValue;
202
- if (operator === "in" || operator === "nin" || operator === "between" || operator === "nbetween") {
203
- paramValue = Array.isArray(value) ? value.join(",") : String(value);
204
- } else if (operator === "null" || operator === "nnull") {
205
- paramValue = "true";
206
- } else {
207
- paramValue = String(value);
208
- }
209
- result = result.filter(paramKey, "eq", paramValue);
210
- }
211
- }
212
- return result;
341
+ const tree = convertRefineFiltersToBackendTree(filters);
342
+ if (!tree) return query;
343
+ return query.filters(tree);
213
344
  }
214
345
  function applySorters(query, sorters) {
215
- if (!sorters || sorters.length === 0) return query;
216
- let result = query;
217
- for (const sorter of sorters) {
218
- result = result.sort(sorter.field, sorter.order === "desc" ? "desc" : "asc");
219
- }
220
- return result;
346
+ const ordering = convertRefineSorters(sorters);
347
+ return ordering ? query.orderBy(ordering) : query;
221
348
  }
222
349
  function applyPagination(query, pagination) {
223
350
  if (!pagination || pagination.mode === "off") return query;
@@ -305,7 +432,7 @@ function dataProvider(client) {
305
432
  }
306
433
  const idColumn = getIdColumn(taruviMeta);
307
434
  let query = new sdk.Database(client).from(tableName);
308
- query = query.filter(idColumn, "in", ids.map(String));
435
+ query = query.filters(idColumn, "in", ids.map(String));
309
436
  query = applyPopulate(query, taruviMeta);
310
437
  const response = await query.execute();
311
438
  return { data: response.data };
@@ -422,7 +549,7 @@ function storageDataProvider(client) {
422
549
  const getBucketName = (resource, meta) => meta?.bucketName ?? resource;
423
550
  const buildFilters = (params) => {
424
551
  const filters = {
425
- ...convertRefineFilters(params.filters),
552
+ ...convertRefineFilters(params.filters, { logicalEncoding: "flatten" }),
426
553
  ...convertRefinePagination(params.pagination)
427
554
  };
428
555
  const ordering = convertRefineSorters(params.sorters);
@@ -930,22 +1057,27 @@ function analyticsDataProvider(client) {
930
1057
  exports._cachedUser = null;
931
1058
  function authProvider(client) {
932
1059
  const auth = new sdk.Auth(client);
933
- function redirectToLogin() {
934
- exports._cachedUser = null;
935
- auth.login();
936
- }
937
1060
  return {
938
1061
  login: async (params = {}) => {
939
- const { callbackUrl } = params;
940
- if (auth.hasToken()) {
1062
+ const { callbackUrl, redirect = true } = params;
1063
+ if (auth.isUserAuthenticated()) {
941
1064
  return {
942
1065
  success: true,
943
1066
  redirectTo: callbackUrl || "/"
944
1067
  };
945
1068
  }
946
- auth.login(callbackUrl);
1069
+ if (redirect) {
1070
+ auth.login(callbackUrl);
1071
+ return {
1072
+ success: true
1073
+ };
1074
+ }
947
1075
  return {
948
- success: true
1076
+ success: false,
1077
+ error: {
1078
+ name: "LoginError",
1079
+ message: "Login failed. Please try again."
1080
+ }
949
1081
  };
950
1082
  },
951
1083
  logout: async (params = {}) => {
@@ -958,22 +1090,19 @@ function authProvider(client) {
958
1090
  };
959
1091
  },
960
1092
  check: async () => {
961
- if (!auth.hasToken()) {
962
- redirectToLogin();
963
- return { authenticated: false };
964
- }
965
- const isValid = await auth.isUserAuthenticated();
966
- if (!isValid) {
967
- redirectToLogin();
968
- return { authenticated: false };
1093
+ if (!auth.isUserAuthenticated()) {
1094
+ return { authenticated: false, redirectTo: "/login" };
969
1095
  }
970
1096
  return { authenticated: true };
971
1097
  },
972
1098
  onError: async (error) => {
973
1099
  const status = error?.statusCode || error?.status || error?.response?.status;
974
1100
  if (status === 401) {
975
- redirectToLogin();
976
- return { error };
1101
+ return {
1102
+ logout: true,
1103
+ redirectTo: "/login",
1104
+ error
1105
+ };
977
1106
  }
978
1107
  if (status === 403) {
979
1108
  return { error };
@@ -1116,15 +1245,20 @@ exports.REFINE_OPERATOR_MAP = REFINE_OPERATOR_MAP;
1116
1245
  exports.accessControlProvider = accessControlProvider;
1117
1246
  exports.analyticsDataProvider = analyticsDataProvider;
1118
1247
  exports.appDataProvider = appDataProvider;
1248
+ exports.applyRefineQueryParamsToDatabase = applyRefineQueryParamsToDatabase;
1119
1249
  exports.authProvider = authProvider;
1120
1250
  exports.buildQueryString = buildQueryString;
1121
1251
  exports.buildRefineQueryParams = buildRefineQueryParams;
1122
1252
  exports.convertRefineFilters = convertRefineFilters;
1253
+ exports.convertRefineFiltersToBackendTree = convertRefineFiltersToBackendTree;
1123
1254
  exports.convertRefinePagination = convertRefinePagination;
1124
1255
  exports.convertRefineSorters = convertRefineSorters;
1125
1256
  exports.dataProvider = dataProvider;
1257
+ exports.encodeCrudFilterBracket = encodeCrudFilterBracket;
1258
+ exports.formatRefineLeafValue = formatRefineLeafValue;
1126
1259
  exports.functionsDataProvider = functionsDataProvider;
1127
1260
  exports.handleError = handleError;
1261
+ exports.refineOperatorToBackendKey = refineOperatorToBackendKey;
1128
1262
  exports.storageDataProvider = storageDataProvider;
1129
1263
  exports.userDataProvider = userDataProvider;
1130
1264
  //# sourceMappingURL=index.cjs.map