@b9g/zen 0.1.2 → 0.1.3
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/CHANGELOG.md +22 -0
- package/README.md +120 -99
- package/package.json +3 -3
- package/{chunk-NBXBBEMA.js → src/_chunks/chunk-2C6KOX4F.js} +1 -1
- package/{chunk-ARUUB3H4.js → src/_chunks/chunk-2R6FDKLS.js} +1 -1
- package/{chunk-BEX6VPES.js → src/_chunks/chunk-DKLSJISE.js} +24 -0
- package/{ddl-OT6HPLQY.js → src/_chunks/ddl-32B7E53E.js} +2 -2
- package/src/bun.js +4 -4
- package/src/impl/builtins.d.ts +52 -0
- package/src/impl/database.d.ts +495 -0
- package/src/impl/ddl.d.ts +34 -0
- package/src/impl/errors.d.ts +195 -0
- package/src/impl/query.d.ts +249 -0
- package/src/impl/sql.d.ts +47 -0
- package/src/impl/table.d.ts +961 -0
- package/src/impl/template.d.ts +75 -0
- package/src/mysql.js +3 -3
- package/src/postgres.js +3 -3
- package/src/sqlite.js +3 -3
- package/src/zen.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.3] - 2025-12-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Validation that compound constraints (`indexes`, `unique`, `references` options) have 2+ fields
|
|
13
|
+
- Single-field constraints now throw `TableDefinitionError` with helpful message pointing to field-level API
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **TypeScript types now work in published package** - Updated libuild to fix module augmentation and `.d.ts` path resolution
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- New tagline: "Define Zod tables. Write raw SQL. Get typed objects."
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
|
|
25
|
+
- Added `.db.inserted()`, `.db.updated()`, `.db.upserted()` documentation with examples
|
|
26
|
+
- Added compound unique constraints example
|
|
27
|
+
- Fixed table naming consistency (all plural)
|
|
28
|
+
- Various README cleanups and improvements
|
|
29
|
+
|
|
8
30
|
## [0.1.2] - 2025-12-22
|
|
9
31
|
|
|
10
32
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ZenDB
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Define Zod tables. Write raw SQL. Get typed objects.
|
|
4
4
|
|
|
5
5
|
[Website](https://zendb.org) · [GitHub](https://github.com/bikeshaving/zen) · [npm](https://www.npmjs.com/package/@b9g/zen)
|
|
6
6
|
|
|
@@ -47,7 +47,7 @@ import SQLiteDriver from "@b9g/zen/sqlite";
|
|
|
47
47
|
const driver = new SQLiteDriver("file:app.db");
|
|
48
48
|
|
|
49
49
|
// 1. Define tables
|
|
50
|
-
|
|
50
|
+
let Users = table("users", {
|
|
51
51
|
id: z.string().uuid().db.primary().db.auto(),
|
|
52
52
|
email: z.string().email().db.unique(),
|
|
53
53
|
name: z.string(),
|
|
@@ -73,14 +73,14 @@ db.addEventListener("upgradeneeded", (e) => {
|
|
|
73
73
|
}
|
|
74
74
|
if (e.oldVersion < 2) {
|
|
75
75
|
// Evolve schema: add avatar column (safe, additive)
|
|
76
|
-
|
|
76
|
+
Users = table("users", {
|
|
77
77
|
id: z.string().uuid().db.primary().db.auto(),
|
|
78
78
|
email: z.string().email().db.unique(),
|
|
79
79
|
name: z.string(),
|
|
80
|
-
avatar: z.string().optional(),
|
|
80
|
+
avatar: z.string().optional(), // new field
|
|
81
81
|
});
|
|
82
|
-
await db.ensureTable(
|
|
83
|
-
await db.ensureConstraints(
|
|
82
|
+
await db.ensureTable(Users); // adds missing columns/indexes
|
|
83
|
+
await db.ensureConstraints(Users); // applies new constraints on existing data
|
|
84
84
|
}
|
|
85
85
|
})());
|
|
86
86
|
});
|
|
@@ -115,14 +115,8 @@ await db.update(Users, {name: "Alice Smith"}, user.id);
|
|
|
115
115
|
Zen is the missing link between SQL and typed data. By writing tables with Zod schema, you get idempotent migration helpers, typed CRUD, normalized object references, and many features other database clients cannot provide.
|
|
116
116
|
|
|
117
117
|
### What Zen is not:
|
|
118
|
-
- **Zen is not a query builder** — Rather than
|
|
119
|
-
|
|
120
|
-
db.get(Posts)`
|
|
121
|
-
WHERE ${Posts.cols.published} = ${true}
|
|
122
|
-
ORDER BY ${Posts.cols.publishDate}
|
|
123
|
-
DESC LIMIT 20
|
|
124
|
-
```.
|
|
125
|
-
- **Zen is not an ORM** — Tables are not classes, they are Zod-powered singletons which provide schema-aware SQL-fragment helpers. These tables can be passed to CRUD helpers to validate writes, generate DDL, and normalize joined data into an object graph.
|
|
118
|
+
- **Zen is not a query builder** — Rather than building SQL with fluent chains `.where().orderBy().limit()`, you write it directly with templates: `` db.all(Posts)`WHERE published = ${true} ORDER BY created_at DESC LIMIT 20` `` Helper functions help you write the tedious parts of SQL without hiding it or limiting your queries.
|
|
119
|
+
- **Zen is not an ORM** — Tables are not classes. They are Zod-powered singletons which provide schema-aware utilities. These tables can be used to validate writes, generate DDL, and deduplicate joined data.
|
|
126
120
|
- **Zen is not a startup** — Zen is an open-source library, not a venture-backed SaaS. There will never be a managed “ZenDB” instance or a “Zen Studio.” The library is a thin wrapper around Zod and JavaScript SQL drivers, with a focus on runtime abstractions rather than complicated tooling.
|
|
127
121
|
|
|
128
122
|
### Safety
|
|
@@ -133,9 +127,6 @@ Zen is the missing link between SQL and typed data. By writing tables with Zod s
|
|
|
133
127
|
- **No destructive helpers** — No `dropColumn()`, `dropTable()`, `renameColumn()`
|
|
134
128
|
- **No automatic migrations** — Schema changes are explicit in upgrade events
|
|
135
129
|
|
|
136
|
-
Migrations are **additive and idempotent** by design. Use `ensureColumn()`, `ensureIndex()`, `copyColumn()` for safe schema evolution. Breaking changes require multi-step migrations. Rollbacks are new forward migrations.
|
|
137
|
-
|
|
138
|
-
|
|
139
130
|
## Table Definitions
|
|
140
131
|
|
|
141
132
|
```typescript
|
|
@@ -214,10 +205,37 @@ const Users = table("users", {
|
|
|
214
205
|
|
|
215
206
|
// id and createdAt are optional - auto-generated if not provided
|
|
216
207
|
const user = await db.insert(Users, {name: "Alice"});
|
|
217
|
-
user.id;
|
|
208
|
+
user.id; // "550e8400-e29b-41d4-a716-446655440000"
|
|
218
209
|
user.createdAt; // 2024-01-15T10:30:00.000Z
|
|
219
210
|
```
|
|
220
211
|
|
|
212
|
+
**Default values with `.db.inserted()`, `.db.updated()`, `.db.upserted()`:**
|
|
213
|
+
|
|
214
|
+
These methods set default values for write operations. They accept JS functions or SQL builtins (`NOW`, `TODAY`, `CURRENT_TIMESTAMP`, `CURRENT_DATE`, `CURRENT_TIME`):
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import {z, table, NOW} from "@b9g/zen";
|
|
218
|
+
|
|
219
|
+
const Posts = table("posts", {
|
|
220
|
+
id: z.string().uuid().db.primary().db.auto(),
|
|
221
|
+
title: z.string(),
|
|
222
|
+
// JS function — runs client-side
|
|
223
|
+
slug: z.string().db.inserted(() => generateSlug()),
|
|
224
|
+
// SQL builtin — runs database-side
|
|
225
|
+
createdAt: z.date().db.inserted(NOW),
|
|
226
|
+
updatedAt: z.date().db.upserted(NOW), // set on insert AND update
|
|
227
|
+
viewCount: z.number().db.inserted(() => 0).db.updated(() => 0), // reset on update
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
| Method | When applied | Field becomes optional for |
|
|
232
|
+
|--------|--------------|---------------------------|
|
|
233
|
+
| `.db.inserted(value)` | INSERT only | insert |
|
|
234
|
+
| `.db.updated(value)` | UPDATE only | update |
|
|
235
|
+
| `.db.upserted(value)` | INSERT and UPDATE | insert and update |
|
|
236
|
+
|
|
237
|
+
**Why not Zod's `.default()`?** Zod's `.default()` applies at *parse time*, not *write time*. This means defaults would be applied when reading data, not when inserting. Zen throws an error if you use `.default()` — use `.db.inserted()` instead.
|
|
238
|
+
|
|
221
239
|
**Automatic JSON encoding/decoding:**
|
|
222
240
|
|
|
223
241
|
Objects (`z.object()`) and arrays (`z.array()`) are automatically serialized to JSON when stored and parsed back when read:
|
|
@@ -238,7 +256,7 @@ const settings = await db.insert(Settings, {
|
|
|
238
256
|
|
|
239
257
|
// On read: JSON strings are parsed back to objects/arrays
|
|
240
258
|
settings.config.theme; // "dark" (object, not string)
|
|
241
|
-
settings.tags[0];
|
|
259
|
+
settings.tags[0]; // "admin" (array, not string)
|
|
242
260
|
```
|
|
243
261
|
|
|
244
262
|
**Custom encoding/decoding:**
|
|
@@ -246,7 +264,7 @@ settings.tags[0]; // "admin" (array, not string)
|
|
|
246
264
|
Override automatic JSON encoding with custom transformations:
|
|
247
265
|
|
|
248
266
|
```typescript
|
|
249
|
-
const
|
|
267
|
+
const Articles = table("articles", {
|
|
250
268
|
id: z.string().db.primary(),
|
|
251
269
|
// Store array as CSV instead of JSON
|
|
252
270
|
tags: z.array(z.string())
|
|
@@ -255,8 +273,8 @@ const Custom = table("custom", {
|
|
|
255
273
|
.db.type("TEXT"), // Required: explicit column type for DDL
|
|
256
274
|
});
|
|
257
275
|
|
|
258
|
-
await db.insert(
|
|
259
|
-
// Stored as: tags='
|
|
276
|
+
await db.insert(Articles, {id: "a1", tags: ["news", "tech", "featured"]});
|
|
277
|
+
// Stored as: tags='news,tech,featured' (not '["news","tech","featured"]')
|
|
260
278
|
```
|
|
261
279
|
|
|
262
280
|
**Explicit column types:**
|
|
@@ -301,14 +319,16 @@ const posts = await db.all([Posts, Users.active])`
|
|
|
301
319
|
`;
|
|
302
320
|
```
|
|
303
321
|
|
|
304
|
-
**Compound indexes** via table options:
|
|
322
|
+
**Compound indexes and unique constraints** via table options:
|
|
305
323
|
```typescript
|
|
306
324
|
const Posts = table("posts", {
|
|
307
325
|
id: z.string().db.primary(),
|
|
308
326
|
authorId: z.string(),
|
|
327
|
+
slug: z.string(),
|
|
309
328
|
createdAt: z.date(),
|
|
310
329
|
}, {
|
|
311
330
|
indexes: [["authorId", "createdAt"]],
|
|
331
|
+
unique: [["authorId", "slug"]], // unique together
|
|
312
332
|
});
|
|
313
333
|
```
|
|
314
334
|
|
|
@@ -355,16 +375,16 @@ type Post = Row<typeof Posts>;
|
|
|
355
375
|
// Post includes: id, title, authorId, titleUpper, tags
|
|
356
376
|
|
|
357
377
|
const posts = await db.all([Posts, Users, PostTags, Tags])`
|
|
358
|
-
JOIN
|
|
359
|
-
LEFT JOIN
|
|
360
|
-
LEFT JOIN
|
|
378
|
+
JOIN ${Users} ON ${Users.on(Posts)}
|
|
379
|
+
LEFT JOIN ${PostTags} ON ${PostTags.cols.postId} = ${Posts.cols.id}
|
|
380
|
+
LEFT JOIN ${Tags} ON ${Tags.on(PostTags)}
|
|
361
381
|
`;
|
|
362
382
|
|
|
363
383
|
const post = posts[0];
|
|
364
384
|
post.titleUpper; // "HELLO WORLD" — typed as string
|
|
365
|
-
post.tags;
|
|
366
|
-
Object.keys(post);
|
|
367
|
-
JSON.stringify(post);
|
|
385
|
+
post.tags; // ["javascript", "typescript"] — traverses relationships
|
|
386
|
+
Object.keys(post); // ["id", "title", "authorId", "author"] (no derived props)
|
|
387
|
+
JSON.stringify(post); // Excludes derived properties (non-enumerable)
|
|
368
388
|
```
|
|
369
389
|
|
|
370
390
|
Derived properties:
|
|
@@ -376,9 +396,9 @@ Derived properties:
|
|
|
376
396
|
|
|
377
397
|
**Partial selects** with `pick()`:
|
|
378
398
|
```typescript
|
|
379
|
-
const
|
|
380
|
-
const posts = await db.all([Posts,
|
|
381
|
-
JOIN
|
|
399
|
+
const UserSummaries = Users.pick("id", "name");
|
|
400
|
+
const posts = await db.all([Posts, UserSummaries])`
|
|
401
|
+
JOIN ${UserSummaries} ON ${UserSummaries.on(Posts)}
|
|
382
402
|
`;
|
|
383
403
|
// posts[0].author has only id and name
|
|
384
404
|
```
|
|
@@ -470,6 +490,29 @@ await db.exec`CREATE INDEX idx_posts_author ON ${Posts}(${Posts.cols.authorId})`
|
|
|
470
490
|
const count = await db.val<number>`SELECT COUNT(*) FROM ${Posts}`;
|
|
471
491
|
```
|
|
472
492
|
|
|
493
|
+
## CRUD Helpers
|
|
494
|
+
```typescript
|
|
495
|
+
// Insert with Zod validation (uses RETURNING to get actual row)
|
|
496
|
+
const user = await db.insert(Users, {
|
|
497
|
+
email: "alice@example.com",
|
|
498
|
+
name: "Alice",
|
|
499
|
+
});
|
|
500
|
+
// Returns actual row from DB, including auto-generated id and DB-computed defaults
|
|
501
|
+
const userId = user.id;
|
|
502
|
+
|
|
503
|
+
// Update by primary key (uses RETURNING)
|
|
504
|
+
const updated = await db.update(Users, {name: "Bob"}, userId);
|
|
505
|
+
|
|
506
|
+
// Delete by primary key
|
|
507
|
+
await db.delete(Users, userId);
|
|
508
|
+
|
|
509
|
+
// Soft delete (sets deletedAt timestamp, requires softDelete() field)
|
|
510
|
+
await db.softDelete(Users, userId);
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**RETURNING support:** `insert()` and `update()` use `RETURNING *` on SQLite and PostgreSQL to return the actual row from the database, including DB-computed defaults and triggers. MySQL falls back to a separate SELECT.
|
|
514
|
+
|
|
515
|
+
|
|
473
516
|
## Fragment Helpers
|
|
474
517
|
|
|
475
518
|
Type-safe SQL fragments as methods on Table objects:
|
|
@@ -519,29 +562,6 @@ const posts = await db.all(Posts)`WHERE ${Posts.in("id", [])}`;
|
|
|
519
562
|
// → WHERE 1 = 0
|
|
520
563
|
```
|
|
521
564
|
|
|
522
|
-
## CRUD Helpers
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
// Insert with Zod validation (uses RETURNING to get actual row)
|
|
526
|
-
const user = await db.insert(Users, {
|
|
527
|
-
email: "alice@example.com",
|
|
528
|
-
name: "Alice",
|
|
529
|
-
});
|
|
530
|
-
// Returns actual row from DB, including auto-generated id and DB-computed defaults
|
|
531
|
-
const userId = user.id;
|
|
532
|
-
|
|
533
|
-
// Update by primary key (uses RETURNING)
|
|
534
|
-
const updated = await db.update(Users, {name: "Bob"}, userId);
|
|
535
|
-
|
|
536
|
-
// Delete by primary key
|
|
537
|
-
await db.delete(Users, userId);
|
|
538
|
-
|
|
539
|
-
// Soft delete (sets deletedAt timestamp, requires softDelete() field)
|
|
540
|
-
await db.softDelete(Users, userId);
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
**RETURNING support:** `insert()` and `update()` use `RETURNING *` on SQLite and PostgreSQL to return the actual row from the database, including DB-computed defaults and triggers. MySQL falls back to a separate SELECT.
|
|
544
|
-
|
|
545
565
|
## Transactions
|
|
546
566
|
|
|
547
567
|
```typescript
|
|
@@ -818,6 +838,7 @@ const refs = Posts.references(); // [{fieldName: "authorId", table: Users,
|
|
|
818
838
|
- Tagged template queries are cached by template object identity (compiled once per call site)
|
|
819
839
|
- Normalization cost is O(rows) with hash maps per table
|
|
820
840
|
- Reference resolution is zero-cost after deduplication
|
|
841
|
+
- Zod validation happens on writes, never on reads.
|
|
821
842
|
|
|
822
843
|
## Driver Interface
|
|
823
844
|
|
|
@@ -970,41 +991,41 @@ Override with `.db.type("CUSTOM")` when using custom encode/decode.
|
|
|
970
991
|
```typescript
|
|
971
992
|
import {
|
|
972
993
|
// Zod (extended with .db namespace)
|
|
973
|
-
z,
|
|
994
|
+
z, // Re-exported Zod with .db already available
|
|
995
|
+
extendZod, // Extend a separate Zod instance (advanced)
|
|
974
996
|
|
|
975
997
|
// Table and view definition
|
|
976
|
-
table,
|
|
977
|
-
view,
|
|
978
|
-
isTable,
|
|
979
|
-
isView,
|
|
980
|
-
extendZod, // Extend a separate Zod instance (advanced)
|
|
998
|
+
table, // Create a table definition from Zod schema
|
|
999
|
+
view, // Create a read-only view from a table
|
|
1000
|
+
isTable, // Type guard for Table objects
|
|
1001
|
+
isView, // Type guard for View objects
|
|
981
1002
|
|
|
982
1003
|
// Database
|
|
983
|
-
Database,
|
|
984
|
-
Transaction,
|
|
985
|
-
DatabaseUpgradeEvent,
|
|
1004
|
+
Database, // Main database class
|
|
1005
|
+
Transaction, // Transaction context (passed to transaction callbacks)
|
|
1006
|
+
DatabaseUpgradeEvent, // Event object for "upgradeneeded" handler
|
|
986
1007
|
|
|
987
1008
|
// SQL builtins (for .db.inserted() / .db.updated())
|
|
988
|
-
NOW,
|
|
989
|
-
TODAY,
|
|
990
|
-
CURRENT_TIMESTAMP,
|
|
991
|
-
CURRENT_DATE,
|
|
992
|
-
CURRENT_TIME,
|
|
1009
|
+
NOW, // CURRENT_TIMESTAMP alias
|
|
1010
|
+
TODAY, // CURRENT_DATE alias
|
|
1011
|
+
CURRENT_TIMESTAMP, // SQL CURRENT_TIMESTAMP
|
|
1012
|
+
CURRENT_DATE, // SQL CURRENT_DATE
|
|
1013
|
+
CURRENT_TIME, // SQL CURRENT_TIME
|
|
993
1014
|
|
|
994
1015
|
// Errors
|
|
995
|
-
DatabaseError,
|
|
996
|
-
ValidationError,
|
|
997
|
-
TableDefinitionError,
|
|
998
|
-
MigrationError,
|
|
999
|
-
MigrationLockError,
|
|
1000
|
-
QueryError,
|
|
1001
|
-
NotFoundError,
|
|
1002
|
-
AlreadyExistsError,
|
|
1016
|
+
DatabaseError, // Base error class
|
|
1017
|
+
ValidationError, // Schema validation failed
|
|
1018
|
+
TableDefinitionError, // Invalid table definition
|
|
1019
|
+
MigrationError, // Migration failed
|
|
1020
|
+
MigrationLockError, // Failed to acquire migration lock
|
|
1021
|
+
QueryError, // SQL execution failed
|
|
1022
|
+
NotFoundError, // Entity not found
|
|
1023
|
+
AlreadyExistsError, // Unique constraint violated
|
|
1003
1024
|
ConstraintViolationError, // Database constraint violated
|
|
1004
|
-
ConnectionError,
|
|
1005
|
-
TransactionError,
|
|
1006
|
-
isDatabaseError,
|
|
1007
|
-
hasErrorCode,
|
|
1025
|
+
ConnectionError, // Connection failed
|
|
1026
|
+
TransactionError, // Transaction failed
|
|
1027
|
+
isDatabaseError, // Type guard for DatabaseError
|
|
1028
|
+
hasErrorCode, // Check error code
|
|
1008
1029
|
} from "@b9g/zen";
|
|
1009
1030
|
```
|
|
1010
1031
|
|
|
@@ -1013,34 +1034,34 @@ import {
|
|
|
1013
1034
|
```typescript
|
|
1014
1035
|
import type {
|
|
1015
1036
|
// Table types
|
|
1016
|
-
Table,
|
|
1017
|
-
PartialTable,
|
|
1018
|
-
DerivedTable,
|
|
1019
|
-
TableOptions,
|
|
1020
|
-
ReferenceInfo,
|
|
1021
|
-
CompoundReference,
|
|
1037
|
+
Table, // Table definition object
|
|
1038
|
+
PartialTable, // Table created via .pick()
|
|
1039
|
+
DerivedTable, // Table with derived fields via .derive()
|
|
1040
|
+
TableOptions, // Options for table()
|
|
1041
|
+
ReferenceInfo, // Foreign key reference metadata
|
|
1042
|
+
CompoundReference, // Compound foreign key reference
|
|
1022
1043
|
|
|
1023
1044
|
// Field types
|
|
1024
|
-
FieldMeta,
|
|
1025
|
-
FieldType,
|
|
1026
|
-
FieldDBMeta,
|
|
1045
|
+
FieldMeta, // Field metadata for form generation
|
|
1046
|
+
FieldType, // Field type enum
|
|
1047
|
+
FieldDBMeta, // Database-specific field metadata
|
|
1027
1048
|
|
|
1028
1049
|
// Type inference
|
|
1029
|
-
Row,
|
|
1030
|
-
Insert,
|
|
1031
|
-
Update,
|
|
1050
|
+
Row, // Infer row type from Table (after read)
|
|
1051
|
+
Insert, // Infer insert type from Table (respects defaults/.db.auto())
|
|
1052
|
+
Update, // Infer update type from Table (all fields optional)
|
|
1032
1053
|
|
|
1033
1054
|
// Fragment types
|
|
1034
|
-
SetValues,
|
|
1035
|
-
SQLTemplate,
|
|
1036
|
-
SQLDialect,
|
|
1055
|
+
SetValues, // Values accepted by Table.set()
|
|
1056
|
+
SQLTemplate, // SQL template object (return type of set(), on(), etc.)
|
|
1057
|
+
SQLDialect, // "sqlite" | "postgresql" | "mysql"
|
|
1037
1058
|
|
|
1038
1059
|
// Driver types
|
|
1039
|
-
Driver,
|
|
1040
|
-
TaggedQuery,
|
|
1060
|
+
Driver, // Driver interface for adapters
|
|
1061
|
+
TaggedQuery, // Tagged template query function
|
|
1041
1062
|
|
|
1042
1063
|
// Error types
|
|
1043
|
-
DatabaseErrorCode,
|
|
1064
|
+
DatabaseErrorCode, // Error code string literals
|
|
1044
1065
|
} from "@b9g/zen";
|
|
1045
1066
|
```
|
|
1046
1067
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/zen",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Define Zod tables. Write raw SQL. Get typed objects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"database",
|
|
7
7
|
"sql",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"zod": "^4.0.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@b9g/libuild": "^0.1.
|
|
29
|
+
"@b9g/libuild": "^0.1.21",
|
|
30
30
|
"@eslint/js": "^9.39.2",
|
|
31
31
|
"@types/better-sqlite3": "^7.6.0",
|
|
32
32
|
"@types/bun": "^1.3.4",
|
|
@@ -793,6 +793,30 @@ function table(name, shape, options = {}) {
|
|
|
793
793
|
name
|
|
794
794
|
);
|
|
795
795
|
}
|
|
796
|
+
for (const idx of options.indexes ?? []) {
|
|
797
|
+
if (idx.length < 2) {
|
|
798
|
+
throw new TableDefinitionError(
|
|
799
|
+
`Compound index in table "${name}" must have at least 2 fields. Use .db.index() for single-field indexes.`,
|
|
800
|
+
name
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
for (const u of options.unique ?? []) {
|
|
805
|
+
if (u.length < 2) {
|
|
806
|
+
throw new TableDefinitionError(
|
|
807
|
+
`Compound unique constraint in table "${name}" must have at least 2 fields. Use .db.unique() for single-field constraints.`,
|
|
808
|
+
name
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
for (const ref of options.references ?? []) {
|
|
813
|
+
if (ref.fields.length < 2) {
|
|
814
|
+
throw new TableDefinitionError(
|
|
815
|
+
`Compound foreign key in table "${name}" must have at least 2 fields. Use .db.references() for single-field foreign keys.`,
|
|
816
|
+
name
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
796
820
|
const zodShape = {};
|
|
797
821
|
const meta = {
|
|
798
822
|
primary: null,
|
package/src/bun.js
CHANGED
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
placeholder,
|
|
4
4
|
quoteIdent,
|
|
5
5
|
renderDDL
|
|
6
|
-
} from "
|
|
6
|
+
} from "./_chunks/chunk-2C6KOX4F.js";
|
|
7
7
|
import {
|
|
8
8
|
generateDDL,
|
|
9
9
|
generateViewDDL
|
|
10
|
-
} from "
|
|
10
|
+
} from "./_chunks/chunk-2R6FDKLS.js";
|
|
11
11
|
import {
|
|
12
12
|
getTableMeta,
|
|
13
13
|
resolveSQLBuiltin
|
|
14
|
-
} from "
|
|
14
|
+
} from "./_chunks/chunk-DKLSJISE.js";
|
|
15
15
|
|
|
16
16
|
// src/bun.ts
|
|
17
17
|
import { SQL } from "bun";
|
|
@@ -742,7 +742,7 @@ var BunDriver = class {
|
|
|
742
742
|
return applied;
|
|
743
743
|
}
|
|
744
744
|
async #addColumn(table, fieldName) {
|
|
745
|
-
const { generateColumnDDL } = await import("
|
|
745
|
+
const { generateColumnDDL } = await import("./_chunks/ddl-32B7E53E.js");
|
|
746
746
|
const zodType = table.schema.shape[fieldName];
|
|
747
747
|
const fieldMeta = table.meta.fields[fieldName] || {};
|
|
748
748
|
const colTemplate = generateColumnDDL(
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL Builtins - SQL-native values with no JavaScript equivalent.
|
|
3
|
+
*
|
|
4
|
+
* These symbols represent database functions that are evaluated at query time,
|
|
5
|
+
* not in JavaScript. They're used for default values and expressions.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Current timestamp - resolves to CURRENT_TIMESTAMP (standard SQL).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* createdAt: z.date().db.inserted(CURRENT_TIMESTAMP)
|
|
12
|
+
* updatedAt: z.date().db.updated(NOW)
|
|
13
|
+
*/
|
|
14
|
+
export declare const CURRENT_TIMESTAMP: unique symbol;
|
|
15
|
+
/**
|
|
16
|
+
* Current date - resolves to CURRENT_DATE (standard SQL).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* dateOnly: z.date().db.inserted(CURRENT_DATE)
|
|
20
|
+
*/
|
|
21
|
+
export declare const CURRENT_DATE: unique symbol;
|
|
22
|
+
/**
|
|
23
|
+
* Current time - resolves to CURRENT_TIME (standard SQL).
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* timeOnly: z.string().db.inserted(CURRENT_TIME)
|
|
27
|
+
*/
|
|
28
|
+
export declare const CURRENT_TIME: unique symbol;
|
|
29
|
+
/**
|
|
30
|
+
* Ergonomic alias for CURRENT_TIMESTAMP.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* createdAt: z.date().db.inserted(NOW)
|
|
34
|
+
*/
|
|
35
|
+
export declare const NOW: typeof CURRENT_TIMESTAMP;
|
|
36
|
+
/**
|
|
37
|
+
* Ergonomic alias for CURRENT_DATE.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* dateOnly: z.date().db.inserted(TODAY)
|
|
41
|
+
*/
|
|
42
|
+
export declare const TODAY: typeof CURRENT_DATE;
|
|
43
|
+
/** SQL builtins - SQL-native values with no JavaScript representation */
|
|
44
|
+
export type SQLBuiltin = typeof CURRENT_TIMESTAMP | typeof CURRENT_DATE | typeof CURRENT_TIME;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a value is a known SQL builtin.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isSQLBuiltin(value: unknown): value is SQLBuiltin;
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a SQL builtin symbol to its SQL representation.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveSQLBuiltin(sym: symbol): string;
|