@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 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 toonstoredb/toonstoredb:latest
53
+ docker run -d -p 6379:6379 samso9th/toonstore:latest
54
54
 
55
55
  # Or install from releases
56
- # Download from: https://github.com/toonstore/toonstoredb/releases
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. No additional servers or middleware needed.
59
+ > **Note:** ToonStoreDB is a high-performance database with Redis-compatible protocol. The SDK connects directly to your ToonStoreDB instance.
60
60
 
61
- ### Basic Usage
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
- // 1. Connect to ToonStoreDB
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
- // 2. Define your model interface
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
- // 3. Create model with validation schema
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
- // 4. Create document (ID auto-generated)
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); // Auto-generated: "1736076000000-abc123xyz"
136
+ console.log('Created user:', user._id);
115
137
 
116
- // 5. Find documents
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
- // 6. Query with filters
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
- // 7. Update document
129
- const updated = await User.update(user._id, { age: 31 });
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
- // 8. Delete document
132
- await User.delete(user._id);
182
+ ### Step 5: Launch TORM Studio (Optional)
133
183
 
134
- // 9. Delete many documents
135
- await User.deleteMany({ age: { $lt: 18 } });
184
+ Visual database manager for browsing and editing data:
136
185
 
137
- // 10. Count documents
138
- const count = await User.count();
186
+ ```bash
187
+ # First time - auto-generates config
188
+ npx torm studio
139
189
 
140
- // 11. Disconnect when done
141
- await torm.disconnect();
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
- Create or edit `torm.config.ts` in your project root:
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 (Coming Soon)
274
+ ### Migrations
275
+
276
+ Create and run data-shape migrations for your schemaless database:
193
277
 
194
278
  ```bash
195
- # Generate migration from schema changes
196
- npx torm generate
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
- // v0.1.x (OLD - required TORM server via HTTP)
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
- // v0.2.x (NEW - direct ToonStoreDB connection)
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', // No id needed
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
- - [ ] Schema migrations (`torm generate`, `torm migrate`)
659
- - [ ] Type generation from schemas
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
- - [ ] Schema validation and linting
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
- # Generate migration from model changes
673
- torm generate --name add_users_table
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:rollback
1100
+ # Rollback last migration
1101
+ torm migrate:down
680
1102
 
681
- # Check status
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.