@toonstore/torm 0.3.0 β 0.4.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/README.md +497 -43
- package/dist/cli.js +404 -65
- package/dist/cli.js.map +1 -1
- package/dist/migrations.d.ts +62 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +275 -0
- package/dist/migrations.js.map +1 -0
- package/dist/studio-server.d.ts +1 -0
- package/dist/studio-server.d.ts.map +1 -1
- package/dist/studio-server.js +284 -0
- package/dist/studio-server.js.map +1 -1
- package/package.json +1 -1
- package/studio/index.html +54 -22
package/README.md
CHANGED
|
@@ -50,20 +50,28 @@ pnpm add @toonstore/torm
|
|
|
50
50
|
# Default: localhost:6379
|
|
51
51
|
|
|
52
52
|
# Or using Docker
|
|
53
|
-
docker run -d -p 6379:6379
|
|
53
|
+
docker run -d -p 6379:6379 samso9th/toonstore:latest
|
|
54
54
|
|
|
55
55
|
# Or install from releases
|
|
56
|
-
# Download from: https://github.com/
|
|
56
|
+
# Download from: https://github.com/kalama-tech/toonstoredb/releases
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
> **Note:** ToonStoreDB is a high-performance database with Redis-compatible protocol. The SDK connects directly to your ToonStoreDB instance.
|
|
59
|
+
> **Note:** ToonStoreDB is a high-performance database with Redis-compatible protocol. The SDK connects directly to your ToonStoreDB instance.
|
|
60
60
|
|
|
61
|
-
###
|
|
61
|
+
### Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install @toonstore/torm
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 1: Connect to ToonStoreDB
|
|
68
|
+
|
|
69
|
+
Create a new file (e.g., `app.ts`):
|
|
62
70
|
|
|
63
71
|
```typescript
|
|
64
72
|
import { TormClient } from '@toonstore/torm';
|
|
65
73
|
|
|
66
|
-
//
|
|
74
|
+
// Connect to ToonStoreDB
|
|
67
75
|
const torm = new TormClient({
|
|
68
76
|
host: 'localhost',
|
|
69
77
|
port: 6379,
|
|
@@ -71,7 +79,17 @@ const torm = new TormClient({
|
|
|
71
79
|
// url: 'toonstoredb://localhost:6379'
|
|
72
80
|
});
|
|
73
81
|
|
|
74
|
-
//
|
|
82
|
+
// Check connection
|
|
83
|
+
const health = await torm.health();
|
|
84
|
+
console.log('Database health:', health);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Step 2: Define Your Models
|
|
88
|
+
|
|
89
|
+
Define TypeScript interfaces and create models with validation:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// Define your data structure
|
|
75
93
|
interface User {
|
|
76
94
|
name: string;
|
|
77
95
|
email: string;
|
|
@@ -79,7 +97,7 @@ interface User {
|
|
|
79
97
|
website?: string;
|
|
80
98
|
}
|
|
81
99
|
|
|
82
|
-
//
|
|
100
|
+
// Create model with schema validation
|
|
83
101
|
const User = torm.model<User>('User', {
|
|
84
102
|
name: {
|
|
85
103
|
type: 'string',
|
|
@@ -103,44 +121,79 @@ const User = torm.model<User>('User', {
|
|
|
103
121
|
url: true,
|
|
104
122
|
},
|
|
105
123
|
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Step 3: Perform CRUD Operations
|
|
106
127
|
|
|
107
|
-
|
|
128
|
+
```typescript
|
|
129
|
+
// Create a user (ID auto-generated)
|
|
108
130
|
const user = await User.create({
|
|
109
131
|
name: 'Alice',
|
|
110
132
|
email: 'alice@example.com',
|
|
111
133
|
age: 30,
|
|
112
134
|
website: 'https://alice.dev',
|
|
113
135
|
});
|
|
114
|
-
console.log(user._id);
|
|
136
|
+
console.log('Created user:', user._id);
|
|
115
137
|
|
|
116
|
-
//
|
|
138
|
+
// Find all users
|
|
117
139
|
const allUsers = await User.find();
|
|
118
|
-
const specificUser = await User.findById(user._id);
|
|
119
|
-
const foundUser = await User.findOne({ email: 'alice@example.com' });
|
|
120
140
|
|
|
121
|
-
//
|
|
141
|
+
// Find user by ID
|
|
142
|
+
const foundUser = await User.findById(user._id);
|
|
143
|
+
|
|
144
|
+
// Find one user by criteria
|
|
145
|
+
const alice = await User.findOne({ email: 'alice@example.com' });
|
|
146
|
+
|
|
147
|
+
// Update user
|
|
148
|
+
const updated = await User.update(user._id, { age: 31 });
|
|
149
|
+
|
|
150
|
+
// Delete user
|
|
151
|
+
await User.delete(user._id);
|
|
152
|
+
|
|
153
|
+
// Count users
|
|
154
|
+
const count = await User.count();
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Step 4: Query with Filters
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Find users 18 and older
|
|
122
161
|
const adults = await User.query()
|
|
123
162
|
.where('age', 'gte', 18)
|
|
124
163
|
.sort('name', 'asc')
|
|
164
|
+
.exec();
|
|
165
|
+
|
|
166
|
+
// Pagination
|
|
167
|
+
const page1 = await User.query()
|
|
168
|
+
.sort('_createdAt', 'desc')
|
|
125
169
|
.limit(10)
|
|
170
|
+
.skip(0)
|
|
126
171
|
.exec();
|
|
127
172
|
|
|
128
|
-
//
|
|
129
|
-
const
|
|
173
|
+
// Complex queries
|
|
174
|
+
const result = await User.query()
|
|
175
|
+
.where('age', 'gte', 18)
|
|
176
|
+
.where('age', 'lte', 65)
|
|
177
|
+
.sort('name', 'asc')
|
|
178
|
+
.limit(20)
|
|
179
|
+
.exec();
|
|
180
|
+
```
|
|
130
181
|
|
|
131
|
-
|
|
132
|
-
await User.delete(user._id);
|
|
182
|
+
### Step 5: Launch TORM Studio (Optional)
|
|
133
183
|
|
|
134
|
-
|
|
135
|
-
await User.deleteMany({ age: { $lt: 18 } });
|
|
184
|
+
Visual database manager for browsing and editing data:
|
|
136
185
|
|
|
137
|
-
|
|
138
|
-
|
|
186
|
+
```bash
|
|
187
|
+
# First time - auto-generates config
|
|
188
|
+
npx torm studio
|
|
139
189
|
|
|
140
|
-
|
|
141
|
-
|
|
190
|
+
# Edit torm.config.ts with your database credentials
|
|
191
|
+
# Then run again:
|
|
192
|
+
npx torm studio
|
|
142
193
|
```
|
|
143
194
|
|
|
195
|
+
Opens at http://localhost:4983 with a visual interface to browse collections, edit records, and view stats.
|
|
196
|
+
|
|
144
197
|
## π οΈ CLI Commands
|
|
145
198
|
|
|
146
199
|
TORM includes a powerful CLI for database management and development workflows:
|
|
@@ -159,7 +212,12 @@ npx torm studio
|
|
|
159
212
|
**Access:** http://localhost:4983
|
|
160
213
|
|
|
161
214
|
**Configuration:**
|
|
162
|
-
|
|
215
|
+
|
|
216
|
+
You can use either TypeScript or JavaScript config files:
|
|
217
|
+
|
|
218
|
+
**Option 1: TypeScript Config (Recommended)**
|
|
219
|
+
|
|
220
|
+
Create `torm.config.ts` in your project root:
|
|
163
221
|
```typescript
|
|
164
222
|
export default {
|
|
165
223
|
dbCredentials: {
|
|
@@ -175,6 +233,30 @@ export default {
|
|
|
175
233
|
};
|
|
176
234
|
```
|
|
177
235
|
|
|
236
|
+
For TypeScript config files, install `tsx` (recommended) or `ts-node`:
|
|
237
|
+
```bash
|
|
238
|
+
npm install -D tsx
|
|
239
|
+
# or
|
|
240
|
+
npm install -D ts-node
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Option 2: JavaScript Config**
|
|
244
|
+
|
|
245
|
+
Create `torm.config.js` in your project root:
|
|
246
|
+
```javascript
|
|
247
|
+
module.exports = {
|
|
248
|
+
dbCredentials: {
|
|
249
|
+
host: 'localhost',
|
|
250
|
+
port: 6379,
|
|
251
|
+
},
|
|
252
|
+
studio: {
|
|
253
|
+
port: 4983,
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
No additional dependencies needed for `.js` config files.
|
|
259
|
+
|
|
178
260
|
**Features:**
|
|
179
261
|
- π Visual database browser - browse collections and documents
|
|
180
262
|
- βοΈ Create, read, update, delete records with UI
|
|
@@ -189,19 +271,174 @@ export default {
|
|
|
189
271
|
npx torm studio --port 8080
|
|
190
272
|
```
|
|
191
273
|
|
|
192
|
-
### Migrations
|
|
274
|
+
### Migrations
|
|
275
|
+
|
|
276
|
+
Create and run data-shape migrations for your schemaless database:
|
|
193
277
|
|
|
194
278
|
```bash
|
|
195
|
-
#
|
|
196
|
-
npx torm
|
|
279
|
+
# Create a new migration
|
|
280
|
+
npx torm migrate:create add_age_field
|
|
197
281
|
|
|
198
282
|
# Run pending migrations
|
|
199
|
-
npx torm migrate
|
|
283
|
+
npx torm migrate:up
|
|
284
|
+
|
|
285
|
+
# Run specific number of migrations
|
|
286
|
+
npx torm migrate:up 1
|
|
287
|
+
|
|
288
|
+
# Rollback last migration
|
|
289
|
+
npx torm migrate:down
|
|
290
|
+
|
|
291
|
+
# Rollback multiple migrations
|
|
292
|
+
npx torm migrate:down 2
|
|
200
293
|
|
|
201
294
|
# Check migration status
|
|
202
295
|
npx torm migrate:status
|
|
203
296
|
```
|
|
204
297
|
|
|
298
|
+
**How Migrations Work with ToonStoreDB:**
|
|
299
|
+
|
|
300
|
+
ToonStoreDB is schemaless at the storage layer, but your application code assumes a particular data shape. Migrations help you evolve that shape over time.
|
|
301
|
+
|
|
302
|
+
**Example Workflow:**
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// Step 1: Create a migration file
|
|
306
|
+
// $ npx torm migrate:create add_age_to_users
|
|
307
|
+
// Creates: migrations/20260105_add_age_to_users.ts
|
|
308
|
+
|
|
309
|
+
// migrations/20260105_add_age_to_users.ts
|
|
310
|
+
export default {
|
|
311
|
+
async up(client) {
|
|
312
|
+
// Add 'age' field to all existing users with a default value
|
|
313
|
+
const pattern = 'toonstore:User:*';
|
|
314
|
+
const keys = await client.keys(pattern);
|
|
315
|
+
|
|
316
|
+
for (const key of keys) {
|
|
317
|
+
const data = JSON.parse(await client.get(key));
|
|
318
|
+
if (!data.age) {
|
|
319
|
+
data.age = 18; // Default value
|
|
320
|
+
await client.set(key, JSON.stringify(data));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.log(`Updated ${keys.length} users with age field`);
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
async down(client) {
|
|
328
|
+
// Remove 'age' field from all users
|
|
329
|
+
const pattern = 'toonstore:User:*';
|
|
330
|
+
const keys = await client.keys(pattern);
|
|
331
|
+
|
|
332
|
+
for (const key of keys) {
|
|
333
|
+
const data = JSON.parse(await client.get(key));
|
|
334
|
+
delete data.age;
|
|
335
|
+
await client.set(key, JSON.stringify(data));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
console.log(`Removed age field from ${keys.length} users`);
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Step 2: Run migration
|
|
343
|
+
// $ npx torm migrate:up
|
|
344
|
+
// Applies: 20260105_add_age_to_users
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Common Migration Patterns:**
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// 1. Add field with default value
|
|
351
|
+
export default {
|
|
352
|
+
async up(client) {
|
|
353
|
+
const keys = await client.keys('toonstore:User:*');
|
|
354
|
+
for (const key of keys) {
|
|
355
|
+
const user = JSON.parse(await client.get(key));
|
|
356
|
+
user.verified = user.verified ?? false;
|
|
357
|
+
await client.set(key, JSON.stringify(user));
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
async down(client) {
|
|
361
|
+
const keys = await client.keys('toonstore:User:*');
|
|
362
|
+
for (const key of keys) {
|
|
363
|
+
const user = JSON.parse(await client.get(key));
|
|
364
|
+
delete user.verified;
|
|
365
|
+
await client.set(key, JSON.stringify(user));
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// 2. Rename field
|
|
371
|
+
export default {
|
|
372
|
+
async up(client) {
|
|
373
|
+
const keys = await client.keys('toonstore:Post:*');
|
|
374
|
+
for (const key of keys) {
|
|
375
|
+
const post = JSON.parse(await client.get(key));
|
|
376
|
+
if (post.author_id) {
|
|
377
|
+
post.authorId = post.author_id;
|
|
378
|
+
delete post.author_id;
|
|
379
|
+
await client.set(key, JSON.stringify(post));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
async down(client) {
|
|
384
|
+
const keys = await client.keys('toonstore:Post:*');
|
|
385
|
+
for (const key of keys) {
|
|
386
|
+
const post = JSON.parse(await client.get(key));
|
|
387
|
+
if (post.authorId) {
|
|
388
|
+
post.author_id = post.authorId;
|
|
389
|
+
delete post.authorId;
|
|
390
|
+
await client.set(key, JSON.stringify(post));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// 3. Data transformation
|
|
397
|
+
export default {
|
|
398
|
+
async up(client) {
|
|
399
|
+
const keys = await client.keys('toonstore:User:*');
|
|
400
|
+
for (const key of keys) {
|
|
401
|
+
const user = JSON.parse(await client.get(key));
|
|
402
|
+
// Split full name into first and last
|
|
403
|
+
if (user.name && !user.firstName) {
|
|
404
|
+
const [firstName, ...lastName] = user.name.split(' ');
|
|
405
|
+
user.firstName = firstName;
|
|
406
|
+
user.lastName = lastName.join(' ');
|
|
407
|
+
delete user.name;
|
|
408
|
+
await client.set(key, JSON.stringify(user));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
async down(client) {
|
|
413
|
+
const keys = await client.keys('toonstore:User:*');
|
|
414
|
+
for (const key of keys) {
|
|
415
|
+
const user = JSON.parse(await client.get(key));
|
|
416
|
+
if (user.firstName) {
|
|
417
|
+
user.name = `${user.firstName} ${user.lastName || ''}`.trim();
|
|
418
|
+
delete user.firstName;
|
|
419
|
+
delete user.lastName;
|
|
420
|
+
await client.set(key, JSON.stringify(user));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Migration Tracking:**
|
|
428
|
+
|
|
429
|
+
TORM tracks which migrations have run using a special key:
|
|
430
|
+
```
|
|
431
|
+
toonstore:_migrations -> ["20260105_add_age_to_users", "20260106_rename_author_field"]
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Best Practices:**
|
|
435
|
+
|
|
436
|
+
1. **Never modify old migrations** - create new ones to fix issues
|
|
437
|
+
2. **Always provide `down()` for rollback** - test it works
|
|
438
|
+
3. **Use transactions when available** - ensure atomic changes
|
|
439
|
+
4. **Test on copy of production data** - before running in prod
|
|
440
|
+
5. **Version control migrations** - commit with code changes
|
|
441
|
+
|
|
205
442
|
### Type Generation (Coming Soon)
|
|
206
443
|
|
|
207
444
|
```bash
|
|
@@ -212,6 +449,47 @@ npx torm generate
|
|
|
212
449
|
npx torm generate --watch
|
|
213
450
|
```
|
|
214
451
|
|
|
452
|
+
**How Type Generation Will Work:**
|
|
453
|
+
|
|
454
|
+
TORM will introspect your database and generate TypeScript interfaces:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
# Scan your database and generate types
|
|
458
|
+
$ npx torm generate
|
|
459
|
+
|
|
460
|
+
# Output: src/generated/torm-types.ts
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Generated Types:**
|
|
464
|
+
```typescript
|
|
465
|
+
// src/generated/torm-types.ts (auto-generated)
|
|
466
|
+
export interface User {
|
|
467
|
+
_id: string;
|
|
468
|
+
name: string;
|
|
469
|
+
email: string;
|
|
470
|
+
age: number;
|
|
471
|
+
website?: string;
|
|
472
|
+
_createdAt: string;
|
|
473
|
+
_updatedAt: string;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface Post {
|
|
477
|
+
_id: string;
|
|
478
|
+
title: string;
|
|
479
|
+
content: string;
|
|
480
|
+
authorId: string;
|
|
481
|
+
published: boolean;
|
|
482
|
+
_createdAt: string;
|
|
483
|
+
_updatedAt: string;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Use generated types
|
|
487
|
+
import { User, Post } from './generated/torm-types';
|
|
488
|
+
|
|
489
|
+
const User = torm.model<User>('User');
|
|
490
|
+
const Post = torm.model<Post>('Post');
|
|
491
|
+
```
|
|
492
|
+
|
|
215
493
|
## π API Documentation
|
|
216
494
|
|
|
217
495
|
### TormClient
|
|
@@ -560,32 +838,47 @@ Version 0.2.0 introduces breaking changes:
|
|
|
560
838
|
- **No more HTTP server required** - SDK now connects directly to ToonStoreDB
|
|
561
839
|
- **Auto-generated IDs** - No need to provide `id` field, use `_id` instead
|
|
562
840
|
- **New connection options** - Use `host`, `port`, or `url` for ToonStoreDB connection
|
|
841
|
+
- **TORM Studio bundled** - Visual database manager included (run `npx torm studio`)
|
|
563
842
|
|
|
564
843
|
### Migration Steps
|
|
565
844
|
|
|
845
|
+
**Old (v0.1.x):**
|
|
566
846
|
```typescript
|
|
567
|
-
//
|
|
847
|
+
// Required TORM server via HTTP
|
|
568
848
|
const torm = new TormClient({
|
|
569
849
|
baseURL: 'http://localhost:3001',
|
|
570
850
|
});
|
|
571
851
|
|
|
572
852
|
const user = await User.create({
|
|
573
|
-
id: 'user:1',
|
|
853
|
+
id: 'user:1', // Manual ID
|
|
574
854
|
name: 'Alice',
|
|
575
855
|
});
|
|
856
|
+
```
|
|
576
857
|
|
|
577
|
-
|
|
858
|
+
**New (v0.2.x):**
|
|
859
|
+
```typescript
|
|
860
|
+
// Direct ToonStoreDB connection
|
|
578
861
|
const torm = new TormClient({
|
|
579
862
|
host: 'localhost',
|
|
580
863
|
port: 6379,
|
|
581
864
|
});
|
|
582
865
|
|
|
583
866
|
const user = await User.create({
|
|
584
|
-
name: 'Alice',
|
|
867
|
+
name: 'Alice', // No id needed
|
|
585
868
|
});
|
|
586
|
-
console.log(user._id); // Auto-generated
|
|
869
|
+
console.log(user._id); // Auto-generated: "1736076000000-abc123xyz"
|
|
870
|
+
|
|
871
|
+
// Launch Studio
|
|
872
|
+
// $ npx torm studio
|
|
873
|
+
// Opens http://localhost:4983
|
|
587
874
|
```
|
|
588
875
|
|
|
876
|
+
**Steps:**
|
|
877
|
+
1. Update connection config (remove `baseURL`, add `host`/`port`)
|
|
878
|
+
2. Remove manual `id` fields from `create()` calls
|
|
879
|
+
3. Use `_id` to access document IDs
|
|
880
|
+
4. (Optional) Create `torm.config.ts` for Studio
|
|
881
|
+
|
|
589
882
|
## π§ͺ Running Examples
|
|
590
883
|
|
|
591
884
|
```bash
|
|
@@ -616,6 +909,128 @@ npm run dev
|
|
|
616
909
|
|
|
617
910
|
# Run tests
|
|
618
911
|
npm test
|
|
912
|
+
|
|
913
|
+
# Start Studio
|
|
914
|
+
npm run studio
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
## π― Complete Example
|
|
918
|
+
|
|
919
|
+
Here's a complete example building a blog application:
|
|
920
|
+
|
|
921
|
+
```typescript
|
|
922
|
+
import { TormClient } from '@toonstore/torm';
|
|
923
|
+
|
|
924
|
+
// 1. Connect to database
|
|
925
|
+
const torm = new TormClient({
|
|
926
|
+
host: 'localhost',
|
|
927
|
+
port: 6379,
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
// 2. Define models
|
|
931
|
+
interface User {
|
|
932
|
+
username: string;
|
|
933
|
+
email: string;
|
|
934
|
+
bio?: string;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
interface Post {
|
|
938
|
+
title: string;
|
|
939
|
+
content: string;
|
|
940
|
+
authorId: string;
|
|
941
|
+
published: boolean;
|
|
942
|
+
tags: string[];
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const User = torm.model<User>('User', {
|
|
946
|
+
username: {
|
|
947
|
+
type: 'string',
|
|
948
|
+
required: true,
|
|
949
|
+
minLength: 3,
|
|
950
|
+
pattern: /^[a-zA-Z0-9_]+$/,
|
|
951
|
+
},
|
|
952
|
+
email: {
|
|
953
|
+
type: 'string',
|
|
954
|
+
required: true,
|
|
955
|
+
email: true,
|
|
956
|
+
},
|
|
957
|
+
bio: {
|
|
958
|
+
type: 'string',
|
|
959
|
+
maxLength: 500,
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
const Post = torm.model<Post>('Post', {
|
|
964
|
+
title: {
|
|
965
|
+
type: 'string',
|
|
966
|
+
required: true,
|
|
967
|
+
minLength: 5,
|
|
968
|
+
maxLength: 200,
|
|
969
|
+
},
|
|
970
|
+
content: {
|
|
971
|
+
type: 'string',
|
|
972
|
+
required: true,
|
|
973
|
+
minLength: 10,
|
|
974
|
+
},
|
|
975
|
+
authorId: {
|
|
976
|
+
type: 'string',
|
|
977
|
+
required: true,
|
|
978
|
+
},
|
|
979
|
+
published: {
|
|
980
|
+
type: 'boolean',
|
|
981
|
+
},
|
|
982
|
+
tags: {
|
|
983
|
+
type: 'array',
|
|
984
|
+
},
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
// 3. Use the models
|
|
988
|
+
async function main() {
|
|
989
|
+
// Create a user
|
|
990
|
+
const user = await User.create({
|
|
991
|
+
username: 'alice',
|
|
992
|
+
email: 'alice@example.com',
|
|
993
|
+
bio: 'Love coding and coffee β',
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
// Create posts
|
|
997
|
+
await Post.create({
|
|
998
|
+
title: 'Getting Started with ToonStoreDB',
|
|
999
|
+
content: 'ToonStoreDB is a blazingly fast database...',
|
|
1000
|
+
authorId: user._id,
|
|
1001
|
+
published: true,
|
|
1002
|
+
tags: ['database', 'toonstoredb', 'tutorial'],
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
await Post.create({
|
|
1006
|
+
title: 'Building a Blog with TORM',
|
|
1007
|
+
content: 'TORM makes it easy to build applications...',
|
|
1008
|
+
authorId: user._id,
|
|
1009
|
+
published: false,
|
|
1010
|
+
tags: ['torm', 'tutorial'],
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// Query published posts
|
|
1014
|
+
const publishedPosts = await Post.query()
|
|
1015
|
+
.where('published', 'eq', true)
|
|
1016
|
+
.where('authorId', 'eq', user._id)
|
|
1017
|
+
.sort('_createdAt', 'desc')
|
|
1018
|
+
.exec();
|
|
1019
|
+
|
|
1020
|
+
console.log('Published posts:', publishedPosts.length);
|
|
1021
|
+
|
|
1022
|
+
// Get user's post count
|
|
1023
|
+
const postCount = await Post.query()
|
|
1024
|
+
.where('authorId', 'eq', user._id)
|
|
1025
|
+
.count();
|
|
1026
|
+
|
|
1027
|
+
console.log(`${user.username} has ${postCount} posts`);
|
|
1028
|
+
|
|
1029
|
+
// Clean up
|
|
1030
|
+
await torm.disconnect();
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
main().catch(console.error);
|
|
619
1034
|
```
|
|
620
1035
|
|
|
621
1036
|
## π Why TORM?
|
|
@@ -655,10 +1070,14 @@ Your Node.js App β @toonstore/torm SDK β ToonStoreDB
|
|
|
655
1070
|
|
|
656
1071
|
**CLI Tools:**
|
|
657
1072
|
- [x] TORM Studio (bundled Node.js server) β
|
|
658
|
-
- [
|
|
659
|
-
- [
|
|
1073
|
+
- [x] Schema migrations (data-shape migrations for schemaless DB) β
|
|
1074
|
+
- [x] `torm migrate:create` - Create migration file
|
|
1075
|
+
- [x] `torm migrate:up` - Run pending migrations
|
|
1076
|
+
- [x] `torm migrate:down` - Rollback last migration
|
|
1077
|
+
- [x] `torm migrate:status` - Check migration status
|
|
1078
|
+
- [ ] Type generation from existing data
|
|
660
1079
|
- [ ] Database seeding
|
|
661
|
-
- [ ]
|
|
1080
|
+
- [ ] Data export/import tools
|
|
662
1081
|
|
|
663
1082
|
**Studio Enhancements:**
|
|
664
1083
|
- [ ] Custom domain (local.torm.studio via DNS) - no hosts file needed
|
|
@@ -668,20 +1087,55 @@ Your Node.js App β @toonstore/torm SDK β ToonStoreDB
|
|
|
668
1087
|
- [ ] Schema visualization
|
|
669
1088
|
|
|
670
1089
|
**Migration System (Planned):**
|
|
1090
|
+
|
|
1091
|
+
Unlike SQL databases, ToonStoreDB is schemalessβbut you still need **data-shape migrations** for application-level schema evolution:
|
|
1092
|
+
|
|
671
1093
|
```bash
|
|
672
|
-
#
|
|
673
|
-
torm
|
|
1094
|
+
# Create migration for data transformation
|
|
1095
|
+
torm migrate:create add_age_field
|
|
674
1096
|
|
|
675
|
-
# Run migrations
|
|
676
|
-
torm migrate
|
|
1097
|
+
# Run pending migrations
|
|
1098
|
+
torm migrate:up
|
|
677
1099
|
|
|
678
|
-
# Rollback
|
|
679
|
-
torm migrate:
|
|
1100
|
+
# Rollback last migration
|
|
1101
|
+
torm migrate:down
|
|
680
1102
|
|
|
681
|
-
# Check
|
|
1103
|
+
# Check what's been applied
|
|
682
1104
|
torm migrate:status
|
|
683
1105
|
```
|
|
684
1106
|
|
|
1107
|
+
**Example Migration:**
|
|
1108
|
+
```typescript
|
|
1109
|
+
// migrations/20260105_add_age_field.ts
|
|
1110
|
+
export default {
|
|
1111
|
+
async up(client) {
|
|
1112
|
+
// Add 'age' to all users with default value
|
|
1113
|
+
const keys = await client.keys('toonstore:User:*');
|
|
1114
|
+
for (const key of keys) {
|
|
1115
|
+
const user = JSON.parse(await client.get(key));
|
|
1116
|
+
user.age = user.age ?? 18; // Default age
|
|
1117
|
+
await client.set(key, JSON.stringify(user));
|
|
1118
|
+
}
|
|
1119
|
+
},
|
|
1120
|
+
async down(client) {
|
|
1121
|
+
// Remove 'age' from all users
|
|
1122
|
+
const keys = await client.keys('toonstore:User:*');
|
|
1123
|
+
for (const key of keys) {
|
|
1124
|
+
const user = JSON.parse(await client.get(key));
|
|
1125
|
+
delete user.age;
|
|
1126
|
+
await client.set(key, JSON.stringify(user));
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
};
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
**Common Use Cases:**
|
|
1133
|
+
- Add/remove fields with defaults
|
|
1134
|
+
- Rename fields (`author_id` β `authorId`)
|
|
1135
|
+
- Transform data (split name into first/last)
|
|
1136
|
+
- Back-fill missing values
|
|
1137
|
+
- Data cleanup and normalization
|
|
1138
|
+
|
|
685
1139
|
## π€ Contributing
|
|
686
1140
|
|
|
687
1141
|
Contributions are welcome! Please feel free to submit a Pull Request.
|