@rebasepro/server-postgres 0.13.1-canary.g06dbe5b → 0.13.1-canary.g1bf9303
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/PostgresBootstrapper.d.ts +26 -0
- package/dist/auth/services.d.ts +2 -0
- package/dist/{auth-users-columns-CBEOeYqa.js → auth-users-columns-BxT6FRyu.js} +280 -25
- package/dist/auth-users-columns-BxT6FRyu.js.map +1 -0
- package/dist/{backup-service-Bww-Lg0s.js → backup-service-BH0Dzo_h.js} +2 -3
- package/dist/{backup-service-Bww-Lg0s.js.map → backup-service-BH0Dzo_h.js.map} +1 -1
- package/dist/cli-output.d.ts +34 -0
- package/dist/ensure-collection-policies-CP5_YttS.js +124 -0
- package/dist/ensure-collection-policies-CP5_YttS.js.map +1 -0
- package/dist/{ensure-collection-tables-DzeTEvMv.js → ensure-collection-tables-DWVISfcC.js} +3 -3
- package/dist/{ensure-collection-tables-DzeTEvMv.js.map → ensure-collection-tables-DWVISfcC.js.map} +1 -1
- package/dist/index.es.js +174 -469
- package/dist/index.es.js.map +1 -1
- package/dist/{rls-bootstrap-sql-Bpv3nUZo.js → rls-bootstrap-sql-69hYT8nr.js} +2 -2
- package/dist/{rls-bootstrap-sql-Bpv3nUZo.js.map → rls-bootstrap-sql-69hYT8nr.js.map} +1 -1
- package/dist/rls-enforcement-Ct9ab42o.js +425 -0
- package/dist/rls-enforcement-Ct9ab42o.js.map +1 -0
- package/dist/schema/doctor.d.ts +18 -0
- package/dist/schema/ensure-collection-policies.d.ts +33 -9
- package/dist/services/realtimeService.d.ts +33 -2
- package/dist/{src-C_wvdMnl.js → src-DCdn3Val.js} +35 -3
- package/dist/src-DCdn3Val.js.map +1 -0
- package/dist/{websocket-D1qbmLZ2.js → websocket-C8ZqVBiV.js} +2 -2
- package/dist/{websocket-D1qbmLZ2.js.map → websocket-C8ZqVBiV.js.map} +1 -1
- package/package.json +6 -6
- package/src/PostgresBootstrapper.ts +81 -5
- package/src/auth/ensure-tables.ts +5 -5
- package/src/auth/services.ts +13 -0
- package/src/backup/backup-cli.ts +59 -57
- package/src/cli-errors.ts +6 -6
- package/src/cli-helpers.ts +4 -4
- package/src/cli-output.ts +43 -0
- package/src/cli.ts +155 -147
- package/src/collections/buildRegistry.ts +3 -1
- package/src/history/ensure-history-table.ts +2 -2
- package/src/schema/doctor-cli.ts +2 -2
- package/src/schema/doctor.ts +41 -21
- package/src/schema/ensure-collection-policies.ts +99 -6
- package/src/schema/generate-drizzle-schema.ts +11 -10
- package/src/schema/generate-postgres-ddl.ts +14 -13
- package/src/schema/introspect-db.ts +24 -24
- package/src/security/rls-enforcement.ts +1 -1
- package/src/services/FetchService.ts +64 -0
- package/src/services/RelationService.ts +9 -2
- package/src/services/cdc/trigger-cdc.ts +5 -1
- package/src/services/pg-notify-listener.ts +1 -1
- package/src/services/realtimeService.ts +38 -39
- package/dist/auth-users-columns-CBEOeYqa.js.map +0 -1
- package/dist/ensure-collection-policies-B_JMGa5K.js +0 -57
- package/dist/ensure-collection-policies-B_JMGa5K.js.map +0 -1
- package/dist/src-C_wvdMnl.js.map +0 -1
|
@@ -89,6 +89,32 @@ export interface PostgresDriverInternals {
|
|
|
89
89
|
* and this is the part that was wrong.
|
|
90
90
|
*/
|
|
91
91
|
export declare function resolveDriftCheckName(col: CollectionConfig, registeredTableNames: string[]): string;
|
|
92
|
+
/**
|
|
93
|
+
* Is this the local database `rebase init` scaffolds — i.e. the one case where
|
|
94
|
+
* "you are connected as a superuser" is not news?
|
|
95
|
+
*
|
|
96
|
+
* The scaffold's own `docker-compose.yml` sets `POSTGRES_USER: rebase_app`,
|
|
97
|
+
* which makes that role the cluster superuser, so the superuser advisory below
|
|
98
|
+
* was the only WARN a brand-new project ever saw and it was about a decision
|
|
99
|
+
* the tool had made for the developer.
|
|
100
|
+
*
|
|
101
|
+
* Of the two available fixes — provision a non-superuser table-owner role in
|
|
102
|
+
* the scaffold, or recognise the local shape and stay quiet — this is the
|
|
103
|
+
* second, because the first breaks the scaffold it is meant to improve: a
|
|
104
|
+
* non-superuser owner cannot `CREATE EXTENSION` (search collections need
|
|
105
|
+
* `pg_trgm`/`unaccent`, applied by `rebase db push` and again by the boot
|
|
106
|
+
* schema-ensure), so the very first `pnpm run db:push` on a scaffolded project
|
|
107
|
+
* with a search block would fail. Trading a working first run for a quieter log
|
|
108
|
+
* line is the wrong trade.
|
|
109
|
+
*
|
|
110
|
+
* The condition is deliberately narrow — a *non-production* process talking to
|
|
111
|
+
* a database on the loopback interface. A genuine production superuser
|
|
112
|
+
* connection still warns, and so does a non-production process pointed at a
|
|
113
|
+
* remote database (the usual "my dev machine writes to staging" mistake, where
|
|
114
|
+
* the advisory is exactly right). NODE_ENV alone would not do: the scaffold
|
|
115
|
+
* ships `NODE_ENV=development` and some deployments inherit it.
|
|
116
|
+
*/
|
|
117
|
+
export declare function isScaffoldedLocalDatabase(connectionString: string | undefined): boolean;
|
|
92
118
|
/**
|
|
93
119
|
* Default PostgreSQL bootstrapper.
|
|
94
120
|
*
|
package/dist/auth/services.d.ts
CHANGED
|
@@ -287,6 +287,7 @@ export declare class PostgresAuthRepository implements AuthRepository {
|
|
|
287
287
|
secretEncrypted: string;
|
|
288
288
|
}) | null>;
|
|
289
289
|
verifyMfaFactor(factorId: string): Promise<void>;
|
|
290
|
+
updateMfaFactorSecret(factorId: string, secretEncrypted: string): Promise<void>;
|
|
290
291
|
deleteMfaFactor(factorId: string, uid: string): Promise<void>;
|
|
291
292
|
createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
|
|
292
293
|
getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
|
|
@@ -323,6 +324,7 @@ export declare class MfaService implements MfaRepository {
|
|
|
323
324
|
*/
|
|
324
325
|
claimMfaFactorCounter(factorId: string, counter: number): Promise<boolean>;
|
|
325
326
|
verifyMfaFactor(factorId: string): Promise<void>;
|
|
327
|
+
updateMfaFactorSecret(factorId: string, secretEncrypted: string): Promise<void>;
|
|
326
328
|
deleteMfaFactor(factorId: string, uid: string): Promise<void>;
|
|
327
329
|
createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
|
|
328
330
|
getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
|
|
@@ -3,7 +3,7 @@ import "process";
|
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
4
|
import { l as __require, s as __commonJSMin } from "./connection-BuZ97wsr.js";
|
|
5
5
|
import { a as policy, i as ANONYMOUS_USER_IDS, r as ANONYMOUS_USER_ID } from "./data_driver-ULAyJEi9.js";
|
|
6
|
-
import { a as rewriteLegacyRlsFunctions, c as isPostgresCollectionConfig, d as getDataSourceCapabilities, f as
|
|
6
|
+
import { a as rewriteLegacyRlsFunctions, c as isPostgresCollectionConfig, d as getDataSourceCapabilities, f as ALL_WHERE_FILTER_OPS, g as toCanonicalOp, h as REST_TO_CANONICAL, i as RLS_UID_SQL, l as isRelationalCollectionConfig, m as NULL_OPS, p as CANONICAL_TO_REST, r as RLS_ROLES_SQL, s as getDeclaredSubcollections } from "./src-DCdn3Val.js";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
//#region ../types/src/types/entities.ts
|
|
9
9
|
/**
|
|
@@ -2115,11 +2115,32 @@ function sqlToPolicy(sql) {
|
|
|
2115
2115
|
* is the more dangerous spelling, because it inverts a rule instead of
|
|
2116
2116
|
* emptying a table.
|
|
2117
2117
|
*/
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2118
|
+
/**
|
|
2119
|
+
* A `Map`, not an object literal.
|
|
2120
|
+
*
|
|
2121
|
+
* As `Record<string, string>` this was indexed with a literal taken straight
|
|
2122
|
+
* out of a policy, so every key on `Object.prototype` answered: a rule
|
|
2123
|
+
* comparing `rebase.uid()` to `"valueOf"`, `"toString"`, `"constructor"` or
|
|
2124
|
+
* `"hasOwnProperty"` found a truthy "platform" and reported an anonymous-grant
|
|
2125
|
+
* risk that does not exist — with the matched function interpolated into the
|
|
2126
|
+
* explanation as the platform's name. A security warning that fires on
|
|
2127
|
+
* innocent input is worse than none: it is what teaches people to skip the
|
|
2128
|
+
* warnings that are real.
|
|
2129
|
+
*
|
|
2130
|
+
* Same shape as the prototype-pollution class swept out of `setIn`, `getIn`,
|
|
2131
|
+
* `mergeDeep` and `unflattenObject` — a data-derived key reaching a plain
|
|
2132
|
+
* object. Found by a property test, on the input `"valueOf"`.
|
|
2133
|
+
*/
|
|
2134
|
+
var FOREIGN_CONVENTION_UIDS = /* @__PURE__ */ new Map([
|
|
2135
|
+
["anon", "Supabase"],
|
|
2136
|
+
["authenticated", "Supabase"],
|
|
2137
|
+
["service_role", "Supabase"]
|
|
2138
|
+
]);
|
|
2139
|
+
/**
|
|
2140
|
+
* The same foreign literals, as a pattern for SQL that could not be parsed
|
|
2141
|
+
* back into structure.
|
|
2142
|
+
*/
|
|
2143
|
+
var FOREIGN_UID_LITERAL_SQL = new RegExp(String.raw`rebase\.uid\(\)\s*=\s*'(${[...FOREIGN_CONVENTION_UIDS.keys()].join("|")})'`, "i");
|
|
2123
2144
|
/**
|
|
2124
2145
|
* `rebase.uid() IS NOT NULL` in raw SQL, the clause that is always true.
|
|
2125
2146
|
*
|
|
@@ -2166,17 +2187,27 @@ function findAnonymousGrants(expr) {
|
|
|
2166
2187
|
case "existsIn":
|
|
2167
2188
|
visit(e.where);
|
|
2168
2189
|
return;
|
|
2169
|
-
case "raw":
|
|
2190
|
+
case "raw": {
|
|
2170
2191
|
if (UID_NOT_NULL.test(e.sql)) found.push({
|
|
2171
2192
|
pattern: "uid-not-null",
|
|
2172
2193
|
detail: e.sql,
|
|
2173
2194
|
explanation: `\`rebase.uid() IS NOT NULL\` is true for every request that came from a client, including anonymous ones — they carry '${ANONYMOUS_USER_ID}', not NULL. Use \`condition: policy.authenticated()\` to mean "signed in".`
|
|
2174
2195
|
});
|
|
2196
|
+
const foreign = FOREIGN_UID_LITERAL_SQL.exec(e.sql);
|
|
2197
|
+
if (foreign) {
|
|
2198
|
+
const literal = foreign[1];
|
|
2199
|
+
found.push({
|
|
2200
|
+
pattern: "foreign-uid-literal",
|
|
2201
|
+
detail: literal,
|
|
2202
|
+
explanation: `'${literal}' is a ${FOREIGN_CONVENTION_UIDS.get(literal)} convention. Rebase reports an anonymous request as '${ANONYMOUS_USER_ID}', so comparing against '${literal}' passes for every caller. Use \`condition: policy.authenticated()\` to mean "signed in".`
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2175
2205
|
return;
|
|
2206
|
+
}
|
|
2176
2207
|
case "compare": {
|
|
2177
2208
|
const literal = [e.left, e.right].find((o) => o.kind === "literal");
|
|
2178
2209
|
if (!(e.left.kind === "authUid" || e.right.kind === "authUid") || typeof literal?.value !== "string") return;
|
|
2179
|
-
const platform = FOREIGN_CONVENTION_UIDS
|
|
2210
|
+
const platform = FOREIGN_CONVENTION_UIDS.get(literal.value);
|
|
2180
2211
|
if (!platform) return;
|
|
2181
2212
|
found.push({
|
|
2182
2213
|
pattern: "foreign-uid-literal",
|
|
@@ -2451,11 +2482,28 @@ function getIdPropertyName$1(collection) {
|
|
|
2451
2482
|
*
|
|
2452
2483
|
* Collections that opt out via `disableDefaultPolicies` are returned unchanged.
|
|
2453
2484
|
*/
|
|
2485
|
+
/**
|
|
2486
|
+
* The restrictive write gate for an auth collection.
|
|
2487
|
+
*
|
|
2488
|
+
* Restrictive, so it is ANDed with everything else: whatever an author's
|
|
2489
|
+
* permissive rules allow, a write to this table still has to satisfy this too.
|
|
2490
|
+
* It is the only thing standing between "users may edit their own row" and
|
|
2491
|
+
* "users may grant themselves any role".
|
|
2492
|
+
*/
|
|
2493
|
+
function adminWriteGate(tableName) {
|
|
2494
|
+
return {
|
|
2495
|
+
name: `${tableName}_require_admin_write`,
|
|
2496
|
+
mode: "restrictive",
|
|
2497
|
+
operations: [...DEFAULT_GUARDED_OPS],
|
|
2498
|
+
condition: SERVER_OR_ADMIN_EXPR$1,
|
|
2499
|
+
check: SERVER_OR_ADMIN_EXPR$1
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2454
2502
|
function getEffectiveSecurityRules(collection) {
|
|
2455
2503
|
const explicit = [...collection.securityRules ?? []];
|
|
2456
|
-
if (isPostgresCollectionConfig(collection) && collection.disableDefaultPolicies) return explicit;
|
|
2457
2504
|
const tableName = getTableName(collection);
|
|
2458
2505
|
const injected = [];
|
|
2506
|
+
if (isPostgresCollectionConfig(collection) && collection.disableDefaultPolicies) return isAuthCollection$1(collection) ? [...explicit, adminWriteGate(tableName)] : explicit;
|
|
2459
2507
|
injected.push({
|
|
2460
2508
|
name: `${tableName}_default_admin_read`,
|
|
2461
2509
|
operations: ["select"],
|
|
@@ -2473,13 +2521,7 @@ function getEffectiveSecurityRules(collection) {
|
|
|
2473
2521
|
operations: ["select"],
|
|
2474
2522
|
condition: policy.compare(policy.field(getIdPropertyName$1(collection)), "eq", policy.authUid())
|
|
2475
2523
|
});
|
|
2476
|
-
injected.push(
|
|
2477
|
-
name: `${tableName}_require_admin_write`,
|
|
2478
|
-
mode: "restrictive",
|
|
2479
|
-
operations: [...DEFAULT_GUARDED_OPS],
|
|
2480
|
-
condition: SERVER_OR_ADMIN_EXPR$1,
|
|
2481
|
-
check: SERVER_OR_ADMIN_EXPR$1
|
|
2482
|
-
});
|
|
2524
|
+
injected.push(adminWriteGate(tableName));
|
|
2483
2525
|
}
|
|
2484
2526
|
return [...explicit, ...injected];
|
|
2485
2527
|
}
|
|
@@ -3993,6 +4035,17 @@ async function collectAllPages(find, params, label = "collection") {
|
|
|
3993
4035
|
* @module
|
|
3994
4036
|
*/
|
|
3995
4037
|
/**
|
|
4038
|
+
* Escape a value for the wire format: `\` → `\\`, `,` → `\,`, `(` → `\(`,
|
|
4039
|
+
* `)` → `\)`.
|
|
4040
|
+
*/
|
|
4041
|
+
/**
|
|
4042
|
+
* The wire spelling of an empty list.
|
|
4043
|
+
*
|
|
4044
|
+
* A lone backslash: unproducible by {@link escapeWireValue}, which doubles
|
|
4045
|
+
* every backslash it emits, so it cannot collide with any real item.
|
|
4046
|
+
*/
|
|
4047
|
+
var EMPTY_LIST_TOKEN = "\\";
|
|
4048
|
+
/**
|
|
3996
4049
|
* Unescape a wire-format value.
|
|
3997
4050
|
*
|
|
3998
4051
|
* **Conservative**, and deliberately so: only the four sequences
|
|
@@ -4041,7 +4094,199 @@ function splitListItems(inner) {
|
|
|
4041
4094
|
items.push(unescapeWireValue(current));
|
|
4042
4095
|
return items;
|
|
4043
4096
|
}
|
|
4044
|
-
|
|
4097
|
+
/**
|
|
4098
|
+
* Operator tables as `Map`s, because the key comes off the wire.
|
|
4099
|
+
*
|
|
4100
|
+
* Indexed as plain objects, every `Object.prototype` member answered: a query
|
|
4101
|
+
* string of `?f=valueOf.x` found a truthy "operator" — the inherited function —
|
|
4102
|
+
* and `deserializeTuple` returned it *as the operator*, so a function object
|
|
4103
|
+
* travelled on into the compilers in place of a `WhereFilterOp`. The guard one
|
|
4104
|
+
* line below (`if (!canonicalOp)`) reads as though it rejects anything unknown,
|
|
4105
|
+
* and does not: `Object.prototype` is not unknown to a plain object.
|
|
4106
|
+
*
|
|
4107
|
+
* Same shape as the prototype-key defects swept out of `setIn`, `getIn`,
|
|
4108
|
+
* `mergeDeep`, `unflattenObject` and `FOREIGN_CONVENTION_UIDS`.
|
|
4109
|
+
*/
|
|
4110
|
+
var REST_OP_LOOKUP = new Map(Object.entries(REST_TO_CANONICAL));
|
|
4111
|
+
new Map(Object.entries(CANONICAL_TO_REST));
|
|
4112
|
+
/** The operator spellings a rejection lists back to the caller. */
|
|
4113
|
+
var VALID_OPERATOR_LIST = ALL_WHERE_FILTER_OPS.join(", ");
|
|
4114
|
+
/**
|
|
4115
|
+
* A filter condition named an operator this dialect does not have.
|
|
4116
|
+
*
|
|
4117
|
+
* ## Why this throws, rather than returning a typed rejection
|
|
4118
|
+
*
|
|
4119
|
+
* `deserializeFilter` is the *shared* codec: the REST ingress
|
|
4120
|
+
* (`packages/server/src/api/rest/query-parser.ts`), the browser SDK and the
|
|
4121
|
+
* admin panel (`buildRebaseData.ts`) all decode through it. Two constraints
|
|
4122
|
+
* follow.
|
|
4123
|
+
*
|
|
4124
|
+
* - It cannot throw the server's `ApiError`. `@rebasepro/common` does not
|
|
4125
|
+
* depend on `@rebasepro/server` (the dependency runs the other way), and a
|
|
4126
|
+
* browser client has no error handler to render an `ApiError` with. So the
|
|
4127
|
+
* rejection is this plain `Error` subclass, whose `message` reads correctly
|
|
4128
|
+
* wherever it surfaces — a rejected promise in an app, a 400 body over HTTP.
|
|
4129
|
+
* - It cannot be a returned rejection *value*. Every caller assigns the result
|
|
4130
|
+
* straight into a query it is about to run; a sentinel that none of them
|
|
4131
|
+
* check would be ignored, which is exactly the silently-wrong-filter failure
|
|
4132
|
+
* this exists to stop. Throwing is also what this file already does for the
|
|
4133
|
+
* sibling cases — `serializeTuple` on an unknown canonical operator,
|
|
4134
|
+
* `deserializeLogicalCondition` past the nesting bound — and the REST parser
|
|
4135
|
+
* already converts the latter into a 400.
|
|
4136
|
+
*
|
|
4137
|
+
* `statusCode`, `code` and `details` are carried as fields because the server's
|
|
4138
|
+
* Hono error handler duck-types those off any thrown error: a decode path that
|
|
4139
|
+
* forgets to convert still answers 400 with the canonical envelope instead of a
|
|
4140
|
+
* 500 that says "An unexpected error occurred". `query-parser.ts` converts
|
|
4141
|
+
* explicitly all the same — that is the path the contract is stated on, and an
|
|
4142
|
+
* incidental 400 is not a contract.
|
|
4143
|
+
*/
|
|
4144
|
+
var UnknownFilterOperatorError = class extends Error {
|
|
4145
|
+
/** The field the condition was written against. */
|
|
4146
|
+
field;
|
|
4147
|
+
/** The operator string as it arrived, verbatim. */
|
|
4148
|
+
operator;
|
|
4149
|
+
/** Every operator this dialect accepts, in canonical spelling. */
|
|
4150
|
+
validOperators = ALL_WHERE_FILTER_OPS;
|
|
4151
|
+
/** See the class docblock: read by the server's error handler. */
|
|
4152
|
+
statusCode = 400;
|
|
4153
|
+
code = "UNKNOWN_FILTER_OPERATOR";
|
|
4154
|
+
details;
|
|
4155
|
+
constructor(field, operator) {
|
|
4156
|
+
super(`Unknown filter operator '${operator}' on field '${field}'. Valid operators: ${VALID_OPERATOR_LIST}`);
|
|
4157
|
+
this.name = "UnknownFilterOperatorError";
|
|
4158
|
+
this.field = field;
|
|
4159
|
+
this.operator = operator;
|
|
4160
|
+
this.details = {
|
|
4161
|
+
field,
|
|
4162
|
+
operator,
|
|
4163
|
+
validOperators: ALL_WHERE_FILTER_OPS
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
};
|
|
4167
|
+
/**
|
|
4168
|
+
* Two to three characters of ASCII punctuation and nothing else — the shape
|
|
4169
|
+
* every symbolic operator has (`==`, `>=`, `<>`, `~~`, `!!`, `>>`, `===`), and
|
|
4170
|
+
* one a column value effectively never has.
|
|
4171
|
+
*
|
|
4172
|
+
* Two characters minimum on purpose. A *single* punctuation character is a
|
|
4173
|
+
* perfectly ordinary value — `{ grade: ["-", "+"] }` is a two-item list, not a
|
|
4174
|
+
* condition — and the only single-character operator anyone actually mistypes
|
|
4175
|
+
* is `=`, which is named separately below. `<` and `>` need no special case:
|
|
4176
|
+
* they are real operators and resolve.
|
|
4177
|
+
*/
|
|
4178
|
+
var SYMBOLIC_OPERATOR = /^[^\p{L}\p{N}\s]{2,3}$/u;
|
|
4179
|
+
/** Lowercase, strip everything that is not a letter or digit. */
|
|
4180
|
+
function normalizeOperatorName(op) {
|
|
4181
|
+
return op.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4182
|
+
}
|
|
4183
|
+
/**
|
|
4184
|
+
* Every real operator name with its case and separators removed, so a
|
|
4185
|
+
* respelling of one — `arrayContains`, `not_in`, `NOT-LIKE`, `isNull` — is
|
|
4186
|
+
* recognised as an attempt at an operator rather than read as a value.
|
|
4187
|
+
*
|
|
4188
|
+
* These are rejected rather than accepted: admitting a second spelling of an
|
|
4189
|
+
* operator would leave two wire spellings of one thing, and the rejection
|
|
4190
|
+
* message names the one that works.
|
|
4191
|
+
*/
|
|
4192
|
+
var RESPELLED_OPERATORS = new Set([...ALL_WHERE_FILTER_OPS, ...Object.keys(REST_TO_CANONICAL)].map(normalizeOperatorName));
|
|
4193
|
+
/**
|
|
4194
|
+
* Operator names *other* query dialects use, which this one does not have.
|
|
4195
|
+
*
|
|
4196
|
+
* This list is curated, and deliberately so. For a word-shaped string there is
|
|
4197
|
+
* no rule that separates "an operator the caller guessed" from "a value that
|
|
4198
|
+
* happens to be a word": `{ tags: ["a", "b"] }` has to keep meaning a two-item
|
|
4199
|
+
* `in` list, so the codec cannot simply refuse every unrecognised word in
|
|
4200
|
+
* position 0. The line is therefore drawn by name, and only around names whose
|
|
4201
|
+
* use as an operator is far more likely than their use as one of two sibling
|
|
4202
|
+
* values. `contains` is the motivating case — the first thing a developer
|
|
4203
|
+
* reaches for, and until now it compiled to `title IN ('contains', 'Hell')`.
|
|
4204
|
+
*
|
|
4205
|
+
* Genuinely ambiguous single words (`any`, `all`, `exists`, `search`, `not`)
|
|
4206
|
+
* are left off: as operators they are rare, and as enum values they are common.
|
|
4207
|
+
* Everywhere else the tie goes to *rejecting*, because a 400 naming the
|
|
4208
|
+
* supported set costs the caller one round trip, and the alternative — which is
|
|
4209
|
+
* what every name on this list used to produce — is a query that runs, returns
|
|
4210
|
+
* rows, and is wrong.
|
|
4211
|
+
*/
|
|
4212
|
+
var NEAR_MISS_OPERATORS = /* @__PURE__ */ new Set([
|
|
4213
|
+
"contains",
|
|
4214
|
+
"notcontains",
|
|
4215
|
+
"doesnotcontain",
|
|
4216
|
+
"doesnotcontains",
|
|
4217
|
+
"includes",
|
|
4218
|
+
"notincludes",
|
|
4219
|
+
"startswith",
|
|
4220
|
+
"notstartswith",
|
|
4221
|
+
"beginswith",
|
|
4222
|
+
"startingwith",
|
|
4223
|
+
"endswith",
|
|
4224
|
+
"notendswith",
|
|
4225
|
+
"matches",
|
|
4226
|
+
"notmatches",
|
|
4227
|
+
"regex",
|
|
4228
|
+
"regexp",
|
|
4229
|
+
"between",
|
|
4230
|
+
"notbetween",
|
|
4231
|
+
"equals",
|
|
4232
|
+
"notequals",
|
|
4233
|
+
"equalto",
|
|
4234
|
+
"isequalto",
|
|
4235
|
+
"isnotequalto",
|
|
4236
|
+
"greaterthan",
|
|
4237
|
+
"greaterthanorequal",
|
|
4238
|
+
"greaterthanorequalto",
|
|
4239
|
+
"lessthan",
|
|
4240
|
+
"lessthanorequal",
|
|
4241
|
+
"lessthanorequalto",
|
|
4242
|
+
"isempty",
|
|
4243
|
+
"isnotempty",
|
|
4244
|
+
"oneof",
|
|
4245
|
+
"noneof",
|
|
4246
|
+
"anyof",
|
|
4247
|
+
"allof",
|
|
4248
|
+
"null",
|
|
4249
|
+
"isnullorempty"
|
|
4250
|
+
]);
|
|
4251
|
+
/**
|
|
4252
|
+
* Was this string *meant* as an operator?
|
|
4253
|
+
*
|
|
4254
|
+
* Only consulted after {@link toCanonicalOp} has already failed to resolve it,
|
|
4255
|
+
* so a `true` here is always a rejection.
|
|
4256
|
+
*/
|
|
4257
|
+
function isOperatorShaped(op) {
|
|
4258
|
+
if (op === "=") return true;
|
|
4259
|
+
if (SYMBOLIC_OPERATOR.test(op)) return true;
|
|
4260
|
+
const normalized = normalizeOperatorName(op);
|
|
4261
|
+
if (!normalized) return false;
|
|
4262
|
+
return RESPELLED_OPERATORS.has(normalized) || NEAR_MISS_OPERATORS.has(normalized);
|
|
4263
|
+
}
|
|
4264
|
+
/**
|
|
4265
|
+
* Read a `[op, value]` tuple, if that is what this is.
|
|
4266
|
+
*
|
|
4267
|
+
* Three outcomes, and the middle one is the defect this function exists for:
|
|
4268
|
+
*
|
|
4269
|
+
* - the operator resolves (canonical *or* REST spelling) → the canonical tuple;
|
|
4270
|
+
* - the operator does not resolve but was plainly meant as one → throw;
|
|
4271
|
+
* - it does not look like an operator at all → `undefined`, and the caller
|
|
4272
|
+
* falls back to reading the array as a list of values.
|
|
4273
|
+
*
|
|
4274
|
+
* The old test was `toCanonicalOp(raw[0]) === raw[0]`, i.e. canonical spelling
|
|
4275
|
+
* only, with *everything else* — including every REST short-code — dropping
|
|
4276
|
+
* through to `["in", raw]`. So the operator string itself became a value in a
|
|
4277
|
+
* membership test: `["!!", "Hello"]` compiled to `title IN ('!!','Hello')`,
|
|
4278
|
+
* which matches, and the caller got back rows their filter was written to
|
|
4279
|
+
* exclude. `["eq", "active"]` had the same shape of failure.
|
|
4280
|
+
*/
|
|
4281
|
+
function readTuple(field, raw) {
|
|
4282
|
+
if (!Array.isArray(raw) || raw.length !== 2) return void 0;
|
|
4283
|
+
const [op, value] = raw;
|
|
4284
|
+
if (typeof op !== "string") return void 0;
|
|
4285
|
+
const canonical = toCanonicalOp(op);
|
|
4286
|
+
if (canonical) return [canonical, value];
|
|
4287
|
+
if (op.includes(".")) return void 0;
|
|
4288
|
+
if (isOperatorShaped(op)) throw new UnknownFilterOperatorError(field, op);
|
|
4289
|
+
}
|
|
4045
4290
|
/**
|
|
4046
4291
|
* Parse a single PostgREST dot-string into a `[WhereFilterOp, unknown]` tuple.
|
|
4047
4292
|
*
|
|
@@ -4058,10 +4303,13 @@ function deserializeSingle(raw) {
|
|
|
4058
4303
|
if (dotIndex === -1) return ["==", raw];
|
|
4059
4304
|
const prefix = raw.substring(0, dotIndex);
|
|
4060
4305
|
const rest = raw.substring(dotIndex + 1);
|
|
4061
|
-
const canonicalOp = REST_OP_LOOKUP
|
|
4306
|
+
const canonicalOp = REST_OP_LOOKUP.get(prefix);
|
|
4062
4307
|
if (!canonicalOp) return ["==", raw];
|
|
4063
4308
|
if (NULL_OPS.has(canonicalOp)) return [canonicalOp, null];
|
|
4064
|
-
if (rest.startsWith("(") && rest.endsWith(")"))
|
|
4309
|
+
if (rest.startsWith("(") && rest.endsWith(")")) {
|
|
4310
|
+
const inner = rest.slice(1, -1);
|
|
4311
|
+
return [canonicalOp, inner === EMPTY_LIST_TOKEN ? [] : splitListItems(inner)];
|
|
4312
|
+
}
|
|
4065
4313
|
return [canonicalOp, rest];
|
|
4066
4314
|
}
|
|
4067
4315
|
/**
|
|
@@ -4076,20 +4324,27 @@ function deserializeSingle(raw) {
|
|
|
4076
4324
|
*
|
|
4077
4325
|
* deserializeFilter({ age: ["gte.18", "lt.65"] })
|
|
4078
4326
|
* // → { age: [[">=", "18"], ["<", "65"]] }
|
|
4327
|
+
*
|
|
4328
|
+
* @throws {UnknownFilterOperatorError} when a condition names an operator this
|
|
4329
|
+
* dialect does not have. See that class for why a rejection here is a throw.
|
|
4079
4330
|
*/
|
|
4080
4331
|
function deserializeFilter(query) {
|
|
4081
4332
|
const result = {};
|
|
4082
4333
|
for (const [field, raw] of Object.entries(query)) {
|
|
4083
4334
|
if (raw === void 0) continue;
|
|
4084
|
-
|
|
4085
|
-
|
|
4335
|
+
const tuple = readTuple(field, raw);
|
|
4336
|
+
if (tuple) {
|
|
4337
|
+
result[field] = tuple;
|
|
4086
4338
|
continue;
|
|
4087
4339
|
}
|
|
4088
4340
|
if (Array.isArray(raw)) {
|
|
4089
4341
|
if (raw.length === 0) continue;
|
|
4090
|
-
if (Array.isArray(raw[0])
|
|
4091
|
-
|
|
4092
|
-
|
|
4342
|
+
if (Array.isArray(raw[0])) {
|
|
4343
|
+
const tuples = raw.map((item) => readTuple(field, item));
|
|
4344
|
+
if (tuples.every((t) => t !== void 0)) {
|
|
4345
|
+
result[field] = tuples;
|
|
4346
|
+
continue;
|
|
4347
|
+
}
|
|
4093
4348
|
}
|
|
4094
4349
|
if (raw.length === 1) result[field] = typeof raw[0] === "string" ? deserializeSingle(raw[0]) : ["==", raw[0]];
|
|
4095
4350
|
else if (typeof raw[0] === "string" && raw[0].includes(".")) result[field] = raw.map((r) => typeof r === "string" ? deserializeSingle(r) : ["==", r]);
|
|
@@ -5068,4 +5323,4 @@ function isAuthCollection(collection) {
|
|
|
5068
5323
|
//#endregion
|
|
5069
5324
|
export { isManyToMany as $, getTableName as A, normalizeToEntityRelation as B, getEffectiveSecurityRules as C, findRelation as D, findAnonymousGrants as E, isAddressableId as F, getPolicyNamesForRule as G, generateForeignKeyName as H, parseIdValues as I, camelCase as J, isPrototypePollutingKey as K, sortCollectionsBySlug as L, resolveCollectionRelations as M, buildCompositeId as N, getColumnName as O, getDeclaredPrimaryKeys as P, hasForeignKeyOnTarget as Q, createRelationRef as R, resolveJunctionSpecs as S, securityRuleToConditions as T, legacyForeignKeyName as U, updateDateAutoValues as V, toPostgresIdentifier as W, DEFAULT_ONE_OF_TYPE as X, toSnakeCase as Y, DEFAULT_ONE_OF_VALUE as Z, CollectionRegistry as _, SEARCH_STAMP_PREFIX as a, getJunctionCollectionConfig as b, assertSearchIsPostgresOnly as c, searchColumnStamps as d, Vector as et, searchExtensionStatements as f, buildSdkData as g, visibleColumnProjection as h, isAuthCollection as i, getTableVarName as j, getEnumVarName as k, buildSearchColumnSpec as l, searchIndexStatements as m, authUsersColumnDefinition as n, SEARCH_TEXT_FN as o, searchHelperFunctions as p, mergeDeep as q, authUsersColumnSql as r, SEARCH_UNACCENT_FN as s, AUTH_USERS_COLUMNS as t, hiddenColumnsOption as u, relationalCollections as v, policyToPostgres as w, getJunctionSecurityRules as x, resolveStringColumnLength as y, createRelationRefWithData as z };
|
|
5070
5325
|
|
|
5071
|
-
//# sourceMappingURL=auth-users-columns-
|
|
5326
|
+
//# sourceMappingURL=auth-users-columns-BxT6FRyu.js.map
|