@svadmin/pocketbase 0.13.16 → 0.13.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/pocketbase",
3
- "version": "0.13.16",
3
+ "version": "0.13.18",
4
4
  "description": "PocketBase adapters — DataProvider, AuthProvider, LiveProvider for svadmin",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -20,7 +20,7 @@
20
20
  }
21
21
  },
22
22
  "peerDependencies": {
23
- "@svadmin/core": "^0.69.2",
23
+ "@svadmin/core": "^0.70.0",
24
24
  "pocketbase": ">=0.28.0"
25
25
  },
26
26
  "dependencies": {
@@ -1,9 +1,8 @@
1
1
  // @svadmin/pocketbase — PocketBase DataProvider
2
2
  // Maps svadmin CRUD operations to PocketBase SDK
3
3
 
4
- import type {
5
- DataProvider,
6
- } from '@svadmin/core';
4
+ import { assertFiltersExecutable } from '@svadmin/core/provider-filters';
5
+ import type { DataProvider, Filter } from '@svadmin/core';
7
6
  import { definedOptions } from '@svadmin/core/options';
8
7
  import { withValidatedResponses, type DataTransport } from '@svadmin/core/schema';
9
8
 
@@ -41,27 +40,86 @@ function buildSort(sorters?: { field: string; order: 'asc' | 'desc' }[]): string
41
40
  return sorters.map(s => s.order === 'desc' ? `-${s.field}` : s.field).join(',');
42
41
  }
43
42
 
