@bunbase-ae/js 2.13.3-next.300.4a69028 → 2.13.3-next.310.9b923b7
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 +1 -1
- package/src/admin.ts +27 -9
- package/src/collection.ts +12 -8
- package/src/types.ts +4 -2
package/package.json
CHANGED
package/src/admin.ts
CHANGED
|
@@ -650,23 +650,25 @@ class AdminCollectionsClient {
|
|
|
650
650
|
if (opts.includeDeleted) params.include_deleted = "true";
|
|
651
651
|
if (opts.search) params.search = opts.search;
|
|
652
652
|
if (opts.filter) {
|
|
653
|
+
// Encode filters as a single ?filter=[{field,op,value}, …] JSON array.
|
|
654
|
+
// See buildQueryString in collection.ts for the rationale (issue #533):
|
|
655
|
+
// the bracket form silently corrupts values containing commas.
|
|
656
|
+
const fieldFilters: { field: string; op: string; value: unknown }[] = [];
|
|
653
657
|
for (const [key, value] of Object.entries(opts.filter)) {
|
|
654
658
|
if (value === undefined || value === null) continue;
|
|
655
|
-
if (
|
|
656
|
-
|
|
659
|
+
if (Array.isArray(value)) {
|
|
660
|
+
fieldFilters.push({ field: key, op: "in", value });
|
|
661
|
+
} else if (typeof value === "object") {
|
|
657
662
|
for (const [op, opVal] of Object.entries(value as Record<string, unknown>)) {
|
|
658
663
|
if (opVal !== undefined && opVal !== null) {
|
|
659
|
-
|
|
664
|
+
fieldFilters.push({ field: key, op, value: opVal });
|
|
660
665
|
}
|
|
661
666
|
}
|
|
662
|
-
} else if (Array.isArray(value)) {
|
|
663
|
-
// Array shorthand: { stage: ["a", "b"] } → filter[stage][in]=a,b
|
|
664
|
-
params[`filter[${key}][in]`] = value.join(",");
|
|
665
667
|
} else {
|
|
666
|
-
|
|
667
|
-
params[`filter[${key}]`] = String(value);
|
|
668
|
+
fieldFilters.push({ field: key, op: "eq", value });
|
|
668
669
|
}
|
|
669
670
|
}
|
|
671
|
+
if (fieldFilters.length) params.filter = JSON.stringify(fieldFilters);
|
|
670
672
|
}
|
|
671
673
|
return this.http.request<AdminListResult<T & AdminRecord>>(
|
|
672
674
|
"GET",
|
|
@@ -1097,11 +1099,27 @@ export interface NamedQueryParamDef {
|
|
|
1097
1099
|
required?: boolean;
|
|
1098
1100
|
}
|
|
1099
1101
|
|
|
1102
|
+
/**
|
|
1103
|
+
* Access rules valid for a named query.
|
|
1104
|
+
*
|
|
1105
|
+
* Named queries don't carry a record-level owner — `"owner"` (which is valid
|
|
1106
|
+
* on collections) has no per-row meaning here and is rejected at create/update
|
|
1107
|
+
* time. Use `"authenticated"` for logged-in-only access, or `{ role: "..." }`
|
|
1108
|
+
* for role-gated access.
|
|
1109
|
+
*/
|
|
1110
|
+
export type NamedQueryAccessRule = Exclude<AccessRule, "owner">;
|
|
1111
|
+
|
|
1100
1112
|
export interface NamedQuery {
|
|
1101
1113
|
name: string;
|
|
1102
1114
|
description: string | null;
|
|
1103
1115
|
sql: string;
|
|
1104
1116
|
params: Record<string, NamedQueryParamDef>;
|
|
1117
|
+
/**
|
|
1118
|
+
* The persisted access rule. New queries are restricted to
|
|
1119
|
+
* {@link NamedQueryAccessRule}; this remains the broader {@link AccessRule}
|
|
1120
|
+
* union so legacy rows persisted before the validator was tightened can
|
|
1121
|
+
* still be read.
|
|
1122
|
+
*/
|
|
1105
1123
|
access: AccessRule;
|
|
1106
1124
|
row_limit: number;
|
|
1107
1125
|
timeout_ms: number;
|
|
@@ -1122,7 +1140,7 @@ export interface CreateNamedQueryInput {
|
|
|
1122
1140
|
description?: string | null;
|
|
1123
1141
|
sql: string;
|
|
1124
1142
|
params?: Record<string, NamedQueryParamDef>;
|
|
1125
|
-
access?:
|
|
1143
|
+
access?: NamedQueryAccessRule;
|
|
1126
1144
|
row_limit?: number;
|
|
1127
1145
|
timeout_ms?: number;
|
|
1128
1146
|
result_cache_ttl_s?: number;
|
package/src/collection.ts
CHANGED
|
@@ -161,24 +161,28 @@ export function buildQueryString<T extends Record<string, unknown> = Record<stri
|
|
|
161
161
|
const params: Record<string, string> = {};
|
|
162
162
|
|
|
163
163
|
if (query.filter) {
|
|
164
|
+
// Encode filters as a single ?filter=[{field,op,value}, …] JSON array.
|
|
165
|
+
// The bracket form (?filter[field][op]=value) joins array values on ",", which
|
|
166
|
+
// silently corrupts any value containing a comma — see issue #533. The JSON
|
|
167
|
+
// form preserves array values verbatim and is parsed by the server's
|
|
168
|
+
// parseFilterJson (apps/server/src/routes/_filterParams.ts).
|
|
169
|
+
const fieldFilters: { field: string; op: string; value: unknown }[] = [];
|
|
164
170
|
for (const [field, value] of Object.entries(query.filter)) {
|
|
165
171
|
if (value === undefined || value === null) continue;
|
|
166
172
|
|
|
167
|
-
if (
|
|
168
|
-
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
fieldFilters.push({ field, op: "in", value });
|
|
175
|
+
} else if (typeof value === "object") {
|
|
169
176
|
for (const [op, opVal] of Object.entries(value as Record<string, unknown>)) {
|
|
170
177
|
if (opVal !== undefined && opVal !== null) {
|
|
171
|
-
|
|
178
|
+
fieldFilters.push({ field, op, value: opVal });
|
|
172
179
|
}
|
|
173
180
|
}
|
|
174
|
-
} else if (Array.isArray(value)) {
|
|
175
|
-
// Array shorthand: { stage: ["a", "b"] } → filter[stage][in]=a,b
|
|
176
|
-
params[`filter[${field}][in]`] = value.join(",");
|
|
177
181
|
} else {
|
|
178
|
-
|
|
179
|
-
params[`filter[${field}]`] = String(value);
|
|
182
|
+
fieldFilters.push({ field, op: "eq", value });
|
|
180
183
|
}
|
|
181
184
|
}
|
|
185
|
+
if (fieldFilters.length) params.filter = JSON.stringify(fieldFilters);
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
if (query.sort) params.sort = query.sort;
|
package/src/types.ts
CHANGED
|
@@ -166,8 +166,10 @@ export type FilterFieldValue<V> = V | FilterArrayShorthandValue<V> | FilterOpera
|
|
|
166
166
|
* filter: { rfid_tag: { in: ["a", "b"] } }
|
|
167
167
|
* filter: { score: { gte: 0, lte: 100 } }
|
|
168
168
|
*/
|
|
169
|
-
//
|
|
170
|
-
//
|
|
169
|
+
// Wire format: a single ?filter=[{field,op,value}, …] JSON entry.
|
|
170
|
+
// Simple { status: "published" } → [{ field: "status", op: "eq", value: "published" }]
|
|
171
|
+
// Operator { age: { gte: 18 } } → [{ field: "age", op: "gte", value: 18 }]
|
|
172
|
+
// Array { id: ["a", "b"] } → [{ field: "id", op: "in", value: ["a", "b"] }]
|
|
171
173
|
export type Filter<T = Record<string, unknown>> = {
|
|
172
174
|
[K in Exclude<keyof T, keyof BunBaseRecord>]?: FilterFieldValue<T[K]>;
|
|
173
175
|
} & {
|