cogsbox-shape 0.5.186 → 0.5.188
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/cogsbox-shape-db/dist/connect.d.ts +73 -0
- package/cogsbox-shape-db/dist/connect.js +146 -0
- package/cogsbox-shape-db/dist/errors.d.ts +7 -0
- package/cogsbox-shape-db/dist/errors.js +14 -0
- package/cogsbox-shape-db/dist/index.d.ts +4 -0
- package/cogsbox-shape-db/dist/index.js +3 -0
- package/cogsbox-shape-db/dist/remap.d.ts +5 -0
- package/cogsbox-shape-db/dist/remap.js +22 -0
- package/cogsbox-shape-db/dist/result-mapper.d.ts +7 -0
- package/cogsbox-shape-db/dist/result-mapper.js +64 -0
- package/cogsbox-shape-db/dist/sql-builder.d.ts +68 -0
- package/cogsbox-shape-db/dist/sql-builder.js +94 -0
- package/cogsbox-shape-db/dist/sqlite/index.d.ts +1 -0
- package/cogsbox-shape-db/dist/sqlite/index.js +1 -0
- package/cogsbox-shape-db/dist/sqlite/sqlite-driver.d.ts +2 -0
- package/cogsbox-shape-db/dist/sqlite/sqlite-driver.js +16 -0
- package/cogsbox-shape-db/dist/table-db.d.ts +50 -0
- package/cogsbox-shape-db/dist/table-db.js +307 -0
- package/cogsbox-shape-db/dist/types.d.ts +39 -0
- package/cogsbox-shape-db/dist/types.js +1 -0
- package/cogsbox-shape-db/dist/where-builder.d.ts +4 -0
- package/cogsbox-shape-db/dist/where-builder.js +71 -0
- package/dist/schema.d.ts +15 -1
- package/dist/schema.js +92 -2
- package/package.json +27 -5
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
import { TableDB } from "./table-db.js";
|
|
3
|
+
type FirstArg<T> = T extends (arg: infer A, ...args: any[]) => any ? A : never;
|
|
4
|
+
type Return<T> = T extends (...args: any[]) => infer R ? R : never;
|
|
5
|
+
type Prettify<T> = {
|
|
6
|
+
[K in keyof T]: T[K];
|
|
7
|
+
} & {};
|
|
8
|
+
type SchemaMetaKey = "_tableName" | "__primaryKeySQL" | "__derives" | "primaryKeySQL" | "derive";
|
|
9
|
+
type SqlConfigOf<TField> = TField extends {
|
|
10
|
+
config: {
|
|
11
|
+
sql: infer TSql;
|
|
12
|
+
};
|
|
13
|
+
} ? TSql : TField extends {
|
|
14
|
+
__meta: {
|
|
15
|
+
_fieldType: infer TInner;
|
|
16
|
+
};
|
|
17
|
+
} ? SqlConfigOf<TInner> : never;
|
|
18
|
+
type SqlConfigBaseValue<TSql> = TSql extends {
|
|
19
|
+
type: "int" | "boolean";
|
|
20
|
+
} ? number : TSql extends {
|
|
21
|
+
type: "date" | "datetime" | "timestamp";
|
|
22
|
+
} ? Date : TSql extends {
|
|
23
|
+
type: "varchar" | "char" | "text" | "longtext";
|
|
24
|
+
} ? string : unknown;
|
|
25
|
+
type SqlOnlyValue<TField> = SqlConfigOf<TField> extends infer TSql ? TSql extends {
|
|
26
|
+
nullable: true;
|
|
27
|
+
} ? SqlConfigBaseValue<TSql> | null : SqlConfigBaseValue<TSql> : unknown;
|
|
28
|
+
type IsSqlOnlyField<TField> = SqlConfigOf<TField> extends infer TSql ? TSql extends {
|
|
29
|
+
sqlOnly?: infer TSqlOnly;
|
|
30
|
+
} ? true extends TSqlOnly ? true : false : false : false;
|
|
31
|
+
type IsOptionalSqlOnly<TField> = TField extends {
|
|
32
|
+
config: {
|
|
33
|
+
sql: {
|
|
34
|
+
nullable: true;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
} ? true : TField extends {
|
|
38
|
+
config: {
|
|
39
|
+
sql: {
|
|
40
|
+
default: any;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
} ? true : TField extends {
|
|
44
|
+
config: {
|
|
45
|
+
sql: {
|
|
46
|
+
defaultValue: any;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
} ? true : false;
|
|
50
|
+
type SqlOnlyInput<T> = T extends {
|
|
51
|
+
definition: infer TDefinition;
|
|
52
|
+
} ? Prettify<{
|
|
53
|
+
[K in keyof TDefinition as IsSqlOnlyField<TDefinition[K]> extends true ? K extends SchemaMetaKey ? never : IsOptionalSqlOnly<TDefinition[K]> extends true ? never : K : never]: SqlOnlyValue<TDefinition[K]>;
|
|
54
|
+
} & {
|
|
55
|
+
[K in keyof TDefinition as IsSqlOnlyField<TDefinition[K]> extends true ? K extends SchemaMetaKey ? never : IsOptionalSqlOnly<TDefinition[K]> extends true ? K : never : never]?: SqlOnlyValue<TDefinition[K]>;
|
|
56
|
+
}> : Record<string, never>;
|
|
57
|
+
type ConnectedTable<T> = T extends {
|
|
58
|
+
transforms: {
|
|
59
|
+
parseForDb: (...args: any[]) => any;
|
|
60
|
+
parseFromDb: (...args: any[]) => any;
|
|
61
|
+
};
|
|
62
|
+
} ? T & {
|
|
63
|
+
db: TableDB<Return<T["transforms"]["parseFromDb"]>, FirstArg<T["transforms"]["parseForDb"]>, SqlOnlyInput<T>>;
|
|
64
|
+
} : T;
|
|
65
|
+
type ConnectedBox<T extends Record<string, unknown>> = {
|
|
66
|
+
[K in keyof T]: ConnectedTable<T[K]>;
|
|
67
|
+
} & {
|
|
68
|
+
db: {
|
|
69
|
+
transaction: <R>(fn: (txBox: ConnectedBox<T>) => Promise<R>) => Promise<R>;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
export declare function connect<T extends Record<string, unknown>>(box: T, db: Kysely<unknown>): ConnectedBox<T>;
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
import { TableDB } from "./table-db.js";
|
|
3
|
+
function extractTableMeta(entry) {
|
|
4
|
+
const definition = entry.definition;
|
|
5
|
+
const tableName = definition?._tableName ?? "unknown";
|
|
6
|
+
const dbFields = new Map();
|
|
7
|
+
const clientToDbName = new Map();
|
|
8
|
+
const pkFields = [];
|
|
9
|
+
const clientPkFields = [];
|
|
10
|
+
const sqlOnlyFields = new Set();
|
|
11
|
+
const sqlOnlyClientFields = new Set();
|
|
12
|
+
const sqlOnlyRequiredClientFields = new Set();
|
|
13
|
+
const sqlOnlyValidators = new Map();
|
|
14
|
+
const deriveDependencies = new Map(Object.entries((entry.deriveDependencies ?? {})));
|
|
15
|
+
if (!definition) {
|
|
16
|
+
return {
|
|
17
|
+
tableName,
|
|
18
|
+
dbFields,
|
|
19
|
+
clientToDbName,
|
|
20
|
+
pkFields,
|
|
21
|
+
clientPkFields,
|
|
22
|
+
sqlOnlyFields,
|
|
23
|
+
sqlOnlyClientFields,
|
|
24
|
+
sqlOnlyRequiredClientFields,
|
|
25
|
+
sqlOnlyValidators,
|
|
26
|
+
deriveDependencies,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
for (const [key, field] of Object.entries(definition)) {
|
|
30
|
+
if (key === "_tableName" || key.startsWith("__"))
|
|
31
|
+
continue;
|
|
32
|
+
if (typeof field !== "object" || field === null)
|
|
33
|
+
continue;
|
|
34
|
+
const config = field.config;
|
|
35
|
+
if (!config)
|
|
36
|
+
continue;
|
|
37
|
+
const sqlConfig = config.sql;
|
|
38
|
+
if (!sqlConfig)
|
|
39
|
+
continue;
|
|
40
|
+
const type = sqlConfig.type;
|
|
41
|
+
if (type && ["hasMany", "hasOne", "belongsTo", "manyToMany"].includes(type))
|
|
42
|
+
continue;
|
|
43
|
+
const dbName = sqlConfig.field ?? key;
|
|
44
|
+
const transforms = config.transforms;
|
|
45
|
+
dbFields.set(key, {
|
|
46
|
+
dbName,
|
|
47
|
+
toDb: transforms?.toDb,
|
|
48
|
+
toClient: transforms?.toClient,
|
|
49
|
+
});
|
|
50
|
+
clientToDbName.set(key, dbName);
|
|
51
|
+
if (sqlConfig.pk)
|
|
52
|
+
pkFields.push(dbName);
|
|
53
|
+
if (sqlConfig.isClientPk)
|
|
54
|
+
clientPkFields.push(key);
|
|
55
|
+
if (sqlConfig.sqlOnly) {
|
|
56
|
+
sqlOnlyFields.add(dbName);
|
|
57
|
+
sqlOnlyClientFields.add(key);
|
|
58
|
+
if (!sqlConfig.nullable &&
|
|
59
|
+
!Object.prototype.hasOwnProperty.call(sqlConfig, "default") &&
|
|
60
|
+
!Object.prototype.hasOwnProperty.call(sqlConfig, "defaultValue")) {
|
|
61
|
+
sqlOnlyRequiredClientFields.add(key);
|
|
62
|
+
}
|
|
63
|
+
const zodSqlSchema = config.zodSqlSchema;
|
|
64
|
+
if (zodSqlSchema?.parse) {
|
|
65
|
+
sqlOnlyValidators.set(key, (val) => zodSqlSchema.parse(val));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
tableName,
|
|
71
|
+
dbFields,
|
|
72
|
+
clientToDbName,
|
|
73
|
+
pkFields,
|
|
74
|
+
clientPkFields,
|
|
75
|
+
sqlOnlyFields,
|
|
76
|
+
sqlOnlyClientFields,
|
|
77
|
+
sqlOnlyRequiredClientFields,
|
|
78
|
+
sqlOnlyValidators,
|
|
79
|
+
deriveDependencies,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function enhanceTable(entry, meta, db) {
|
|
83
|
+
const transforms = entry.transforms ?? {};
|
|
84
|
+
const tableDb = new TableDB(db, meta, {
|
|
85
|
+
toClient: transforms.toClient ?? ((r) => r),
|
|
86
|
+
toDb: transforms.toDb ?? ((r) => r),
|
|
87
|
+
parseForDb: transforms.parseForDb ?? ((r) => r),
|
|
88
|
+
parsePatchForDb: transforms.parsePatchForDb ?? transforms.toDb ?? ((r) => r),
|
|
89
|
+
parseFromDb: transforms.parseFromDb ?? ((r) => r),
|
|
90
|
+
});
|
|
91
|
+
return new Proxy(entry, {
|
|
92
|
+
get(target, prop, receiver) {
|
|
93
|
+
if (prop === "db")
|
|
94
|
+
return tableDb;
|
|
95
|
+
return Reflect.get(target, prop, receiver);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export function connect(box, db) {
|
|
100
|
+
const result = {};
|
|
101
|
+
for (const key of Object.keys(box)) {
|
|
102
|
+
const entry = box[key];
|
|
103
|
+
if (typeof entry !== "object" || entry === null) {
|
|
104
|
+
result[key] = entry;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if ("definition" in entry && "schemas" in entry && "transforms" in entry) {
|
|
108
|
+
const meta = extractTableMeta(entry);
|
|
109
|
+
result[key] = enhanceTable(entry, meta, db);
|
|
110
|
+
const originalCreateView = entry.createView;
|
|
111
|
+
if (originalCreateView) {
|
|
112
|
+
result[key].createView = (selection) => {
|
|
113
|
+
const view = originalCreateView(selection);
|
|
114
|
+
const viewMeta = { ...meta };
|
|
115
|
+
const viewTransforms = view.transforms ?? {};
|
|
116
|
+
const reconcile = view.reconcile;
|
|
117
|
+
const viewDb = new TableDB(db, viewMeta, {
|
|
118
|
+
toClient: viewTransforms.toClient ?? ((r) => r),
|
|
119
|
+
toDb: viewTransforms.toDb ?? ((r) => r),
|
|
120
|
+
parseForDb: viewTransforms.parseForDb ?? ((r) => r),
|
|
121
|
+
parsePatchForDb: viewTransforms.parsePatchForDb ?? viewTransforms.toDb ?? ((r) => r),
|
|
122
|
+
parseFromDb: viewTransforms.parseFromDb ?? ((r) => r),
|
|
123
|
+
}, reconcile);
|
|
124
|
+
return new Proxy(view, {
|
|
125
|
+
get(target, prop, receiver) {
|
|
126
|
+
if (prop === "db")
|
|
127
|
+
return viewDb;
|
|
128
|
+
return Reflect.get(target, prop, receiver);
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
result[key] = entry;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const transaction = async (fn) => {
|
|
139
|
+
return db.transaction().execute(async (trx) => {
|
|
140
|
+
const txBox = connect(box, trx);
|
|
141
|
+
return fn(txBox);
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
result.db = { transaction };
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class RecordNotFoundError extends Error {
|
|
2
|
+
constructor(table, pk) {
|
|
3
|
+
super(`Record not found in "${table}" with pk: ${JSON.stringify(pk)}`);
|
|
4
|
+
this.name = "RecordNotFoundError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class ValidationError extends Error {
|
|
8
|
+
issues;
|
|
9
|
+
constructor(issues) {
|
|
10
|
+
super("Validation failed");
|
|
11
|
+
this.name = "ValidationError";
|
|
12
|
+
this.issues = issues;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class RemapStore {
|
|
2
|
+
map = new Map();
|
|
3
|
+
set(table, tempId, realId) {
|
|
4
|
+
let tableMap = this.map.get(table);
|
|
5
|
+
if (!tableMap) {
|
|
6
|
+
tableMap = new Map();
|
|
7
|
+
this.map.set(table, tableMap);
|
|
8
|
+
}
|
|
9
|
+
tableMap.set(tempId, realId);
|
|
10
|
+
}
|
|
11
|
+
toObject() {
|
|
12
|
+
const obj = {};
|
|
13
|
+
for (const [table, tableMap] of this.map) {
|
|
14
|
+
const entries = {};
|
|
15
|
+
for (const [temp, real] of tableMap) {
|
|
16
|
+
entries[String(temp)] = real;
|
|
17
|
+
}
|
|
18
|
+
obj[table] = entries;
|
|
19
|
+
}
|
|
20
|
+
return obj;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JoinDef, TableMeta } from "./types.js";
|
|
2
|
+
export declare function aliasRelationColumn(relationKey: string, field: string): string;
|
|
3
|
+
export declare function isRelationAlias(alias: string): false | {
|
|
4
|
+
relationKey: string;
|
|
5
|
+
field: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function flattenJoinedRows(rows: Record<string, unknown>[], basePkFields: string[], joins: JoinDef[], baseMeta: TableMeta, childMetas: Map<string, TableMeta>): Record<string, unknown>[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const REL_PREFIX = "__rel_";
|
|
2
|
+
export function aliasRelationColumn(relationKey, field) {
|
|
3
|
+
return `${REL_PREFIX}${relationKey}_${field}`;
|
|
4
|
+
}
|
|
5
|
+
export function isRelationAlias(alias) {
|
|
6
|
+
if (!alias.startsWith(REL_PREFIX))
|
|
7
|
+
return false;
|
|
8
|
+
const rest = alias.slice(REL_PREFIX.length);
|
|
9
|
+
const underscoreIdx = rest.indexOf("_");
|
|
10
|
+
if (underscoreIdx === -1)
|
|
11
|
+
return false;
|
|
12
|
+
return {
|
|
13
|
+
relationKey: rest.slice(0, underscoreIdx),
|
|
14
|
+
field: rest.slice(underscoreIdx + 1),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function flattenJoinedRows(rows, basePkFields, joins, baseMeta, childMetas) {
|
|
18
|
+
const groups = new Map();
|
|
19
|
+
const children = new Map();
|
|
20
|
+
for (const row of rows) {
|
|
21
|
+
const pkKey = basePkFields.map(f => String(row[f] ?? "")).join("|");
|
|
22
|
+
if (!groups.has(pkKey)) {
|
|
23
|
+
const base = {};
|
|
24
|
+
for (const [clientKey, field] of baseMeta.dbFields) {
|
|
25
|
+
base[clientKey] = row[field.dbName];
|
|
26
|
+
}
|
|
27
|
+
if (baseMeta.clientToDbName.size === 0) {
|
|
28
|
+
for (const key of Object.keys(row)) {
|
|
29
|
+
if (!isRelationAlias(key)) {
|
|
30
|
+
base[key] = row[key];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
groups.set(pkKey, base);
|
|
35
|
+
children.set(pkKey, []);
|
|
36
|
+
}
|
|
37
|
+
for (const join of joins) {
|
|
38
|
+
const childRow = {};
|
|
39
|
+
let allNull = true;
|
|
40
|
+
for (const col of join.columns) {
|
|
41
|
+
const alias = aliasRelationColumn(join.relationKey, col);
|
|
42
|
+
const val = row[alias];
|
|
43
|
+
childRow[col] = val;
|
|
44
|
+
if (val !== null && val !== undefined) {
|
|
45
|
+
allNull = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!allNull) {
|
|
49
|
+
children.get(pkKey).push(childRow);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const result = [];
|
|
54
|
+
for (const [pkKey, base] of groups) {
|
|
55
|
+
const item = { ...base };
|
|
56
|
+
for (const join of joins) {
|
|
57
|
+
item[join.relationKey] = children.get(pkKey).filter(r => {
|
|
58
|
+
return Object.values(r).some(v => v !== null && v !== undefined);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
result.push(item);
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { WhereClause, OrderByEntry, JoinDef } from "./types.js";
|
|
2
|
+
export interface SelectOpts {
|
|
3
|
+
table: string;
|
|
4
|
+
columns: string[];
|
|
5
|
+
where?: WhereClause;
|
|
6
|
+
orderBy?: OrderByEntry[];
|
|
7
|
+
limit?: number;
|
|
8
|
+
offset?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function buildSelect(opts: SelectOpts): {
|
|
11
|
+
sql: string;
|
|
12
|
+
bindings: unknown[];
|
|
13
|
+
};
|
|
14
|
+
export interface SelectWithJoinsOpts {
|
|
15
|
+
baseTable: string;
|
|
16
|
+
baseAlias: string;
|
|
17
|
+
baseColumns: string[];
|
|
18
|
+
joins: JoinDef[];
|
|
19
|
+
where?: WhereClause;
|
|
20
|
+
orderBy?: OrderByEntry[];
|
|
21
|
+
limit?: number;
|
|
22
|
+
offset?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildSelectWithJoins(opts: SelectWithJoinsOpts): {
|
|
25
|
+
sql: string;
|
|
26
|
+
bindings: unknown[];
|
|
27
|
+
};
|
|
28
|
+
export interface InsertOpts {
|
|
29
|
+
table: string;
|
|
30
|
+
values: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
export declare function buildInsert(opts: InsertOpts): {
|
|
33
|
+
sql: string;
|
|
34
|
+
bindings: unknown[];
|
|
35
|
+
};
|
|
36
|
+
export interface UpdateOpts {
|
|
37
|
+
table: string;
|
|
38
|
+
values: Record<string, unknown>;
|
|
39
|
+
where: WhereClause;
|
|
40
|
+
}
|
|
41
|
+
export declare function buildUpdate(opts: UpdateOpts): {
|
|
42
|
+
sql: string;
|
|
43
|
+
bindings: unknown[];
|
|
44
|
+
};
|
|
45
|
+
export interface DeleteOpts {
|
|
46
|
+
table: string;
|
|
47
|
+
where: WhereClause;
|
|
48
|
+
}
|
|
49
|
+
export declare function buildDelete(opts: DeleteOpts): {
|
|
50
|
+
sql: string;
|
|
51
|
+
bindings: unknown[];
|
|
52
|
+
};
|
|
53
|
+
export declare function buildCount(opts: {
|
|
54
|
+
table: string;
|
|
55
|
+
where?: WhereClause;
|
|
56
|
+
}): {
|
|
57
|
+
sql: string;
|
|
58
|
+
bindings: unknown[];
|
|
59
|
+
};
|
|
60
|
+
export declare function buildSelectById(opts: {
|
|
61
|
+
table: string;
|
|
62
|
+
columns: string[];
|
|
63
|
+
pkFields: string[];
|
|
64
|
+
pkValues: unknown[];
|
|
65
|
+
}): {
|
|
66
|
+
sql: string;
|
|
67
|
+
bindings: unknown[];
|
|
68
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export function buildSelect(opts) {
|
|
2
|
+
const { table, columns, where, orderBy, limit, offset } = opts;
|
|
3
|
+
const sqlParts = [];
|
|
4
|
+
const allBindings = [];
|
|
5
|
+
sqlParts.push(`SELECT ${columns.map(c => `${table}.${c}`).join(", ")} FROM ${table}`);
|
|
6
|
+
if (where && where.sql) {
|
|
7
|
+
sqlParts.push(`WHERE ${where.sql}`);
|
|
8
|
+
allBindings.push(...where.bindings);
|
|
9
|
+
}
|
|
10
|
+
if (orderBy && orderBy.length > 0) {
|
|
11
|
+
const clauses = orderBy.map(([col, dir]) => `${col} ${dir}`);
|
|
12
|
+
sqlParts.push(`ORDER BY ${clauses.join(", ")}`);
|
|
13
|
+
}
|
|
14
|
+
if (limit !== undefined) {
|
|
15
|
+
sqlParts.push(`LIMIT ?`);
|
|
16
|
+
allBindings.push(limit);
|
|
17
|
+
}
|
|
18
|
+
if (offset !== undefined) {
|
|
19
|
+
sqlParts.push(`OFFSET ?`);
|
|
20
|
+
allBindings.push(offset);
|
|
21
|
+
}
|
|
22
|
+
return { sql: sqlParts.join(" "), bindings: allBindings };
|
|
23
|
+
}
|
|
24
|
+
export function buildSelectWithJoins(opts) {
|
|
25
|
+
const { baseTable, baseAlias, baseColumns, joins, where, orderBy, limit, offset } = opts;
|
|
26
|
+
const sqlParts = [];
|
|
27
|
+
const allBindings = [];
|
|
28
|
+
const allCols = [
|
|
29
|
+
...baseColumns.map(c => `${baseAlias}.${c}`),
|
|
30
|
+
...joins.flatMap(j => j.columns.map(c => `${j.alias}.${c} AS ${c}`)),
|
|
31
|
+
];
|
|
32
|
+
sqlParts.push(`SELECT ${allCols.join(", ")} FROM ${baseTable} ${baseAlias}`);
|
|
33
|
+
for (const join of joins) {
|
|
34
|
+
sqlParts.push(`LEFT JOIN ${join.table} ${join.alias} ON ${join.on}`);
|
|
35
|
+
}
|
|
36
|
+
if (where && where.sql) {
|
|
37
|
+
sqlParts.push(`WHERE ${where.sql}`);
|
|
38
|
+
allBindings.push(...where.bindings);
|
|
39
|
+
}
|
|
40
|
+
if (orderBy && orderBy.length > 0) {
|
|
41
|
+
const clauses = orderBy.map(([col, dir]) => `${col} ${dir}`);
|
|
42
|
+
sqlParts.push(`ORDER BY ${clauses.join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
if (limit !== undefined) {
|
|
45
|
+
sqlParts.push(`LIMIT ?`);
|
|
46
|
+
allBindings.push(limit);
|
|
47
|
+
}
|
|
48
|
+
if (offset !== undefined) {
|
|
49
|
+
sqlParts.push(`OFFSET ?`);
|
|
50
|
+
allBindings.push(offset);
|
|
51
|
+
}
|
|
52
|
+
return { sql: sqlParts.join(" "), bindings: allBindings };
|
|
53
|
+
}
|
|
54
|
+
export function buildInsert(opts) {
|
|
55
|
+
const { table, values } = opts;
|
|
56
|
+
const keys = Object.keys(values);
|
|
57
|
+
const placeholders = keys.map(() => "?");
|
|
58
|
+
const bindings = keys.map(k => values[k]);
|
|
59
|
+
return {
|
|
60
|
+
sql: `INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders.join(", ")})`,
|
|
61
|
+
bindings,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function buildUpdate(opts) {
|
|
65
|
+
const { table, values, where } = opts;
|
|
66
|
+
const keys = Object.keys(values);
|
|
67
|
+
const setClauses = keys.map(k => `${k} = ?`);
|
|
68
|
+
const setBindings = keys.map(k => values[k]);
|
|
69
|
+
return {
|
|
70
|
+
sql: `UPDATE ${table} SET ${setClauses.join(", ")} WHERE ${where.sql}`,
|
|
71
|
+
bindings: [...setBindings, ...where.bindings],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function buildDelete(opts) {
|
|
75
|
+
return {
|
|
76
|
+
sql: `DELETE FROM ${opts.table} WHERE ${opts.where.sql}`,
|
|
77
|
+
bindings: opts.where.bindings,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function buildCount(opts) {
|
|
81
|
+
const { table, where } = opts;
|
|
82
|
+
const sqlParts = [`SELECT COUNT(*) as count FROM ${table}`];
|
|
83
|
+
const allBindings = [];
|
|
84
|
+
if (where && where.sql) {
|
|
85
|
+
sqlParts.push(`WHERE ${where.sql}`);
|
|
86
|
+
allBindings.push(...where.bindings);
|
|
87
|
+
}
|
|
88
|
+
return { sql: sqlParts.join(" "), bindings: allBindings };
|
|
89
|
+
}
|
|
90
|
+
export function buildSelectById(opts) {
|
|
91
|
+
const conditions = opts.pkFields.map(f => `${f} = ?`);
|
|
92
|
+
const sql = `SELECT ${opts.columns.map(c => `${opts.table}.${c}`).join(", ")} FROM ${opts.table} WHERE ${conditions.join(" AND ")}`;
|
|
93
|
+
return { sql, bindings: [...opts.pkValues] };
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSqliteDb } from "./sqlite-driver.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createSqliteDb } from "./sqlite-driver.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Kysely, SqliteDialect } from "kysely";
|
|
2
|
+
export async function createSqliteDb(path) {
|
|
3
|
+
try {
|
|
4
|
+
const mod = await import("better-sqlite3");
|
|
5
|
+
const Database = mod.default;
|
|
6
|
+
return new Kysely({
|
|
7
|
+
dialect: new SqliteDialect({
|
|
8
|
+
database: new Database(path),
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
throw new Error("Failed to initialize better-sqlite3. It may be missing, blocked by pnpm builds, or built for a different Node version.\n" +
|
|
14
|
+
"Try: pnpm approve-builds && pnpm rebuild better-sqlite3", { cause: err });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
import type { TableMeta, FindManyOpts, WhereInput } from "./types.js";
|
|
3
|
+
type DbOnlyArg<T extends Record<string, unknown>> = keyof T extends never ? never : Partial<T>;
|
|
4
|
+
type RequiredKeys<T> = {
|
|
5
|
+
[K in keyof T]-?: Record<string, never> extends Pick<T, K> ? never : K;
|
|
6
|
+
}[keyof T];
|
|
7
|
+
type InsertDbOnlyArgs<T extends Record<string, unknown>> = keyof T extends never ? [] : RequiredKeys<T> extends never ? [dbOnlyData?: Partial<T>] : [dbOnlyData: T];
|
|
8
|
+
export declare class TableDB<TClient extends Record<string, unknown>, TCreate, TDbOnly extends Record<string, unknown> = Record<string, never>> {
|
|
9
|
+
private db;
|
|
10
|
+
private meta;
|
|
11
|
+
private transforms;
|
|
12
|
+
private reconcile?;
|
|
13
|
+
constructor(db: Kysely<unknown>, meta: TableMeta, transforms: {
|
|
14
|
+
toClient: (row: Record<string, unknown>) => TClient;
|
|
15
|
+
toDb: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
16
|
+
parseForDb: (data: Record<string, unknown>) => Record<string, unknown>;
|
|
17
|
+
parsePatchForDb: (data: Record<string, unknown>) => Record<string, unknown>;
|
|
18
|
+
parseFromDb: (data: Record<string, unknown>) => TClient;
|
|
19
|
+
}, reconcile?: ((clientData: unknown) => {
|
|
20
|
+
withServer: (serverData: unknown) => unknown;
|
|
21
|
+
}) | undefined);
|
|
22
|
+
findMany(opts?: FindManyOpts<TClient>): Promise<TClient[]>;
|
|
23
|
+
findById(id: unknown): Promise<TClient | null>;
|
|
24
|
+
insert(data: TCreate, ...args: InsertDbOnlyArgs<TDbOnly>): {
|
|
25
|
+
ids: () => Promise<Record<string, unknown>>;
|
|
26
|
+
full: () => Promise<TClient>;
|
|
27
|
+
};
|
|
28
|
+
create(data: TCreate, ...args: InsertDbOnlyArgs<TDbOnly>): Promise<Record<string, unknown>>;
|
|
29
|
+
private insertIds;
|
|
30
|
+
update(id: unknown, data: Partial<TCreate>, dbOnlyData?: DbOnlyArg<TDbOnly>): {
|
|
31
|
+
ids: () => Promise<Record<string, unknown>>;
|
|
32
|
+
full: () => Promise<TClient>;
|
|
33
|
+
};
|
|
34
|
+
private updateIds;
|
|
35
|
+
private affectedDbBackedDerives;
|
|
36
|
+
private missingDeriveDependencies;
|
|
37
|
+
private fetchClientFieldsById;
|
|
38
|
+
private pickDbPatchFields;
|
|
39
|
+
private parseDbOnlyData;
|
|
40
|
+
reconcileIds(clientData: unknown, ids: unknown): unknown;
|
|
41
|
+
private reconcileFlatIds;
|
|
42
|
+
private mapIdsToClientFields;
|
|
43
|
+
private clientKeyForDbField;
|
|
44
|
+
private firstPkValue;
|
|
45
|
+
delete(id: unknown): Promise<{
|
|
46
|
+
deleted: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
count(where?: WhereInput<Partial<TClient>>): Promise<number>;
|
|
49
|
+
}
|
|
50
|
+
export {};
|