44
- function buildFilter(filters?: { field: string; operator: string; value: unknown }[]): string | undefined {
45
- if (!filters?.length) return undefined;
46
- return filters.map(f => {
47
- const v = f.value;
48
- switch (f.operator) {
49
- case 'eq': return `${f.field} = '${v}'`;
50
- case 'ne': return `${f.field} != '${v}'`;
51
- case 'contains': return `${f.field} ~ '${v}'`;
52
- case 'ncontains': return `${f.field} !~ '${v}'`;
53
- case 'gt': return `${f.field} > '${v}'`;
54
- case 'gte': return `${f.field} >= '${v}'`;
55
- case 'lt': return `${f.field} < '${v}'`;
56
- case 'lte': return `${f.field} <= '${v}'`;
57
- case 'null': return `${f.field} = ''`;
58
- case 'nnull': return `${f.field} != ''`;
59
- case 'in':
60
- if (!Array.isArray(v)) throw new TypeError('PocketBase in filter requires an array');
61
- return `${f.field} ?= '${v.join("' || " + f.field + " ?= '")}'`;
62
- default: return `${f.field} = '${v}'`;
43
+ const FIELD_PATH = /^(?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/;
44
+
45
+ function pocketBaseField(field: string): string {
46
+ if (!FIELD_PATH.test(field)) throw new TypeError(`Unsupported PocketBase filter field: ${field}`);
47
+ return field;
48
+ }
49
+
50
+ function quoteString(value: string): string {
51
+ return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
52
+ }
53
+
54
+ function pocketBaseLiteral(value: unknown): string {
55
+ if (value === null) return 'null';
56
+ if (typeof value === 'number') {
57
+ if (!Number.isFinite(value)) throw new TypeError('PocketBase filters reject non-finite numbers');
58
+ return String(value);
59
+ }
60
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
61
+ if (typeof value === 'string') return quoteString(value);
62
+ throw new TypeError('PocketBase filters reject this value');
63
+ }
64
+
65
+ function likeLiteral(value: unknown): string {
66
+ if (typeof value !== 'string') throw new TypeError('PocketBase text filters require a string');
67
+ return value.replaceAll('\\', '\\\\').replaceAll('%', '\\%').replaceAll('_', '\\_');
68
+ }
69
+
70
+ function listLiterals(value: unknown, operator: string): string[] {
71
+ if (!Array.isArray(value) || value.length === 0) {
72
+ throw new TypeError(`PocketBase ${operator} filter requires a non-empty array`);
73
+ }
74
+ return value.map(pocketBaseLiteral);
75
+ }
76
+
77
+ function compilePocketBaseFilter(filter: Filter): string {
78
+ if ('field' in filter) {
79
+ const field = pocketBaseField(filter.field);
80
+ switch (filter.operator) {
81
+ case 'eq': return `${field} = ${pocketBaseLiteral(filter.value)}`;
82
+ case 'ne': return `${field} != ${pocketBaseLiteral(filter.value)}`;
83
+ case 'gt': return `${field} > ${pocketBaseLiteral(filter.value)}`;
84
+ case 'gte': return `${field} >= ${pocketBaseLiteral(filter.value)}`;
85
+ case 'lt': return `${field} < ${pocketBaseLiteral(filter.value)}`;
86
+ case 'lte': return `${field} <= ${pocketBaseLiteral(filter.value)}`;
87
+ case 'contains': return `${field} ~ ${quoteString(likeLiteral(filter.value))}`;
88
+ case 'ncontains': return `${field} !~ ${quoteString(likeLiteral(filter.value))}`;
89
+ case 'startswith': return `${field} ~ ${quoteString(`${likeLiteral(filter.value)}%`)}`;
90
+ case 'nstartswith': return `${field} !~ ${quoteString(`${likeLiteral(filter.value)}%`)}`;
91
+ case 'endswith': return `${field} ~ ${quoteString(`%${likeLiteral(filter.value)}`)}`;
92
+ case 'nendswith': return `${field} !~ ${quoteString(`%${likeLiteral(filter.value)}`)}`;
93
+ case 'null': return `${field} = null`;
94
+ case 'nnull': return `${field} != null`;
95
+ case 'in': return `(${listLiterals(filter.value, 'in').map((item) => `${field} = ${item}`).join(' || ')})`;
96
+ case 'nin': return `(${listLiterals(filter.value, 'nin').map((item) => `${field} != ${item}`).join(' && ')})`;
97
+ case 'between':
98
+ case 'nbetween': {
99
+ const bounds = listLiterals(filter.value, filter.operator);
100
+ if (bounds.length !== 2) throw new TypeError(`PocketBase ${filter.operator} filter requires two values`);
101
+ const [min, max] = bounds;
102
+ return filter.operator === 'between'
103
+ ? `(${field} >= ${min} && ${field} <= ${max})`
104
+ : `(${field} < ${min} || ${field} > ${max})`;
105
+ }
106
+ default:
107
+ throw new Error(`Unsupported PocketBase filter operator: ${String(filter.operator)}`);
63
108
  }
64
- }).join(' && ');
109
+ }
110
+ if (filter.operator !== 'and' && filter.operator !== 'or') {
111
+ throw new Error(`Unsupported PocketBase logical operator: ${String(filter.operator)}`);
112
+ }
113
+ if (!Array.isArray(filter.value)) throw new TypeError('PocketBase logical filter requires an array');
114
+ if (filter.value.length === 0) return filter.operator === 'and' ? '(1=1)' : '(1=0)';
115
+ const joiner = filter.operator === 'and' ? ' && ' : ' || ';
116
+ return `(${filter.value.map(compilePocketBaseFilter).join(joiner)})`;
117
+ }
118
+
119
+ function buildFilter(filters?: Filter[]): string | undefined {
120
+ if (!filters?.length) return undefined;
121
+ assertFiltersExecutable('pocketbase', filters);
122
+ return filters.map(compilePocketBaseFilter).join(' && ');
65
123
  }
66
124
 
67
125
  /**
@@ -87,7 +145,7 @@ export function createPocketBaseDataProvider(options: PocketBaseProviderOptions)
87
145
  const perPage = params.pagination?.pageSize ?? 10;
88
146
  const result = await pb.collection(params.resource).getList(page, perPage, definedOptions({
89
147
  sort: buildSort(params.sorters),
90
- filter: buildFilter((params.filters || []).filter((f) => 'field' in f)),
148
+ filter: buildFilter(params.filters),
91
149
  }));
92
150
  if (!isObject(result)) return result;
93
151
  return {