turbine-orm 0.30.0 → 0.32.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/cjs/client.js +14 -3
- package/dist/cjs/mssql.js +24 -3
- package/dist/cjs/powdb.js +197 -50
- package/dist/cjs/powql.js +45 -2
- package/dist/cjs/query/batched-loader.js +34 -0
- package/dist/cjs/query/builder.js +758 -139
- package/dist/cjs/query/filters.js +77 -2
- package/dist/client.d.ts +13 -0
- package/dist/client.js +14 -3
- package/dist/index.d.ts +1 -1
- package/dist/mssql.js +24 -3
- package/dist/powdb.d.ts +33 -0
- package/dist/powdb.js +197 -50
- package/dist/powql.js +45 -2
- package/dist/query/batched-loader.d.ts +12 -0
- package/dist/query/batched-loader.js +33 -0
- package/dist/query/builder.d.ts +171 -5
- package/dist/query/builder.js +760 -141
- package/dist/query/filters.d.ts +40 -1
- package/dist/query/filters.js +73 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/types.d.ts +213 -22
- package/package.json +1 -1
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* and execution rather than filter-shape bookkeeping.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.VECTOR_DISTANCE_COMPARATORS = exports.VECTOR_METRIC_OPERATORS = exports.TEXT_SEARCH_KEYS = exports.ARRAY_UNIQUE_KEYS = exports.ARRAY_OPERATOR_KEYS = exports.JSONB_UNIQUE_KEYS = exports.JSON_RANGE_OPERATORS = exports.JSONB_OPERATOR_KEYS = exports.UPDATE_OPERATOR_KEYS = void 0;
|
|
10
|
+
exports.VECTOR_DISTANCE_COMPARATORS = exports.VECTOR_METRIC_OPERATORS = exports.TEXT_SEARCH_KEYS = exports.ARRAY_UNIQUE_KEYS = exports.ARRAY_OPERATOR_KEYS = exports.JSONB_UNIQUE_KEYS = exports.JSON_RANGE_OPERATORS = exports.JSONB_OPERATOR_KEYS = exports.UPDATE_OPERATOR_KEYS = exports.COLUMN_REF_OPERATORS = void 0;
|
|
11
11
|
exports.isWhereOperator = isWhereOperator;
|
|
12
12
|
exports.isUnmatchedPlainObject = isUnmatchedPlainObject;
|
|
13
|
+
exports.isColumnRef = isColumnRef;
|
|
13
14
|
exports.fingerprintOperatorShape = fingerprintOperatorShape;
|
|
14
15
|
exports.assertBindableEqualsOperand = assertBindableEqualsOperand;
|
|
15
16
|
exports.sortedKeys = sortedKeys;
|
|
@@ -24,6 +25,8 @@ exports.validateTextSearchConfig = validateTextSearchConfig;
|
|
|
24
25
|
exports.isVectorFilter = isVectorFilter;
|
|
25
26
|
exports.isVectorOrderBy = isVectorOrderBy;
|
|
26
27
|
exports.isOrderBySpec = isOrderBySpec;
|
|
28
|
+
exports.isJsonPathOrderBy = isJsonPathOrderBy;
|
|
29
|
+
exports.isRelationPickOrderBy = isRelationPickOrderBy;
|
|
27
30
|
exports.normalizeOrderBy = normalizeOrderBy;
|
|
28
31
|
const errors_js_1 = require("../errors.js");
|
|
29
32
|
const utils_js_1 = require("./utils.js");
|
|
@@ -56,17 +59,48 @@ function isUnmatchedPlainObject(value) {
|
|
|
56
59
|
const proto = Object.getPrototypeOf(value);
|
|
57
60
|
return proto === Object.prototype || proto === null;
|
|
58
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Operator keys that accept a {@link ColumnRef} (`{ col: 'otherField' }`)
|
|
64
|
+
* value for column-to-column comparison. `in`/`notIn` and the LIKE operators
|
|
65
|
+
* take values only.
|
|
66
|
+
*/
|
|
67
|
+
exports.COLUMN_REF_OPERATORS = new Set(['equals', 'not', 'gt', 'gte', 'lt', 'lte']);
|
|
68
|
+
/**
|
|
69
|
+
* Check if an operator value is a column reference: a plain object whose ONLY
|
|
70
|
+
* key is `col` with a string value. Anything else (extra keys, non-string
|
|
71
|
+
* `col`) is treated as a plain value so JSON payloads that merely contain a
|
|
72
|
+
* `col` property keep their equality meaning.
|
|
73
|
+
*/
|
|
74
|
+
function isColumnRef(value) {
|
|
75
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value) || value instanceof Date)
|
|
76
|
+
return false;
|
|
77
|
+
const keys = Object.keys(value);
|
|
78
|
+
return keys.length === 1 && keys[0] === 'col' && typeof value.col === 'string';
|
|
79
|
+
}
|
|
59
80
|
/**
|
|
60
81
|
* Fingerprint the SHAPE of a where-operator object. Null-valued `equals` /
|
|
61
82
|
* `not` compile to parameterless `IS NULL` / `IS NOT NULL` (different SQL, no
|
|
62
83
|
* param pushed), so null-ness is part of the shape — without it a cache entry
|
|
63
84
|
* warmed by `{ not: 5 }` would serve `{ not: null }` with a desynced param list.
|
|
85
|
+
*
|
|
86
|
+
* Column references ({@link ColumnRef}) compile the referenced column into the
|
|
87
|
+
* SQL TEXT (no param bound), so the referenced field name is part of the shape
|
|
88
|
+
*: `{ equals: { col: 'a' } }` and `{ equals: { col: 'b' } }` must never share
|
|
89
|
+
* a cache entry. The name is JSON-encoded so exotic field names cannot collide
|
|
90
|
+
* with other fingerprint tokens.
|
|
64
91
|
*/
|
|
65
92
|
function fingerprintOperatorShape(value) {
|
|
66
93
|
const obj = value;
|
|
67
94
|
const opKeys = Object.keys(obj)
|
|
68
95
|
.filter((k) => k !== 'mode')
|
|
69
|
-
.map((k) =>
|
|
96
|
+
.map((k) => {
|
|
97
|
+
const v = obj[k];
|
|
98
|
+
if ((k === 'equals' || k === 'not') && v === null)
|
|
99
|
+
return `${k}:null`;
|
|
100
|
+
if (exports.COLUMN_REF_OPERATORS.has(k) && isColumnRef(v))
|
|
101
|
+
return `${k}:col(${JSON.stringify(v.col)})`;
|
|
102
|
+
return k;
|
|
103
|
+
})
|
|
70
104
|
.sort();
|
|
71
105
|
const modeStr = value.mode === 'insensitive' ? ':i' : '';
|
|
72
106
|
return `op(${opKeys.join(',')}${modeStr})`;
|
|
@@ -269,6 +303,47 @@ function isVectorOrderBy(value) {
|
|
|
269
303
|
function isOrderBySpec(value) {
|
|
270
304
|
return typeof value === 'object' && value !== null && !Array.isArray(value) && 'sort' in value;
|
|
271
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Check if an orderBy value is a JSON-path ordering: `{ path: [...] }` with an
|
|
308
|
+
* ARRAY path. The array requirement disambiguates from relation orderBy values
|
|
309
|
+
* (whose entries are directions/specs keyed by target column: a target column
|
|
310
|
+
* literally named `path` maps to a string direction, never an array), and the
|
|
311
|
+
* `distance`/`sort` exclusions keep vector and spec shapes out.
|
|
312
|
+
*/
|
|
313
|
+
function isJsonPathOrderBy(value) {
|
|
314
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
315
|
+
return false;
|
|
316
|
+
if ('distance' in value || 'sort' in value)
|
|
317
|
+
return false;
|
|
318
|
+
return Array.isArray(value.path);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Check if an orderBy value is a pick-row relation ordering:
|
|
322
|
+
* `{ pick: { orderBy, ... }, by, direction?, nulls? }`. The full shape is
|
|
323
|
+
* required — `pick` must be an object carrying `orderBy`, `by` must be
|
|
324
|
+
* present, and no keys outside `{ pick, by, direction, nulls }` — so a to-one
|
|
325
|
+
* relation whose target has real columns literally named `pick` and `by`
|
|
326
|
+
* (whose values are direction strings or `{ sort, nulls }` specs, never an
|
|
327
|
+
* object with `orderBy`) still falls through to column ordering. `distance`
|
|
328
|
+
* (vector), `sort` (OrderBySpec), and a top-level array `path` (JSON-path
|
|
329
|
+
* ordering) are excluded up front.
|
|
330
|
+
*/
|
|
331
|
+
function isRelationPickOrderBy(value) {
|
|
332
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
333
|
+
return false;
|
|
334
|
+
if ('distance' in value || 'sort' in value || Array.isArray(value.path))
|
|
335
|
+
return false;
|
|
336
|
+
const v = value;
|
|
337
|
+
if (!('pick' in v) || !('by' in v))
|
|
338
|
+
return false;
|
|
339
|
+
if (typeof v.pick !== 'object' || v.pick === null || Array.isArray(v.pick) || !('orderBy' in v.pick))
|
|
340
|
+
return false;
|
|
341
|
+
for (const key of Object.keys(v)) {
|
|
342
|
+
if (key !== 'pick' && key !== 'by' && key !== 'direction' && key !== 'nulls')
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
272
347
|
/**
|
|
273
348
|
* Normalize an orderBy value into `{ direction, nulls }`. Accepts a plain
|
|
274
349
|
* direction string or an {@link OrderBySpec}. Used by every ORDER BY compile
|
package/dist/client.d.ts
CHANGED
|
@@ -68,6 +68,19 @@ export interface PgCompatPoolClient {
|
|
|
68
68
|
* sequential.
|
|
69
69
|
*/
|
|
70
70
|
readonly supportsPipelining?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Optional engine seam: scope a transaction's user callback to its own
|
|
73
|
+
* async subtree. When present, `TurbineClient.transaction` / `$transaction`
|
|
74
|
+
* invoke the callback as `wrapTransactionCallback(() => fn(tx))` instead of
|
|
75
|
+
* `fn(tx)` directly. Single-writer engines (PowDB) implement it with
|
|
76
|
+
* `AsyncLocalStorage.run()` to plant their re-entrancy marker so that it
|
|
77
|
+
* exists ONLY inside the callback's async subtree: a transaction opened
|
|
78
|
+
* from inside the callback is detected as re-entrant (typed E017), while
|
|
79
|
+
* the CALLER's context stays unmarked, so same-tick sibling transactions
|
|
80
|
+
* queue FIFO instead of being falsely flagged. Absent on pg and every other
|
|
81
|
+
* engine, in which case the callback runs unwrapped (zero behavior change).
|
|
82
|
+
*/
|
|
83
|
+
wrapTransactionCallback?<R>(fn: () => Promise<R>): Promise<R>;
|
|
71
84
|
}
|
|
72
85
|
/**
|
|
73
86
|
* Minimal pg-compatible pool. Pass any driver that satisfies this interface
|
package/dist/client.js
CHANGED
|
@@ -775,7 +775,12 @@ export class TurbineClient {
|
|
|
775
775
|
try {
|
|
776
776
|
await client.query(this.dialect.beginStatement());
|
|
777
777
|
began = true;
|
|
778
|
-
|
|
778
|
+
// Engine seam: single-writer engines scope their transaction re-entrancy
|
|
779
|
+
// marker to the callback's async subtree (see
|
|
780
|
+
// PgCompatPoolClient.wrapTransactionCallback). Absent everywhere else.
|
|
781
|
+
const wrap = client.wrapTransactionCallback;
|
|
782
|
+
// `.call` erases the generic, so the callback's Promise<T> is re-asserted.
|
|
783
|
+
const result = wrap ? (await wrap.call(client, () => fn(client))) : await fn(client);
|
|
779
784
|
await client.query(this.dialect.commitStatement());
|
|
780
785
|
return result;
|
|
781
786
|
}
|
|
@@ -868,6 +873,12 @@ export class TurbineClient {
|
|
|
868
873
|
}
|
|
869
874
|
}
|
|
870
875
|
let result;
|
|
876
|
+
// Engine seam: when the checked-out connection exposes
|
|
877
|
+
// wrapTransactionCallback (single-writer engines such as PowDB), run the user
|
|
878
|
+
// callback through it so the engine can scope its re-entrancy marker to
|
|
879
|
+
// the callback's async subtree. All other drivers: plain fn(tx).
|
|
880
|
+
const wrap = client.wrapTransactionCallback;
|
|
881
|
+
const runCallback = () => (wrap ? wrap.call(client, () => fn(tx)) : fn(tx));
|
|
871
882
|
if (timeout) {
|
|
872
883
|
// Race between the function and a timeout. If the timeout fires we
|
|
873
884
|
// need to actually abort the in-flight query — otherwise the backend
|
|
@@ -889,14 +900,14 @@ export class TurbineClient {
|
|
|
889
900
|
}, timeout);
|
|
890
901
|
});
|
|
891
902
|
try {
|
|
892
|
-
result = await Promise.race([
|
|
903
|
+
result = await Promise.race([runCallback(), timeoutPromise]);
|
|
893
904
|
}
|
|
894
905
|
finally {
|
|
895
906
|
clearTimeout(timer);
|
|
896
907
|
}
|
|
897
908
|
}
|
|
898
909
|
else {
|
|
899
|
-
result = await
|
|
910
|
+
result = await runCallback();
|
|
900
911
|
}
|
|
901
912
|
await client.query(this.dialect.commitStatement());
|
|
902
913
|
if (this.logging) {
|
package/dist/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export { type IntrospectOptions, introspect } from './introspect.js';
|
|
|
43
43
|
export { executeNestedCreate, executeNestedUpdate, hasRelationFields, type NestedWriteContext, } from './nested-write.js';
|
|
44
44
|
export type { ObserveConfig, ObserveHandle } from './observe.js';
|
|
45
45
|
export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
|
|
46
|
-
export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByArgs, type HavingClause, type JsonFilter, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type SelectResult, type SkipGlobalFilters, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
|
|
46
|
+
export { type AggregateArgs, type AggregateResult, type ArrayFilter, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type HavingClause, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithResult, } from './query/index.js';
|
|
47
47
|
export { type ActiveSubscription, type NotificationHandler, type Subscription, validateChannel } from './realtime.js';
|
|
48
48
|
export type { CheckMetadata, ColumnMetadata, IndexMetadata, ReferentialAction, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
|
|
49
49
|
export { camelToSnake, isDateType, normalizeKeyColumns, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, } from './schema.js';
|
package/dist/mssql.js
CHANGED
|
@@ -745,11 +745,32 @@ function buildForJsonSubquery(dialect, ctx) {
|
|
|
745
745
|
if (hasOrder) {
|
|
746
746
|
const orderBy = orderEntries
|
|
747
747
|
.map(([k, dir]) => {
|
|
748
|
-
|
|
748
|
+
// FOR JSON nested ordering supports plain directions and { sort }
|
|
749
|
+
// specs only. Object shapes the core builder compiles with params
|
|
750
|
+
// (JSON-path / vector / relation ordering) must throw here: the
|
|
751
|
+
// shared param-collect mirror is gated on the native path, so a
|
|
752
|
+
// silently-ignored object would desync SQL text from params.
|
|
753
|
+
let rawDir = dir;
|
|
754
|
+
if (typeof dir === 'object' && dir !== null) {
|
|
755
|
+
const sortValue = dir.sort;
|
|
756
|
+
if (typeof sortValue !== 'string') {
|
|
757
|
+
throw new ValidationError(`[turbine] Nested orderBy on "${k}" (table "${targetTable}"): only plain directions and ` +
|
|
758
|
+
`{ sort } specs are supported inside a relation orderBy on SQL Server.`);
|
|
759
|
+
}
|
|
760
|
+
if (dir.nulls !== undefined) {
|
|
761
|
+
throw new UnsupportedFeatureError('NULLS FIRST/LAST ordering', 'sqlserver', 'Explicit nulls placement in orderBy is only available on PostgreSQL and SQLite.');
|
|
762
|
+
}
|
|
763
|
+
rawDir = sortValue;
|
|
764
|
+
}
|
|
765
|
+
// columnMap-first resolution (camelToSnake fallback): matches the
|
|
766
|
+
// core builder's nested orderBy path so camelCase-named DB columns
|
|
767
|
+
// resolve on SQL Server too.
|
|
768
|
+
const col = targetMeta.columnMap[k] ?? camelToSnake(k);
|
|
749
769
|
if (!targetMeta.allColumns.includes(col)) {
|
|
750
|
-
throw new ValidationError(`[turbine] Unknown
|
|
770
|
+
throw new ValidationError(`[turbine] Unknown field "${k}" in orderBy on table "${targetTable}". ` +
|
|
771
|
+
`Known fields: ${Object.keys(targetMeta.columnMap).join(', ') || '(none)'}.`);
|
|
751
772
|
}
|
|
752
|
-
const safeDir = String(
|
|
773
|
+
const safeDir = String(rawDir).toLowerCase() === 'desc' ? 'DESC' : 'ASC';
|
|
753
774
|
return `${a}.${q(col)} ${safeDir}`;
|
|
754
775
|
})
|
|
755
776
|
.join(', ');
|
package/dist/powdb.d.ts
CHANGED
|
@@ -120,6 +120,18 @@ interface PowdbClientPool {
|
|
|
120
120
|
withClient<T>(fn: (c: PowdbClient) => Promise<T>): Promise<T>;
|
|
121
121
|
close(): Promise<void>;
|
|
122
122
|
}
|
|
123
|
+
interface PowdbModule {
|
|
124
|
+
Client: {
|
|
125
|
+
connect(opts: PowdbConnOptions): Promise<PowdbClient>;
|
|
126
|
+
};
|
|
127
|
+
Pool: new (opts: PowdbConnOptions & {
|
|
128
|
+
max?: number;
|
|
129
|
+
}) => PowdbClientPool;
|
|
130
|
+
isPowDBError?(err: unknown): err is {
|
|
131
|
+
code: string;
|
|
132
|
+
message: string;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
123
135
|
/** Connection options for {@link turbinePowDB} — host/port, not a connection string. */
|
|
124
136
|
export interface PowdbConnOptions {
|
|
125
137
|
host: string;
|
|
@@ -260,8 +272,22 @@ export declare class PowdbPool implements PgCompatPool {
|
|
|
260
272
|
private readonly txGate;
|
|
261
273
|
/** Hold taken by a `begin` issued via `query()` directly (no checked-out client). */
|
|
262
274
|
private poolHold;
|
|
275
|
+
/**
|
|
276
|
+
* Clients currently checked out via {@link connect}. The driver pool's
|
|
277
|
+
* `close()` only closes IDLE clients (checked-out ones are documented as the
|
|
278
|
+
* caller's responsibility), so {@link end} destroys these explicitly;
|
|
279
|
+
* otherwise a `disconnect()` racing an unreleased connection would leave a
|
|
280
|
+
* live socket holding the process open until the server's idle timeout.
|
|
281
|
+
*/
|
|
282
|
+
private readonly checkedOut;
|
|
263
283
|
constructor(pool: PowdbClientPool, toParam?: (v: unknown, i: number) => PowdbParam, options?: PowdbPoolOptions);
|
|
264
284
|
query(text: QueryArg, values?: unknown[]): Promise<any>;
|
|
285
|
+
/**
|
|
286
|
+
* Typed guard mirroring {@link PowdbEmbeddedPool}: after `end()` the driver
|
|
287
|
+
* pool throws a raw `Error('pool closed')` that {@link wrapPowdbError}
|
|
288
|
+
* cannot classify — surface the same ConnectionError on both transports.
|
|
289
|
+
*/
|
|
290
|
+
private assertOpen;
|
|
265
291
|
connect(): Promise<PgCompatPoolClient>;
|
|
266
292
|
end(): Promise<void>;
|
|
267
293
|
}
|
|
@@ -361,6 +387,13 @@ export interface TurbinePowdbOptions extends Pick<TurbineConfig, 'logging' | 'de
|
|
|
361
387
|
* E017 — queueing that shape would deadlock.
|
|
362
388
|
*/
|
|
363
389
|
transactionQueueTimeoutMs?: number;
|
|
390
|
+
/**
|
|
391
|
+
* Driver-module injection for the networked target forms (URL / host+port):
|
|
392
|
+
* bypasses the dynamic `import('@zvndev/powdb-client')` and uses this object
|
|
393
|
+
* as the driver module instead. Intended for tests (a fake pool that counts
|
|
394
|
+
* connections) and advanced embedding; everyday callers never set it.
|
|
395
|
+
*/
|
|
396
|
+
powdbClientModule?: PowdbModule;
|
|
364
397
|
}
|
|
365
398
|
/**
|
|
366
399
|
* Selects the **embedded** transport — an in-process `@zvndev/powdb-embedded`
|