@rebasepro/server-postgres 0.13.1-canary.g18cfeb7 → 0.13.1-canary.g249daa1
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/{auth-users-columns-Do9mw5Y5.js → auth-users-columns-Dt9g712t.js} +405 -7
- package/dist/auth-users-columns-Dt9g712t.js.map +1 -0
- package/dist/backup-service-Bww-Lg0s.js.map +1 -1
- package/dist/cli-helpers.d.ts +56 -0
- package/dist/{ensure-collection-policies-DMUdRQdM.js → ensure-collection-policies-CwYUliAa.js} +2 -2
- package/dist/{ensure-collection-policies-DMUdRQdM.js.map → ensure-collection-policies-CwYUliAa.js.map} +1 -1
- package/dist/{ensure-collection-tables-DSIxvLLD.js → ensure-collection-tables-DRxaUG96.js} +34 -7
- package/dist/ensure-collection-tables-DRxaUG96.js.map +1 -0
- package/dist/index.es.js +234 -26
- package/dist/index.es.js.map +1 -1
- package/dist/schema/ensure-collection-tables.d.ts +1 -1
- package/dist/schema/generate-postgres-ddl-logic.d.ts +48 -0
- package/dist/schema/search-column.d.ts +199 -0
- package/dist/services/FetchService.d.ts +15 -0
- package/dist/services/dataService.d.ts +1 -0
- package/dist/services/realtimeService.d.ts +2 -0
- package/dist/src-C_wvdMnl.js.map +1 -1
- package/dist/utils/drizzle-conditions.d.ts +71 -2
- package/dist/websocket-D0TBU3ia.js.map +1 -1
- package/package.json +6 -6
- package/src/cli-helpers.ts +107 -1
- package/src/cli.ts +57 -0
- package/src/data-transformer.ts +18 -0
- package/src/schema/ensure-collection-tables.ts +77 -1
- package/src/schema/generate-drizzle-schema-logic.ts +25 -1
- package/src/schema/generate-postgres-ddl-logic.ts +176 -9
- package/src/schema/generate-postgres-ddl.ts +25 -2
- package/src/schema/search-column.ts +558 -0
- package/src/services/FetchService.ts +138 -15
- package/src/services/RelationService.ts +2 -1
- package/src/services/dataService.ts +1 -0
- package/src/services/realtimeService.ts +10 -4
- package/src/utils/drizzle-conditions.ts +223 -2
- package/dist/auth-users-columns-Do9mw5Y5.js.map +0 -1
- package/dist/ensure-collection-tables-DSIxvLLD.js.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "module";
|
|
|
2
2
|
import process from "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
4
|
import { a as guardPoolAgainstDirtyRelease, i as createReadReplicaConnection, n as createDirectDatabaseConnection, o as pinSearchPath, r as createPostgresDatabaseConnection } from "./connection-BuZ97wsr.js";
|
|
5
|
-
import { A as
|
|
5
|
+
import { A as resolveCollectionRelations, B as legacyForeignKeyName, C as securityRuleToConditions, D as getEnumVarName, E as getColumnName, F as createRelationRef, G as camelCase, H as getPolicyNamesForRule, I as createRelationRefWithData, K as toSnakeCase, L as normalizeToEntityRelation, M as getDeclaredPrimaryKeys, N as isAddressableId, O as getTableName$1, P as parseIdValues, Q as Vector, R as updateDateAutoValues, S as policyToPostgres, T as findRelation, U as isPrototypePollutingKey, W as mergeDeep, X as hasForeignKeyOnTarget, Y as resolveClientListLimit, Z as isManyToMany, _ as resolveStringColumnLength, b as resolveJunctionSpecs, c as buildSearchColumnSpec, g as relationalCollections, h as CollectionRegistry, j as buildCompositeId, k as getTableVarName, l as hiddenColumnsOption, m as buildSdkData, o as SEARCH_UNACCENT_FN, p as visibleColumnProjection, r as authUsersColumnSql, t as AUTH_USERS_COLUMNS, v as getJunctionCollectionConfig, w as findAnonymousGrants, x as getEffectiveSecurityRules, y as getJunctionSecurityRules, z as generateForeignKeyName } from "./auth-users-columns-Dt9g712t.js";
|
|
6
6
|
import { c as isPostgresCollectionConfig, l as isRelationalCollectionConfig, n as REBASE_SCHEMA, o as usesLegacyRlsFunctions } from "./src-C_wvdMnl.js";
|
|
7
7
|
import { t as ANONYMOUS_USER_ID } from "./policy-CPkCqVTz.js";
|
|
8
8
|
import { t as createPostgresWebSocket } from "./websocket-D0TBU3ia.js";
|
|
@@ -554,6 +554,12 @@ function deriveRowAddress(row, collection, registry) {
|
|
|
554
554
|
//#endregion
|
|
555
555
|
//#region src/utils/drizzle-conditions.ts
|
|
556
556
|
/**
|
|
557
|
+
* Postgres's own default for `pg_trgm.word_similarity_threshold`. Named here
|
|
558
|
+
* because the fuzzy predicate has to know when the index-backed operator agrees
|
|
559
|
+
* with the collection's declared threshold and when it would narrow too far.
|
|
560
|
+
*/
|
|
561
|
+
var PG_TRGM_WORD_SIMILARITY_DEFAULT = .6;
|
|
562
|
+
/**
|
|
557
563
|
* Process-wide default, set once when the driver is constructed.
|
|
558
564
|
*
|
|
559
565
|
* The condition builder is a set of *static* methods reached from a dozen
|
|
@@ -611,7 +617,7 @@ function toMembershipList(value) {
|
|
|
611
617
|
* @example
|
|
612
618
|
* const builder: ConditionBuilderStatic<SQL> = DrizzleConditionBuilder;
|
|
613
619
|
*/
|
|
614
|
-
var DrizzleConditionBuilder = class {
|
|
620
|
+
var DrizzleConditionBuilder = class DrizzleConditionBuilder {
|
|
615
621
|
/**
|
|
616
622
|
* Express "reachable from this parent through this relation" as a plain
|
|
617
623
|
* `WHERE` condition on the target table.
|
|
@@ -1237,10 +1243,28 @@ var DrizzleConditionBuilder = class {
|
|
|
1237
1243
|
return or(...conditions);
|
|
1238
1244
|
}
|
|
1239
1245
|
/**
|
|
1240
|
-
* Build search conditions for text fields
|
|
1246
|
+
* Build search conditions for text fields.
|
|
1247
|
+
*
|
|
1248
|
+
* Two shapes, chosen by whether the collection declared a `search` block:
|
|
1249
|
+
*
|
|
1250
|
+
* - **Declared** — one `@@ websearch_to_tsquery` against the generated
|
|
1251
|
+
* `tsvector` column. Stems, drops stopwords, AND-es the terms, reaches
|
|
1252
|
+
* inside JSONB and arrays, and uses the GIN index.
|
|
1253
|
+
* - **Not declared** — the original `ILIKE '%term%'` OR-ed across top-level
|
|
1254
|
+
* string properties, unchanged.
|
|
1255
|
+
*
|
|
1256
|
+
* The second is the default and stays the default. A collection that has
|
|
1257
|
+
* not opted in compiles to exactly the SQL it compiled to before this
|
|
1258
|
+
* branch existed, which is the only reason it is safe to have added it.
|
|
1259
|
+
*
|
|
1260
|
+
* `collection` is optional so that the callers which genuinely have no
|
|
1261
|
+
* collection in hand — nested paths, derived views — keep working; without
|
|
1262
|
+
* one there is no `search` block to read and the ILIKE path is correct.
|
|
1241
1263
|
*/
|
|
1242
|
-
static buildSearchConditions(searchString, properties, table) {
|
|
1264
|
+
static buildSearchConditions(searchString, properties, table, collection) {
|
|
1243
1265
|
const searchConditions = [];
|
|
1266
|
+
const ftsCondition = collection ? DrizzleConditionBuilder.buildFullTextCondition(searchString, table, collection) : void 0;
|
|
1267
|
+
if (ftsCondition) return [ftsCondition];
|
|
1244
1268
|
for (const [key, prop] of Object.entries(properties)) {
|
|
1245
1269
|
const p = prop;
|
|
1246
1270
|
if (p.type === "string" && !p.enum && p.isId !== "uuid") {
|
|
@@ -1253,6 +1277,117 @@ var DrizzleConditionBuilder = class {
|
|
|
1253
1277
|
return searchConditions;
|
|
1254
1278
|
}
|
|
1255
1279
|
/**
|
|
1280
|
+
* The `@@` predicate for a collection that declared a `search` block, or
|
|
1281
|
+
* undefined for one that did not.
|
|
1282
|
+
*
|
|
1283
|
+
* The query is normalized exactly as the indexed content was — same text
|
|
1284
|
+
* search configuration, same accent folding. Skipping that on the query
|
|
1285
|
+
* side is the subtle way to get a search that matches nothing: the column
|
|
1286
|
+
* would hold `gestion` while the query asked for `gestión`.
|
|
1287
|
+
*
|
|
1288
|
+
* `websearch_to_tsquery` rather than `plainto_tsquery` because it is the
|
|
1289
|
+
* one that behaves the way a search box looks like it should — quoted
|
|
1290
|
+
* phrases, `or`, and a leading `-` to exclude — and because it never throws
|
|
1291
|
+
* on user input, which `to_tsquery` does on so much as a stray parenthesis.
|
|
1292
|
+
*/
|
|
1293
|
+
static buildFullTextCondition(searchString, table, collection) {
|
|
1294
|
+
let spec;
|
|
1295
|
+
try {
|
|
1296
|
+
spec = buildSearchColumnSpec(collection);
|
|
1297
|
+
} catch {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
if (!spec) return void 0;
|
|
1301
|
+
const column = table[spec.column];
|
|
1302
|
+
if (!column) return;
|
|
1303
|
+
const exact = sql`${column} @@ ${DrizzleConditionBuilder.normalizedTsQuery(searchString, spec)}`;
|
|
1304
|
+
if (!spec.fuzzy) return exact;
|
|
1305
|
+
const fuzzyColumn = table[spec.fuzzy.column];
|
|
1306
|
+
if (!fuzzyColumn) return exact;
|
|
1307
|
+
const needle = spec.unaccent ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})` : sql`${searchString}`;
|
|
1308
|
+
const similar = sql`public.word_similarity(${needle}, ${fuzzyColumn}) >= ${spec.fuzzy.threshold}`;
|
|
1309
|
+
return sql`(${exact} OR ${spec.fuzzy.threshold > PG_TRGM_WORD_SIMILARITY_DEFAULT ? sql`(${needle} OPERATOR(public.<%) ${fuzzyColumn} AND ${similar})` : similar})`;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* `websearch_to_tsquery(<config>, <normalized search string>)`.
|
|
1313
|
+
*
|
|
1314
|
+
* Split out because the ranking expression needs the identical query — a
|
|
1315
|
+
* row ranked against a different tsquery than it was matched against is a
|
|
1316
|
+
* ranking of something else.
|
|
1317
|
+
*/
|
|
1318
|
+
static normalizedTsQuery(searchString, spec) {
|
|
1319
|
+
const normalized = spec.unaccent ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})` : sql`${searchString}`;
|
|
1320
|
+
return sql`websearch_to_tsquery(${spec.language}, ${normalized})`;
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* A JSONB array of `{ field, snippet }` naming which declared fields matched
|
|
1324
|
+
* and showing the text around each hit — what backs `_matches`.
|
|
1325
|
+
*
|
|
1326
|
+
* A ranked list answers "which rows", never "why this row". For a talent
|
|
1327
|
+
* pool that difference is the product: a candidate surfacing for
|
|
1328
|
+
* "iso 14001" on a *certification* is a different candidate from one whose
|
|
1329
|
+
* bio happens to mention the standard, and the score cannot tell them apart.
|
|
1330
|
+
*
|
|
1331
|
+
* Built as a correlated subquery over a `VALUES` list of the declared
|
|
1332
|
+
* fields, rather than one `CASE` per field, so the shape does not change
|
|
1333
|
+
* with the number of fields and the empty result is a plain `[]`.
|
|
1334
|
+
*
|
|
1335
|
+
* `ts_headline` runs over the same normalized text that was indexed. Over
|
|
1336
|
+
* the *original* text it would find nothing to mark whenever `unaccent` is
|
|
1337
|
+
* on — the query's lexemes are folded and the document's are not — and
|
|
1338
|
+
* would return the text silently unhighlighted. Folded-but-marked beats
|
|
1339
|
+
* pretty-but-inert.
|
|
1340
|
+
*
|
|
1341
|
+
* Undefined when the collection has not opted in, when the column is not on
|
|
1342
|
+
* the table yet, or when the caller did not ask: this costs a `ts_headline`
|
|
1343
|
+
* per field per row and `ts_headline` re-parses the document.
|
|
1344
|
+
*/
|
|
1345
|
+
static buildSearchMatchesExpression(searchString, table, collection) {
|
|
1346
|
+
let spec;
|
|
1347
|
+
try {
|
|
1348
|
+
spec = buildSearchColumnSpec(collection);
|
|
1349
|
+
} catch {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
if (!spec || spec.fields.length === 0) return void 0;
|
|
1353
|
+
if (!table[spec.column]) return void 0;
|
|
1354
|
+
const query = DrizzleConditionBuilder.normalizedTsQuery(searchString, spec);
|
|
1355
|
+
const config = sql`${spec.language}`;
|
|
1356
|
+
const rows = spec.fields.map((f, i) => sql`(${i}, ${f.path}, ${sql.raw(f.textSql)})`);
|
|
1357
|
+
return sql`(
|
|
1358
|
+
SELECT coalesce(jsonb_agg(s.m ORDER BY f.ord), '[]'::jsonb)
|
|
1359
|
+
FROM (VALUES ${sql.join(rows, sql`, `)}) AS f(ord, path, txt)
|
|
1360
|
+
CROSS JOIN LATERAL (
|
|
1361
|
+
SELECT jsonb_build_object(
|
|
1362
|
+
'field', f.path,
|
|
1363
|
+
'snippet', ts_headline(${config}::regconfig, f.txt, ${query},
|
|
1364
|
+
'StartSel=<mark>,StopSel=</mark>,MaxWords=14,MinWords=1,MaxFragments=1,FragmentDelimiter= … ')
|
|
1365
|
+
) AS m
|
|
1366
|
+
WHERE to_tsvector(${config}::regconfig, f.txt) @@ ${query}
|
|
1367
|
+
) s
|
|
1368
|
+
)`;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* `ts_rank(<column>, <query>)` for the collection, or undefined when it has
|
|
1372
|
+
* not opted in. This is what backs `orderBy: ["_score", "desc"]`.
|
|
1373
|
+
*/
|
|
1374
|
+
static buildSearchRankExpression(searchString, table, collection) {
|
|
1375
|
+
let spec;
|
|
1376
|
+
try {
|
|
1377
|
+
spec = buildSearchColumnSpec(collection);
|
|
1378
|
+
} catch {
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (!spec) return void 0;
|
|
1382
|
+
const column = table[spec.column];
|
|
1383
|
+
if (!column) return void 0;
|
|
1384
|
+
const rank = sql`ts_rank(${column}, ${DrizzleConditionBuilder.normalizedTsQuery(searchString, spec)})`;
|
|
1385
|
+
if (!spec.fuzzy) return rank;
|
|
1386
|
+
const fuzzyColumn = table[spec.fuzzy.column];
|
|
1387
|
+
if (!fuzzyColumn) return rank;
|
|
1388
|
+
return sql`(${rank} + public.word_similarity(${spec.unaccent ? sql`${sql.raw(SEARCH_UNACCENT_FN)}(${searchString})` : sql`${searchString}`}, ${fuzzyColumn}))`;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1256
1391
|
* Build a unique field check condition
|
|
1257
1392
|
*/
|
|
1258
1393
|
static buildUniqueFieldCondition(fieldColumn, value, idColumn, excludeId) {
|
|
@@ -1793,6 +1928,18 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
1793
1928
|
* from the result (used by `normalizeDbValues` where
|
|
1794
1929
|
* Drizzle's relational API already hydrates them).
|
|
1795
1930
|
*/
|
|
1931
|
+
/**
|
|
1932
|
+
* Keys a query computes and attaches to a row, rather than reads from a column.
|
|
1933
|
+
*
|
|
1934
|
+
* An explicit set rather than a `_` prefix rule: a user's column may perfectly
|
|
1935
|
+
* well be called `_internal`, and passing it through here would put a value on
|
|
1936
|
+
* the row that no property describes and nothing downstream knows how to type.
|
|
1937
|
+
*/
|
|
1938
|
+
var QUERY_METADATA_KEYS = /* @__PURE__ */ new Set([
|
|
1939
|
+
"_score",
|
|
1940
|
+
"_distance",
|
|
1941
|
+
"_matches"
|
|
1942
|
+
]);
|
|
1796
1943
|
function normalizeScalarValues(data, properties, collection, resolvedRelations, options) {
|
|
1797
1944
|
const result = {};
|
|
1798
1945
|
const internalFKColumns = /* @__PURE__ */ new Set();
|
|
@@ -1804,6 +1951,10 @@ function normalizeScalarValues(data, properties, collection, resolvedRelations,
|
|
|
1804
1951
|
result[key] = value === null ? null : typeof value === "number" ? value : String(value);
|
|
1805
1952
|
continue;
|
|
1806
1953
|
}
|
|
1954
|
+
if (QUERY_METADATA_KEYS.has(key)) {
|
|
1955
|
+
result[key] = value;
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1807
1958
|
const property = properties[key];
|
|
1808
1959
|
if (!property) continue;
|
|
1809
1960
|
if (options.skipRelations && property.type === "relation") continue;
|
|
@@ -2056,7 +2207,7 @@ var RelationService = class {
|
|
|
2056
2207
|
let query = this.db.select().from(targetTable).$dynamic();
|
|
2057
2208
|
const additionalFilters = [];
|
|
2058
2209
|
if (options.searchString) {
|
|
2059
|
-
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, targetCollection.properties, targetTable);
|
|
2210
|
+
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, targetCollection.properties, targetTable, targetCollection);
|
|
2060
2211
|
if (searchConditions.length === 0) return [];
|
|
2061
2212
|
const searchCombined = DrizzleConditionBuilder.combineConditionsWithOr(searchConditions);
|
|
2062
2213
|
if (searchCombined) additionalFilters.push(searchCombined);
|
|
@@ -3162,7 +3313,7 @@ function sanitizeErrorForClient(error, context) {
|
|
|
3162
3313
|
* Service for handling all row read operations.
|
|
3163
3314
|
* Handles fetching, searching, counting, and filtering rows.
|
|
3164
3315
|
*/
|
|
3165
|
-
var FetchService = class {
|
|
3316
|
+
var FetchService = class FetchService {
|
|
3166
3317
|
db;
|
|
3167
3318
|
registry;
|
|
3168
3319
|
relationService;
|
|
@@ -3235,6 +3386,24 @@ var FetchService = class {
|
|
|
3235
3386
|
* and skips rows rather than erroring. The guesses stay, last, for a
|
|
3236
3387
|
* caller that hands over no collection to resolve against.
|
|
3237
3388
|
*/
|
|
3389
|
+
/**
|
|
3390
|
+
* The ORDER BY target, which may be relevance rather than a column.
|
|
3391
|
+
*
|
|
3392
|
+
* `_score` is only meaningful for a collection that declared a `search`
|
|
3393
|
+
* block *and* for a request that carried a search string — ranking rows
|
|
3394
|
+
* against no query ranks them all at zero. Outside those two conditions it
|
|
3395
|
+
* is an unknown field and gets the same 400 as any other typo, which is the
|
|
3396
|
+
* behaviour that matters: a sort that is silently dropped returns 200 with
|
|
3397
|
+
* rows in arbitrary order, and paging over that repeats and skips rows.
|
|
3398
|
+
*/
|
|
3399
|
+
static SCORE_FIELD = "_score";
|
|
3400
|
+
resolveOrderTarget(table, orderBy, collection, searchString) {
|
|
3401
|
+
if (orderBy === FetchService.SCORE_FIELD && collection && searchString) {
|
|
3402
|
+
const rank = DrizzleConditionBuilder.buildSearchRankExpression(searchString, table, collection);
|
|
3403
|
+
if (rank) return rank;
|
|
3404
|
+
}
|
|
3405
|
+
return this.resolveOrderByField(table, orderBy, collection);
|
|
3406
|
+
}
|
|
3238
3407
|
resolveOrderByField(table, orderBy, collection) {
|
|
3239
3408
|
const columnAt = (key) => (key in table ? table[key] : void 0) || void 0;
|
|
3240
3409
|
const direct = columnAt(orderBy);
|
|
@@ -3362,12 +3531,14 @@ var FetchService = class {
|
|
|
3362
3531
|
*/
|
|
3363
3532
|
buildDrizzleQueryOptions(table, idField, idInfo, options, collectionPath, withConfig, scopeCondition) {
|
|
3364
3533
|
const queryOpts = {};
|
|
3534
|
+
const hidden = hiddenColumnsOption(getTableColumns(table), this.registry.getCollectionByPath(collectionPath) ?? void 0);
|
|
3535
|
+
if (hidden) queryOpts.columns = hidden;
|
|
3365
3536
|
if (withConfig) queryOpts.with = withConfig;
|
|
3366
3537
|
const allConditions = [];
|
|
3367
3538
|
if (scopeCondition) allConditions.push(scopeCondition);
|
|
3368
3539
|
if (options.searchString) {
|
|
3369
3540
|
const collection = getCollectionByPath(collectionPath, this.registry);
|
|
3370
|
-
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, collection.properties, table);
|
|
3541
|
+
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, collection.properties, table, collection);
|
|
3371
3542
|
if (searchConditions.length === 0) {
|
|
3372
3543
|
queryOpts.where = and(eq(idField, -99999999));
|
|
3373
3544
|
return queryOpts;
|
|
@@ -3390,7 +3561,7 @@ var FetchService = class {
|
|
|
3390
3561
|
const orderExpressions = [];
|
|
3391
3562
|
if (options.orderBy) {
|
|
3392
3563
|
const collection = getCollectionByPath(collectionPath, this.registry);
|
|
3393
|
-
const orderByField = this.
|
|
3564
|
+
const orderByField = this.resolveOrderTarget(table, options.orderBy, collection, options.searchString);
|
|
3394
3565
|
if (orderByField) orderExpressions.push(options.order === "asc" ? asc(orderByField) : desc(orderByField));
|
|
3395
3566
|
}
|
|
3396
3567
|
orderExpressions.push(desc(idField));
|
|
@@ -3407,6 +3578,7 @@ var FetchService = class {
|
|
|
3407
3578
|
if (!options.startAfter) return [];
|
|
3408
3579
|
const cursor = options.startAfter;
|
|
3409
3580
|
if (options.orderBy) {
|
|
3581
|
+
if (options.orderBy === FetchService.SCORE_FIELD) throw ApiError.badRequest("Cursor pagination (`startAfter`) cannot be combined with `orderBy: \"_score\"`. Relevance is computed per query rather than stored, so it cannot key a cursor. Use `limit`/`offset` for relevance-ordered pages, or order by a column.", "SCORE_CURSOR_UNSUPPORTED", { field: FetchService.SCORE_FIELD });
|
|
3410
3582
|
const collection = collectionPath ? getCollectionByPath(collectionPath, this.registry) : void 0;
|
|
3411
3583
|
const orderByField = this.resolveOrderByField(table, options.orderBy, collection);
|
|
3412
3584
|
if (orderByField) {
|
|
@@ -3477,9 +3649,11 @@ var FetchService = class {
|
|
|
3477
3649
|
const qb = this.getQueryBuilder(tableName);
|
|
3478
3650
|
if (qb) try {
|
|
3479
3651
|
const withConfig = this.buildWithConfig(collection);
|
|
3652
|
+
const hidden = hiddenColumnsOption(getTableColumns(table), collection);
|
|
3480
3653
|
const row = await qb.findFirst({
|
|
3481
3654
|
where: eq(idField, parsedId),
|
|
3482
|
-
with: withConfig
|
|
3655
|
+
with: withConfig,
|
|
3656
|
+
...hidden ? { columns: hidden } : {}
|
|
3483
3657
|
});
|
|
3484
3658
|
if (!row) return void 0;
|
|
3485
3659
|
const flatRow = toFlatRow(row, collection, this.registry);
|
|
@@ -3493,7 +3667,8 @@ var FetchService = class {
|
|
|
3493
3667
|
if (reachedDatabase(e)) throw e;
|
|
3494
3668
|
logger.warn(`[FetchService] db.query.findFirst failed for ${collectionPath}, falling back to db.select`, { error: e });
|
|
3495
3669
|
}
|
|
3496
|
-
const
|
|
3670
|
+
const visibleOne = visibleColumnProjection(getTableColumns(table), collection);
|
|
3671
|
+
const result = await this.db.select(visibleOne).from(table).where(eq(idField, parsedId)).limit(1);
|
|
3497
3672
|
if (result.length === 0) return void 0;
|
|
3498
3673
|
const raw = result[0];
|
|
3499
3674
|
const values = await parseDataFromServer(raw, collection, this.db, this.registry);
|
|
@@ -3549,14 +3724,21 @@ var FetchService = class {
|
|
|
3549
3724
|
}
|
|
3550
3725
|
let vectorMeta;
|
|
3551
3726
|
if (options.vectorSearch) vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch);
|
|
3727
|
+
const visible = visibleColumnProjection(getTableColumns(table), collection);
|
|
3728
|
+
const rankSelect = options.searchString ? DrizzleConditionBuilder.buildSearchRankExpression(options.searchString, table, collection) : void 0;
|
|
3729
|
+
const matchesSelect = options.searchString && options.searchExplain ? DrizzleConditionBuilder.buildSearchMatchesExpression(options.searchString, table, collection) : void 0;
|
|
3552
3730
|
let query = vectorMeta ? this.db.select({
|
|
3553
|
-
table_row: table,
|
|
3731
|
+
table_row: visible ?? table,
|
|
3554
3732
|
_distance: vectorMeta.distanceSelect
|
|
3555
|
-
}).from(table).$dynamic() : this.db.select(
|
|
3733
|
+
}).from(table).$dynamic() : rankSelect ? this.db.select({
|
|
3734
|
+
table_row: visible ?? table,
|
|
3735
|
+
_score: rankSelect,
|
|
3736
|
+
...matchesSelect ? { _matches: matchesSelect } : {}
|
|
3737
|
+
}).from(table).$dynamic() : visible ? this.db.select(visible).from(table).$dynamic() : this.db.select().from(table).$dynamic();
|
|
3556
3738
|
const allConditions = [];
|
|
3557
3739
|
if (scopeCondition) allConditions.push(scopeCondition);
|
|
3558
3740
|
if (options.searchString) {
|
|
3559
|
-
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, collection.properties, table);
|
|
3741
|
+
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, collection.properties, table, collection);
|
|
3560
3742
|
if (searchConditions.length === 0) return [];
|
|
3561
3743
|
allConditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions));
|
|
3562
3744
|
}
|
|
@@ -3576,7 +3758,7 @@ var FetchService = class {
|
|
|
3576
3758
|
const orderExpressions = [];
|
|
3577
3759
|
if (vectorMeta) orderExpressions.push(asc(vectorMeta.orderBy));
|
|
3578
3760
|
else if (options.orderBy) {
|
|
3579
|
-
const orderByField = this.
|
|
3761
|
+
const orderByField = this.resolveOrderTarget(table, options.orderBy, collection, options.searchString);
|
|
3580
3762
|
if (orderByField) orderExpressions.push(options.order === "asc" ? asc(orderByField) : desc(orderByField));
|
|
3581
3763
|
}
|
|
3582
3764
|
orderExpressions.push(desc(idField));
|
|
@@ -3596,6 +3778,10 @@ var FetchService = class {
|
|
|
3596
3778
|
const results = vectorMeta ? rawResults.map((r) => ({
|
|
3597
3779
|
...r.table_row,
|
|
3598
3780
|
_distance: typeof r._distance === "number" ? r._distance : parseFloat(String(r._distance))
|
|
3781
|
+
})) : rankSelect ? rawResults.map((r) => ({
|
|
3782
|
+
...r.table_row,
|
|
3783
|
+
_score: typeof r._score === "number" ? r._score : parseFloat(String(r._score)),
|
|
3784
|
+
...matchesSelect ? { _matches: r._matches ?? [] } : {}
|
|
3599
3785
|
})) : rawResults;
|
|
3600
3786
|
return this.processRowResults(results, collection, collectionPath, idInfo, options.databaseId, false, idInfoArray);
|
|
3601
3787
|
}
|
|
@@ -3687,7 +3873,7 @@ var FetchService = class {
|
|
|
3687
3873
|
const allConditions = [];
|
|
3688
3874
|
if (hop) allConditions.push(this.buildRelationScope(hop));
|
|
3689
3875
|
if (options.searchString) {
|
|
3690
|
-
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, collection.properties, table);
|
|
3876
|
+
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, collection.properties, table, collection);
|
|
3691
3877
|
if (searchConditions.length === 0) return 0;
|
|
3692
3878
|
allConditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions));
|
|
3693
3879
|
}
|
|
@@ -3832,7 +4018,8 @@ var FetchService = class {
|
|
|
3832
4018
|
if (reachedDatabase(e)) throw e;
|
|
3833
4019
|
logger.warn(`[fetchOneForRest] db.query.findFirst failed for ${collectionPath}, falling back`, { error: e });
|
|
3834
4020
|
}
|
|
3835
|
-
const
|
|
4021
|
+
const visibleOne = visibleColumnProjection(getTableColumns(table), collection);
|
|
4022
|
+
const result = await this.db.select(visibleOne).from(table).where(eq(idField, parsedId)).limit(1);
|
|
3836
4023
|
if (result.length === 0) return null;
|
|
3837
4024
|
const flatEntity = { ...result[0] };
|
|
3838
4025
|
if (!include || include.length === 0) return flatEntity;
|
|
@@ -3870,14 +4057,21 @@ var FetchService = class {
|
|
|
3870
4057
|
const idField = table[requirePrimaryKeys(collection, this.registry)[0].fieldName];
|
|
3871
4058
|
let vectorMeta;
|
|
3872
4059
|
if (options.vectorSearch) vectorMeta = DrizzleConditionBuilder.buildVectorSearchConditions(table, options.vectorSearch);
|
|
4060
|
+
const visible = visibleColumnProjection(getTableColumns(table), collection);
|
|
4061
|
+
const rankSelect = options.searchString ? DrizzleConditionBuilder.buildSearchRankExpression(options.searchString, table, collection) : void 0;
|
|
4062
|
+
const matchesSelect = options.searchString && options.searchExplain ? DrizzleConditionBuilder.buildSearchMatchesExpression(options.searchString, table, collection) : void 0;
|
|
3873
4063
|
let query = vectorMeta ? this.db.select({
|
|
3874
|
-
table_row: table,
|
|
4064
|
+
table_row: visible ?? table,
|
|
3875
4065
|
_distance: vectorMeta.distanceSelect
|
|
3876
|
-
}).from(table).$dynamic() : this.db.select(
|
|
4066
|
+
}).from(table).$dynamic() : rankSelect ? this.db.select({
|
|
4067
|
+
table_row: visible ?? table,
|
|
4068
|
+
_score: rankSelect,
|
|
4069
|
+
...matchesSelect ? { _matches: matchesSelect } : {}
|
|
4070
|
+
}).from(table).$dynamic() : visible ? this.db.select(visible).from(table).$dynamic() : this.db.select().from(table).$dynamic();
|
|
3877
4071
|
const allConditions = [];
|
|
3878
4072
|
if (options.relatedTo) allConditions.push(this.buildRelationScope(options.relatedTo));
|
|
3879
4073
|
if (options.searchString) {
|
|
3880
|
-
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, collection.properties, table);
|
|
4074
|
+
const searchConditions = DrizzleConditionBuilder.buildSearchConditions(options.searchString, collection.properties, table, collection);
|
|
3881
4075
|
if (searchConditions.length === 0) return [];
|
|
3882
4076
|
allConditions.push(DrizzleConditionBuilder.combineConditionsWithOr(searchConditions));
|
|
3883
4077
|
}
|
|
@@ -3893,7 +4087,7 @@ var FetchService = class {
|
|
|
3893
4087
|
const orderExpressions = [];
|
|
3894
4088
|
if (vectorMeta) orderExpressions.push(asc(vectorMeta.orderBy));
|
|
3895
4089
|
else if (options.orderBy) {
|
|
3896
|
-
const orderByField = this.
|
|
4090
|
+
const orderByField = this.resolveOrderTarget(table, options.orderBy, collection, options.searchString);
|
|
3897
4091
|
if (orderByField) orderExpressions.push(options.order === "asc" ? asc(orderByField) : desc(orderByField));
|
|
3898
4092
|
}
|
|
3899
4093
|
orderExpressions.push(desc(idField));
|
|
@@ -3906,6 +4100,11 @@ var FetchService = class {
|
|
|
3906
4100
|
...r.table_row,
|
|
3907
4101
|
_distance: typeof r._distance === "number" ? r._distance : parseFloat(String(r._distance))
|
|
3908
4102
|
}));
|
|
4103
|
+
if (rankSelect) return rawResults.map((r) => ({
|
|
4104
|
+
...r.table_row,
|
|
4105
|
+
_score: typeof r._score === "number" ? r._score : parseFloat(String(r._score)),
|
|
4106
|
+
...matchesSelect ? { _matches: r._matches ?? [] } : {}
|
|
4107
|
+
}));
|
|
3909
4108
|
return rawResults;
|
|
3910
4109
|
}
|
|
3911
4110
|
/**
|
|
@@ -6416,6 +6615,7 @@ var generateSchema = async (allCollections, stripPolicies = false) => {
|
|
|
6416
6615
|
const hasUuid = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "string" && (p.autoValue === "uuid" || p.isId === "uuid")));
|
|
6417
6616
|
const hasVector = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "vector"));
|
|
6418
6617
|
const hasBinary = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "binary"));
|
|
6618
|
+
const hasSearch = collections.some((c) => buildSearchColumnSpec(c) !== void 0);
|
|
6419
6619
|
const pgCoreImports = [
|
|
6420
6620
|
"primaryKey",
|
|
6421
6621
|
"pgTable",
|
|
@@ -6440,7 +6640,7 @@ var generateSchema = async (allCollections, stripPolicies = false) => {
|
|
|
6440
6640
|
];
|
|
6441
6641
|
if (hasUuid) pgCoreImports.push("uuid");
|
|
6442
6642
|
if (hasVector) pgCoreImports.push("vector");
|
|
6443
|
-
if (hasBinary) pgCoreImports.push("customType");
|
|
6643
|
+
if (hasBinary || hasSearch) pgCoreImports.push("customType");
|
|
6444
6644
|
const uniqueSchemas = Array.from(new Set(collections.map((c) => isPostgresCollectionConfig(c) ? c.schema : void 0).filter(Boolean)));
|
|
6445
6645
|
if (uniqueSchemas.length > 0) pgCoreImports.push("pgSchema");
|
|
6446
6646
|
schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';\n`;
|
|
@@ -6522,6 +6722,11 @@ var generateSchema = async (allCollections, stripPolicies = false) => {
|
|
|
6522
6722
|
const columnString = getDrizzleColumn(propName, prop, collection, collections);
|
|
6523
6723
|
if (columnString) columns.add(columnString);
|
|
6524
6724
|
});
|
|
6725
|
+
const searchSpec = buildSearchColumnSpec(collection);
|
|
6726
|
+
if (searchSpec) {
|
|
6727
|
+
columns.add(` ${searchSpec.column}: customType({ dataType() { return 'tsvector'; } })("${searchSpec.column}").generatedAlwaysAs(sql\`${searchSpec.expression}\`)`);
|
|
6728
|
+
if (searchSpec.fuzzy) columns.add(` ${searchSpec.fuzzy.column}: text("${searchSpec.fuzzy.column}").generatedAlwaysAs(sql\`${searchSpec.fuzzy.expression}\`)`);
|
|
6729
|
+
}
|
|
6525
6730
|
if (!Array.from(columns).some((col) => col.includes(".primaryKey()"))) columns.add(" id: text(\"id\").primaryKey()");
|
|
6526
6731
|
schemaContent += `${Array.from(columns).join(",\n")}`;
|
|
6527
6732
|
const securityRules = getEffectiveSecurityRules(collection);
|
|
@@ -7941,7 +8146,8 @@ var RealtimeService = class RealtimeService extends EventEmitter {
|
|
|
7941
8146
|
limit: config.limit,
|
|
7942
8147
|
startAfter: config.startAfter,
|
|
7943
8148
|
databaseId: config.databaseId,
|
|
7944
|
-
searchString: config.searchString
|
|
8149
|
+
searchString: config.searchString,
|
|
8150
|
+
searchExplain: config.searchExplain
|
|
7945
8151
|
}
|
|
7946
8152
|
});
|
|
7947
8153
|
if (callback) this.subscriptionCallbacks.set(subscriptionId, callback);
|
|
@@ -8069,7 +8275,8 @@ var RealtimeService = class RealtimeService extends EventEmitter {
|
|
|
8069
8275
|
offset: request.offset,
|
|
8070
8276
|
startAfter: request.startAfter,
|
|
8071
8277
|
databaseId: request.collection?.databaseId,
|
|
8072
|
-
searchString: request.searchString
|
|
8278
|
+
searchString: request.searchString,
|
|
8279
|
+
searchExplain: request.searchExplain
|
|
8073
8280
|
},
|
|
8074
8281
|
authContext
|
|
8075
8282
|
});
|
|
@@ -8253,7 +8460,8 @@ var RealtimeService = class RealtimeService extends EventEmitter {
|
|
|
8253
8460
|
orderBy: collectionRequest.orderBy,
|
|
8254
8461
|
order: collectionRequest.order,
|
|
8255
8462
|
limit: collectionRequest.limit,
|
|
8256
|
-
databaseId: collectionRequest.databaseId
|
|
8463
|
+
databaseId: collectionRequest.databaseId,
|
|
8464
|
+
searchExplain: collectionRequest.searchExplain
|
|
8257
8465
|
});
|
|
8258
8466
|
else fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
|
|
8259
8467
|
filter: collectionRequest.filter,
|
|
@@ -12611,7 +12819,7 @@ function createPostgresBootstrapper(pgConfig) {
|
|
|
12611
12819
|
*/
|
|
12612
12820
|
async ensureCollectionSchema(collections, driverResult, log) {
|
|
12613
12821
|
const internals = driverResult.internals;
|
|
12614
|
-
const { ensureCollectionTables } = await import("./ensure-collection-tables-
|
|
12822
|
+
const { ensureCollectionTables } = await import("./ensure-collection-tables-DRxaUG96.js");
|
|
12615
12823
|
const plan = await ensureCollectionTables({ async query(text) {
|
|
12616
12824
|
const result = await internals.db.execute(sql.raw(text));
|
|
12617
12825
|
return { rows: result.rows ?? (Array.isArray(result) ? result : []) };
|
|
@@ -12636,7 +12844,7 @@ function createPostgresBootstrapper(pgConfig) {
|
|
|
12636
12844
|
*/
|
|
12637
12845
|
async ensureCollectionPolicies(collections, driverResult, log) {
|
|
12638
12846
|
const internals = driverResult.internals;
|
|
12639
|
-
const { ensureCollectionPolicies } = await import("./ensure-collection-policies-
|
|
12847
|
+
const { ensureCollectionPolicies } = await import("./ensure-collection-policies-CwYUliAa.js");
|
|
12640
12848
|
const outcome = await ensureCollectionPolicies({ async query(text) {
|
|
12641
12849
|
const result = await internals.db.execute(sql.raw(text));
|
|
12642
12850
|
return { rows: result.rows ?? (Array.isArray(result) ? result : []) };
|