@xwxfox/foxdb 0.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.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xwxfox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,274 @@
1
+ # foxdb ~ a typed sqlite orm for bun :3
2
+
3
+ > built on top of [typebox](https://github.com/sinclairzx81/typebox) schemas and `bun:sqlite`. fast, tiny, fully typed :3
4
+
5
+ foxdb gives you a repository-style orm where your database schema *is* your typescript types. no codegen, no decorators, no magic. just plain typebox objects that compile to sqlite tables and give you autocomplete everywhere ~
6
+
7
+
8
+ > Credit to [@deadlinecode](https://github.com/deadlinecode) for the original idea and base :3
9
+
10
+ ## quick start
11
+
12
+ ```bash
13
+ bun add @xwxfox/foxdb
14
+ ```
15
+
16
+ ```typescript
17
+ import { Object, String, Number, Integer } from "typebox";
18
+ import { createORM, table } from "@xwxfox/foxdb";
19
+
20
+ const UserSchema = Object({
21
+ id: String(),
22
+ name: String(),
23
+ email: String(),
24
+ age: Integer(),
25
+ });
26
+
27
+ const orm = createORM({
28
+ tables: {
29
+ users: table(UserSchema, (s) => ({
30
+ primaryKey: s.id,
31
+ indexes: [{ columns: [s.email] }],
32
+ })),
33
+ },
34
+ });
35
+
36
+ // insert
37
+ const user = orm.users.insert({
38
+ id: "usr-1",
39
+ name: "alice",
40
+ email: "alice@example.com",
41
+ age: 30,
42
+ });
43
+
44
+ // find
45
+ const found = orm.users.findById("usr-1");
46
+
47
+ // update
48
+ orm.users.update({ id: "usr-1", name: "alice smith" });
49
+
50
+ // query
51
+ const adults = orm.users.findMany({
52
+ where: { age: { gte: 18 } },
53
+ orderBy: { column: "name", direction: "ASC" },
54
+ });
55
+
56
+ // paginate
57
+ const page = orm.users.findPage({
58
+ where: { age: { gte: 18 } },
59
+ limit: 10,
60
+ offset: 0,
61
+ });
62
+
63
+ orm._close();
64
+ ```
65
+
66
+ ## why foxdb
67
+
68
+ - **zero codegen** - your typebox schema *is* the source of truth. no `prisma generate`, no migration files to keep in sync :3
69
+ - **fully typed** - every query, insert, update, and relation is typed end-to-end. try passing the wrong column name and typescript will bonk you
70
+ - **tiny** - ~2kb overhead on top of `bun:sqlite`. no external query builder, no connection pool, no bloat
71
+ - **relations** - scalar relations (lazy) and sub-table relations (batch resolved) with a fluent builder
72
+ - **events** - listen to table events (`insert`, `update`, `read`, `write`, etc.) typed to your schema. zero overhead unless you subscribe ~
73
+ - **lifecycle hooks** - `onStart`, `onReady`, `onShutdown`, `onExit` for seeding, migrating, cleaning up
74
+ - **sub-tables** - arrays of objects are automatically split into separate sqlite tables with proper indexing
75
+
76
+ ## core concepts
77
+
78
+ ### schemas
79
+
80
+ use [typebox](https://github.com/sinclairzx81/typebox) to define your data shape. foxdb supports all scalar types (`String`, `Number`, `Integer`, `Boolean`, `Literal`) plus arrays of objects (sub-tables).
81
+
82
+ ```typescript
83
+ import { Object, String, Number, Integer, Array, Optional } from "typebox";
84
+
85
+ const LineItemSchema = Object({
86
+ sku: String(),
87
+ qty: Integer(),
88
+ price: Number(),
89
+ });
90
+
91
+ const OrderSchema = Object({
92
+ id: String(),
93
+ customerId: String(),
94
+ status: String(),
95
+ total: Number(),
96
+ lineItems: Array(LineItemSchema), // becomes a sub-table ~
97
+ });
98
+ ```
99
+
100
+ ### tables
101
+
102
+ the `table()` helper turns a schema into a table descriptor. you pick the primary key, add indexes, and optionally enable timestamps.
103
+
104
+ ```typescript
105
+ table(OrderSchema, (s) => ({
106
+ primaryKey: s.id,
107
+ indexes: [
108
+ { columns: [s.customerId] },
109
+ { columns: [s.status] },
110
+ ],
111
+ timestamps: true, // adds createdAt / updatedAt
112
+ }))
113
+ ```
114
+
115
+ ### repositories
116
+
117
+ every table becomes a repository on the orm object. all crud methods are fully typed:
118
+
119
+ - `insert(data)` - insert a record
120
+ - `insertMany(records)` - batch insert in a transaction
121
+ - `findById(id)` - find by primary key
122
+ - `findMany(opts)` - query with where, orderBy, limit, offset
123
+ - `findPage(opts)` - findMany + total count for pagination
124
+ - `findOne(opts)` - findMany with limit 1
125
+ - `update(data)` - merge partial data (must include pk)
126
+ - `upsert(opts)` - insert or update on conflict
127
+ - `deleteById(id)` - delete by pk
128
+ - `deleteWhere(where)` - delete matching records
129
+ - `count(where?)` - count matching records
130
+ - `flush()` - truncate the table and sub-tables
131
+
132
+ ### relations
133
+
134
+ define cross-table relations with a fluent builder:
135
+
136
+ ```typescript
137
+ const orm = createORM({
138
+ tables: {
139
+ orders: table(OrderSchema, (s) => ({ primaryKey: s.id })),
140
+ products: table(ProductSchema, (s) => ({ primaryKey: s.sku })),
141
+ },
142
+ relations: (r) => [
143
+ r.from("orders")
144
+ .subTable("lineItems", "sku")
145
+ .to("products", "sku", { as: "product" }),
146
+ ],
147
+ });
148
+
149
+ // sub-table items now have a .product property
150
+ const order = orm.orders.findById("ord-1");
151
+ for (const item of order.lineItems) {
152
+ console.log(item.product.name); // lazy or batch resolved :3
153
+ }
154
+ ```
155
+
156
+ ### events
157
+
158
+ listen to table or lifecycle events with full type safety:
159
+
160
+ ```typescript
161
+ // table-specific fine-grained event
162
+ const off = orm._events.on("users", "insert", (e) => {
163
+ console.log(`user ${e.data.id} inserted at ${e.timestamp}`);
164
+ });
165
+
166
+ // broad category - catches all writes (insert, update, upsert)
167
+ orm._events.on("users", "write", (e) => {
168
+ console.log(`write op: ${e.operation}`);
169
+ });
170
+
171
+ // lifecycle events
172
+ orm._events.on("ready", (e) => {
173
+ console.log("orm is ready ~");
174
+ });
175
+
176
+ // cleanup
177
+ off();
178
+ ```
179
+
180
+ events have **zero overhead** unless you subscribe. the event bus only builds payloads when a listener exists.
181
+
182
+ ### lifecycle hooks
183
+
184
+ hook into startup and shutdown to seed, migrate, or clean up:
185
+
186
+ ```typescript
187
+ const orm = createORM({
188
+ tables: { /* ... */ },
189
+
190
+ seed: (o) => {
191
+ o.products.insert({ sku: "WIDGET", name: "widget", price: 9.99 });
192
+ },
193
+
194
+ onReady: (ctx) => {
195
+ console.log("tables:", ctx.tables.join(", "));
196
+ },
197
+
198
+ onShutdown: (ctx) => {
199
+ ctx.orm.activity.insert({
200
+ id: "shutdown",
201
+ message: "shutting down",
202
+ level: "info",
203
+ });
204
+ },
205
+
206
+ rebuildOnLaunch: true, // wipe db on every start
207
+ unlinkDbFilesOnExit: true, // delete .db files on close
208
+ });
209
+ ```
210
+
211
+ ### configuration
212
+
213
+ ```typescript
214
+ createORM({
215
+ path: "myapp.db", // sqlite file path (default: ":memory:")
216
+ cacheSize: -64000, // sqlite cache size in pages
217
+ busyTimeout: 5000, // ms to wait for write locks
218
+ synchronous: "NORMAL", // pragma synchronous level
219
+ rebuildOnLaunch: false, // wipe and rebuild on start
220
+ flushOnStart: ["logs"], // truncate tables before seeding
221
+ dropOnExit: ["temp"], // drop tables before close
222
+ autoMigrate: true, // run migrations on startup
223
+ migrations: { dir: "./migrations" },
224
+ errorPolicy: "throw", // "throw" | "emit" | "emit-swallow" | "crash"
225
+ unlinkDbFilesOnExit: false, // true | "onlyGraceful" | "any"
226
+ })
227
+ ```
228
+
229
+ ## error handling
230
+
231
+ foxdb uses `ORMError` with invisible trace context. every throw includes the operation name, table, sql, and parameters that led to the error:
232
+
233
+ ```typescript
234
+ try {
235
+ orm.users.insert({ id: null, name: "oops" });
236
+ } catch (e) {
237
+ if (e instanceof ORMError) {
238
+ console.log(e.code); // "VALIDATION_FAILED"
239
+ console.log(e.trace); // [{ label: "repository.insert", time: ... }]
240
+ console.log(e.context); // { table: "users" }
241
+ }
242
+ }
243
+ ```
244
+
245
+ configure the error policy to crash, emit, swallow, or just throw.
246
+
247
+ ## migrations
248
+
249
+ foxdb can auto-run migrations on startup. create migration files and point `migrations.dir` at them:
250
+
251
+ ```typescript
252
+ // migrations/001-init.ts
253
+ import type { Migration } from "@xwxfox/foxdb";
254
+
255
+ export default {
256
+ name: "init",
257
+ date: "2024-01-15",
258
+ up(db) {
259
+ // raw sql via db.exec() if needed
260
+ },
261
+ } satisfies Migration;
262
+ ```
263
+
264
+ ```typescript
265
+ createORM({
266
+ tables: { /* ... */ },
267
+ migrations: { dir: "./migrations" },
268
+ autoMigrate: true,
269
+ });
270
+ ```
271
+
272
+ ## license
273
+
274
+ mit uwu