@rebasepro/common 0.22.0 → 0.23.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/dist/collections/default-collections.d.ts +1 -1
- package/dist/collections/field-access.d.ts +5 -0
- package/dist/collections/index.d.ts +1 -0
- package/dist/collections/soft-delete.d.ts +23 -0
- package/dist/data/filter-dialect.d.ts +13 -0
- package/dist/index.es.js +250 -46
- package/dist/index.es.js.map +1 -1
- package/dist/util/policy/index.d.ts +1 -0
- package/dist/util/policy/policyToSecurityRule.d.ts +27 -0
- package/package.json +3 -3
- package/dist/util/callback-errors.test.d.ts +0 -1
- package/dist/util/internal-tables.test.d.ts +0 -1
- package/dist/util/permissions.test.d.ts +0 -1
- package/dist/util/table-name.test.d.ts +0 -1
|
@@ -17,8 +17,8 @@ export declare const defaultUsersCollection: import("@rebasepro/types").Postgres
|
|
|
17
17
|
} & {
|
|
18
18
|
id?: string | undefined;
|
|
19
19
|
metadata?: import("@rebasepro/types").InferEntityType<{}> | undefined;
|
|
20
|
-
photoURL?: string | undefined;
|
|
21
20
|
roles?: string[] | undefined;
|
|
21
|
+
photoURL?: string | undefined;
|
|
22
22
|
passwordHash?: string | undefined;
|
|
23
23
|
emailVerified?: boolean | undefined;
|
|
24
24
|
emailVerificationToken?: string | undefined;
|
|
@@ -60,6 +60,11 @@ export declare function canWriteField(property: Property | undefined, viewer: Fi
|
|
|
60
60
|
* who knows the table can send `password_hash` as readily as `passwordHash`, and
|
|
61
61
|
* a rule that only knew the wire name would be one rename away from useless.
|
|
62
62
|
*
|
|
63
|
+
* A restricted to-one relation takes its foreign key with it. `bandId: 7` names
|
|
64
|
+
* the band exactly as `band: { id: 7 }` does, and the row carries the column
|
|
65
|
+
* beside the relation — so a rule on the relation that left the key alone
|
|
66
|
+
* withheld the value from the row and still let `?bandId=7` ask for it.
|
|
67
|
+
*
|
|
63
68
|
* `kind` picks which half of the rule is read; nothing else differs.
|
|
64
69
|
*/
|
|
65
70
|
export declare function restrictedFieldNames(collection: CollectionConfig, viewer: FieldViewer | undefined, kind: "read" | "write"): {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CollectionConfig } from "@rebasepro/types";
|
|
2
|
+
/** The field `softDelete: true` means, when the object form names none. */
|
|
3
|
+
export declare const DEFAULT_SOFT_DELETE_FIELD = "deletedAt";
|
|
4
|
+
/**
|
|
5
|
+
* The property a `softDelete` collection records a deletion in — its wire
|
|
6
|
+
* name, what a delete stamps and what a restore (an update to `null`) clears —
|
|
7
|
+
* or `undefined` for a collection without soft delete.
|
|
8
|
+
*
|
|
9
|
+
* One reading of the declaration for every layer that needs it: the driver
|
|
10
|
+
* that stamps and filters on it, and the REST route that has to recognise a
|
|
11
|
+
* restore before it looks the row up.
|
|
12
|
+
*/
|
|
13
|
+
export declare function softDeleteFieldOf(collection: CollectionConfig | undefined): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Whether an update's values restore a soft-deleted row: they set the
|
|
16
|
+
* soft-delete field back to `null`, the documented restore.
|
|
17
|
+
*
|
|
18
|
+
* A restore is an ordinary update of a row every default read hides, so a door
|
|
19
|
+
* that looks the row up before updating it has to include the trashed rows for
|
|
20
|
+
* this update — and only for this one: any other edit of a trashed row is a
|
|
21
|
+
* 404.
|
|
22
|
+
*/
|
|
23
|
+
export declare function restoresSoftDeletedRow(collection: CollectionConfig | undefined, values: Record<string, unknown> | undefined): boolean;
|
|
@@ -64,6 +64,19 @@ export declare class UnknownFilterOperatorError extends Error {
|
|
|
64
64
|
};
|
|
65
65
|
constructor(field: string, operator: string);
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The query-parameter names a REST listing reads as something other than a
|
|
69
|
+
* field filter: pagination, sorting, includes, projection, search, the logical
|
|
70
|
+
* groups, the JSON `where` dialect, an aggregate's `select`/`groupBy`, and the
|
|
71
|
+
* soft-delete switches.
|
|
72
|
+
*
|
|
73
|
+
* One list for both ends. The server skips these names when it collects
|
|
74
|
+
* `?<field>=<op>.<value>` filters, and the SDK sends a filter on a column with
|
|
75
|
+
* one of these names inside `?where=` instead — a collection is free to have a
|
|
76
|
+
* `page`, `select` or `hard` column, and a filter on it sent as its own
|
|
77
|
+
* parameter was read as the parameter and never applied.
|
|
78
|
+
*/
|
|
79
|
+
export declare const RESERVED_QUERY_KEYS: ReadonlySet<string>;
|
|
67
80
|
/**
|
|
68
81
|
* Convert `FilterValues` (or `WireFilterValues`) to a PostgREST-style
|
|
69
82
|
* querystring record.
|
package/dist/index.es.js
CHANGED
|
@@ -2130,6 +2130,81 @@ function evaluateCompare(op, left, right, ctx) {
|
|
|
2130
2130
|
return "unknown";
|
|
2131
2131
|
}
|
|
2132
2132
|
//#endregion
|
|
2133
|
+
//#region src/util/policy/policyToSecurityRule.ts
|
|
2134
|
+
/**
|
|
2135
|
+
* The rule a Postgres policy compiles back from — a `pg_policies` row, or what
|
|
2136
|
+
* a policy editor produced in that shape.
|
|
2137
|
+
*
|
|
2138
|
+
* One definition for every place that turns a policy into a rule: Studio's RLS
|
|
2139
|
+
* editor ("Save" and "Import to codebase"), the collection editor's RLS tab
|
|
2140
|
+
* ("Import to codebase") and "Import from table"
|
|
2141
|
+
* (`buildCollectionFromTableMetadata`). The rule is what `db push` compiles back
|
|
2142
|
+
* into the database, so anything this gets wrong is a policy that changes on
|
|
2143
|
+
* the way back:
|
|
2144
|
+
*
|
|
2145
|
+
* - The `TO` list names *database* roles, which is `pgRoles`. `roles` holds
|
|
2146
|
+
* *application* roles and compiles to a `rebase.roles()` check: filed there,
|
|
2147
|
+
* `TO public` is a check no user passes, and on a restrictive policy — which
|
|
2148
|
+
* compiles to `NOT (roles) OR condition` — a gate every user passes.
|
|
2149
|
+
* `pgRoles` is omitted at the `public` default, so a rule that targets every
|
|
2150
|
+
* connection, nearly all of them, carries no advanced field it does not need.
|
|
2151
|
+
* - The WITH CHECK is kept whether or not there is a USING beside it. Every
|
|
2152
|
+
* INSERT policy has only a check, and a rule with neither clause compiles to
|
|
2153
|
+
* `WITH CHECK (false)`, which stops every insert.
|
|
2154
|
+
* - `mode` is written when the policy states one: a restrictive policy read
|
|
2155
|
+
* back as permissive is OR'd with every grant beside it.
|
|
2156
|
+
* - Retired RLS helpers (`auth.uid()`) are respelled, as `sqlToPolicy` does
|
|
2157
|
+
* for what it reads, because this rule is written into the project's config.
|
|
2158
|
+
*/
|
|
2159
|
+
function policyToSecurityRule(policy) {
|
|
2160
|
+
const operation = toSecurityOperation(policy.cmd);
|
|
2161
|
+
const mode = toSecurityMode(policy.permissive);
|
|
2162
|
+
const pgRoles = toPgRoles(policy.roles);
|
|
2163
|
+
const base = {
|
|
2164
|
+
...policy.policyname ? { name: policy.policyname } : {},
|
|
2165
|
+
...operation ? { operation } : {},
|
|
2166
|
+
...mode ? { mode } : {},
|
|
2167
|
+
...pgRoles ? { pgRoles } : {}
|
|
2168
|
+
};
|
|
2169
|
+
const using = policy.qual ? rewriteLegacyRlsFunctions(policy.qual) : void 0;
|
|
2170
|
+
const withCheck = policy.with_check ? rewriteLegacyRlsFunctions(policy.with_check) : void 0;
|
|
2171
|
+
if (using) return withCheck ? {
|
|
2172
|
+
...base,
|
|
2173
|
+
using,
|
|
2174
|
+
withCheck
|
|
2175
|
+
} : {
|
|
2176
|
+
...base,
|
|
2177
|
+
using
|
|
2178
|
+
};
|
|
2179
|
+
if (withCheck) return {
|
|
2180
|
+
...base,
|
|
2181
|
+
withCheck
|
|
2182
|
+
};
|
|
2183
|
+
return base;
|
|
2184
|
+
}
|
|
2185
|
+
function toPgRoles(roles) {
|
|
2186
|
+
if (!roles || roles.length === 0) return void 0;
|
|
2187
|
+
if (roles.length === 1 && roles[0] === "public") return void 0;
|
|
2188
|
+
return [...roles];
|
|
2189
|
+
}
|
|
2190
|
+
function toSecurityOperation(cmd) {
|
|
2191
|
+
switch (cmd) {
|
|
2192
|
+
case "SELECT": return "select";
|
|
2193
|
+
case "INSERT": return "insert";
|
|
2194
|
+
case "UPDATE": return "update";
|
|
2195
|
+
case "DELETE": return "delete";
|
|
2196
|
+
case "ALL": return "all";
|
|
2197
|
+
default: return;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
function toSecurityMode(permissive) {
|
|
2201
|
+
switch (permissive) {
|
|
2202
|
+
case "PERMISSIVE": return "permissive";
|
|
2203
|
+
case "RESTRICTIVE": return "restrictive";
|
|
2204
|
+
default: return;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
//#endregion
|
|
2133
2208
|
//#region src/util/permissions.ts
|
|
2134
2209
|
/** Combine clause results with AND under three-valued (Kleene) logic. */
|
|
2135
2210
|
function kleeneAnd(values) {
|
|
@@ -3293,6 +3368,26 @@ function buildConditionContext(params) {
|
|
|
3293
3368
|
*/
|
|
3294
3369
|
//#endregion
|
|
3295
3370
|
//#region src/util/pg-column-to-property.ts
|
|
3371
|
+
var POLICY_COMMANDS = [
|
|
3372
|
+
"SELECT",
|
|
3373
|
+
"INSERT",
|
|
3374
|
+
"UPDATE",
|
|
3375
|
+
"DELETE",
|
|
3376
|
+
"ALL"
|
|
3377
|
+
];
|
|
3378
|
+
/**
|
|
3379
|
+
* A policy's command, as `pg_policies.cmd` names it.
|
|
3380
|
+
*
|
|
3381
|
+
* Anything else is refused rather than guessed at. A rule with no operation
|
|
3382
|
+
* compiles to FOR ALL, where the USING doubles as the WITH CHECK — so a
|
|
3383
|
+
* `SELECT ... USING (true)` whose command went unrecognised (`pg_policy.polcmd`
|
|
3384
|
+
* spells it `r`) would come back a write grant.
|
|
3385
|
+
*/
|
|
3386
|
+
function policyCommand(policy, tableName) {
|
|
3387
|
+
const cmd = POLICY_COMMANDS.find((c) => c === policy.cmd);
|
|
3388
|
+
if (!cmd) throw new Error(`Policy "${policy.policy_name}" on "${tableName}" has the command "${policy.cmd}", not one of ${POLICY_COMMANDS.join(", ")}, so it cannot be imported as a security rule.`);
|
|
3389
|
+
return cmd;
|
|
3390
|
+
}
|
|
3296
3391
|
/**
|
|
3297
3392
|
* Maps a PostgreSQL column data type to a Rebase property type.
|
|
3298
3393
|
*/
|
|
@@ -3490,40 +3585,14 @@ function buildCollectionFromTableMetadata(tableName, metadata) {
|
|
|
3490
3585
|
}
|
|
3491
3586
|
});
|
|
3492
3587
|
}
|
|
3493
|
-
if (metadata.policies) for (const policy of metadata.policies) {
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
break;
|
|
3502
|
-
case "INSERT":
|
|
3503
|
-
operations = ["insert"];
|
|
3504
|
-
break;
|
|
3505
|
-
case "UPDATE":
|
|
3506
|
-
operations = ["update"];
|
|
3507
|
-
break;
|
|
3508
|
-
case "DELETE":
|
|
3509
|
-
operations = ["delete"];
|
|
3510
|
-
break;
|
|
3511
|
-
}
|
|
3512
|
-
const qual = policy.qual ? rewriteLegacyRlsFunctions(policy.qual) : void 0;
|
|
3513
|
-
const withCheck = policy.with_check ? rewriteLegacyRlsFunctions(policy.with_check) : void 0;
|
|
3514
|
-
if (qual) securityRules.push({
|
|
3515
|
-
name: policy.policy_name,
|
|
3516
|
-
operations,
|
|
3517
|
-
roles: policy.roles ?? [],
|
|
3518
|
-
using: qual,
|
|
3519
|
-
...withCheck ? { withCheck } : {}
|
|
3520
|
-
});
|
|
3521
|
-
else securityRules.push({
|
|
3522
|
-
name: policy.policy_name,
|
|
3523
|
-
operations,
|
|
3524
|
-
roles: policy.roles ?? []
|
|
3525
|
-
});
|
|
3526
|
-
}
|
|
3588
|
+
if (metadata.policies) for (const policy of metadata.policies) securityRules.push(policyToSecurityRule({
|
|
3589
|
+
policyname: policy.policy_name,
|
|
3590
|
+
cmd: policyCommand(policy, tableName),
|
|
3591
|
+
permissive: policy.permissive,
|
|
3592
|
+
roles: policy.roles,
|
|
3593
|
+
qual: policy.qual ?? null,
|
|
3594
|
+
with_check: policy.with_check ?? null
|
|
3595
|
+
}));
|
|
3527
3596
|
return {
|
|
3528
3597
|
name: prettifyIdentifier(tableName),
|
|
3529
3598
|
slug: tableName,
|
|
@@ -3662,6 +3731,7 @@ var REBASE_INTERNAL_TABLES = [
|
|
|
3662
3731
|
"api_keys",
|
|
3663
3732
|
"cron_logs",
|
|
3664
3733
|
"cron_claims",
|
|
3734
|
+
"cron_job_state",
|
|
3665
3735
|
"jobs",
|
|
3666
3736
|
"rate_limit_hits",
|
|
3667
3737
|
"idempotency_keys",
|
|
@@ -4368,18 +4438,36 @@ function canWriteField(property, viewer) {
|
|
|
4368
4438
|
* who knows the table can send `password_hash` as readily as `passwordHash`, and
|
|
4369
4439
|
* a rule that only knew the wire name would be one rename away from useless.
|
|
4370
4440
|
*
|
|
4441
|
+
* A restricted to-one relation takes its foreign key with it. `bandId: 7` names
|
|
4442
|
+
* the band exactly as `band: { id: 7 }` does, and the row carries the column
|
|
4443
|
+
* beside the relation — so a rule on the relation that left the key alone
|
|
4444
|
+
* withheld the value from the row and still let `?bandId=7` ask for it.
|
|
4445
|
+
*
|
|
4371
4446
|
* `kind` picks which half of the rule is read; nothing else differs.
|
|
4372
4447
|
*/
|
|
4373
4448
|
function restrictedFieldNames(collection, viewer, kind) {
|
|
4374
4449
|
const declared = [];
|
|
4375
4450
|
const refused = /* @__PURE__ */ new Set();
|
|
4376
4451
|
const allowed = kind === "read" ? canReadField : canWriteField;
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4452
|
+
const properties = collection.properties ?? {};
|
|
4453
|
+
let relations;
|
|
4454
|
+
const restrict = (name) => {
|
|
4455
|
+
if (name in properties && !declared.includes(name)) declared.push(name);
|
|
4380
4456
|
refused.add(name);
|
|
4457
|
+
};
|
|
4458
|
+
for (const [name, property] of Object.entries(properties)) {
|
|
4459
|
+
if (allowed(property, viewer)) continue;
|
|
4460
|
+
restrict(name);
|
|
4381
4461
|
const columnName = property.columnName;
|
|
4382
4462
|
if (columnName) refused.add(columnName);
|
|
4463
|
+
if (property.type === "relation") {
|
|
4464
|
+
relations ??= resolveCollectionRelations(collection);
|
|
4465
|
+
const relation = findRelation(relations, name);
|
|
4466
|
+
if (relation?.kind === "belongsTo") {
|
|
4467
|
+
restrict(fieldKeyForColumn(collection, relation.localKey));
|
|
4468
|
+
refused.add(relation.localKey);
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4383
4471
|
}
|
|
4384
4472
|
return {
|
|
4385
4473
|
declared,
|
|
@@ -4398,6 +4486,37 @@ function hasFieldAccessRules(collection) {
|
|
|
4398
4486
|
return false;
|
|
4399
4487
|
}
|
|
4400
4488
|
//#endregion
|
|
4489
|
+
//#region src/collections/soft-delete.ts
|
|
4490
|
+
/** The field `softDelete: true` means, when the object form names none. */
|
|
4491
|
+
var DEFAULT_SOFT_DELETE_FIELD = "deletedAt";
|
|
4492
|
+
/**
|
|
4493
|
+
* The property a `softDelete` collection records a deletion in — its wire
|
|
4494
|
+
* name, what a delete stamps and what a restore (an update to `null`) clears —
|
|
4495
|
+
* or `undefined` for a collection without soft delete.
|
|
4496
|
+
*
|
|
4497
|
+
* One reading of the declaration for every layer that needs it: the driver
|
|
4498
|
+
* that stamps and filters on it, and the REST route that has to recognise a
|
|
4499
|
+
* restore before it looks the row up.
|
|
4500
|
+
*/
|
|
4501
|
+
function softDeleteFieldOf(collection) {
|
|
4502
|
+
const declared = collection && "softDelete" in collection ? collection.softDelete : void 0;
|
|
4503
|
+
if (!declared) return void 0;
|
|
4504
|
+
return typeof declared === "object" && declared.field || "deletedAt";
|
|
4505
|
+
}
|
|
4506
|
+
/**
|
|
4507
|
+
* Whether an update's values restore a soft-deleted row: they set the
|
|
4508
|
+
* soft-delete field back to `null`, the documented restore.
|
|
4509
|
+
*
|
|
4510
|
+
* A restore is an ordinary update of a row every default read hides, so a door
|
|
4511
|
+
* that looks the row up before updating it has to include the trashed rows for
|
|
4512
|
+
* this update — and only for this one: any other edit of a trashed row is a
|
|
4513
|
+
* 404.
|
|
4514
|
+
*/
|
|
4515
|
+
function restoresSoftDeletedRow(collection, values) {
|
|
4516
|
+
const field = softDeleteFieldOf(collection);
|
|
4517
|
+
return field !== void 0 && values?.[field] === null;
|
|
4518
|
+
}
|
|
4519
|
+
//#endregion
|
|
4401
4520
|
//#region src/data/cursor.ts
|
|
4402
4521
|
/** A cursor that cannot be read at all — truncated, re-encoded, or invented. */
|
|
4403
4522
|
var CursorError = class CursorError extends Error {
|
|
@@ -5295,19 +5414,23 @@ function normalizeMaxRows(raw) {
|
|
|
5295
5414
|
* @param label the collection name, so an error says which walk failed
|
|
5296
5415
|
*/
|
|
5297
5416
|
async function* paginateFind(find, params, label = "collection") {
|
|
5298
|
-
const { pageSize, cursor, maxPages, ...rest } = params ?? {};
|
|
5417
|
+
const { pageSize, cursor, maxPages, after: startAfter, offset: startOffset, page: startPage, ...rest } = params ?? {};
|
|
5299
5418
|
const findParams = { ...rest };
|
|
5300
5419
|
const size = normalizePageSize(pageSize);
|
|
5301
5420
|
const pageCap = normalizeMaxPages(maxPages);
|
|
5302
|
-
const seekRequested = cursor !== void 0 && cursor !== null;
|
|
5303
|
-
if (
|
|
5421
|
+
const seekRequested = cursor !== void 0 && cursor !== null || !!startAfter;
|
|
5422
|
+
if (cursor !== void 0 && cursor !== null) {
|
|
5304
5423
|
const field = typeof cursor === "string" ? cursor : cursor.field;
|
|
5305
5424
|
const requested = typeof cursor === "object" && cursor !== null ? cursor.direction : void 0;
|
|
5306
5425
|
if (!normalizeOrderBy(findParams.orderBy)) findParams.orderBy = [field, requested ?? "asc"];
|
|
5307
5426
|
}
|
|
5308
|
-
let offset =
|
|
5427
|
+
let offset = resolveFindWindow({
|
|
5428
|
+
limit: size,
|
|
5429
|
+
offset: startOffset,
|
|
5430
|
+
page: startPage
|
|
5431
|
+
}).offset;
|
|
5309
5432
|
let pages = 0;
|
|
5310
|
-
let after;
|
|
5433
|
+
let after = startAfter || void 0;
|
|
5311
5434
|
for (;;) {
|
|
5312
5435
|
if (pages >= pageCap) throw new RebasePaginationError("max-pages", `Iterating "${label}" made ${pages} requests without the server reporting the end of the collection. Stopping rather than looping forever — raise \`maxPages\` if the walk is genuinely this long, or check that the backend sets \`meta.hasMore\`.`);
|
|
5313
5436
|
const pageParams = {
|
|
@@ -5384,9 +5507,16 @@ function createPaginationHelpers(find, label) {
|
|
|
5384
5507
|
* Relation values (`EntityRelation` instances or `{ __type: "relation", id, path }`
|
|
5385
5508
|
* objects) are serialized as their raw id — the wire format only carries the
|
|
5386
5509
|
* value to compare against the FK column.
|
|
5510
|
+
*
|
|
5511
|
+
* A `Date` is its ISO 8601 instant — what JSON makes of it on the socket and
|
|
5512
|
+
* in-process, so one filter names one instant through every door. `String()`
|
|
5513
|
+
* gave the viewer's local zone, no milliseconds and a parenthesised zone name
|
|
5514
|
+
* Postgres cannot parse. An invalid date keeps its "Invalid Date" spelling for
|
|
5515
|
+
* the server to refuse, rather than throwing a `RangeError` from here.
|
|
5387
5516
|
*/
|
|
5388
5517
|
function stringifyValue(value) {
|
|
5389
5518
|
if (value === null) return "null";
|
|
5519
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
|
|
5390
5520
|
const relation = normalizeToEntityRelation(value);
|
|
5391
5521
|
if (relation) return String(relation.id);
|
|
5392
5522
|
return String(value);
|
|
@@ -5444,6 +5574,41 @@ function unescapeWireValue(value) {
|
|
|
5444
5574
|
return result;
|
|
5445
5575
|
}
|
|
5446
5576
|
/**
|
|
5577
|
+
* Whether a scalar, sent raw as a query parameter's operand, would be read
|
|
5578
|
+
* back as something other than itself.
|
|
5579
|
+
*
|
|
5580
|
+
* A query parameter owns its whole value, so its scalar goes out unescaped —
|
|
5581
|
+
* `eq.a,b`, `eq.C:\x` — and the decoder returns it verbatim. Two shapes break
|
|
5582
|
+
* that. A parenthesised operand is a list for every operator (so that
|
|
5583
|
+
* `["==", ["a","b"]]` on an array column stays a list), so raw, the scalar
|
|
5584
|
+
* `"(deleted)"` would arrive as `["deleted"]`: an `==` matching nothing, a
|
|
5585
|
+
* `!=` returning the very rows it excludes. Such a scalar is sent escaped,
|
|
5586
|
+
* `eq.\(deleted\)`. And that spelling has to be told apart from a raw value
|
|
5587
|
+
* that looks the same, so a scalar of *that* shape is escaped in turn.
|
|
5588
|
+
*
|
|
5589
|
+
* Everything else stays raw, so a value an older client sends decodes as it
|
|
5590
|
+
* always has — unless it is exactly the escaped spelling of such a scalar,
|
|
5591
|
+
* which decodes to the scalar it spells.
|
|
5592
|
+
*/
|
|
5593
|
+
function scalarNeedsEscape(value) {
|
|
5594
|
+
if (value.startsWith("(") && value.endsWith(")")) return true;
|
|
5595
|
+
return decodeEscapedScalar(value) !== void 0;
|
|
5596
|
+
}
|
|
5597
|
+
/**
|
|
5598
|
+
* The scalar an operand is the escaped spelling of, when it is one.
|
|
5599
|
+
*
|
|
5600
|
+
* Exact on purpose: only an operand that {@link escapeWireValue} produces from
|
|
5601
|
+
* a scalar that {@link scalarNeedsEscape} is decoded. Any other operand —
|
|
5602
|
+
* including a raw backslash an older client sent, like `\\server\share` — is
|
|
5603
|
+
* left verbatim. Terminates because an operand that matches contains an
|
|
5604
|
+
* escape pair, so the value is strictly shorter than it.
|
|
5605
|
+
*/
|
|
5606
|
+
function decodeEscapedScalar(operand) {
|
|
5607
|
+
if (!operand.startsWith("\\")) return void 0;
|
|
5608
|
+
const value = unescapeWireValue(operand);
|
|
5609
|
+
return escapeWireValue(value) === operand && scalarNeedsEscape(value) ? value : void 0;
|
|
5610
|
+
}
|
|
5611
|
+
/**
|
|
5447
5612
|
* Split a parenthesized list string on unescaped commas.
|
|
5448
5613
|
* Input is the content between `(` and `)`.
|
|
5449
5614
|
*
|
|
@@ -5688,6 +5853,42 @@ function readTuple(field, raw) {
|
|
|
5688
5853
|
if (isOperatorShaped(op)) throw new UnknownFilterOperatorError(field, op);
|
|
5689
5854
|
}
|
|
5690
5855
|
/**
|
|
5856
|
+
* The query-parameter names a REST listing reads as something other than a
|
|
5857
|
+
* field filter: pagination, sorting, includes, projection, search, the logical
|
|
5858
|
+
* groups, the JSON `where` dialect, an aggregate's `select`/`groupBy`, and the
|
|
5859
|
+
* soft-delete switches.
|
|
5860
|
+
*
|
|
5861
|
+
* One list for both ends. The server skips these names when it collects
|
|
5862
|
+
* `?<field>=<op>.<value>` filters, and the SDK sends a filter on a column with
|
|
5863
|
+
* one of these names inside `?where=` instead — a collection is free to have a
|
|
5864
|
+
* `page`, `select` or `hard` column, and a filter on it sent as its own
|
|
5865
|
+
* parameter was read as the parameter and never applied.
|
|
5866
|
+
*/
|
|
5867
|
+
var RESERVED_QUERY_KEYS = /* @__PURE__ */ new Set([
|
|
5868
|
+
"limit",
|
|
5869
|
+
"offset",
|
|
5870
|
+
"page",
|
|
5871
|
+
"after",
|
|
5872
|
+
"orderBy",
|
|
5873
|
+
"include",
|
|
5874
|
+
"fields",
|
|
5875
|
+
"distinct",
|
|
5876
|
+
"searchString",
|
|
5877
|
+
"searchExplain",
|
|
5878
|
+
"vector_search",
|
|
5879
|
+
"vector",
|
|
5880
|
+
"vector_distance",
|
|
5881
|
+
"vector_threshold",
|
|
5882
|
+
"or",
|
|
5883
|
+
"and",
|
|
5884
|
+
"not",
|
|
5885
|
+
"where",
|
|
5886
|
+
"select",
|
|
5887
|
+
"groupBy",
|
|
5888
|
+
"deleted",
|
|
5889
|
+
"hard"
|
|
5890
|
+
]);
|
|
5891
|
+
/**
|
|
5691
5892
|
* Encode the `<op>.<value>` half of a wire condition.
|
|
5692
5893
|
*
|
|
5693
5894
|
* This is the single leaf encoder. Both wire positions that carry a condition
|
|
@@ -5715,7 +5916,7 @@ function serializeOperatorAndValue(op, value, { escapeScalar, where }) {
|
|
|
5715
5916
|
return `${restOp}.(${value.map((v) => escapeWireValue(stringifyValue(v))).join(",")})`;
|
|
5716
5917
|
}
|
|
5717
5918
|
const scalar = stringifyValue(value);
|
|
5718
|
-
return `${restOp}.${escapeScalar ? escapeWireValue(scalar) : scalar}`;
|
|
5919
|
+
return `${restOp}.${escapeScalar || !LIST_OPS.has(op) && scalarNeedsEscape(scalar) ? escapeWireValue(scalar) : scalar}`;
|
|
5719
5920
|
}
|
|
5720
5921
|
/**
|
|
5721
5922
|
* Serialize a single canonical condition tuple to a PostgREST dot-string.
|
|
@@ -5811,7 +6012,10 @@ var NULL_OPERANDS = /* @__PURE__ */ new Set([
|
|
|
5811
6012
|
* operand: only {@link NULL_OPERANDS}. `notnull.reason` is the value
|
|
5812
6013
|
* `"notnull.reason"`, not "reason is not null".
|
|
5813
6014
|
* - **Everything else** takes one scalar, and any remainder is one — including
|
|
5814
|
-
* the empty string, so `eq.` really is "equals the empty string".
|
|
6015
|
+
* the empty string, so `eq.` really is "equals the empty string". A
|
|
6016
|
+
* parenthesised remainder is a list for these too (`eq.(a,b)` is array
|
|
6017
|
+
* equality); a scalar that is itself parenthesised arrives escaped,
|
|
6018
|
+
* `eq.\(none\)`, and is decoded here — see {@link scalarNeedsEscape}.
|
|
5815
6019
|
*
|
|
5816
6020
|
* ### The one ambiguity that remains, and how to write past it
|
|
5817
6021
|
*
|
|
@@ -5846,7 +6050,7 @@ function deserializeSingle(raw) {
|
|
|
5846
6050
|
return [canonicalOp, inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner)];
|
|
5847
6051
|
}
|
|
5848
6052
|
if (LIST_OPS.has(canonicalOp)) return ["==", raw];
|
|
5849
|
-
return [canonicalOp, rest];
|
|
6053
|
+
return [canonicalOp, decodeEscapedScalar(rest) ?? rest];
|
|
5850
6054
|
}
|
|
5851
6055
|
/**
|
|
5852
6056
|
* Convert a PostgREST-style querystring record to `FilterValues`.
|
|
@@ -7026,6 +7230,6 @@ async function detectJunctionTables(executeSql) {
|
|
|
7026
7230
|
return junctionTables;
|
|
7027
7231
|
}
|
|
7028
7232
|
//#endregion
|
|
7029
|
-
export { ADMIN_ROLE, CALLBACK_REJECTED, COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, CursorError, CursorMismatchError, DEFAULT_FIND_ALL_MAX_ROWS, DEFAULT_MAX_PAGES, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, DEFAULT_PAGE_SIZE, DEFAULT_STRING_COLUMN_LENGTH, IncludeSpecError, JUNCTION_TABLES_SQL, MAX_LOGICAL_NESTING_DEPTH, OrderBySpecError, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_INTERNAL_TABLES, REBASE_USER_ROLE, RebasePaginationError, TENANT_INDEX_REASON, UnknownFilterOperatorError, aggregateAlias, and, applyDefaultValuesOnCreate, assertBeforeQueryIsPostgresOnly, authSecretsMissingExclusion, buildCollectionFromTableMetadata, buildCompositeId, buildConditionContext, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, buildTenantSecurityRule, callbackRefusal, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, canReadField, canWriteField, checkOperation, classifyTable, collectAllPages, cond, createDataSourceRegistry, createPaginationHelpers, createRelationRef, createRelationRefWithData, cursorToStartAfter, decodeCursor, defaultUsersCollection, defineCollection, denormalizeInclude, deserializeFilter, deserializeInclude, deserializeLogicalCondition, deserializeOrderBy, deserializeOrderByList, detectJunctionTables, effectiveAccess, embedParentExpression, encodeCursor, enumToObjectEntries, evaluateCondition, evaluatePolicy, fieldKeyForColumn, findAnonymousGrants, findRelation, firstSqlRow, fullPathToCollectionSegments, getArrayResolvedProperties, getChildViewDeclaringProperties, getChildViewRelationPropertyKeys, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityChildViews, getEnumVarName, getGeneratedPolicyNames, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionConfigForRelation, getJunctionSecurityRules, getLabelOrConfigFrom, getPrimaryKeys, getReferenceFrom, getRelationFrom, getRelationTargetPath, getSubcollections, getTableName, getTableVarName, getTenantConfig, hasFieldAccessRules, includePaths, isAddressableId, isJunctionBackedRelation, isPropertyBuilder, isRebaseInternalTable, isRelationRequired, isRelationalCollection, mergeIncludeSpecs, normalizeDriverOrderBy, normalizeEmail, normalizeInclude, normalizeOrderBy, normalizeToEntityRelation, not, or, paginateFind, parseIdValues, parseOrderBySpecStrict, policyToPostgres, primaryOrderBy, reconcileCursorOrder, registerConditionOperations, relationDeclaringProperty, relationalCollections, requireCallbackClient, requireCallbackCollection, resolveArrayProperties, resolveCollectionRelations, resolveDataSource, resolveEnumValues, resolveFindWindow, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolveRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, resolveStringColumnLength, resolveTenantWrite, restrictedFieldNames, revokeInternalTableAccess, revokeInternalTableSql, sanitizeData, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeInclude, serializeLogicalCondition, serializeOrderBy, sortCollectionsBySlug, sortProperties, sqlRows, sqlToPolicy, stripCollectionPath, tenantBypassRoles, tenantPolicyName, tenantScopeExpression, toCallbackError, toFilterTuples, topLevelIncludeNames, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, updateUserAutoValues, wrapAsEntityData, wrapAsSdkData };
|
|
7233
|
+
export { ADMIN_ROLE, CALLBACK_REJECTED, COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, CursorError, CursorMismatchError, DEFAULT_FIND_ALL_MAX_ROWS, DEFAULT_MAX_PAGES, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, DEFAULT_PAGE_SIZE, DEFAULT_SOFT_DELETE_FIELD, DEFAULT_STRING_COLUMN_LENGTH, IncludeSpecError, JUNCTION_TABLES_SQL, MAX_LOGICAL_NESTING_DEPTH, OrderBySpecError, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, REBASE_INTERNAL_TABLES, REBASE_USER_ROLE, RESERVED_QUERY_KEYS, RebasePaginationError, TENANT_INDEX_REASON, UnknownFilterOperatorError, aggregateAlias, and, applyDefaultValuesOnCreate, assertBeforeQueryIsPostgresOnly, authSecretsMissingExclusion, buildCollectionFromTableMetadata, buildCompositeId, buildConditionContext, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, buildTenantSecurityRule, callbackRefusal, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, canReadField, canWriteField, checkOperation, classifyTable, collectAllPages, cond, createDataSourceRegistry, createPaginationHelpers, createRelationRef, createRelationRefWithData, cursorToStartAfter, decodeCursor, defaultUsersCollection, defineCollection, denormalizeInclude, deserializeFilter, deserializeInclude, deserializeLogicalCondition, deserializeOrderBy, deserializeOrderByList, detectJunctionTables, effectiveAccess, embedParentExpression, encodeCursor, enumToObjectEntries, evaluateCondition, evaluatePolicy, fieldKeyForColumn, findAnonymousGrants, findRelation, firstSqlRow, fullPathToCollectionSegments, getArrayResolvedProperties, getChildViewDeclaringProperties, getChildViewRelationPropertyKeys, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityChildViews, getEnumVarName, getGeneratedPolicyNames, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionConfigForRelation, getJunctionSecurityRules, getLabelOrConfigFrom, getPrimaryKeys, getReferenceFrom, getRelationFrom, getRelationTargetPath, getSubcollections, getTableName, getTableVarName, getTenantConfig, hasFieldAccessRules, includePaths, isAddressableId, isJunctionBackedRelation, isPropertyBuilder, isRebaseInternalTable, isRelationRequired, isRelationalCollection, mergeIncludeSpecs, normalizeDriverOrderBy, normalizeEmail, normalizeInclude, normalizeOrderBy, normalizeToEntityRelation, not, or, paginateFind, parseIdValues, parseOrderBySpecStrict, policyToPostgres, policyToSecurityRule, primaryOrderBy, reconcileCursorOrder, registerConditionOperations, relationDeclaringProperty, relationalCollections, requireCallbackClient, requireCallbackCollection, resolveArrayProperties, resolveCollectionRelations, resolveDataSource, resolveEnumValues, resolveFindWindow, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolveRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, resolveStringColumnLength, resolveTenantWrite, restoresSoftDeletedRow, restrictedFieldNames, revokeInternalTableAccess, revokeInternalTableSql, sanitizeData, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeInclude, serializeLogicalCondition, serializeOrderBy, softDeleteFieldOf, sortCollectionsBySlug, sortProperties, sqlRows, sqlToPolicy, stripCollectionPath, tenantBypassRoles, tenantPolicyName, tenantScopeExpression, toCallbackError, toFilterTuples, topLevelIncludeNames, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, updateUserAutoValues, wrapAsEntityData, wrapAsSdkData };
|
|
7030
7234
|
|
|
7031
7235
|
//# sourceMappingURL=index.es.js.map
|