@sigitex/outlaw 1.0.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/LICENSE +7 -0
- package/README.md +9 -0
- package/package.json +52 -0
- package/src/api/DatabaseTable.ts +51 -0
- package/src/api/DatabaseView.ts +22 -0
- package/src/api/api.types.ts +200 -0
- package/src/api/createDatabase.ts +24 -0
- package/src/api/index.ts +4 -0
- package/src/bun/bun.ts +27 -0
- package/src/bun/index.ts +1 -0
- package/src/cloudflare/cloudflare.ts +18 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/console.ts +14 -0
- package/src/cowboyMigration/Compare.ts +97 -0
- package/src/cowboyMigration/CowboyConnection.ts +57 -0
- package/src/cowboyMigration/CowboyMigrator.ts +437 -0
- package/src/cowboyMigration/CowboySeeder.ts +245 -0
- package/src/cowboyMigration/cowboyMigration.types.ts +29 -0
- package/src/cowboyMigration/createSchemaHacker.ts +28 -0
- package/src/cowboyMigration/index.ts +3 -0
- package/src/crypto.d.ts +9 -0
- package/src/framework/Format.ts +31 -0
- package/src/framework/definitions.d.ts +4 -0
- package/src/framework/index.ts +1 -0
- package/src/index.ts +10 -0
- package/src/queryBuilder/DeleteBuilder.ts +40 -0
- package/src/queryBuilder/InsertBuilder.ts +31 -0
- package/src/queryBuilder/Mappings.ts +55 -0
- package/src/queryBuilder/SelectBuilder.ts +39 -0
- package/src/queryBuilder/SelectQueryBuilder.ts +93 -0
- package/src/queryBuilder/UpdateBuilder.ts +44 -0
- package/src/queryBuilder/addWhereClause.ts +35 -0
- package/src/queryBuilder/index.ts +7 -0
- package/src/queryBuilder/operators.ts +35 -0
- package/src/queryBuilder/queryBuilders.types.ts +105 -0
- package/src/queryGenerator/Clause.ts +79 -0
- package/src/queryGenerator/generateDelete.ts +18 -0
- package/src/queryGenerator/generateInsert.ts +29 -0
- package/src/queryGenerator/generateSelect.ts +89 -0
- package/src/queryGenerator/generateUpdate.ts +29 -0
- package/src/queryGenerator/index.ts +4 -0
- package/src/reflection/Reflector.ts +47 -0
- package/src/reflection/index.ts +2 -0
- package/src/reflection/reflection.types.ts +12 -0
- package/src/schemaBuilder/Mapping.ts +44 -0
- package/src/schemaBuilder/columnBuilders.ts +67 -0
- package/src/schemaBuilder/createFixture.ts +112 -0
- package/src/schemaBuilder/createIndex.ts +33 -0
- package/src/schemaBuilder/createSchema.ts +23 -0
- package/src/schemaBuilder/createTable.ts +80 -0
- package/src/schemaBuilder/createView.ts +21 -0
- package/src/schemaBuilder/index.ts +9 -0
- package/src/schemaBuilder/metadata.ts +73 -0
- package/src/schemaBuilder/schemaBuilder.types.ts +139 -0
- package/src/schemaGenerator/generateCreateIndex.ts +18 -0
- package/src/schemaGenerator/generateCreateTable.ts +68 -0
- package/src/schemaGenerator/generateCreateView.ts +13 -0
- package/src/schemaGenerator/index.ts +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright © 2026 Sigitex
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sigitex/outlaw",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Sigitex",
|
|
7
|
+
"url": "http://github.com/sigitex"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/sigitex/outlaw.git"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./src/index.ts",
|
|
16
|
+
"types": "./src/index.ts"
|
|
17
|
+
},
|
|
18
|
+
"./bun": "./src/bun/index.ts",
|
|
19
|
+
"./cloudflare": "./src/cloudflare/index.ts"
|
|
20
|
+
},
|
|
21
|
+
"types": "./src/index.ts",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@cloudflare/workers-types": "^4.20250425.0",
|
|
24
|
+
"@commitlint/cli": "^20.5.3",
|
|
25
|
+
"@commitlint/config-conventional": "^20.5.3",
|
|
26
|
+
"@markwylde/semantic-release-gitea": "^2.2.0",
|
|
27
|
+
"@semantic-release/exec": "^7.0.3",
|
|
28
|
+
"@types/bun": "^1.3.13",
|
|
29
|
+
"@types/fast-json-stable-stringify": "^2.1.2",
|
|
30
|
+
"@typescript/native-preview": "beta",
|
|
31
|
+
"husky": "^9.1.7",
|
|
32
|
+
"multi-semantic-release": "^3.1.0",
|
|
33
|
+
"oxfmt": "^0.47.0",
|
|
34
|
+
"oxlint": "^1.62.0",
|
|
35
|
+
"semantic-release": "^25.0.3"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@sigitex/print": "*",
|
|
39
|
+
"fast-json-stable-stringify": "^2.1.0",
|
|
40
|
+
"sqlstring-sqlite": "^0.1.1"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"prepare": "husky",
|
|
44
|
+
"lint": "oxlint",
|
|
45
|
+
"check": "tsgo --build",
|
|
46
|
+
"test": "bun test --pass-with-no-tests --tsconfig-override tsconfig.test.json"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"src"
|
|
50
|
+
],
|
|
51
|
+
"version": "1.0.0"
|
|
52
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// oxlint-disable typescript/no-explicit-any
|
|
2
|
+
import type { Delete, Connection, Insert, Select, TableApi, Update } from "./api.types"
|
|
3
|
+
import type { TableData } from "../schemaBuilder"
|
|
4
|
+
import {
|
|
5
|
+
DeleteBuilder,
|
|
6
|
+
InsertBuilder,
|
|
7
|
+
SelectBuilder,
|
|
8
|
+
UpdateBuilder,
|
|
9
|
+
} from "../queryBuilder"
|
|
10
|
+
|
|
11
|
+
export class DatabaseTable implements TableApi<any> {
|
|
12
|
+
private connection: Connection
|
|
13
|
+
private table: TableData
|
|
14
|
+
|
|
15
|
+
constructor(connection: Connection, table: TableData) {
|
|
16
|
+
this.connection = connection
|
|
17
|
+
this.table = table
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
select(all: "*"): Select<any, any>
|
|
21
|
+
select<Column extends string>(columns: Column[]): Select<Pick<any, any>, any>
|
|
22
|
+
select(
|
|
23
|
+
columns: "*" | string[],
|
|
24
|
+
): Select<any, any> | Select<Pick<any, any>, any> {
|
|
25
|
+
return new SelectBuilder(this.connection, this.table, columns)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
insert(row: Partial<any>): Insert<any, any, number>
|
|
29
|
+
insert<Column extends string | number | symbol>(
|
|
30
|
+
columns: Column[],
|
|
31
|
+
rows: Pick<any, Column>[],
|
|
32
|
+
): Insert<Column, any, number>
|
|
33
|
+
insert(
|
|
34
|
+
columnsOrRow: any,
|
|
35
|
+
rows?: any,
|
|
36
|
+
): Insert<any, any, number> | Insert<any, any, number> {
|
|
37
|
+
if (rows) {
|
|
38
|
+
return new InsertBuilder(this.connection, this.table, columnsOrRow, rows)
|
|
39
|
+
}
|
|
40
|
+
const row = columnsOrRow as Record<string, unknown>
|
|
41
|
+
return new InsertBuilder(this.connection, this.table, Object.keys(row), [row])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
update(row: Partial<any>): Update<any, number> {
|
|
45
|
+
return new UpdateBuilder(this.connection, this.table, row)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
delete(): Delete<TableData, number> {
|
|
49
|
+
return new DeleteBuilder(this.connection, this.table)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// oxlint-disable typescript/no-explicit-any
|
|
2
|
+
import type { Connection, Select, ViewApi } from "./api.types"
|
|
3
|
+
import type { TableData } from "../schemaBuilder"
|
|
4
|
+
import { SelectBuilder } from "../queryBuilder"
|
|
5
|
+
|
|
6
|
+
export class DatabaseView implements ViewApi<any> {
|
|
7
|
+
private connection: Connection
|
|
8
|
+
private tableData: TableData
|
|
9
|
+
|
|
10
|
+
constructor(connection: Connection, tableData: TableData) {
|
|
11
|
+
this.connection = connection
|
|
12
|
+
this.tableData = tableData
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
select(all: "*"): Select<any, any>
|
|
16
|
+
select<Column extends string>(columns: Column[]): Select<Pick<any, any>, any>
|
|
17
|
+
select(
|
|
18
|
+
columns: "*" | string[],
|
|
19
|
+
): Select<any, any> | Select<Pick<any, any>, any> {
|
|
20
|
+
return new SelectBuilder(this.connection, this.tableData, columns)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// oxlint-disable typescript/consistent-type-definitions
|
|
2
|
+
// oxlint-disable typescript/no-explicit-any
|
|
3
|
+
import type { BINARY_OPERATORS, UNARY_OPERATORS } from "../queryBuilder"
|
|
4
|
+
import type {
|
|
5
|
+
BuildColumn,
|
|
6
|
+
BuildTable,
|
|
7
|
+
BuildView,
|
|
8
|
+
ColumnRef,
|
|
9
|
+
SchemaMembers,
|
|
10
|
+
TablesOf,
|
|
11
|
+
ViewsOf,
|
|
12
|
+
} from "../schemaBuilder"
|
|
13
|
+
|
|
14
|
+
// Utilities
|
|
15
|
+
|
|
16
|
+
export type ElementOf<ArrayType extends readonly unknown[]> =
|
|
17
|
+
ArrayType extends readonly (infer ElementType)[] ? ElementType : never
|
|
18
|
+
|
|
19
|
+
export type InsertRecord<R> = Partial<R> &
|
|
20
|
+
Pick<
|
|
21
|
+
R,
|
|
22
|
+
{
|
|
23
|
+
[P in keyof R]: R[P] extends Exclude<R[P], null> ? P : never
|
|
24
|
+
}[keyof R]
|
|
25
|
+
>
|
|
26
|
+
|
|
27
|
+
// Deconstruction
|
|
28
|
+
|
|
29
|
+
export type ColumnsOf<BT> =
|
|
30
|
+
BT extends BuildTable<infer Columns> ? Columns : never
|
|
31
|
+
|
|
32
|
+
export type ColumnTypeOf<BC> =
|
|
33
|
+
BC extends BuildColumn<infer Type, infer Constraints>
|
|
34
|
+
? Constraints extends "notNull" | "primaryKey"
|
|
35
|
+
? Type
|
|
36
|
+
: Type | null
|
|
37
|
+
: never
|
|
38
|
+
|
|
39
|
+
export type ColumnTypesOf<BCS> = {
|
|
40
|
+
readonly [BCK in keyof BCS]: ColumnTypeOf<BCS[BCK]>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// API
|
|
44
|
+
|
|
45
|
+
export type DefaultRow = Record<string, unknown>
|
|
46
|
+
|
|
47
|
+
export type Connection = {
|
|
48
|
+
readonly query: <Row = DefaultRow>(sql: string) => Promise<Row[]>
|
|
49
|
+
readonly script: (statements: string[]) => Promise<void>
|
|
50
|
+
readonly reset?: () => Promise<void>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type DatabaseApi<M extends SchemaMembers> = {
|
|
54
|
+
readonly [K in keyof TablesOf<M>]: TableApi<ColumnsOf<TablesOf<M>[K]>>
|
|
55
|
+
} & {
|
|
56
|
+
readonly [K in keyof ViewsOf<M>]: ViewApi<ColumnsOfView<ViewsOf<M>[K]>>
|
|
57
|
+
} & {
|
|
58
|
+
readonly connection: Connection
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type ColumnsOfView<BV> = BV extends BuildView<infer Columns> ? Columns : never
|
|
62
|
+
|
|
63
|
+
export type ViewApi<Columns> = {
|
|
64
|
+
/** Issue a `SELECT *` query to the API. */
|
|
65
|
+
select(all: "*"): Select<Columns, Columns>
|
|
66
|
+
/** Issue a `SELECT` query to the API with the chosen columns. */
|
|
67
|
+
select<Column extends keyof Columns>(
|
|
68
|
+
...columns: Column[]
|
|
69
|
+
): Select<Pick<Columns, Column>, Columns>
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type TableApi<Columns> = {
|
|
73
|
+
/** Issue a `SELECT *` query to the API. */
|
|
74
|
+
select(all: "*"): Select<Columns, Columns>
|
|
75
|
+
/** Issue a `SELECT` query to the API with the chosen columns. */
|
|
76
|
+
select<Column extends keyof Columns>(
|
|
77
|
+
...columns: Column[]
|
|
78
|
+
): Select<Pick<Columns, Column>, Columns>
|
|
79
|
+
/** Issue an `INSERT` statement. */
|
|
80
|
+
insert<InsertColumns extends Partial<ColumnTypesOf<Columns>>>(
|
|
81
|
+
...rows: InsertColumns[]
|
|
82
|
+
): Insert<InsertColumns, Columns, number>
|
|
83
|
+
/** Issue an `UPDATE` statement. */
|
|
84
|
+
update(row: Partial<ColumnTypesOf<Columns>>): Update<Columns, number>
|
|
85
|
+
/** Issue a `DELETE` statement. */
|
|
86
|
+
delete(): Delete<Columns, number>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Represents queries/statments with a `WHERE` clause. */
|
|
90
|
+
export interface HasWhereClause<Columns> {
|
|
91
|
+
/** Add a condition where the given column equals the given value. */
|
|
92
|
+
where<Column extends keyof Columns>(
|
|
93
|
+
column: Column,
|
|
94
|
+
value: Columns[Column],
|
|
95
|
+
): this
|
|
96
|
+
/** Add a unary `WHERE` condition. */
|
|
97
|
+
where<Column extends keyof Columns>(
|
|
98
|
+
column: Column,
|
|
99
|
+
operator: UnaryOperator,
|
|
100
|
+
): this
|
|
101
|
+
/** Add a binary `WHERE` condition. */
|
|
102
|
+
where<Column extends keyof Columns>(
|
|
103
|
+
column: Column,
|
|
104
|
+
operator: BinaryOperator,
|
|
105
|
+
value: ColumnTypeOf<Columns[Column]>,
|
|
106
|
+
): this
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Select query API. */
|
|
110
|
+
export interface Select<SelectColumns, Columns>
|
|
111
|
+
extends HasWhereClause<Columns> {
|
|
112
|
+
/** Issue the query, expecting an array of results. */
|
|
113
|
+
fetch(): Promise<ColumnTypesOf<SelectColumns>[]>
|
|
114
|
+
/** Issue the query, returning a single result, or throwing.. */
|
|
115
|
+
first(): Promise<ColumnTypesOf<SelectColumns>>
|
|
116
|
+
/** Add a `LIMIT` clause. */
|
|
117
|
+
limit(n: number): this
|
|
118
|
+
/** Add an `OFFSET` clause. */
|
|
119
|
+
offset(n: number): this
|
|
120
|
+
/** Add an `ORDER BY` sort expression. */
|
|
121
|
+
orderBy(sorts: [keyof Columns, "asc" | "desc"][]): this
|
|
122
|
+
|
|
123
|
+
/** Inner join on a table. */
|
|
124
|
+
join<JC>(
|
|
125
|
+
table: BuildTable<JC>,
|
|
126
|
+
): Select<SelectColumns & ColumnTypesOf<JC>, Columns & JC>
|
|
127
|
+
/** Inner join on a subquery. */
|
|
128
|
+
join<SC>(
|
|
129
|
+
query: Select<SC, any>,
|
|
130
|
+
): Select<SelectColumns & ColumnTypesOf<SC>, Columns & SC>
|
|
131
|
+
|
|
132
|
+
/** Left join on a table — joined columns become nullable. */
|
|
133
|
+
leftJoin<JC>(
|
|
134
|
+
table: BuildTable<JC>,
|
|
135
|
+
): Select<SelectColumns & Partial<ColumnTypesOf<JC>>, Columns & JC>
|
|
136
|
+
/** Left join on a subquery — joined columns become nullable. */
|
|
137
|
+
leftJoin<SC>(
|
|
138
|
+
query: Select<SC, any>,
|
|
139
|
+
): Select<SelectColumns & Partial<ColumnTypesOf<SC>>, Columns & SC>
|
|
140
|
+
|
|
141
|
+
/** Right join on a table. */
|
|
142
|
+
rightJoin<JC>(
|
|
143
|
+
table: BuildTable<JC>,
|
|
144
|
+
): Select<SelectColumns & ColumnTypesOf<JC>, Columns & JC>
|
|
145
|
+
/** Right join on a subquery. */
|
|
146
|
+
rightJoin<SC>(
|
|
147
|
+
query: Select<SC, any>,
|
|
148
|
+
): Select<SelectColumns & ColumnTypesOf<SC>, Columns & SC>
|
|
149
|
+
|
|
150
|
+
/** Cross join on a table. */
|
|
151
|
+
crossJoin<JC>(
|
|
152
|
+
table: BuildTable<JC>,
|
|
153
|
+
): Select<SelectColumns & ColumnTypesOf<JC>, Columns & JC>
|
|
154
|
+
/** Cross join on a subquery. */
|
|
155
|
+
crossJoin<SC>(
|
|
156
|
+
query: Select<SC, any>,
|
|
157
|
+
): Select<SelectColumns & ColumnTypesOf<SC>, Columns & SC>
|
|
158
|
+
|
|
159
|
+
/** Add an ON condition to the most recent join. */
|
|
160
|
+
on(left: ColumnRef, operator: BinaryOperator, right: ColumnRef): this
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Insert statement API. */
|
|
164
|
+
export interface Insert<InsertColumns, Columns, Returning> {
|
|
165
|
+
/** Execute the statement. */
|
|
166
|
+
execute(): Promise<Returning>
|
|
167
|
+
/** Specify a `RETURNING *` clause. */
|
|
168
|
+
returning(all: "*"): Insert<InsertColumns, Columns, ColumnTypesOf<Columns>[]>
|
|
169
|
+
/** Specify a `RETURNING` clause with the chosen columns. */
|
|
170
|
+
returning<Column extends keyof Columns>(
|
|
171
|
+
...columns: Column[]
|
|
172
|
+
): Insert<InsertColumns, Columns, ColumnTypesOf<Pick<Columns, Column>>[]>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Update statement API. */
|
|
176
|
+
export interface Update<Columns, Returning> extends HasWhereClause<Columns> {
|
|
177
|
+
/** Execute the statement. */
|
|
178
|
+
execute(): Promise<Returning>
|
|
179
|
+
/** Specify a `RETURNING *` clause. */
|
|
180
|
+
returning(all: "*"): Update<Columns, Columns[]>
|
|
181
|
+
/** Specify a `RETURNING` clause with the chosen columns. */
|
|
182
|
+
returning<Column extends keyof Columns>(
|
|
183
|
+
columns: Column[],
|
|
184
|
+
): Update<Columns, Pick<Columns, Column>[]>
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Delete statement API. */
|
|
188
|
+
export interface Delete<Columns, Returning> extends HasWhereClause<Columns> {
|
|
189
|
+
/** Execute the statement. */
|
|
190
|
+
execute(): Promise<Returning>
|
|
191
|
+
/** Specify a `RETURNING *` clause. */
|
|
192
|
+
returning(all: "*"): Delete<Columns, Columns[]>
|
|
193
|
+
/** Specify a `RETURNING` clause with the chosen columns. */
|
|
194
|
+
returning<Column extends keyof Columns>(
|
|
195
|
+
columns: Column[],
|
|
196
|
+
): Delete<Columns, Pick<Columns, Column>[]>
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export type UnaryOperator = ElementOf<typeof UNARY_OPERATORS>
|
|
200
|
+
export type BinaryOperator = ElementOf<typeof BINARY_OPERATORS>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BuildTable, BuildView, SchemaMembers, Schema } from "../schemaBuilder"
|
|
2
|
+
import type { Connection, DatabaseApi } from "./api.types"
|
|
3
|
+
import { DatabaseTable } from "./DatabaseTable"
|
|
4
|
+
import { DatabaseView } from "./DatabaseView"
|
|
5
|
+
|
|
6
|
+
export function createDatabase<M extends SchemaMembers>(
|
|
7
|
+
connection: Connection,
|
|
8
|
+
schema: Schema<M>,
|
|
9
|
+
) {
|
|
10
|
+
const api = { connection }
|
|
11
|
+
for (const [property, member] of Object.entries(schema.tables)) {
|
|
12
|
+
const table = member as BuildTable<unknown>
|
|
13
|
+
Object.defineProperty(api, property, {
|
|
14
|
+
value: new DatabaseTable(connection, table.$meta),
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
for (const [property, member] of Object.entries(schema.views)) {
|
|
18
|
+
const view = member as BuildView
|
|
19
|
+
Object.defineProperty(api, property, {
|
|
20
|
+
value: new DatabaseView(connection, view.$tableData),
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
return api as DatabaseApi<M>
|
|
24
|
+
}
|
package/src/api/index.ts
ADDED
package/src/bun/bun.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import type { DefaultRow, Connection } from "../api/api.types"
|
|
3
|
+
|
|
4
|
+
export class BunConnection implements Connection {
|
|
5
|
+
private readonly db: Database
|
|
6
|
+
|
|
7
|
+
constructor(db: Database) {
|
|
8
|
+
this.db = db
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async query<Row = DefaultRow>(sql: string) {
|
|
12
|
+
return this.db.query(sql).all() as Row[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async script(statements: string[]) {
|
|
16
|
+
this.db.run("BEGIN")
|
|
17
|
+
try {
|
|
18
|
+
for (const s of statements) {
|
|
19
|
+
this.db.run(s)
|
|
20
|
+
}
|
|
21
|
+
this.db.run("COMMIT")
|
|
22
|
+
} catch (error) {
|
|
23
|
+
this.db.run("ROLLBACK")
|
|
24
|
+
throw error
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/bun/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./bun"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { D1Database } from "@cloudflare/workers-types"
|
|
2
|
+
import type { DefaultRow, Connection } from "../api/api.types"
|
|
3
|
+
|
|
4
|
+
export class CloudflareConnection implements Connection {
|
|
5
|
+
private readonly d1: D1Database
|
|
6
|
+
|
|
7
|
+
constructor(d1: D1Database) {
|
|
8
|
+
this.d1 = d1
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async query<Row = DefaultRow>(sql: string) {
|
|
12
|
+
return (await this.d1.prepare(sql).run()).results as Row[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async script(statements: string[]) {
|
|
16
|
+
await this.d1.batch(statements.map((s) => this.d1.prepare(s)))
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./cloudflare"
|
package/src/console.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// oxlint-disable typescript/consistent-type-definitions
|
|
2
|
+
declare global {
|
|
3
|
+
interface Console {
|
|
4
|
+
log(...args: unknown[]): void
|
|
5
|
+
error(...args: unknown[]): void
|
|
6
|
+
warn(...args: unknown[]): void
|
|
7
|
+
info(...args: unknown[]): void
|
|
8
|
+
debug(...args: unknown[]): void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var console: Console
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColumnData,
|
|
3
|
+
TableCheckData,
|
|
4
|
+
TableConstraintData,
|
|
5
|
+
TableData,
|
|
6
|
+
} from "../schemaBuilder"
|
|
7
|
+
|
|
8
|
+
export namespace Compare {
|
|
9
|
+
export function table(ta: TableData, tb: TableData) {
|
|
10
|
+
if (ta.columns.length !== tb.columns.length) {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
if (ta.constraints.length !== tb.constraints.length) {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
for (let c = 0; c < ta.columns.length; c++) {
|
|
17
|
+
const ca = ta.columns[c]
|
|
18
|
+
const cb = tb.columns[c]
|
|
19
|
+
const equals = columnEquals(ca, cb)
|
|
20
|
+
if (!equals) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
for (let c = 0; c < ta.constraints.length; c++) {
|
|
25
|
+
const ca = ta.constraints[c]
|
|
26
|
+
const cb = tb.constraints[c]
|
|
27
|
+
const equals = constraintEquals(ca, cb, ta, tb)
|
|
28
|
+
if (!equals) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function columnEquals(a: ColumnData, b: ColumnData) {
|
|
36
|
+
return (
|
|
37
|
+
a.name === b.name &&
|
|
38
|
+
a.datatype === b.datatype &&
|
|
39
|
+
a.default === b.default &&
|
|
40
|
+
a.notNull === b.notNull &&
|
|
41
|
+
a.unique === b.unique &&
|
|
42
|
+
typeEquals(a.primaryKey, b.primaryKey) &&
|
|
43
|
+
a.primaryKey?.autoincrement === b.primaryKey?.autoincrement &&
|
|
44
|
+
typeEquals(a.foreignKey, b.foreignKey) &&
|
|
45
|
+
a.foreignKey?.column === b.foreignKey?.column &&
|
|
46
|
+
a.foreignKey?.table === b.foreignKey?.table &&
|
|
47
|
+
resolveColumnCheck(a) === resolveColumnCheck(b)
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function constraintEquals(
|
|
52
|
+
a: TableConstraintData,
|
|
53
|
+
b: TableConstraintData,
|
|
54
|
+
ta: TableData,
|
|
55
|
+
tb: TableData,
|
|
56
|
+
) {
|
|
57
|
+
if (a.type !== b.type) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
if (a.type === "check" && b.type === "check") {
|
|
61
|
+
return (
|
|
62
|
+
resolveTableCheck(a.expression, ta) ===
|
|
63
|
+
resolveTableCheck(b.expression, tb)
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
if (a.type !== "check" && b.type !== "check") {
|
|
67
|
+
return a.columns.join(" ") === b.columns.join(" ")
|
|
68
|
+
}
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveColumnCheck(col: ColumnData): string | undefined {
|
|
73
|
+
if (col.check == null) {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
return typeof col.check === "function"
|
|
77
|
+
? col.check(col.name, col)
|
|
78
|
+
: col.check
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resolveTableCheck(
|
|
82
|
+
expression: TableCheckData["expression"],
|
|
83
|
+
table: TableData,
|
|
84
|
+
): string {
|
|
85
|
+
if (typeof expression === "function") {
|
|
86
|
+
const columnNames = Object.fromEntries(
|
|
87
|
+
table.columns.map((c) => [c.name, c.name]),
|
|
88
|
+
)
|
|
89
|
+
return expression(columnNames, table)
|
|
90
|
+
}
|
|
91
|
+
return expression
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function typeEquals(a: unknown, b: unknown) {
|
|
95
|
+
return typeof a === typeof b
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Fixtures, Schema, SchemaMembers, Seeds } from "../schemaBuilder"
|
|
2
|
+
import type { Connection, DefaultRow } from "../api"
|
|
3
|
+
import type { SchemaHack } from "./cowboyMigration.types"
|
|
4
|
+
import { CowboyMigrator } from "./CowboyMigrator"
|
|
5
|
+
import { CowboySeeder } from "./CowboySeeder"
|
|
6
|
+
|
|
7
|
+
export type CowboyOptions = {
|
|
8
|
+
hacks?: SchemaHack[]
|
|
9
|
+
seeds?: Seeds
|
|
10
|
+
fixtures?: Fixtures
|
|
11
|
+
runFixtures?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class CowboyConnection implements Connection {
|
|
15
|
+
private readonly raw: Connection
|
|
16
|
+
private readonly schema: Schema<SchemaMembers>
|
|
17
|
+
private readonly options: CowboyOptions
|
|
18
|
+
private shotFirst = false
|
|
19
|
+
|
|
20
|
+
constructor(raw: Connection, schema: Schema<SchemaMembers>, options?: CowboyOptions) {
|
|
21
|
+
this.raw = raw
|
|
22
|
+
this.schema = schema
|
|
23
|
+
this.options = options ?? {}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async query<Row = DefaultRow>(sql: string) {
|
|
27
|
+
if (!this.shotFirst) {
|
|
28
|
+
await this.shotgun()
|
|
29
|
+
}
|
|
30
|
+
return this.raw.query(sql) as Promise<Row[]>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async script(statements: string[]) {
|
|
34
|
+
if (!this.shotFirst) {
|
|
35
|
+
await this.shotgun()
|
|
36
|
+
}
|
|
37
|
+
await this.raw.script(statements)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async reset() {
|
|
41
|
+
this.shotFirst = false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private async shotgun() {
|
|
45
|
+
this.shotFirst = true
|
|
46
|
+
await new CowboyMigrator(this.raw, this.schema, this.options.hacks ?? []).migrate()
|
|
47
|
+
const hasSeeds = this.options.seeds || (this.options.runFixtures && this.options.fixtures)
|
|
48
|
+
if (hasSeeds) {
|
|
49
|
+
await new CowboySeeder(
|
|
50
|
+
this.raw,
|
|
51
|
+
this.options.seeds ?? {},
|
|
52
|
+
this.options.fixtures ?? {},
|
|
53
|
+
this.options.runFixtures ?? false,
|
|
54
|
+
).seed()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|