@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.
Files changed (58) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +9 -0
  3. package/package.json +52 -0
  4. package/src/api/DatabaseTable.ts +51 -0
  5. package/src/api/DatabaseView.ts +22 -0
  6. package/src/api/api.types.ts +200 -0
  7. package/src/api/createDatabase.ts +24 -0
  8. package/src/api/index.ts +4 -0
  9. package/src/bun/bun.ts +27 -0
  10. package/src/bun/index.ts +1 -0
  11. package/src/cloudflare/cloudflare.ts +18 -0
  12. package/src/cloudflare/index.ts +1 -0
  13. package/src/console.ts +14 -0
  14. package/src/cowboyMigration/Compare.ts +97 -0
  15. package/src/cowboyMigration/CowboyConnection.ts +57 -0
  16. package/src/cowboyMigration/CowboyMigrator.ts +437 -0
  17. package/src/cowboyMigration/CowboySeeder.ts +245 -0
  18. package/src/cowboyMigration/cowboyMigration.types.ts +29 -0
  19. package/src/cowboyMigration/createSchemaHacker.ts +28 -0
  20. package/src/cowboyMigration/index.ts +3 -0
  21. package/src/crypto.d.ts +9 -0
  22. package/src/framework/Format.ts +31 -0
  23. package/src/framework/definitions.d.ts +4 -0
  24. package/src/framework/index.ts +1 -0
  25. package/src/index.ts +10 -0
  26. package/src/queryBuilder/DeleteBuilder.ts +40 -0
  27. package/src/queryBuilder/InsertBuilder.ts +31 -0
  28. package/src/queryBuilder/Mappings.ts +55 -0
  29. package/src/queryBuilder/SelectBuilder.ts +39 -0
  30. package/src/queryBuilder/SelectQueryBuilder.ts +93 -0
  31. package/src/queryBuilder/UpdateBuilder.ts +44 -0
  32. package/src/queryBuilder/addWhereClause.ts +35 -0
  33. package/src/queryBuilder/index.ts +7 -0
  34. package/src/queryBuilder/operators.ts +35 -0
  35. package/src/queryBuilder/queryBuilders.types.ts +105 -0
  36. package/src/queryGenerator/Clause.ts +79 -0
  37. package/src/queryGenerator/generateDelete.ts +18 -0
  38. package/src/queryGenerator/generateInsert.ts +29 -0
  39. package/src/queryGenerator/generateSelect.ts +89 -0
  40. package/src/queryGenerator/generateUpdate.ts +29 -0
  41. package/src/queryGenerator/index.ts +4 -0
  42. package/src/reflection/Reflector.ts +47 -0
  43. package/src/reflection/index.ts +2 -0
  44. package/src/reflection/reflection.types.ts +12 -0
  45. package/src/schemaBuilder/Mapping.ts +44 -0
  46. package/src/schemaBuilder/columnBuilders.ts +67 -0
  47. package/src/schemaBuilder/createFixture.ts +112 -0
  48. package/src/schemaBuilder/createIndex.ts +33 -0
  49. package/src/schemaBuilder/createSchema.ts +23 -0
  50. package/src/schemaBuilder/createTable.ts +80 -0
  51. package/src/schemaBuilder/createView.ts +21 -0
  52. package/src/schemaBuilder/index.ts +9 -0
  53. package/src/schemaBuilder/metadata.ts +73 -0
  54. package/src/schemaBuilder/schemaBuilder.types.ts +139 -0
  55. package/src/schemaGenerator/generateCreateIndex.ts +18 -0
  56. package/src/schemaGenerator/generateCreateTable.ts +68 -0
  57. package/src/schemaGenerator/generateCreateView.ts +13 -0
  58. 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
@@ -0,0 +1,9 @@
1
+ # Outlaw
2
+
3
+ It's sqlite.
4
+
5
+ `bun add @sigitex/outlaw`
6
+
7
+ > **Note:** This package currently exports TypeScript sources directly. A TypeScript-compatible runtime or bundler (Bun, etc.) is required.
8
+
9
+ - TODO: conform to "adapts-to" stuff
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
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./api.types"
2
+ export * from "./DatabaseTable"
3
+ export * from "./DatabaseView"
4
+ export * from "./createDatabase"
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
+ }
@@ -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
+ }