@easy-cms/drizzle 0.7.0 → 0.8.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/index.d.ts +18 -6
- package/dist/index.js +78 -38
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -21,11 +21,21 @@ interface SqlRunner {
|
|
|
21
21
|
}
|
|
22
22
|
/** How to read a value out of a JSON element for comparing. */
|
|
23
23
|
type JsonValueType = 'text' | 'number' | 'integer' | 'boolean';
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/** The JSON element being tested. Paths are field names, already checked against the config. */
|
|
25
|
+
interface JsonElement {
|
|
26
|
+
/** A value inside the element, e.g. `['heading', 'en']`; `[]` for the element itself. */
|
|
27
|
+
get(path: readonly string[], type: JsonValueType): SQL;
|
|
28
|
+
/** A JSON array inside the element, to test with `someElement`. */
|
|
29
|
+
list(path: readonly string[]): SQL;
|
|
30
|
+
}
|
|
26
31
|
/** What differs between SQL dialects. */
|
|
27
32
|
interface Dialect {
|
|
28
33
|
readonly name: 'sqlite' | 'postgres';
|
|
34
|
+
/**
|
|
35
|
+
* One writer at a time (SQLite): writes from this process wait for each other instead of
|
|
36
|
+
* failing with "database is locked".
|
|
37
|
+
*/
|
|
38
|
+
readonly singleWriter?: boolean;
|
|
29
39
|
table(name: string, columns: Record<string, ColumnBuilder>, indexes: (t: Record<string, AnyColumn>) => IndexBuilder[]): AnyTable;
|
|
30
40
|
index(name: string): {
|
|
31
41
|
on(column: AnyColumn): IndexBuilder;
|
|
@@ -44,10 +54,12 @@ interface Dialect {
|
|
|
44
54
|
/** Case-insensitive substring match; `pattern` is already escaped and wrapped in `%`. */
|
|
45
55
|
like(column: AnyColumn, pattern: string): SQL;
|
|
46
56
|
/**
|
|
47
|
-
* True when some element of a JSON array column matches `condition`
|
|
48
|
-
* returns undefined).
|
|
57
|
+
* True when some element of a JSON array (a column, or `JsonElement.list`) matches `condition`
|
|
58
|
+
* (any element when it returns undefined). Anything but an array has no elements.
|
|
49
59
|
*/
|
|
50
|
-
someElement(
|
|
60
|
+
someElement(array: AnyColumn | SQL, condition: (el: JsonElement) => SQL | undefined): SQL;
|
|
61
|
+
/** The value at `path` in the first element of a JSON array that has one, for sorting. */
|
|
62
|
+
firstElementValue(array: AnyColumn | SQL, path: readonly string[], type: JsonValueType): SQL;
|
|
51
63
|
/** Placeholder for the n-th parameter (1-based) in raw SQL. */
|
|
52
64
|
param(n: number): string;
|
|
53
65
|
/** CREATE TABLE IF NOT EXISTS for the migrations table. */
|
|
@@ -182,4 +194,4 @@ declare class Migrator {
|
|
|
182
194
|
private files;
|
|
183
195
|
}
|
|
184
196
|
|
|
185
|
-
export { type AnyColumn, type AnyTable, type ColumnBuilder, type Connection, DEFAULT_MIGRATION_DIR, DEFAULT_TABLE_PREFIX, type Dialect, type DrizzleAdapterOptions, type DrizzleDb, type IndexBuilder, type
|
|
197
|
+
export { type AnyColumn, type AnyTable, type ColumnBuilder, type Connection, DEFAULT_MIGRATION_DIR, DEFAULT_TABLE_PREFIX, type Dialect, type DrizzleAdapterOptions, type DrizzleDb, type IndexBuilder, type JsonElement, type JsonValueType, Migrator, type SchemaModel, type Snapshot, type SqlRunner, type Statement, buildSchema, connectDatabase, snake, unionSnapshot };
|
package/dist/index.js
CHANGED
|
@@ -719,34 +719,43 @@ var JSON_TYPES = {
|
|
|
719
719
|
relationship: "integer",
|
|
720
720
|
upload: "integer"
|
|
721
721
|
};
|
|
722
|
-
function
|
|
723
|
-
if (rest.length ===
|
|
724
|
-
|
|
725
|
-
|
|
722
|
+
function resolveJsonValue(fields, rest, shown, localization, blocks) {
|
|
723
|
+
if (rest.length === 0) return { lists: [], path: null, type: "text", field: void 0 };
|
|
724
|
+
if (blocks && rest.length === 1 && rest[0] === "blockType")
|
|
725
|
+
return { lists: [], path: ["blockType"], type: "text", field: void 0 };
|
|
726
|
+
let list = fields;
|
|
726
727
|
const path = [];
|
|
727
728
|
for (let i = 0; i < rest.length; i++) {
|
|
728
729
|
const segment = rest[i];
|
|
729
|
-
const field =
|
|
730
|
+
const field = list.find((f) => f.name === segment);
|
|
730
731
|
const at = [shown, ...rest.slice(0, i + 1)].join(".");
|
|
731
732
|
if (!field) throw new QueryError(`Unknown field "${at}"`);
|
|
732
733
|
path.push(segment);
|
|
733
734
|
if (field.type === "group") {
|
|
734
|
-
|
|
735
|
+
list = field.fields;
|
|
735
736
|
continue;
|
|
736
737
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
}
|
|
741
|
-
let last = i;
|
|
738
|
+
if (field.type === "json" || field.type === "richText")
|
|
739
|
+
throw new QueryError(`Cannot query "${at}": ${field.type} fields are not queryable`);
|
|
740
|
+
let next = i + 1;
|
|
742
741
|
if (field.localized && localization) {
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
742
|
+
const named = rest[next] !== void 0 && localization.locales.includes(rest[next]);
|
|
743
|
+
path.push(named ? rest[next] : localization.defaultLocale);
|
|
744
|
+
if (named) next++;
|
|
745
|
+
}
|
|
746
|
+
const inner = rest.slice(next);
|
|
747
|
+
const shownHere = [shown, ...rest.slice(0, next)].join(".");
|
|
748
|
+
if ("hasMany" in field && field.hasMany === true) {
|
|
749
|
+
if (inner.length > 0) throw new QueryError(`Cannot query inside "${shownHere}"`);
|
|
750
|
+
return { lists: [path], path: [], type: JSON_TYPES[field.type] ?? "text", field };
|
|
751
|
+
}
|
|
752
|
+
if (field.type === "array" || field.type === "blocks") {
|
|
753
|
+
const rows = field.type === "array" ? field.fields : field.blocks.flatMap((block) => block.fields);
|
|
754
|
+
const value = resolveJsonValue(rows, inner, shownHere, localization, field.type === "blocks");
|
|
755
|
+
return { ...value, lists: [path, ...value.lists] };
|
|
747
756
|
}
|
|
748
|
-
if (
|
|
749
|
-
return { path, type: JSON_TYPES[field.type] ?? "text", field };
|
|
757
|
+
if (inner.length > 0) throw new QueryError(`Cannot query inside "${at}"`);
|
|
758
|
+
return { lists: [], path, type: JSON_TYPES[field.type] ?? "text", field };
|
|
750
759
|
}
|
|
751
760
|
throw new QueryError(`"${[shown, ...rest].join(".")}" is a group; query one of its fields`);
|
|
752
761
|
}
|
|
@@ -799,7 +808,8 @@ function resolvePath(model, segments, drafts, localization = null) {
|
|
|
799
808
|
const column2 = columnOf();
|
|
800
809
|
if (!column2) throw new QueryError(`Unknown locale "${locale}" for "${shown}"`);
|
|
801
810
|
const rest = segments.slice(last + 1);
|
|
802
|
-
const
|
|
811
|
+
const rows = field.blocks.flatMap((block) => block.fields);
|
|
812
|
+
const value = resolveJsonValue(rows, rest, shown, localization, true);
|
|
803
813
|
return { kind: "blocks", column: col2(model, column2.column), value };
|
|
804
814
|
}
|
|
805
815
|
if (last < segments.length - 1) {
|
|
@@ -913,19 +923,22 @@ var WhereBuilder = class {
|
|
|
913
923
|
});
|
|
914
924
|
return parts.length === 1 ? parts[0] : and(...parts);
|
|
915
925
|
}
|
|
916
|
-
/**
|
|
926
|
+
/**
|
|
927
|
+
* Conditions inside blocks test the JSON rows: some block (and, for nested lists, some row in
|
|
928
|
+
* it) must match.
|
|
929
|
+
*/
|
|
917
930
|
blocks(target, op, value, path) {
|
|
918
|
-
const {
|
|
919
|
-
if (
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
);
|
|
931
|
+
const { lists, path: inner, type, field } = target.value;
|
|
932
|
+
if (inner === null && op !== "exists")
|
|
933
|
+
throw new QueryError(`"${path}" is a list; query one of its fields`);
|
|
934
|
+
const match = (el, level) => {
|
|
935
|
+
const list = lists[level];
|
|
936
|
+
if (list) return this.dialect.someElement(el.list(list), (row) => match(row, level + 1));
|
|
937
|
+
if (inner === null) return void 0;
|
|
938
|
+
return operatorSQL(this.dialect, el.get(inner, type), field, op, value, path);
|
|
939
|
+
};
|
|
940
|
+
const some = this.dialect.someElement(target.column, (el) => match(el, 0));
|
|
941
|
+
return inner === null && value === false ? not(some) : some;
|
|
929
942
|
}
|
|
930
943
|
/** Conditions on array rows or hasMany values become (NOT) EXISTS subqueries. */
|
|
931
944
|
child(parent, child, rest, op, value, path, locale) {
|
|
@@ -955,13 +968,21 @@ var WhereBuilder = class {
|
|
|
955
968
|
}
|
|
956
969
|
return subquery(this.build(table, { [rest.join(".")]: { [op]: value } }));
|
|
957
970
|
}
|
|
971
|
+
/** A column, or the value in the first block that has it. */
|
|
972
|
+
sortValue(target, path) {
|
|
973
|
+
if (target.kind === "column") return target.column;
|
|
974
|
+
if (target.kind === "blocks" && target.value.lists.length === 0 && target.value.path) {
|
|
975
|
+
return this.dialect.firstElementValue(target.column, target.value.path, target.value.type);
|
|
976
|
+
}
|
|
977
|
+
throw new QueryError(`Cannot sort by "${path}"`);
|
|
978
|
+
}
|
|
958
979
|
orderBy(model, sort) {
|
|
959
980
|
const order = sort.map((entry) => {
|
|
960
981
|
const descending = entry.startsWith("-");
|
|
961
982
|
const path = descending ? entry.slice(1) : entry;
|
|
962
983
|
const target = resolvePath(model, path.split("."), this.drafts, this.localization);
|
|
963
|
-
|
|
964
|
-
return descending ? desc(
|
|
984
|
+
const value = this.sortValue(target, path);
|
|
985
|
+
return descending ? desc(value) : asc2(value);
|
|
965
986
|
});
|
|
966
987
|
order.push(desc(col2(model, "id")));
|
|
967
988
|
return order;
|
|
@@ -995,6 +1016,7 @@ async function connectDatabase(dialect, connection, args, options = {}) {
|
|
|
995
1016
|
}
|
|
996
1017
|
var DrizzleDatabase = class {
|
|
997
1018
|
constructor(dialect, connection, schema, migrator) {
|
|
1019
|
+
this.dialect = dialect;
|
|
998
1020
|
this.connection = connection;
|
|
999
1021
|
this.schema = schema;
|
|
1000
1022
|
this.migrator = migrator;
|
|
@@ -1006,6 +1028,7 @@ var DrizzleDatabase = class {
|
|
|
1006
1028
|
);
|
|
1007
1029
|
}
|
|
1008
1030
|
}
|
|
1031
|
+
dialect;
|
|
1009
1032
|
connection;
|
|
1010
1033
|
schema;
|
|
1011
1034
|
migrator;
|
|
@@ -1060,23 +1083,38 @@ var DrizzleDatabase = class {
|
|
|
1060
1083
|
const [row] = await this.db.select({ total: count() }).from(model.root.table).where(this.where(args.collection, args.where));
|
|
1061
1084
|
return Number(row?.total ?? 0);
|
|
1062
1085
|
}
|
|
1086
|
+
writing = Promise.resolve();
|
|
1087
|
+
/** Runs a write; with a single-writer database, after the writes before it are done. */
|
|
1088
|
+
write(run) {
|
|
1089
|
+
if (!this.dialect.singleWriter) return run();
|
|
1090
|
+
const result = this.writing.then(run);
|
|
1091
|
+
this.writing = result.catch(() => {
|
|
1092
|
+
});
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
1063
1095
|
async create(args) {
|
|
1064
1096
|
const model = this.model(args.collection);
|
|
1065
|
-
const id = await this.
|
|
1066
|
-
(
|
|
1097
|
+
const id = await this.write(
|
|
1098
|
+
() => this.db.transaction(
|
|
1099
|
+
(tx) => insertDocument(tx, model.root, args.data, model.config.drafts === true)
|
|
1100
|
+
)
|
|
1067
1101
|
);
|
|
1068
1102
|
return await this.findById({ collection: args.collection, id });
|
|
1069
1103
|
}
|
|
1070
1104
|
async update(args) {
|
|
1071
1105
|
const model = this.model(args.collection);
|
|
1072
|
-
await this.
|
|
1073
|
-
(
|
|
1106
|
+
await this.write(
|
|
1107
|
+
() => this.db.transaction(
|
|
1108
|
+
(tx) => replaceDocument(tx, model.root, args.id, args.data, model.config.drafts === true)
|
|
1109
|
+
)
|
|
1074
1110
|
);
|
|
1075
1111
|
return await this.findById({ collection: args.collection, id: args.id });
|
|
1076
1112
|
}
|
|
1077
1113
|
async delete(args) {
|
|
1078
1114
|
const model = this.model(args.collection);
|
|
1079
|
-
await this.
|
|
1115
|
+
await this.write(
|
|
1116
|
+
() => this.db.transaction((tx) => deleteDocument(tx, model.root, args.id))
|
|
1117
|
+
);
|
|
1080
1118
|
}
|
|
1081
1119
|
async findGlobal(args) {
|
|
1082
1120
|
const table = this.schema.globals;
|
|
@@ -1097,7 +1135,9 @@ var DrizzleDatabase = class {
|
|
|
1097
1135
|
status: status ?? null,
|
|
1098
1136
|
updated_at: updatedAt
|
|
1099
1137
|
};
|
|
1100
|
-
await this.
|
|
1138
|
+
await this.write(
|
|
1139
|
+
() => this.db.insert(table).values(values).onConflictDoUpdate({ target: table.slug, set: values })
|
|
1140
|
+
);
|
|
1101
1141
|
return await this.findGlobal({ slug: args.slug });
|
|
1102
1142
|
}
|
|
1103
1143
|
createMigration(args) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easy-cms/drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Shared Drizzle ORM layer for Easy CMS database adapters",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"drizzle-orm": "0.45.3"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@easy-cms/core": "^0.
|
|
26
|
+
"@easy-cms/core": "^0.8.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@easy-cms/core": "^0.
|
|
29
|
+
"@easy-cms/core": "^0.8.0"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|