@sigitex/outlaw 1.0.0 → 1.0.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.
Files changed (2) hide show
  1. package/README.md +333 -4
  2. package/package.json +2 -1
package/README.md CHANGED
@@ -1,9 +1,338 @@
1
1
  # Outlaw
2
2
 
3
- It's sqlite.
3
+ A trigger-happy SQLite framework. Type-safe, schema-first, with automatic migrations.
4
4
 
5
- `bun add @sigitex/outlaw`
5
+ ```
6
+ bun add @sigitex/outlaw
7
+ ```
6
8
 
7
- > **Note:** This package currently exports TypeScript sources directly. A TypeScript-compatible runtime or bundler (Bun, etc.) is required.
9
+ Attempts to mirror Sqlite syntax *very* closely. Currently works with Bun and Cloudflare Functions.
8
10
 
9
- - TODO: conform to "adapts-to" stuff
11
+ Define your schema in code, and Outlaw's [cowboy migrations](#cowboy-migrations) automatically diff and converge your database on startup -- no migration files, no CLI steps. Just change your schema and go.
12
+
13
+ > **Note:** This package exports TypeScript sources directly. A TypeScript-compatible runtime or bundler (Bun, etc.) is required.
14
+
15
+ ## Quick Start
16
+
17
+ ```ts
18
+ // 1. Define your schema
19
+ const users = createTable("users", {
20
+ id: integer.primaryKey.autoincrement,
21
+ name: text.notNull,
22
+ email: text.notNull.unique,
23
+ })
24
+
25
+ const schema = createSchema({ users })
26
+
27
+ // 2. Create a connection with auto-migration
28
+ const bun = new BunConnection(new Database("app.db"))
29
+ const connection = new CowboyConnection(bun, schema)
30
+
31
+ // 3. Use the typed database API
32
+ const db = createDatabase(connection, schema)
33
+
34
+ await db.users.insert({ name: "Wyatt", email: "wyatt@earp.com" }).execute()
35
+
36
+ const allUsers = await db.users.select("*").fetch()
37
+ const user = await db.users.select("*").where("id", 1).first()
38
+ ```
39
+
40
+ ## Schema Definition
41
+
42
+ ### Tables
43
+
44
+ Define tables with `createTable`. Each column uses a builder chain starting from a base type.
45
+
46
+ ```ts
47
+ import { createTable, text, integer, real, blob } from "@sigitex/outlaw"
48
+
49
+ const products = createTable("products", {
50
+ id: integer.primaryKey.autoincrement,
51
+ name: text.notNull,
52
+ description: text, // nullable by default
53
+ price: real.notNull,
54
+ image: blob,
55
+ sku: text.notNull.unique,
56
+ })
57
+ ```
58
+
59
+ #### Column Types
60
+
61
+ | Builder | SQLite Type | TypeScript Type |
62
+ |-----------|-------------|-----------------|
63
+ | `text` | TEXT | `string` |
64
+ | `integer` | INTEGER | `number` |
65
+ | `real` | REAL | `number` |
66
+ | `blob` | BLOB | `ArrayBuffer` |
67
+
68
+ #### Column Modifiers
69
+
70
+ Modifiers are chained as properties or method calls:
71
+
72
+ ```ts
73
+ text.notNull // NOT NULL
74
+ integer.primaryKey // PRIMARY KEY
75
+ integer.primaryKey.autoincrement // PRIMARY KEY AUTOINCREMENT
76
+ text.unique // UNIQUE
77
+ text.default("'unknown'") // DEFAULT 'unknown'
78
+ text.check("length(name) > 0") // CHECK constraint
79
+ integer.foreignKey.references(other.id) // FOREIGN KEY
80
+ ```
81
+
82
+ Each modifier can only be used once per column -- the type system removes it after use.
83
+
84
+ #### Type Mappings
85
+
86
+ Map SQLite storage types to richer TypeScript types:
87
+
88
+ ```ts
89
+ const events = createTable("events", {
90
+ id: integer.primaryKey.autoincrement,
91
+ active: integer.notNull.map.boolean, // stored as 0/1, typed as boolean
92
+ createdAt: integer.notNull.map.timestamp, // stored as epoch ms, typed as Date
93
+ scheduledFor: text.map.date, // stored as ISO string, typed as Date
94
+ metadata: text.map.json<{ tags: string[] }>(), // stored as JSON string, typed as object
95
+ })
96
+ ```
97
+
98
+ #### Table Constraints
99
+
100
+ Add composite constraints after column definitions:
101
+
102
+ ```ts
103
+ const memberships = createTable("memberships", {
104
+ userId: integer.notNull.foreignKey.references(users.id),
105
+ groupId: integer.notNull.foreignKey.references(groups.id),
106
+ })
107
+ .primaryKey("userId", "groupId")
108
+ .unique("userId", "groupId")
109
+ .check("userId != groupId")
110
+ ```
111
+
112
+ ### Views
113
+
114
+ Define views from query builders on existing tables:
115
+
116
+ ```ts
117
+ import { createView } from "@sigitex/outlaw"
118
+
119
+ const activeUsers = createView("active_users",
120
+ users.select("id", "name").where("active", 1)
121
+ )
122
+ ```
123
+
124
+ ### Indexes
125
+
126
+ ```ts
127
+ import { createIndex, createUniqueIndex } from "@sigitex/outlaw"
128
+
129
+ const emailIndex = createUniqueIndex("idx_users_email").on(users.email)
130
+ const nameIndex = createIndex("idx_users_name").on(users.name)
131
+ ```
132
+
133
+ ### Schema
134
+
135
+ Group tables, views, and indexes into a schema:
136
+
137
+ ```ts
138
+ import { createSchema } from "@sigitex/outlaw"
139
+
140
+ const schema = createSchema({
141
+ users,
142
+ products,
143
+ memberships,
144
+ activeUsers,
145
+ emailIndex,
146
+ nameIndex,
147
+ })
148
+ ```
149
+
150
+ ## Connections
151
+
152
+ Outlaw abstracts over any SQLite connection via the `Connection` interface:
153
+
154
+ ```ts
155
+ type Connection = {
156
+ query<Row>(sql: string): Promise<Row[]>
157
+ script(statements: string[]): Promise<void>
158
+ }
159
+ ```
160
+
161
+ Two built-in adapters are provided:
162
+
163
+ ### Bun
164
+
165
+ ```ts
166
+ import { BunConnection } from "@sigitex/outlaw/bun"
167
+ import { Database } from "bun:sqlite"
168
+
169
+ const connection = new BunConnection(new Database("app.db"))
170
+ ```
171
+
172
+ ### Cloudflare D1
173
+
174
+ ```ts
175
+ import { CloudflareConnection } from "@sigitex/outlaw/cloudflare"
176
+
177
+ // Inside a Cloudflare Worker
178
+ const connection = new CloudflareConnection(env.DB)
179
+ ```
180
+
181
+ ## Database API
182
+
183
+ `createDatabase` returns a typed object with an accessor for each table and view in the schema.
184
+
185
+ ```ts
186
+ import { createDatabase } from "@sigitex/outlaw"
187
+
188
+ const db = createDatabase(connection, schema)
189
+ ```
190
+
191
+ ### Select
192
+
193
+ ```ts
194
+ // Select all columns
195
+ const rows = await db.users.select("*").fetch()
196
+
197
+ // Select specific columns
198
+ const names = await db.users.select("name", "email").fetch()
199
+
200
+ // Single result (throws if no match)
201
+ const user = await db.users.select("*").where("id", 1).first()
202
+
203
+ // Filtering
204
+ db.users.select("*")
205
+ .where("name", "Wyatt") // equality
206
+ .where("age", ">=", 21) // comparison operators
207
+ .where("deletedAt", "is null") // unary operators
208
+
209
+ // Sorting, pagination
210
+ db.users.select("*")
211
+ .orderBy([["name", "asc"], ["id", "desc"]])
212
+ .limit(10)
213
+ .offset(20)
214
+ .fetch()
215
+ ```
216
+
217
+ ### Joins
218
+
219
+ ```ts
220
+ db.users.select("*")
221
+ .join(posts).on(users.id, "=", posts.userId)
222
+ .fetch()
223
+
224
+ db.users.select("*")
225
+ .leftJoin(posts).on(users.id, "=", posts.userId)
226
+ .fetch()
227
+ ```
228
+
229
+ Join types: `join`, `leftJoin`, `rightJoin`, `crossJoin`. Each accepts a table or a subquery.
230
+
231
+ ### Insert
232
+
233
+ ```ts
234
+ // Single row
235
+ await db.users.insert({ name: "Doc", email: "doc@ok.com" }).execute()
236
+
237
+ // With returning
238
+ const [inserted] = await db.users
239
+ .insert({ name: "Doc", email: "doc@ok.com" })
240
+ .returning("*")
241
+ .execute()
242
+ ```
243
+
244
+ ### Update
245
+
246
+ ```ts
247
+ await db.users
248
+ .update({ name: "Morgan" })
249
+ .where("id", 3)
250
+ .execute()
251
+
252
+ // With returning
253
+ const updated = await db.users
254
+ .update({ name: "Morgan" })
255
+ .where("id", 3)
256
+ .returning("*")
257
+ .execute()
258
+ ```
259
+
260
+ ### Delete
261
+
262
+ ```ts
263
+ await db.users
264
+ .delete()
265
+ .where("id", 3)
266
+ .execute()
267
+
268
+ // With returning
269
+ const deleted = await db.users
270
+ .delete()
271
+ .where("id", 3)
272
+ .returning("*")
273
+ .execute()
274
+ ```
275
+
276
+ ## Cowboy Migrations
277
+
278
+ Wrap any connection with `CowboyConnection` to enable automatic schema migration. On the first query, Outlaw diffs the database against your schema and applies changes -- creating missing tables, rebuilding tables whose columns have changed, and managing views and indexes.
279
+
280
+ ```ts
281
+ import { CowboyConnection } from "@sigitex/outlaw"
282
+
283
+ const connection = new CowboyConnection(rawConnection, schema)
284
+ ```
285
+
286
+ Schema metadata is stored in a `cowboy_migration` table. When columns change, Outlaw uses an interim table pattern: create the new table, copy data, drop the old one, rename.
287
+
288
+ ### Schema Hacks
289
+
290
+ Destructive changes (renaming or dropping tables/columns) require explicit hints via `createSchemaHacker`, so data isn't silently lost:
291
+
292
+ ```ts
293
+ import { createSchemaHacker } from "@sigitex/outlaw"
294
+
295
+ const hack = createSchemaHacker()
296
+
297
+ hack.renamed.table("old_users", "users")
298
+ hack.renamed.column("users", "firstName", "name")
299
+ hack.dropped.table("legacy_data")
300
+ hack.dropped.column("users", "deprecated_field")
301
+
302
+ const connection = new CowboyConnection(rawConnection, schema, {
303
+ hacks: hack.hacks,
304
+ })
305
+ ```
306
+
307
+ ## Fixtures and Seeds
308
+
309
+ Pre-populate tables with `createFixture` (or its alias `createSeed`). Fixtures can use templates to provide default values and `RefBy` to reference rows in other tables.
310
+
311
+ ```ts
312
+ import { createFixture } from "@sigitex/outlaw"
313
+
314
+ // Simple fixture
315
+ const userFixture = createFixture(users, [
316
+ { name: "Wyatt", email: "wyatt@earp.com" },
317
+ { name: "Doc", email: "doc@ok.com" },
318
+ ])
319
+
320
+ // Fixture with a template for default values
321
+ const postFixture = createFixture(posts,
322
+ { createdAt: () => Date.now() }, // template: default for createdAt
323
+ [
324
+ { title: "First Post", userId: users.by.id(1) }, // RefBy
325
+ { title: "Second Post", userId: users.by.id(1) },
326
+ ],
327
+ )
328
+
329
+ // Pass to CowboyConnection
330
+ const connection = new CowboyConnection(rawConnection, schema, {
331
+ fixtures: { userFixture, postFixture },
332
+ runFixtures: true,
333
+ })
334
+ ```
335
+
336
+ ## License
337
+
338
+ MIT
package/package.json CHANGED
@@ -2,6 +2,7 @@
2
2
  "name": "@sigitex/outlaw",
3
3
  "type": "module",
4
4
  "license": "MIT",
5
+ "description": "A trigger-happy sqlite framework.",
5
6
  "author": {
6
7
  "name": "Sigitex",
7
8
  "url": "http://github.com/sigitex"
@@ -48,5 +49,5 @@
48
49
  "files": [
49
50
  "src"
50
51
  ],
51
- "version": "1.0.0"
52
+ "version": "1.0.1"
52
53
  }