uql-orm 0.27.0 → 0.28.1
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/README.md +6 -6
- package/dist/dialect/abstractSqlDialect.d.ts +69 -44
- package/dist/dialect/abstractSqlDialect.js +278 -315
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +0 -3
- package/dist/dialect/pgLikeSqlDialect.d.ts +1 -1
- package/dist/dialect/pgLikeSqlDialect.js +1 -3
- package/dist/dialect/queryContext.d.ts +7 -5
- package/dist/dialect/queryContext.js +11 -6
- package/dist/dialect/queryJoins.d.ts +53 -0
- package/dist/dialect/queryJoins.js +91 -0
- package/dist/dialect/vectorSqlDialect.d.ts +1 -1
- package/dist/entity/metadata/definition.js +2 -2
- package/dist/mongo/mongoDialect.d.ts +38 -8
- package/dist/mongo/mongoDialect.js +103 -64
- package/dist/mongo/mongodbQuerier.js +19 -42
- package/dist/sqlite/sqliteDialect.d.ts +1 -1
- package/dist/sqlite/sqliteDialect.js +3 -3
- package/dist/type/dialect.d.ts +11 -3
- package/dist/type/query.d.ts +3 -3
- package/dist/util/dialect.util.d.ts +1 -2
- package/dist/util/dialect.util.js +0 -3
- package/dist/util/relationQuery.util.d.ts +17 -2
- package/dist/util/relationQuery.util.js +35 -2
- package/dist/util/sql.util.d.ts +1 -2
- package/dist/util/sql.util.js +1 -12
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
2
|
import { AbstractQuerier, enrichError } from '../querier/index.js';
|
|
3
|
-
import { clone, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, throwNoPendingTransaction, throwPendingTransaction, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
3
|
+
import { clone, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, populatesRelations, throwNoPendingTransaction, throwPendingTransaction, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
4
|
export class MongodbQuerier extends AbstractQuerier {
|
|
5
5
|
dialect;
|
|
6
6
|
conn;
|
|
@@ -30,11 +30,13 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
30
30
|
await this.fillToManyRelations(entity, documents, q.$populate);
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
33
|
-
const relationSummary = getRelationRequestSummary(meta, q.$populate);
|
|
34
33
|
// A relation condition needs `$lookup`, so it forces the aggregation path just like populating
|
|
35
|
-
// one does
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
// one does - and so does ordering by a relation, which reads what a lookup produced. A plain
|
|
35
|
+
// `find` cursor can express none of the three.
|
|
36
|
+
if (populatesRelations(meta, q.$populate) ||
|
|
37
|
+
this.dialect.constrainsRelations(entity, q.$where) ||
|
|
38
|
+
this.dialect.sortsRelations(entity, q.$sort)) {
|
|
39
|
+
const pipeline = this.dialect.aggregationPipeline(entity, q, opts);
|
|
38
40
|
documents = await this.runPipeline(entity, meta, pipeline);
|
|
39
41
|
await this.fillToManyRelations(entity, documents, q.$populate);
|
|
40
42
|
}
|
|
@@ -83,7 +85,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
83
85
|
if (hasKeys(select)) {
|
|
84
86
|
cursor.project(select);
|
|
85
87
|
}
|
|
86
|
-
const sort = this.dialect.sort(entity, q.$sort);
|
|
88
|
+
const sort = this.dialect.sort(entity, q.$sort, q.$populate);
|
|
87
89
|
if (hasKeys(sort)) {
|
|
88
90
|
cursor.sort(sort);
|
|
89
91
|
}
|
|
@@ -105,43 +107,18 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
105
107
|
* `$vectorSearch` is always the first stage; `$where` is merged into its `filter`.
|
|
106
108
|
*/
|
|
107
109
|
buildVectorPipeline(entity, q, vectorSort, opts) {
|
|
108
|
-
const pipeline = [];
|
|
109
|
-
pipeline.push(this.dialect.buildVectorSearchStage(entity, vectorSort.vectorKey, vectorSort.vectorSearch, q.$where, q.$limit ?? 10, opts));
|
|
110
|
-
const meta = getMeta(entity);
|
|
111
|
-
const relationSummary = getRelationRequestSummary(meta, q.$populate);
|
|
112
110
|
const scoreAlias = vectorSort.vectorSearch.$project;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
pipeline.push({ $project: scoreAlias ? { ...projection, [scoreAlias]: 1 } : projection });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
else if (scoreAlias) {
|
|
128
|
-
const select = q.$select || q.$exclude ? this.buildScalarProjection(entity, q) : {};
|
|
129
|
-
pipeline.push({
|
|
130
|
-
$project: {
|
|
131
|
-
...select,
|
|
132
|
-
[scoreAlias]: { $meta: 'vectorSearchScore' },
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
else if ((q.$select && hasKeys(q.$select)) || (q.$exclude && hasKeys(q.$exclude))) {
|
|
137
|
-
pipeline.push({ $project: this.buildScalarProjection(entity, q) });
|
|
138
|
-
}
|
|
139
|
-
// Secondary sort for non-vector fields
|
|
140
|
-
const regularSort = this.dialect.sort(entity, vectorSort.regularSort);
|
|
141
|
-
if (hasKeys(regularSort)) {
|
|
142
|
-
pipeline.push({ $sort: regularSort });
|
|
143
|
-
}
|
|
144
|
-
return pipeline;
|
|
111
|
+
return [
|
|
112
|
+
this.dialect.buildVectorSearchStage(entity, vectorSort.vectorKey, vectorSort.vectorSearch, q.$where, q.$limit ?? 10, opts),
|
|
113
|
+
// The score becomes a real field before anything reads it, so the lookups and the projection
|
|
114
|
+
// that follow treat it like any other - and a query with no projection keeps its own columns.
|
|
115
|
+
...(scoreAlias ? [{ $addFields: { [scoreAlias]: { $meta: 'vectorSearchScore' } } }] : []),
|
|
116
|
+
// `$vectorSearch` has already applied `$limit`, so the pager is its own.
|
|
117
|
+
...this.dialect.readStages(entity, q, opts, {
|
|
118
|
+
sort: this.dialect.sort(entity, vectorSort.regularSort, q.$populate),
|
|
119
|
+
project: scoreAlias ? { [scoreAlias]: 1 } : undefined,
|
|
120
|
+
}),
|
|
121
|
+
];
|
|
145
122
|
}
|
|
146
123
|
async internalAggregate(entity, q, opts) {
|
|
147
124
|
return this.timed('internalAggregate', undefined, async () => {
|
|
@@ -27,7 +27,7 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
27
27
|
* when declared, else `NULL` (which is also how SQLite auto-generates INTEGER PRIMARY KEYs).
|
|
28
28
|
*/
|
|
29
29
|
protected appendDefaultInsertValue(ctx: QueryContext, field: FieldOptions | undefined): void;
|
|
30
|
-
protected
|
|
30
|
+
protected readonly caseInsensitiveMatch = "native";
|
|
31
31
|
protected get neOp(): string;
|
|
32
32
|
normalizeValue(value: unknown): unknown;
|
|
33
33
|
/**
|
|
@@ -53,9 +53,9 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
53
53
|
ctx.append('NULL');
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
// SQLite's `LIKE` already ignores case on both sides, for ASCII - and only ASCII, with or without
|
|
57
|
+
// `NOCASE`, so folding the pattern here would break the accented text the engine leaves alone.
|
|
58
|
+
caseInsensitiveMatch = 'native';
|
|
59
59
|
get neOp() {
|
|
60
60
|
return 'IS NOT';
|
|
61
61
|
}
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -7,9 +7,11 @@ import type { Type } from './utility.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export type QueryComparisonOptions = QueryOptions & {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Whether this fragment is rendered as an operand of an enclosing `AND`/`OR`/`NOT`. An operand
|
|
11
|
+
* parenthesizes itself when it emits more than one term, so no fragment ever depends on the
|
|
12
|
+
* engine's operator precedence. Only the `WHERE` clause as a whole is not an operand.
|
|
11
13
|
*/
|
|
12
|
-
|
|
14
|
+
operand?: boolean;
|
|
13
15
|
};
|
|
14
16
|
/**
|
|
15
17
|
* query filter options.
|
|
@@ -31,6 +33,12 @@ export interface QueryContext {
|
|
|
31
33
|
* alias would let the inner occurrence shadow the outer one it needs to correlate against.
|
|
32
34
|
*/
|
|
33
35
|
nextAlias(prefix: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* A context for a fragment of this same statement: it renders its own SQL in isolation while
|
|
38
|
+
* sharing the bound values and the generated aliases, so both stay unique and correctly numbered
|
|
39
|
+
* across the statement. See {@link AbstractSqlDialect.buildFragment}.
|
|
40
|
+
*/
|
|
41
|
+
createFragment(): QueryContext;
|
|
34
42
|
readonly sql: string;
|
|
35
43
|
readonly values: unknown[];
|
|
36
44
|
}
|
|
@@ -148,7 +156,7 @@ export interface QueryDialect {
|
|
|
148
156
|
* @param forbidQualified don't escape dots
|
|
149
157
|
* @param addDot use a dot as suffix
|
|
150
158
|
*/
|
|
151
|
-
escapeId(val: string, forbidQualified?: boolean, addDot?: boolean): string;
|
|
159
|
+
escapeId(val: string | undefined, forbidQualified?: boolean, addDot?: boolean): string;
|
|
152
160
|
/**
|
|
153
161
|
* escape a value.
|
|
154
162
|
* @param val the value to escape
|
package/dist/type/query.d.ts
CHANGED
|
@@ -121,12 +121,12 @@ export type QuerySortValue = QuerySortDirection | QueryVectorSearch;
|
|
|
121
121
|
* like `QueryWhereMap`), relation sort via nested objects, and vector similarity search on
|
|
122
122
|
* `number[]` fields.
|
|
123
123
|
*/
|
|
124
|
-
export type QuerySortMap<E> = {
|
|
125
|
-
[K in FieldKey<E>]?: NonNullable<E[K]> extends readonly number[] ? QuerySortValue : QuerySortDirection;
|
|
124
|
+
export type QuerySortMap<E, Vector extends boolean = true> = {
|
|
125
|
+
[K in FieldKey<E>]?: Vector extends true ? NonNullable<E[K]> extends readonly number[] ? QuerySortValue : QuerySortDirection : QuerySortDirection;
|
|
126
126
|
} & {
|
|
127
127
|
[P in JsonFieldPaths<E>]?: QuerySortDirection;
|
|
128
128
|
} & {
|
|
129
|
-
[K in RelationKey<E>]?: QuerySortMap<NonNullable<
|
|
129
|
+
[K in RelationKey<E> as NonNullable<E[K]> extends readonly unknown[] ? never : K]?: QuerySortMap<NonNullable<E[K]>, false>;
|
|
130
130
|
};
|
|
131
131
|
/**
|
|
132
132
|
* pager options.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CascadeType, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type
|
|
1
|
+
import { type CascadeType, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QueryVectorSearch, type QueryWhere, type QueryWhereMap, type RelationKey } from '../type/index.js';
|
|
2
2
|
export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
|
|
3
3
|
export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: E, callbackKey: CallbackKey): FieldKey<E>[];
|
|
4
4
|
/**
|
|
@@ -47,7 +47,6 @@ export declare function isCascadable(action: CascadeType, configuration?: boolea
|
|
|
47
47
|
*/
|
|
48
48
|
export declare function asSelectMap<E>(select: QuerySelectValue<E> | undefined): QuerySelect<E> | undefined;
|
|
49
49
|
export declare function normalizeScalarFieldSelection<E>(meta: EntityMeta<E>, select?: QuerySelect<E>, exclude?: QueryExclude<E>): FieldKey<E>[];
|
|
50
|
-
export declare function buildSortMap<E>(sort: QuerySortMap<E> | undefined): QuerySortMap<E>;
|
|
51
50
|
/** Type guard: checks whether a sort value is a vector similarity search. */
|
|
52
51
|
export declare function isVectorSearch(value: unknown): value is QueryVectorSearch;
|
|
53
52
|
/** Type guard: checks whether an update payload value is a JSON operator object. */
|
|
@@ -141,9 +141,6 @@ export function normalizeScalarFieldSelection(meta, select, exclude) {
|
|
|
141
141
|
const excluded = excludedFields;
|
|
142
142
|
return allFields.filter((it) => !excluded.has(it));
|
|
143
143
|
}
|
|
144
|
-
export function buildSortMap(sort) {
|
|
145
|
-
return (sort ?? {});
|
|
146
|
-
}
|
|
147
144
|
/** Type guard: checks whether a sort value is a vector similarity search. */
|
|
148
145
|
export function isVectorSearch(value) {
|
|
149
146
|
return value !== null && typeof value === 'object' && '$vector' in value;
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
import type { EntityMeta, Except, Query, QueryPopulate, RelationKey } from '../type/index.js';
|
|
1
|
+
import type { EntityMeta, Except, Query, QueryPopulate, RelationKey, RelationMeta } from '../type/index.js';
|
|
2
2
|
export type RelationRequestSummary<E> = {
|
|
3
3
|
readonly requestedKeys: RelationKey<E>[];
|
|
4
4
|
readonly joinableKeys: RelationKey<E>[];
|
|
5
5
|
readonly toManyKeys: RelationKey<E>[];
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Whether a relation holds many rows per parent, so it cannot be joined into the parent's row. Takes
|
|
9
|
+
* the one field it reads, so it answers for a relation being declared as well as for a resolved one.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isToManyRelation(relation: Pick<RelationMeta, 'cardinality'>): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* What a joined relation cannot carry, and why. A to-many is loaded by a query of its own, which is
|
|
14
|
+
* what gives these four a meaning there; a to-one is one row of the parent's, so every backend used
|
|
15
|
+
* to drop them without a word. `satisfies` ties each key to {@link RelationQuery}, so renaming one
|
|
16
|
+
* breaks this list at compile time rather than quietly stopping the check.
|
|
17
|
+
*/
|
|
18
|
+
declare const JOINED_RELATION_REJECTIONS: readonly [readonly ["$sort", "a join brings one row per parent, so there is nothing to order"], readonly ["$limit", "a join brings one row per parent, so there is nothing to page"], readonly ["$skip", "a join brings one row per parent, so there is nothing to page"], readonly ["$distinct", "it applies to the whole statement, not to one of its joins"]];
|
|
19
|
+
/** A key only a to-many's own query can carry, and that a joined relation therefore rejects. */
|
|
20
|
+
export type JoinedRelationRejectedKey = (typeof JOINED_RELATION_REJECTIONS)[number][0];
|
|
7
21
|
export declare function getRelationRequestSummary<E>(meta: EntityMeta<E>, populate?: QueryPopulate<E>): RelationRequestSummary<E>;
|
|
8
22
|
/** True when `$populate` includes at least one relation key. */
|
|
9
|
-
export declare function
|
|
23
|
+
export declare function populatesRelations<E>(meta: EntityMeta<E>, populate?: QueryPopulate<E>): boolean;
|
|
10
24
|
export type RelationQuery<E extends object = object> = Except<Query<E>, '$lock'> & {
|
|
11
25
|
$required?: boolean;
|
|
12
26
|
};
|
|
@@ -20,3 +34,4 @@ export declare function parseRelationQueryValue<E extends object = object>(value
|
|
|
20
34
|
/** Parses the relation payload for `relKey` */
|
|
21
35
|
export declare function parseRelationAtKey<E>(relKey: RelationKey<E>, populate?: QueryPopulate<E>): ParsedRelationQuery;
|
|
22
36
|
export declare function forEachRequestedRelation<E extends object>(meta: EntityMeta<E>, populate: QueryPopulate<E> | undefined, fn: (relKey: RelationKey<E>, rawValue: unknown) => void): void;
|
|
37
|
+
export {};
|
|
@@ -1,4 +1,34 @@
|
|
|
1
1
|
import { getKeys } from './object.util.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a relation holds many rows per parent, so it cannot be joined into the parent's row. Takes
|
|
4
|
+
* the one field it reads, so it answers for a relation being declared as well as for a resolved one.
|
|
5
|
+
*/
|
|
6
|
+
export function isToManyRelation(relation) {
|
|
7
|
+
return relation.cardinality === '1m' || relation.cardinality === 'mm';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* What a joined relation cannot carry, and why. A to-many is loaded by a query of its own, which is
|
|
11
|
+
* what gives these four a meaning there; a to-one is one row of the parent's, so every backend used
|
|
12
|
+
* to drop them without a word. `satisfies` ties each key to {@link RelationQuery}, so renaming one
|
|
13
|
+
* breaks this list at compile time rather than quietly stopping the check.
|
|
14
|
+
*/
|
|
15
|
+
const JOINED_RELATION_REJECTIONS = [
|
|
16
|
+
['$sort', 'a join brings one row per parent, so there is nothing to order'],
|
|
17
|
+
['$limit', 'a join brings one row per parent, so there is nothing to page'],
|
|
18
|
+
['$skip', 'a join brings one row per parent, so there is nothing to page'],
|
|
19
|
+
['$distinct', 'it applies to the whole statement, not to one of its joins'],
|
|
20
|
+
];
|
|
21
|
+
const JOINED_RELATION_REJECTED_KEYS = new Map(JOINED_RELATION_REJECTIONS);
|
|
22
|
+
function assertJoinableRelationQuery(relKey, value) {
|
|
23
|
+
if (!value || typeof value !== 'object') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const [key, reason] of JOINED_RELATION_REJECTED_KEYS) {
|
|
27
|
+
if (key in value) {
|
|
28
|
+
throw new TypeError(`'${key}' is not supported inside $populate of the to-one relation '${relKey}': ${reason}.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
2
32
|
export function getRelationRequestSummary(meta, populate) {
|
|
3
33
|
const requestedKeys = [];
|
|
4
34
|
const joinableKeys = [];
|
|
@@ -12,17 +42,20 @@ export function getRelationRequestSummary(meta, populate) {
|
|
|
12
42
|
if (!relOpts)
|
|
13
43
|
continue;
|
|
14
44
|
requestedKeys.push(key);
|
|
15
|
-
if (relOpts
|
|
45
|
+
if (isToManyRelation(relOpts)) {
|
|
16
46
|
toManyKeys.push(key);
|
|
17
47
|
}
|
|
18
48
|
else {
|
|
49
|
+
// Validated where the cardinality is decided, so every backend and every nesting level rejects
|
|
50
|
+
// the same shapes - the SQL dialects, MongoDB's lookups, and whatever reads this summary next.
|
|
51
|
+
assertJoinableRelationQuery(key, populate[key]);
|
|
19
52
|
joinableKeys.push(key);
|
|
20
53
|
}
|
|
21
54
|
}
|
|
22
55
|
return { requestedKeys, joinableKeys, toManyKeys };
|
|
23
56
|
}
|
|
24
57
|
/** True when `$populate` includes at least one relation key. */
|
|
25
|
-
export function
|
|
58
|
+
export function populatesRelations(meta, populate) {
|
|
26
59
|
if (!populate)
|
|
27
60
|
return false;
|
|
28
61
|
return getKeys(populate).some((key) => populate[key] && key in meta.relations);
|
package/dist/util/sql.util.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { InsertIdSource, QueryUpdateResult, RawRow } from '../type/index.js';
|
|
2
2
|
import type { PrimaryKey } from '../type/utility.js';
|
|
3
|
-
export declare function flatObject<E extends object>(obj: E, pre?: string): E;
|
|
4
3
|
export declare function unflatObjects<T extends object>(objects: RawRow[]): T[];
|
|
5
4
|
/**
|
|
6
5
|
* Unflattens a single raw row using pre-computed attribute paths.
|
|
@@ -17,7 +16,7 @@ export declare function obtainAttrsPaths<T extends object>(row: T): {
|
|
|
17
16
|
* @param forbidQualified whether to forbid qualified identifiers (containing dots)
|
|
18
17
|
* @param addDot whether to add a dot suffix
|
|
19
18
|
*/
|
|
20
|
-
export declare function escapeSqlId(val: string, escapeIdChar?: '`' | '"', forbidQualified?: boolean, addDot?: boolean): string;
|
|
19
|
+
export declare function escapeSqlId(val: string | undefined, escapeIdChar?: '`' | '"', forbidQualified?: boolean, addDot?: boolean): string;
|
|
21
20
|
/**
|
|
22
21
|
* Payload for building a QueryUpdateResult.
|
|
23
22
|
*/
|
package/dist/util/sql.util.js
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hasKeys } from './object.util.js';
|
|
2
2
|
/** Pre-computed regex for each SQL identifier escape character to avoid per-call allocation. */
|
|
3
3
|
const escapeIdRegexCache = { '`': /`/g, '"': /"/g };
|
|
4
|
-
export function flatObject(obj, pre) {
|
|
5
|
-
return getKeys(obj).reduce((acc, key) => flatObjectEntry(acc, key, obj[key], typeof obj[key] === 'object' ? '' : pre), {});
|
|
6
|
-
}
|
|
7
|
-
function flatObjectEntry(map, key, val, pre) {
|
|
8
|
-
const prefix = pre ? `${pre}.${key}` : key;
|
|
9
|
-
if (typeof val === 'object' && val !== null) {
|
|
10
|
-
return getKeys(val).reduce((acc, prop) => flatObjectEntry(acc, prop, val[prop], prefix), map);
|
|
11
|
-
}
|
|
12
|
-
map[prefix] = val;
|
|
13
|
-
return map;
|
|
14
|
-
}
|
|
15
4
|
export function unflatObjects(objects) {
|
|
16
5
|
if (!Array.isArray(objects) || !objects.length) {
|
|
17
6
|
return objects;
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uql-orm",
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
|
-
"description": "
|
|
4
|
+
"description": "JSON-native TypeScript ORM: queries are plain JSON, typed to the leaf. One API for SQL databases and MongoDB.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.28.1",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|