s3db.js 5.2.0 โ†’ 6.1.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
@@ -97,9 +97,20 @@
97
97
  - [๐Ÿ’พ Installation](#-installation)
98
98
  - [๐ŸŽฏ Core Concepts](#-core-concepts)
99
99
  - [โšก Advanced Features](#-advanced-features)
100
+ - [๐Ÿ”„ Resource Versioning System](#-resource-versioning-system)
101
+ - [๐Ÿ†” Custom ID Generation](#-custom-id-generation)
102
+ - [๐Ÿ”Œ Plugin System](#-plugin-system)
103
+ - [๐ŸŽ›๏ธ Advanced Behaviors](#๏ธ-advanced-behaviors)
104
+ - [๐Ÿ”„ Advanced Streaming API](#-advanced-streaming-api)
105
+ - [๐Ÿ“ Binary Content Management](#-binary-content-management)
106
+ - [๐Ÿ—‚๏ธ Advanced Partitioning](#๏ธ-advanced-partitioning)
107
+ - [๐ŸŽฃ Advanced Hooks System](#-advanced-hooks-system)
100
108
  - [๐Ÿ“– API Reference](#-api-reference)
101
109
  - [๐ŸŽจ Examples](#-examples)
102
110
  - [๐Ÿ” Security](#-security)
111
+ - [โš™๏ธ Advanced Configuration Options](#๏ธ-advanced-configuration-options)
112
+ - [๐Ÿ“ก Events and Emitters](#-events-and-emitters)
113
+ - [๐Ÿ”ง Troubleshooting](#-troubleshooting)
103
114
  - [๐Ÿ’ฐ Cost Analysis](#-cost-analysis)
104
115
  - [๐Ÿšจ Best Practices](#-best-practices)
105
116
  - [๐Ÿงช Testing](#-testing)
@@ -124,13 +135,17 @@ npm install s3db.js
124
135
  import { S3db } from "s3db.js";
125
136
 
126
137
  const s3db = new S3db({
127
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
138
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
128
139
  });
129
140
 
130
141
  await s3db.connect();
131
142
  console.log("๐ŸŽ‰ Connected to S3 database!");
132
143
  ```
133
144
 
145
+ > **โ„น๏ธ Note:** You do **not** need to provide `ACCESS_KEY` and `SECRET_KEY` in the connection string if your environment already has S3 permissions (e.g., via IAM Role on EKS, EC2, Lambda, or other compatible clouds). s3db.js will use the default AWS credential provider chain, so credentials can be omitted for role-based or environment-based authentication. This also applies to S3-compatible clouds (MinIO, DigitalOcean Spaces, etc.) if they support such mechanisms.
146
+
147
+ ---
148
+
134
149
  ### 3. Create your first resource
135
150
 
136
151
  ```javascript
@@ -140,24 +155,9 @@ const users = await s3db.createResource({
140
155
  name: "string|min:2|max:100",
141
156
  email: "email|unique",
142
157
  age: "number|integer|positive",
143
- isActive: "boolean",
144
- createdAt: "date"
145
- },
146
- timestamps: true,
147
- behavior: "user-management",
148
- partitions: {
149
- byRegion: { fields: { region: "string" } }
158
+ isActive: "boolean"
150
159
  },
151
- paranoid: true,
152
- autoDecrypt: true,
153
- cache: false,
154
- parallelism: 10,
155
- hooks: {
156
- preInsert: [async (data) => {
157
- console.log("Pre-insert:", data);
158
- return data;
159
- }]
160
- }
160
+ timestamps: true
161
161
  });
162
162
  ```
163
163
 
@@ -196,14 +196,34 @@ console.log(`Total users: ${allUsers.length}`);
196
196
  ```bash
197
197
  # npm
198
198
  npm install s3db.js
199
-
200
199
  # pnpm
201
200
  pnpm add s3db.js
202
-
203
201
  # yarn
204
202
  yarn add s3db.js
205
203
  ```
206
204
 
205
+ ### ๐Ÿ“ฆ Optional Dependencies
206
+
207
+ Some features require additional dependencies to be installed manually:
208
+
209
+ #### Replication Dependencies
210
+
211
+ If you plan to use the replication system with external services, install the corresponding dependencies:
212
+
213
+ ```bash
214
+ # For SQS replication (AWS SQS queues)
215
+ npm install @aws-sdk/client-sqs
216
+
217
+ # For BigQuery replication (Google BigQuery)
218
+ npm install @google-cloud/bigquery
219
+
220
+ # For PostgreSQL replication (PostgreSQL databases)
221
+ npm install pg
222
+ ```
223
+
224
+ **Why manual installation?** These are marked as `peerDependencies` to keep the main package lightweight. Only install what you need!
225
+ ```
226
+
207
227
  ### Environment Setup
208
228
 
209
229
  Create a `.env` file with your AWS credentials:
@@ -224,7 +244,7 @@ import dotenv from "dotenv";
224
244
  dotenv.config();
225
245
 
226
246
  const s3db = new S3db({
227
- uri: `s3://${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY}@${process.env.AWS_BUCKET}/databases/${process.env.DATABASE_NAME}`
247
+ connectionString: `s3://${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY}@${process.env.AWS_BUCKET}/databases/${process.env.DATABASE_NAME}`
228
248
  });
229
249
  ```
230
250
 
@@ -236,7 +256,7 @@ const s3db = new S3db({
236
256
  #### 1. Access Keys (Development)
237
257
  ```javascript
238
258
  const s3db = new S3db({
239
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
259
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
240
260
  });
241
261
  ```
242
262
 
@@ -244,14 +264,14 @@ const s3db = new S3db({
244
264
  ```javascript
245
265
  // No credentials needed - uses IAM role permissions
246
266
  const s3db = new S3db({
247
- uri: "s3://BUCKET_NAME/databases/myapp"
267
+ connectionString: "s3://BUCKET_NAME/databases/myapp"
248
268
  });
249
269
  ```
250
270
 
251
271
  #### 3. S3-Compatible Services (MinIO, etc.)
252
272
  ```javascript
253
273
  const s3db = new S3db({
254
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
274
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
255
275
  endpoint: "http://localhost:9000"
256
276
  });
257
277
  ```
@@ -267,92 +287,43 @@ A logical container for your resources, stored in a specific S3 prefix.
267
287
 
268
288
  ```javascript
269
289
  const s3db = new S3db({
270
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
290
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
271
291
  });
272
- // Creates/connects to: s3://bucket/databases/myapp/
273
292
  ```
274
293
 
275
294
  ### ๐Ÿ“‹ Resources (Collections)
276
295
  Resources define the structure of your documents, similar to tables in traditional databases.
277
296
 
278
- #### New Configuration Structure
279
-
280
- The Resource class now uses a unified configuration object where all options are passed directly in the config object:
281
-
282
297
  ```javascript
283
298
  const users = await s3db.createResource({
284
299
  name: "users",
285
300
  attributes: {
286
- // Basic types
287
301
  name: "string|min:2|max:100",
288
302
  email: "email|unique",
289
303
  age: "number|integer|positive",
290
304
  isActive: "boolean",
291
-
292
- // Nested objects
293
305
  profile: {
294
306
  bio: "string|optional",
295
- avatar: "url|optional",
296
- preferences: {
297
- theme: "string|enum:light,dark|default:light",
298
- notifications: "boolean|default:true"
299
- }
307
+ avatar: "url|optional"
300
308
  },
301
-
302
- // Arrays
303
309
  tags: "array|items:string|unique",
304
-
305
- // Encrypted fields
306
310
  password: "secret"
307
311
  },
308
- // All options are now at the root level
309
- timestamps: true, // Automatic createdAt/updatedAt
310
- behavior: "user-management", // How to handle large documents
311
- partitions: { // Organize data for efficient queries
312
+ timestamps: true,
313
+ behavior: "user-managed",
314
+ partitions: {
312
315
  byRegion: { fields: { region: "string" } }
313
- },
314
- paranoid: true, // Security flag for dangerous operations
315
- autoDecrypt: true, // Auto-decrypt secret fields
316
- cache: false, // Enable caching
317
- parallelism: 10, // Parallelism for bulk operations
318
- hooks: { // Custom hooks
319
- preInsert: [async (data) => {
320
- console.log("Pre-insert:", data);
321
- return data;
322
- }]
323
316
  }
324
317
  });
325
318
  ```
326
319
 
327
320
  ### ๐Ÿ” Schema Validation
328
- Built-in validation using [@icebob/fastest-validator](https://github.com/icebob/fastest-validator) for resource creation and partition validation. This powerful validation engine provides comprehensive rule support, excellent performance, and detailed error reporting for all your data validation needs.
329
-
330
- ```javascript
331
- const product = await products.insert({
332
- name: "Wireless Headphones",
333
- price: 99.99,
334
- category: "electronics",
335
- features: ["bluetooth", "noise-cancellation"],
336
- specifications: {
337
- battery: "30 hours",
338
- connectivity: "Bluetooth 5.0"
339
- }
340
- });
341
- ```
342
-
343
- **Validation Features powered by fastest-validator:**
344
- - โœ… **Comprehensive Rules** - String, number, array, object, date validation
345
- - โœ… **Nested Objects** - Deep validation for complex data structures
346
- - โœ… **Custom Rules** - Extend with your own validation logic
347
- - โœ… **Performance** - Optimized validation engine for speed
348
- - โœ… **Error Messages** - Detailed validation error reporting
321
+ Built-in validation using [@icebob/fastest-validator](https://github.com/icebob/fastest-validator) with comprehensive rule support and excellent performance.
349
322
 
350
323
  ---
351
324
 
352
325
  ## โšก Advanced Features
353
326
 
354
- s3db.js leverages [@icebob/fastest-validator](https://github.com/icebob/fastest-validator) as its core validation engine for both resource schemas and partition field validation, ensuring high-performance data validation with comprehensive rule support.
355
-
356
327
  ### ๐Ÿ“ฆ Partitions
357
328
 
358
329
  Organize data efficiently with partitions for faster queries:
@@ -363,32 +334,16 @@ const analytics = await s3db.createResource({
363
334
  attributes: {
364
335
  userId: "string",
365
336
  event: "string",
366
- timestamp: "date",
367
- utm: {
368
- source: "string",
369
- medium: "string",
370
- campaign: "string"
371
- }
337
+ timestamp: "date"
372
338
  },
373
339
  partitions: {
374
340
  byDate: { fields: { timestamp: "date|maxlength:10" } },
375
- byUtmSource: { fields: { "utm.source": "string" } },
376
- byUserAndDate: {
377
- fields: {
378
- userId: "string",
379
- timestamp: "date|maxlength:10"
380
- }
381
- }
341
+ byUserAndDate: { fields: { userId: "string", timestamp: "date|maxlength:10" } }
382
342
  }
383
343
  });
384
344
 
385
345
  // Query by partition for better performance
386
- const googleEvents = await analytics.list({
387
- partition: "byUtmSource",
388
- partitionValues: { "utm.source": "google" }
389
- });
390
-
391
- const todayEvents = await analytics.count({
346
+ const todayEvents = await analytics.list({
392
347
  partition: "byDate",
393
348
  partitionValues: { timestamp: "2024-01-15" }
394
349
  });
@@ -401,105 +356,35 @@ Add custom logic with pre/post operation hooks:
401
356
  ```javascript
402
357
  const products = await s3db.createResource({
403
358
  name: "products",
404
- attributes: {
405
- name: "string",
406
- price: "number",
407
- category: "string"
408
- },
359
+ attributes: { name: "string", price: "number" },
409
360
  hooks: {
410
- preInsert: [
411
- async (data) => {
412
- // Auto-generate SKU
413
- data.sku = `${data.category.toUpperCase()}-${Date.now()}`;
414
- return data;
415
- }
416
- ],
417
- afterInsert: [
418
- async (data) => {
419
- console.log(`๐Ÿ“ฆ Product ${data.name} created with SKU: ${data.sku}`);
420
- // Send notification, update cache, etc.
421
- }
422
- ],
423
- preUpdate: [
424
- async (id, data) => {
425
- // Log price changes
426
- if (data.price) {
427
- console.log(`๐Ÿ’ฐ Price update for ${id}: $${data.price}`);
428
- }
429
- return data;
430
- }
431
- ]
432
- },
433
-
434
- // Optional: Security settings (default: true)
435
- paranoid: true,
436
-
437
- // Optional: Schema options (default: false)
438
- allNestedObjectsOptional: false,
439
-
440
- // Optional: Encryption settings (default: true)
441
- autoDecrypt: true,
442
-
443
- // Optional: Caching (default: false)
444
- cache: false
361
+ preInsert: [async (data) => {
362
+ data.sku = `${data.category.toUpperCase()}-${Date.now()}`;
363
+ return data;
364
+ }],
365
+ afterInsert: [async (data) => {
366
+ console.log(`๐Ÿ“ฆ Product ${data.name} created`);
367
+ }]
368
+ }
445
369
  });
446
370
  ```
447
371
 
448
372
  ### ๐Ÿ”„ Streaming API
449
373
 
450
- Handle large datasets efficiently with streams:
374
+ Handle large datasets efficiently:
451
375
 
452
376
  ```javascript
453
- // Export all users to CSV
377
+ // Export to CSV
454
378
  const readableStream = await users.readable();
455
- const csvWriter = createObjectCsvWriter({
456
- path: "users_export.csv",
457
- header: [
458
- { id: "id", title: "ID" },
459
- { id: "name", title: "Name" },
460
- { id: "email", title: "Email" }
461
- ]
462
- });
463
-
464
379
  const records = [];
465
- readableStream.on("data", (user) => {
466
- records.push(user);
467
- });
468
-
469
- readableStream.on("end", async () => {
470
- await csvWriter.writeRecords(records);
471
- console.log("โœ… Export completed: users_export.csv");
472
- });
380
+ readableStream.on("data", (user) => records.push(user));
381
+ readableStream.on("end", () => console.log("โœ… Export completed"));
473
382
 
474
- // Bulk import from stream
383
+ // Bulk import
475
384
  const writableStream = await users.writable();
476
- importData.forEach(userData => {
477
- writableStream.write(userData);
478
- });
385
+ importData.forEach(userData => writableStream.write(userData));
479
386
  writableStream.end();
480
387
  ```
481
-
482
- ### ๐Ÿ›ก๏ธ Document Behaviors
483
-
484
- Handle documents that exceed S3's 2KB metadata limit:
485
-
486
- ```javascript
487
- // Preserve all data by storing overflow in S3 body
488
- const blogs = await s3db.createResource({
489
- name: "blogs",
490
- attributes: {
491
- title: "string",
492
- content: "string", // Can be very large
493
- author: "string"
494
- },
495
- behavior: "body-overflow" // Handles large content automatically
496
- });
497
-
498
- // Strict validation - throws error if limit exceeded
499
- const settings = await s3db.createResource({
500
- name: "settings",
501
- attributes: {
502
- key: "string",
503
388
  value: "string"
504
389
  },
505
390
  behavior: "enforce-limits" // Ensures data stays within 2KB
@@ -516,561 +401,1206 @@ const summaries = await s3db.createResource({
516
401
  });
517
402
  ```
518
403
 
519
- ---
520
-
521
- ## ๐Ÿ“– API Reference
522
-
523
- ### ๐Ÿ”Œ Database Operations
524
-
525
- | Method | Description | Example |
526
- |--------|-------------|---------|
527
- | `connect()` | Connect to database | `await s3db.connect()` |
528
- | `createResource(config)` | Create new resource | `await s3db.createResource({...})` |
529
- | `resource(name)` | Get resource reference | `const users = s3db.resource("users")` |
530
- | `resourceExists(name)` | Check if resource exists | `s3db.resourceExists("users")` |
531
-
532
- ### ๐Ÿ“ Resource Operations
533
-
534
- | Method | Description | Example |
535
- |--------|-------------|---------|
536
- | `insert(data)` | Create document | `await users.insert({name: "John"})` |
537
- | `get(id)` | Retrieve document | `await users.get("user-123")` |
538
- | `update(id, data)` | Update document | `await users.update("user-123", {age: 31})` |
539
- | `upsert(id, data)` | Insert or update | `await users.upsert("user-123", {...})` |
540
- | `delete(id)` | Delete document | `await users.delete("user-123")` |
541
- | `exists(id)` | Check existence | `await users.exists("user-123")` |
542
-
543
- ### ๐Ÿ“Š Query Operations
544
-
545
- | Method | Description | Example |
546
- |--------|-------------|---------|
547
- | `list(options?)` | List documents | `await users.list()` |
548
- | `listIds(options?)` | List document IDs | `await users.listIds()` |
549
- | `count(options?)` | Count documents | `await users.count()` |
550
- | `page(options)` | Paginate results | `await users.page({offset: 0, size: 10})` |
551
- | `query(filter, options?)` | Filter documents | `await users.query({isActive: true})` |
552
-
553
- ### ๐Ÿš€ Bulk Operations
554
-
555
- | Method | Description | Example |
556
- |--------|-------------|---------|
557
- | `insertMany(docs)` | Insert multiple | `await users.insertMany([{...}, {...}])` |
558
- | `getMany(ids)` | Get multiple | `await users.getMany(["id1", "id2"])` |
559
- | `deleteMany(ids)` | Delete multiple | `await users.deleteMany(["id1", "id2"])` |
560
- | `getAll()` | Get all documents | `await users.getAll()` |
561
- | `deleteAll()` | Delete all documents | `await users.deleteAll()` |
562
-
563
- ---
564
-
565
- ## ๐ŸŽจ Examples
404
+ ### ๐Ÿ”„ Resource Versioning System
566
405
 
567
- ### ๐Ÿ“ Blog Platform
406
+ Automatically manages schema evolution and data migration:
568
407
 
569
408
  ```javascript
570
- // Create blog posts with body-overflow behavior for long content
571
- const posts = await s3db.createResource({
572
- name: "posts",
573
- attributes: {
574
- title: "string|min:5|max:200",
575
- content: "string",
576
- author: "string",
577
- tags: "array|items:string",
578
- published: "boolean|default:false",
579
- publishedAt: "date|optional"
580
- },
581
- behavior: "body-overflow", // Handle long content
582
- timestamps: true,
583
- partitions: {
584
- byAuthor: { fields: { author: "string" } },
585
- byTag: { fields: { "tags.0": "string" } }
586
- }
409
+ // Enable versioning
410
+ const s3db = new S3db({
411
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
412
+ versioningEnabled: true
587
413
  });
588
414
 
589
- // Create a blog post
590
- const post = await posts.insert({
591
- title: "Getting Started with s3db.js",
592
- content: "This is a comprehensive guide to using s3db.js for your next project...",
593
- author: "john_doe",
594
- tags: ["tutorial", "database", "s3"],
595
- published: true,
596
- publishedAt: new Date()
415
+ // Create versioned resource
416
+ const users = await s3db.createResource({
417
+ name: "users",
418
+ attributes: {
419
+ name: "string|required",
420
+ email: "string|required"
421
+ },
422
+ versioningEnabled: true
597
423
  });
598
424
 
599
- // Query posts by author
600
- const johnsPosts = await posts.list({
601
- partition: "byAuthor",
602
- partitionValues: { author: "john_doe" }
425
+ // Insert in version 0
426
+ const user1 = await users.insert({
427
+ name: "John Doe",
428
+ email: "john@example.com"
603
429
  });
604
- ```
605
430
 
606
- ### ๐Ÿ›’ E-commerce Store
607
-
608
- ```javascript
609
- // Products with detailed specifications
610
- const products = await s3db.createResource({
611
- name: "products",
431
+ // Update schema - creates version 1
432
+ const updatedUsers = await s3db.createResource({
433
+ name: "users",
612
434
  attributes: {
613
- name: "string|min:2|max:200",
614
- description: "string",
615
- price: "number|positive",
616
- category: "string",
617
- inventory: {
618
- stock: "number|integer|min:0",
619
- reserved: "number|integer|min:0|default:0"
620
- },
621
- specifications: "object|optional",
622
- images: "array|items:url"
435
+ name: "string|required",
436
+ email: "string|required",
437
+ age: "number|optional"
623
438
  },
624
- behavior: "body-overflow",
625
- timestamps: true,
626
- partitions: {
627
- byCategory: { fields: { category: "string" } }
628
- }
439
+ versioningEnabled: true
629
440
  });
630
441
 
631
- // Orders with customer information
632
- const orders = await s3db.createResource({
633
- name: "orders",
634
- attributes: {
635
- customerId: "string",
636
- items: "array|items:object",
637
- total: "number|positive",
638
- status: "string|enum:pending,processing,shipped,delivered",
639
- shipping: {
640
- address: "string",
641
- city: "string",
642
- country: "string",
643
- zipCode: "string"
644
- }
645
- },
646
- behavior: "enforce-limits",
647
- timestamps: true
648
- });
442
+ // Automatic migration
443
+ const migratedUser = await updatedUsers.get(user1.id);
444
+ console.log(migratedUser._v); // "1" - automatically migrated
649
445
 
650
- // Create a product
651
- const product = await products.insert({
652
- name: "Premium Wireless Headphones",
653
- description: "High-quality audio with active noise cancellation",
654
- price: 299.99,
655
- category: "electronics",
656
- inventory: { stock: 50 },
657
- specifications: {
658
- brand: "AudioTech",
659
- model: "AT-WH1000",
660
- features: ["ANC", "Bluetooth 5.0", "30h battery"]
661
- },
662
- images: ["https://example.com/headphones-1.jpg"]
663
- });
664
-
665
- // Create an order
666
- const order = await orders.insert({
667
- customerId: "customer-123",
668
- items: [
669
- { productId: product.id, quantity: 1, price: 299.99 }
670
- ],
671
- total: 299.99,
672
- status: "pending",
673
- shipping: {
674
- address: "123 Main St",
675
- city: "New York",
676
- country: "USA",
677
- zipCode: "10001"
678
- }
446
+ // Query by version
447
+ const version0Users = await users.list({
448
+ partition: "byVersion",
449
+ partitionValues: { _v: "0" }
679
450
  });
680
451
  ```
681
452
 
682
- ### ๐Ÿ‘ฅ User Management System
453
+ ### ๐Ÿ†” Custom ID Generation
454
+
455
+ Flexible ID generation strategies:
683
456
 
684
457
  ```javascript
685
- // Users with authentication
686
- const users = await s3db.createResource({
687
- name: "users",
688
- attributes: {
689
- username: "string|min:3|max:50|unique",
690
- email: "email|unique",
691
- password: "secret", // Automatically encrypted
692
- role: "string|enum:user,admin,moderator|default:user",
693
- profile: {
694
- firstName: "string",
695
- lastName: "string",
696
- avatar: "url|optional",
697
- bio: "string|max:500|optional"
698
- },
699
- preferences: {
700
- theme: "string|enum:light,dark|default:light",
701
- language: "string|default:en",
702
- notifications: "boolean|default:true"
703
- },
704
- lastLogin: "date|optional"
705
- },
706
- behavior: "enforce-limits",
707
- timestamps: true,
708
- hooks: {
709
- preInsert: [async (data) => {
710
- // Auto-generate secure password if not provided
711
- if (!data.password) {
712
- data.password = generateSecurePassword();
713
- }
714
- return data;
715
- }],
716
- afterInsert: [async (data) => {
717
- console.log(`Welcome ${data.username}! ๐ŸŽ‰`);
718
- }]
719
- }
458
+ // Custom size IDs
459
+ const shortUsers = await s3db.createResource({
460
+ name: "short-users",
461
+ attributes: { name: "string|required" },
462
+ idSize: 8 // Generate 8-character IDs
720
463
  });
721
464
 
722
- // Register a new user
723
- const user = await users.insert({
724
- username: "jane_smith",
725
- email: "jane@example.com",
726
- profile: {
727
- firstName: "Jane",
728
- lastName: "Smith"
729
- },
730
- preferences: {
731
- theme: "dark",
732
- notifications: true
733
- }
465
+ // UUID support
466
+ import { v4 as uuidv4 } from 'uuid';
467
+ const uuidUsers = await s3db.createResource({
468
+ name: "uuid-users",
469
+ attributes: { name: "string|required" },
470
+ idGenerator: uuidv4
734
471
  });
735
472
 
736
- // Password was auto-generated and encrypted
737
- console.log("Generated password:", user.password);
738
- ```
739
-
740
- ---
473
+ // UUID v1 (time-based)
474
+ const timeUsers = await s3db.createResource({
475
+ name: "time-users",
476
+ attributes: { name: "string|required" },
477
+ idGenerator: uuidv1
478
+ });
741
479
 
742
- ## ๐Ÿ” Security
480
+ // Custom ID function
481
+ const timestampUsers = await s3db.createResource({
482
+ name: "timestamp-users",
483
+ attributes: { name: "string|required" },
484
+ idGenerator: () => `user_${Date.now()}`
485
+ });
486
+ ```
743
487
 
744
- ### ๐Ÿ”’ Field-Level Encryption
488
+ ### ๐Ÿ”Œ Plugin System
745
489
 
746
- Sensitive data is automatically encrypted using the `"secret"` type:
490
+ Extend functionality with powerful plugins. s3db.js supports multiple plugins working together seamlessly:
747
491
 
748
492
  ```javascript
749
- const users = await s3db.createResource({
750
- name: "users",
751
- attributes: {
752
- email: "email",
753
- password: "secret", // ๐Ÿ” Encrypted
754
- apiKey: "secret", // ๐Ÿ” Encrypted
755
- creditCard: "secret" // ๐Ÿ” Encrypted
756
- }
757
- });
493
+ import {
494
+ CachePlugin,
495
+ CostsPlugin,
496
+ FullTextPlugin,
497
+ MetricsPlugin,
498
+ ReplicationPlugin,
499
+ AuditPlugin
500
+ } from 's3db.js';
758
501
 
759
- const user = await users.insert({
760
- email: "john@example.com",
761
- password: "my_secure_password",
762
- apiKey: "sk_live_123456789",
763
- creditCard: "4111111111111111"
502
+ const s3db = new S3db({
503
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
504
+ plugins: [
505
+ new CachePlugin({ enabled: true }), // CachePlugin needs instantiation
506
+ CostsPlugin, // CostsPlugin is a static object
507
+ new FullTextPlugin({ fields: ['name', 'description'] }),
508
+ new MetricsPlugin({ enabled: true }),
509
+ new ReplicationPlugin({
510
+ enabled: true,
511
+ replicators: [
512
+ {
513
+ driver: 's3db',
514
+ resources: ['users', 'products'],
515
+ config: {
516
+ connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
517
+ }
518
+ }
519
+ ]
520
+ }),
521
+ new AuditPlugin({ enabled: true })
522
+ ]
764
523
  });
765
524
 
766
- // Data is automatically decrypted when retrieved
767
- const retrieved = await users.get(user.id);
768
- console.log(retrieved.password); // "my_secure_password" โœ…
525
+ // All plugins work together seamlessly
526
+ await users.insert({ name: "John", email: "john@example.com" });
527
+ // - Cache: Caches the operation
528
+ // - Costs: Tracks S3 costs
529
+ // - FullText: Indexes the data for search
530
+ // - Metrics: Records performance metrics
531
+ // - Replication: Syncs to configured replicators
532
+ // - Audit: Logs the operation
769
533
  ```
770
534
 
771
- ### ๐ŸŽฒ Auto-Generated Secure Passwords
535
+ ### ๐Ÿ”„ Replicator System
772
536
 
773
- s3db.js automatically generates secure passwords for `secret` fields when not provided:
537
+ The Replication Plugin now supports a flexible driver-based system for replicating data to different targets. Each replicator driver handles a specific type of target system.
774
538
 
539
+ #### Available Replicators
540
+
541
+ **S3DB Replicator** - Replicates data to another s3db instance:
775
542
  ```javascript
776
- const accounts = await s3db.createResource({
777
- name: "accounts",
778
- attributes: {
779
- name: "string",
780
- password: "secret", // Auto-generated if not provided
781
- apiKey: "secret" // Auto-generated if not provided
543
+ {
544
+ driver: 's3db',
545
+ resources: ['users', 'products'], // <-- root level
546
+ config: {
547
+ connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup",
548
+ region: 'us-west-2'
782
549
  }
783
- });
784
-
785
- const account = await accounts.insert({
786
- name: "Service Account"
787
- // password and apiKey will be auto-generated
788
- });
789
-
790
- console.log(account.password); // "Ax7Kp9mN2qR3" (12-char secure password)
791
- console.log(account.apiKey); // "Bc8Lq0nO3sS4" (12-char secure key)
550
+ }
792
551
  ```
793
552
 
794
- **Features:**
795
- - ๐ŸŽฏ **12-character passwords** with cryptographically secure randomness
796
- - ๐Ÿšซ **No confusing characters** (excludes 0, O, 1, l, I)
797
- - ๐Ÿ”„ **Unique every time** using nanoid generation
798
- - ๐Ÿ›ก๏ธ **Custom passwords supported** when explicitly provided
799
-
800
- ### ๐Ÿ”‘ Custom Encryption Keys
553
+ **SQS Replicator** - Sends data to AWS SQS queues:
554
+ ```javascript
555
+ {
556
+ driver: 'sqs',
557
+ resources: ['orders'],
558
+ config: {
559
+ queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
560
+ region: 'us-east-1',
561
+ messageGroupId: 's3db-replication', // For FIFO queues
562
+ deduplicationId: true // Enable deduplication
563
+ }
564
+ }
565
+ ```
801
566
 
567
+ **BigQuery Replicator** - Sends data to Google BigQuery:
802
568
  ```javascript
803
- import fs from "fs";
569
+ {
570
+ driver: 'bigquery',
571
+ resources: ['users', 'orders'],
572
+ config: {
573
+ projectId: 'my-project',
574
+ datasetId: 'analytics',
575
+ tableId: 's3db_replication',
576
+ location: 'US',
577
+ credentials: {
578
+ // Your Google Cloud service account credentials
579
+ client_email: 'service-account@project.iam.gserviceaccount.com',
580
+ private_key: '-----BEGIN PRIVATE KEY-----\n...'
581
+ }
582
+ }
583
+ }
584
+ ```
804
585
 
805
- const s3db = new S3db({
806
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
807
- passphrase: fs.readFileSync("./private-key.pem") // Custom encryption key
808
- });
586
+ **PostgreSQL Replicator** - Sends data to PostgreSQL databases:
587
+ ```javascript
588
+ {
589
+ driver: 'postgres',
590
+ resources: ['users'],
591
+ config: {
592
+ connectionString: 'postgresql://user:pass@localhost:5432/analytics',
593
+ // OR individual parameters:
594
+ // host: 'localhost',
595
+ // port: 5432,
596
+ // database: 'analytics',
597
+ // user: 'user',
598
+ // password: 'pass',
599
+ tableName: 's3db_replication',
600
+ ssl: false
601
+ }
602
+ }
809
603
  ```
810
604
 
811
- ---
605
+ #### Replicator Features
812
606
 
813
- ## ๐Ÿ’ฐ Cost Analysis
607
+ - **Resource Filtering**: Each replicator can be configured to handle specific resources only
608
+ - **Event Emission**: All replicators emit events for monitoring and debugging
609
+ - **Connection Testing**: Test connections to replicators before use
610
+ - **Batch Operations**: Support for batch replication operations
611
+ - **Error Handling**: Comprehensive error handling and retry logic
612
+ - **Status Monitoring**: Get detailed status and statistics for each replicator
814
613
 
815
- ### ๐Ÿ“Š Understanding S3 Costs
614
+ #### Dependencies
816
615
 
817
- s3db.js is incredibly cost-effective because it uses S3 metadata instead of file storage:
616
+ The replicators use optional peer dependencies. Install only what you need:
818
617
 
819
- | Operation | AWS Cost | s3db.js Usage |
820
- |-----------|----------|---------------|
821
- | **PUT Requests** | $0.0005 per 1,000 | Document inserts/updates |
822
- | **GET Requests** | $0.0004 per 1,000 | Document retrievals |
823
- | **Storage** | $0.023 per GB | ~$0 (uses 0-byte files) |
824
- | **Data Transfer** | $0.09 per GB | Minimal (metadata only) |
618
+ ```bash
619
+ # For SQS replicator
620
+ npm install @aws-sdk/client-sqs
621
+ # or
622
+ yarn add @aws-sdk/client-sqs
623
+ # or
624
+ pnpm add @aws-sdk/client-sqs
625
+
626
+ # For BigQuery replicator
627
+ npm install @google-cloud/bigquery
628
+ # or
629
+ yarn add @google-cloud/bigquery
630
+ # or
631
+ pnpm add @google-cloud/bigquery
632
+
633
+ # For PostgreSQL replicator
634
+ npm install pg
635
+ # or
636
+ yarn add pg
637
+ # or
638
+ pnpm add pg
639
+ ```
825
640
 
826
- ### ๐Ÿ’ก Cost Examples
641
+ **โš ๏ธ Important:** These dependencies are marked as `peerDependencies` in the package.json, which means they are not automatically installed with s3db.js. You must install them manually if you plan to use the corresponding replicators. If you don't install the required dependency, the replicator will throw an error when trying to initialize.
827
642
 
828
- <details>
829
- <summary><strong>๐Ÿ“ˆ Small Application (1,000 users)</strong></summary>
643
+ **Example error without dependency:**
644
+ ```
645
+ Error: Cannot find module '@aws-sdk/client-sqs'
646
+ ```
830
647
 
831
- ```javascript
832
- // One-time setup cost
833
- const setupCost = 0.0005; // 1,000 PUT requests = $0.0005
648
+ **Solution:** Install the missing dependency as shown above.
649
+
650
+ #### Example Usage
834
651
 
835
- // Monthly operations (10 reads per user)
836
- const monthlyReads = 0.004; // 10,000 GET requests = $0.004
837
- const monthlyUpdates = 0.0005; // 1,000 PUT requests = $0.0005
652
+ See `examples/e34-replicators.js` for a complete example using all four replicator types.
838
653
 
839
- const totalMonthlyCost = monthlyReads + monthlyUpdates;
840
- console.log(`Monthly cost: $${totalMonthlyCost.toFixed(4)}`); // $0.0045
654
+ **Prerequisites:** Make sure to install the required dependencies before running the example:
655
+
656
+ ```bash
657
+ # Install all replication dependencies for the full example
658
+ npm install @aws-sdk/client-sqs @google-cloud/bigquery pg
841
659
  ```
842
660
 
843
- </details>
661
+ #### ๐Ÿ”„ Cache Plugin
844
662
 
845
- <details>
846
- <summary><strong>๐Ÿš€ Large Application (1,000,000 users)</strong></summary>
663
+ #### ๐Ÿ”„ Cache Plugin
664
+ Intelligent caching to reduce API calls and improve performance:
847
665
 
848
666
  ```javascript
849
- // One-time setup cost
850
- const setupCost = 0.50; // 1,000,000 PUT requests = $0.50
851
-
852
- // Monthly operations (10 reads per user)
853
- const monthlyReads = 4.00; // 10,000,000 GET requests = $4.00
854
- const monthlyUpdates = 0.50; // 1,000,000 PUT requests = $0.50
667
+ const s3db = new S3db({
668
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
669
+ plugins: [new CachePlugin({
670
+ enabled: true,
671
+ ttl: 300000, // 5 minutes cache
672
+ maxSize: 1000, // Max 1000 items in cache
673
+ driverType: 'memory' // 'memory' or 's3'
674
+ })]
675
+ });
855
676
 
856
- const totalMonthlyCost = monthlyReads + monthlyUpdates;
857
- console.log(`Monthly cost: $${totalMonthlyCost.toFixed(2)}`); // $4.50
677
+ // Automatic caching for reads
678
+ await users.count(); // Cached for 5 minutes
679
+ await users.list(); // Cached for 5 minutes
680
+ await users.insert({...}); // Automatically clears cache
858
681
  ```
859
682
 
860
- </details>
861
-
862
- ### ๐Ÿ“ˆ Cost Tracking
863
-
864
- Monitor your expenses with the built-in cost tracking plugin:
683
+ #### ๐Ÿ’ฐ Costs Plugin
684
+ Track and monitor AWS S3 costs in real-time:
865
685
 
866
686
  ```javascript
867
- import { CostsPlugin } from "s3db.js";
868
-
869
687
  const s3db = new S3db({
870
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
688
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
871
689
  plugins: [CostsPlugin]
872
690
  });
873
691
 
874
- // After operations
875
- console.log("๐Ÿ’ฐ Total cost:", s3db.client.costs.total.toFixed(4), "USD");
876
- console.log("๐Ÿ“Š Requests made:", s3db.client.costs.requests.total);
877
- console.log("๐Ÿ“ˆ Cost breakdown:", s3db.client.costs.breakdown);
692
+ // Track costs automatically
693
+ await users.insert({ name: "John", email: "john@example.com" });
694
+ await users.list();
695
+
696
+ // Get cost information
697
+ console.log(s3db.client.costs);
698
+ // { total: 0.000009, requests: { total: 3, get: 1, put: 1, list: 1 } }
878
699
  ```
879
700
 
880
- ---
881
-
882
- ## ๐Ÿšจ Best Practices
883
-
884
- ### โœ… Do's
701
+ #### ๐Ÿ” Full-Text Search Plugin
702
+ Powerful text search with automatic indexing:
885
703
 
886
- #### **๐ŸŽฏ Design for Document Storage**
887
704
  ```javascript
888
- // โœ… Good: Well-structured documents
889
- const user = {
890
- id: "user-123",
891
- name: "John Doe",
892
- profile: {
893
- bio: "Software developer",
894
- preferences: { theme: "dark" }
705
+ const s3db = new S3db({
706
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
707
+ plugins: [new FullTextPlugin({
708
+ enabled: true,
709
+ fields: ['name', 'description', 'content'], // Fields to index
710
+ minWordLength: 3,
711
+ maxResults: 50,
712
+ language: 'en-US'
713
+ })]
714
+ });
715
+
716
+ // Create resource with searchable fields
717
+ const products = await s3db.createResource({
718
+ name: "products",
719
+ attributes: {
720
+ name: "string|required",
721
+ description: "string",
722
+ content: "string"
895
723
  }
896
- };
724
+ });
725
+
726
+ // Insert data (automatically indexed)
727
+ await products.insert({
728
+ name: "JavaScript Book",
729
+ description: "Learn JavaScript programming",
730
+ content: "Comprehensive guide to modern JavaScript"
731
+ });
732
+
733
+ // Search across all indexed fields
734
+ const results = await s3db.plugins.fulltext.searchRecords('products', 'javascript');
735
+ console.log(results); // Returns products with search scores
736
+
737
+ // Example of search results:
738
+ // [
739
+ // {
740
+ // id: "prod-123",
741
+ // name: "JavaScript Book",
742
+ // description: "Learn JavaScript programming",
743
+ // content: "Comprehensive guide to modern JavaScript",
744
+ // _searchScore: 0.85,
745
+ // _matchedFields: ["name", "description", "content"],
746
+ // _matchedWords: ["javascript"]
747
+ // },
748
+ // {
749
+ // id: "prod-456",
750
+ // name: "Web Development Guide",
751
+ // description: "Includes JavaScript, HTML, and CSS",
752
+ // content: "Complete web development with JavaScript",
753
+ // _searchScore: 0.72,
754
+ // _matchedFields: ["description", "content"],
755
+ // _matchedWords: ["javascript"]
756
+ // }
757
+ // ]
897
758
  ```
898
759
 
899
- #### **๐Ÿ“ˆ Use Sequential IDs for Performance**
760
+ #### ๐Ÿ“Š Metrics Plugin
761
+ Monitor performance and usage metrics:
762
+
900
763
  ```javascript
901
- // โœ… Best: Sequential IDs
902
- const productIds = ["00001", "00002", "00003"];
764
+ const s3db = new S3db({
765
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
766
+ plugins: [new MetricsPlugin({
767
+ enabled: true,
768
+ collectPerformance: true,
769
+ collectErrors: true,
770
+ collectUsage: true,
771
+ flushInterval: 60000 // Flush every minute
772
+ })]
773
+ });
903
774
 
904
- // โœ… Good: UUIDs with sufficient entropy
905
- const userIds = ["a1b2c3d4", "e5f6g7h8", "i9j0k1l2"];
775
+ // Metrics are collected automatically
776
+ await users.insert({ name: "John" });
777
+ await users.list();
778
+
779
+ // Get metrics
780
+ const metrics = await s3db.plugins.metrics.getMetrics();
781
+ console.log(metrics); // Performance and usage data
782
+
783
+ // Example of metrics object:
784
+ // {
785
+ // performance: {
786
+ // averageResponseTime: 245, // milliseconds
787
+ // totalRequests: 1250,
788
+ // requestsPerSecond: 12.5,
789
+ // slowestOperations: [
790
+ // { operation: "list", resource: "users", avgTime: 450, count: 50 },
791
+ // { operation: "get", resource: "products", avgTime: 320, count: 200 }
792
+ // ]
793
+ // },
794
+ // usage: {
795
+ // resources: {
796
+ // users: { inserts: 150, updates: 75, deletes: 10, reads: 800 },
797
+ // products: { inserts: 300, updates: 120, deletes: 25, reads: 1200 }
798
+ // },
799
+ // totalOperations: 2680,
800
+ // mostActiveResource: "products",
801
+ // peakUsageHour: "14:00"
802
+ // },
803
+ // errors: {
804
+ // total: 15,
805
+ // byType: {
806
+ // "ValidationError": 8,
807
+ // "NotFoundError": 5,
808
+ // "PermissionError": 2
809
+ // },
810
+ // byResource: {
811
+ // users: 10,
812
+ // products: 5
813
+ // }
814
+ // },
815
+ // cache: {
816
+ // hitRate: 0.78, // 78% cache hit rate
817
+ // totalHits: 980,
818
+ // totalMisses: 270,
819
+ // averageCacheTime: 120 // milliseconds
820
+ // }
821
+ // }
906
822
  ```
907
823
 
908
- #### **๐Ÿ”„ Leverage Streaming for Large Operations**
824
+ #### ๐Ÿ”„ Replication Plugin
825
+ Replicate data to other buckets or regions:
826
+
909
827
  ```javascript
910
- // โœ… Good: Process large datasets with streams
911
- const stream = await users.readable();
912
- stream.on("data", (user) => {
913
- // Process each user individually
828
+ const s3db = new S3db({
829
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
830
+ plugins: [new ReplicationPlugin({
831
+ enabled: true,
832
+ replicators: [
833
+ {
834
+ driver: 's3db',
835
+ resources: ['users', 'products'],
836
+ config: {
837
+ connectionString: "s3://BACKUP_KEY:BACKUP_SECRET@BACKUP_BUCKET/backup"
838
+ }
839
+ },
840
+ {
841
+ driver: 'sqs',
842
+ resources: ['orders', 'users', 'products'],
843
+ config: {
844
+ // Resource-specific queues
845
+ queues: {
846
+ users: 'https://sqs.us-east-1.amazonaws.com/123456789012/users-events.fifo',
847
+ orders: 'https://sqs.us-east-1.amazonaws.com/123456789012/orders-events.fifo',
848
+ products: 'https://sqs.us-east-1.amazonaws.com/123456789012/products-events.fifo'
849
+ },
850
+ // Fallback queue for unspecified resources
851
+ defaultQueueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/default-events.fifo',
852
+ messageGroupId: 's3db-replication', // For FIFO queues
853
+ deduplicationId: true // Enable deduplication
854
+ }
855
+ },
856
+ {
857
+ driver: 'bigquery',
858
+ resources: ['users', 'orders'],
859
+ config: {
860
+ projectId: 'my-project',
861
+ datasetId: 'analytics',
862
+ tableId: 's3db_replication'
863
+ }
864
+ },
865
+ {
866
+ driver: 'postgres',
867
+ resources: ['users'],
868
+ config: {
869
+ connectionString: 'postgresql://user:pass@localhost:5432/analytics',
870
+ tableName: 's3db_replication'
871
+ }
872
+ }
873
+ ],
874
+ syncInterval: 300000 // Sync every 5 minutes
875
+ })]
914
876
  });
877
+
878
+ // Data is automatically replicated to all configured targets
879
+ await users.insert({ name: "John" }); // Synced to all replicators
880
+
881
+ **SQS Message Structure:**
882
+
883
+ The SQS replicator sends standardized messages with the following structure:
884
+
885
+ ```javascript
886
+ // INSERT operation
887
+ {
888
+ resource: "users",
889
+ action: "insert",
890
+ data: { _v: 0, id: "user-001", name: "John", email: "john@example.com" },
891
+ timestamp: "2024-01-01T10:00:00.000Z",
892
+ source: "s3db-replication"
893
+ }
894
+
895
+ // UPDATE operation (includes before/after data)
896
+ {
897
+ resource: "users",
898
+ action: "update",
899
+ before: { _v: 0, id: "user-001", name: "John", age: 30 },
900
+ data: { _v: 1, id: "user-001", name: "John", age: 31 },
901
+ timestamp: "2024-01-01T10:05:00.000Z",
902
+ source: "s3db-replication"
903
+ }
904
+
905
+ // DELETE operation
906
+ {
907
+ resource: "users",
908
+ action: "delete",
909
+ data: { _v: 1, id: "user-001", name: "John", age: 31 },
910
+ timestamp: "2024-01-01T10:10:00.000Z",
911
+ source: "s3db-replication"
912
+ }
915
913
  ```
916
914
 
917
- #### **๐ŸŽ›๏ธ Choose the Right Behavior Strategy**
915
+ **Queue Routing:**
916
+ - Each resource can have its own dedicated queue
917
+ - Unspecified resources use the default queue
918
+ - FIFO queues supported with deduplication
919
+ - Messages are automatically routed to the appropriate queue
920
+ ```
921
+
922
+ #### ๐Ÿ“ Audit Plugin
923
+ Log all operations for compliance and traceability:
924
+
918
925
  ```javascript
919
- // โœ… Development: Flexible with warnings
920
- { behavior: "user-management" }
926
+ const s3db = new S3db({
927
+ connectionString: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
928
+ plugins: [new AuditPlugin({
929
+ enabled: true,
930
+ trackOperations: ['insert', 'update', 'delete', 'get'],
931
+ includeData: false, // Don't log sensitive data
932
+ retentionDays: 90
933
+ })]
934
+ });
921
935
 
922
- // โœ… Production: Strict validation
923
- { behavior: "enforce-limits" }
936
+ // All operations are logged
937
+ await users.insert({ name: "John" });
938
+ await users.update(userId, { age: 31 });
924
939
 
925
- // โœ… Content: Preserve all data
926
- { behavior: "body-overflow" }
940
+ // Get audit logs
941
+ const logs = await s3db.plugins.audit.getAuditLogs({
942
+ resourceName: 'users',
943
+ operation: 'insert'
944
+ });
945
+ console.log(logs); // Audit trail
927
946
  ```
928
947
 
929
- ### โŒ Don'ts
948
+ ### ๐ŸŽ›๏ธ Advanced Behaviors
949
+
950
+ Choose the right behavior strategy for your use case:
951
+
952
+ #### Behavior Comparison
953
+
954
+ | Behavior | Enforcement | Data Loss | Event Emission | Use Case |
955
+ |------------------|-------------|-----------|----------------|-------------------------|
956
+ | `user-managed` | None | Possible | Warns | Dev/Test/Advanced users |
957
+ | `enforce-limits` | Strict | No | Throws | Production |
958
+ | `data-truncate` | Truncates | Yes | Warns | Content Mgmt |
959
+ | `body-overflow` | Truncates/Splits | Yes | Warns | Large objects |
960
+ | `body-only` | Unlimited | No | No | Large JSON/Logs |
961
+
962
+ #### User Managed Behavior (Default)
963
+
964
+ The `user-managed` behavior is the default for s3db resources. It provides no automatic enforcement of S3 metadata or body size limits, and does not modify or truncate data. Instead, it emits warnings via the `exceedsLimit` event when S3 metadata limits are exceeded, but allows all operations to proceed.
965
+
966
+ **Purpose & Use Cases:**
967
+ - For development, testing, or advanced users who want full control over resource metadata and body size.
968
+ - Useful when you want to handle S3 metadata limits yourself, or implement custom logic for warnings.
969
+ - Not recommended for production unless you have custom enforcement or validation in place.
970
+
971
+ **How It Works:**
972
+ - Emits an `exceedsLimit` event (with details) when a resource's metadata size exceeds the S3 2KB limit.
973
+ - Does NOT block, truncate, or modify dataโ€”operations always proceed.
974
+ - No automatic enforcement of any limits; user is responsible for handling warnings and data integrity.
975
+
976
+ **Event Emission:**
977
+ - Event: `exceedsLimit`
978
+ - Payload:
979
+ - `operation`: 'insert' | 'update' | 'upsert'
980
+ - `id` (for update/upsert): resource id
981
+ - `totalSize`: total metadata size in bytes
982
+ - `limit`: S3 metadata limit (2048 bytes)
983
+ - `excess`: number of bytes over the limit
984
+ - `data`: the offending data object
930
985
 
931
- #### **๐Ÿšซ Avoid Large Arrays in Documents**
932
986
  ```javascript
933
- // โŒ Bad: Large arrays can exceed 2KB limit
934
- const user = {
987
+ // Flexible behavior - warns but doesn't block
988
+ const users = await s3db.createResource({
989
+ name: "users",
990
+ attributes: { name: "string", bio: "string" },
991
+ behavior: "user-managed" // Default
992
+ });
993
+
994
+ // Listen for limit warnings
995
+ users.on('exceedsLimit', (data) => {
996
+ console.warn(`Data exceeds 2KB limit by ${data.excess} bytes`, data);
997
+ });
998
+
999
+ // Operation continues despite warning
1000
+ await users.insert({
935
1001
  name: "John",
936
- purchaseHistory: [/* hundreds of orders */]
937
- };
938
-
939
- // โœ… Better: Use separate resource with references
940
- const user = { name: "John", id: "user-123" };
941
- const orders = [
942
- { userId: "user-123", product: "...", date: "..." },
943
- // Store orders separately
1002
+ bio: "A".repeat(3000) // > 2KB
1003
+ });
1004
+ ```
1005
+
1006
+ **Best Practices & Warnings:**
1007
+ - Exceeding S3 metadata limits will cause silent data loss or errors at the storage layer.
1008
+ - Use this behavior only if you have custom logic to handle warnings and enforce limits.
1009
+ - For production, prefer `enforce-limits` or `data-truncate` to avoid data loss.
1010
+
1011
+ **Migration Tips:**
1012
+ - To migrate to a stricter behavior, change the resource's behavior to `enforce-limits` or `data-truncate`.
1013
+ - Review emitted warnings to identify resources at risk of exceeding S3 limits.
1014
+
1015
+ #### Enforce Limits Behavior
1016
+
1017
+ ```javascript
1018
+ // Strict validation - throws error if limit exceeded
1019
+ const settings = await s3db.createResource({
1020
+ name: "settings",
1021
+ attributes: { key: "string", value: "string" },
1022
+ behavior: "enforce-limits"
1023
+ });
1024
+
1025
+ // Throws error if data > 2KB
1026
+ await settings.insert({
1027
+ key: "large_setting",
1028
+ value: "A".repeat(3000) // Throws: "S3 metadata size exceeds 2KB limit"
1029
+ });
1030
+ ```
1031
+
1032
+ #### Data Truncate Behavior
1033
+
1034
+ ```javascript
1035
+ // Smart truncation - preserves structure, truncates content
1036
+ const summaries = await s3db.createResource({
1037
+ name: "summaries",
1038
+ attributes: {
1039
+ title: "string",
1040
+ description: "string",
1041
+ content: "string"
1042
+ },
1043
+ behavior: "data-truncate"
1044
+ });
1045
+
1046
+ // Automatically truncates to fit within 2KB
1047
+ const result = await summaries.insert({
1048
+ title: "Short Title",
1049
+ description: "A".repeat(1000),
1050
+ content: "B".repeat(2000) // Will be truncated with "..."
1051
+ });
1052
+
1053
+ // Retrieved data shows truncation
1054
+ const retrieved = await summaries.get(result.id);
1055
+ console.log(retrieved.content); // "B...B..." (truncated)
1056
+ ```
1057
+
1058
+ #### Body Overflow Behavior
1059
+
1060
+ ```javascript
1061
+ // Preserve all data by using S3 object body
1062
+ const blogs = await s3db.createResource({
1063
+ name: "blogs",
1064
+ attributes: {
1065
+ title: "string",
1066
+ content: "string", // Can be very large
1067
+ author: "string"
1068
+ },
1069
+ behavior: "body-overflow"
1070
+ });
1071
+
1072
+ // Large content is automatically split between metadata and body
1073
+ const blog = await blogs.insert({
1074
+ title: "My Blog Post",
1075
+ content: "A".repeat(5000), // Large content
1076
+ author: "John Doe"
1077
+ });
1078
+
1079
+ // All data is preserved and accessible
1080
+ const retrieved = await blogs.get(blog.id);
1081
+ console.log(retrieved.content.length); // 5000 (full content preserved)
1082
+ console.log(retrieved._hasContent); // true (indicates body usage)
1083
+ ```
1084
+
1085
+ #### Body Only Behavior
1086
+
1087
+ ```javascript
1088
+ // Store all data in S3 object body as JSON, keeping only version in metadata
1089
+ const documents = await s3db.createResource({
1090
+ name: "documents",
1091
+ attributes: {
1092
+ title: "string",
1093
+ content: "string", // Can be extremely large
1094
+ metadata: "object"
1095
+ },
1096
+ behavior: "body-only"
1097
+ });
1098
+
1099
+ // Store large documents without any size limits
1100
+ const document = await documents.insert({
1101
+ title: "Large Document",
1102
+ content: "A".repeat(100000), // 100KB content
1103
+ metadata: {
1104
+ author: "John Doe",
1105
+ tags: ["large", "document"],
1106
+ version: "1.0"
1107
+ }
1108
+ });
1109
+
1110
+ // All data is stored in the S3 object body
1111
+ const retrieved = await documents.get(document.id);
1112
+ console.log(retrieved.content.length); // 100000 (full content preserved)
1113
+ console.log(retrieved.metadata.author); // "John Doe"
1114
+ console.log(retrieved._hasContent); // true (indicates body usage)
1115
+
1116
+ // Perfect for storing large JSON documents, logs, or any large content
1117
+ const logEntry = await documents.insert({
1118
+ title: "Application Log",
1119
+ content: JSON.stringify({
1120
+ timestamp: new Date().toISOString(),
1121
+ level: "INFO",
1122
+ message: "Application started",
1123
+ details: {
1124
+ // ... large log details
1125
+ }
1126
+ }),
1127
+ metadata: { source: "api-server", environment: "production" }
1128
+ });
1129
+ ```
1130
+
1131
+ ### ๐Ÿ”„ Advanced Streaming API
1132
+
1133
+ Handle large datasets efficiently with advanced streaming capabilities:
1134
+
1135
+ #### Readable Streams
1136
+
1137
+ ```javascript
1138
+ // Configure streaming with custom batch size and concurrency
1139
+ const readableStream = await users.readable({
1140
+ batchSize: 50, // Process 50 items per batch
1141
+ concurrency: 10 // 10 concurrent operations
1142
+ });
1143
+
1144
+ // Process data as it streams
1145
+ readableStream.on('data', (user) => {
1146
+ console.log(`Processing user: ${user.name}`);
1147
+ // Process each user individually
1148
+ });
1149
+
1150
+ readableStream.on('error', (error) => {
1151
+ console.error('Stream error:', error);
1152
+ });
1153
+
1154
+ readableStream.on('end', () => {
1155
+ console.log('Stream completed');
1156
+ });
1157
+
1158
+ // Pause and resume streaming
1159
+ readableStream.pause();
1160
+ setTimeout(() => readableStream.resume(), 1000);
1161
+ ```
1162
+
1163
+ #### Writable Streams
1164
+
1165
+ ```javascript
1166
+ // Configure writable stream for bulk operations
1167
+ const writableStream = await users.writable({
1168
+ batchSize: 25, // Write 25 items per batch
1169
+ concurrency: 5 // 5 concurrent writes
1170
+ });
1171
+
1172
+ // Write data to stream
1173
+ const userData = [
1174
+ { name: 'User 1', email: 'user1@example.com' },
1175
+ { name: 'User 2', email: 'user2@example.com' },
1176
+ // ... thousands more
944
1177
  ];
1178
+
1179
+ userData.forEach(user => {
1180
+ writableStream.write(user);
1181
+ });
1182
+
1183
+ // End stream and wait for completion
1184
+ writableStream.on('finish', () => {
1185
+ console.log('All users written successfully');
1186
+ });
1187
+
1188
+ writableStream.on('error', (error) => {
1189
+ console.error('Write error:', error);
1190
+ });
1191
+
1192
+ writableStream.end();
945
1193
  ```
946
1194
 
947
- #### **๐Ÿšซ Don't Load Everything at Once**
1195
+ #### Stream Error Handling
1196
+
948
1197
  ```javascript
949
- // โŒ Bad: Memory intensive
950
- const allUsers = await users.getAll();
1198
+ // Handle errors gracefully in streams
1199
+ const stream = await users.readable();
1200
+
1201
+ stream.on('error', (error, item) => {
1202
+ console.error(`Error processing item:`, error);
1203
+ console.log('Problematic item:', item);
1204
+ // Continue processing other items
1205
+ });
951
1206
 
952
- // โœ… Better: Use pagination or streaming
953
- const page = await users.page({ offset: 0, size: 100 });
1207
+ // Custom error handling for specific operations
1208
+ stream.on('data', async (user) => {
1209
+ try {
1210
+ await processUser(user);
1211
+ } catch (error) {
1212
+ console.error(`Failed to process user ${user.id}:`, error);
1213
+ }
1214
+ });
954
1215
  ```
955
1216
 
956
- ### ๐ŸŽฏ Performance Tips
957
-
958
- 1. **Enable caching** for frequently accessed data:
959
- ```javascript
960
- const s3db = new S3db({
961
- uri: "s3://...",
962
- cache: true,
963
- ttl: 3600 // 1 hour
964
- });
965
- ```
966
-
967
- 2. **Adjust parallelism** for bulk operations:
968
- ```javascript
969
- const s3db = new S3db({
970
- uri: "s3://...",
971
- parallelism: 25 // Handle 25 concurrent operations
972
- });
973
- ```
974
-
975
- 3. **Use partitions** for efficient queries:
976
- ```javascript
977
- // Query specific partitions instead of scanning all data
978
- const results = await users.list({
979
- partition: "byRegion",
980
- partitionValues: { region: "us-east" }
981
- });
982
- ```
1217
+ ### ๐Ÿ“ Binary Content Management
983
1218
 
984
- ---
1219
+ Store and manage binary content alongside your metadata:
985
1220
 
986
- ## ๐Ÿงช Testing
1221
+ #### Set Binary Content
987
1222
 
988
- s3db.js includes a comprehensive test suite. Run tests with:
1223
+ ```javascript
1224
+ import fs from 'fs';
1225
+
1226
+ // Set image content for user profile
1227
+ const imageBuffer = fs.readFileSync('profile.jpg');
1228
+ await users.setContent({
1229
+ id: 'user-123',
1230
+ buffer: imageBuffer,
1231
+ contentType: 'image/jpeg'
1232
+ });
989
1233
 
990
- ```bash
991
- # Run all tests
992
- npm test
1234
+ // Set document content
1235
+ const documentBuffer = fs.readFileSync('document.pdf');
1236
+ await users.setContent({
1237
+ id: 'user-123',
1238
+ buffer: documentBuffer,
1239
+ contentType: 'application/pdf'
1240
+ });
993
1241
 
994
- # Run specific test file
995
- npm test -- --testNamePattern="Resource"
1242
+ // Set text content
1243
+ await users.setContent({
1244
+ id: 'user-123',
1245
+ buffer: 'Hello World',
1246
+ contentType: 'text/plain'
1247
+ });
1248
+ ```
1249
+
1250
+ #### Retrieve Binary Content
1251
+
1252
+ ```javascript
1253
+ // Get binary content
1254
+ const content = await users.content('user-123');
996
1255
 
997
- # Run with coverage
998
- npm run test:coverage
1256
+ if (content.buffer) {
1257
+ console.log('Content type:', content.contentType);
1258
+ console.log('Content size:', content.buffer.length);
1259
+
1260
+ // Save to file
1261
+ fs.writeFileSync('downloaded.jpg', content.buffer);
1262
+ } else {
1263
+ console.log('No content found');
1264
+ }
999
1265
  ```
1000
1266
 
1001
- ### Test Coverage
1267
+ #### Content Management
1002
1268
 
1003
- - โœ… **Unit Tests** - Individual component testing
1004
- - โœ… **Integration Tests** - End-to-end workflows
1005
- - โœ… **Behavior Tests** - Document handling strategies
1006
- - โœ… **Performance Tests** - Large dataset operations
1007
- - โœ… **Security Tests** - Encryption and validation
1269
+ ```javascript
1270
+ // Check if content exists
1271
+ const hasContent = await users.hasContent('user-123');
1272
+ console.log('Has content:', hasContent);
1008
1273
 
1009
- ---
1274
+ // Delete content but preserve metadata
1275
+ await users.deleteContent('user-123');
1276
+ // User metadata remains, but binary content is removed
1277
+ ```
1010
1278
 
1011
- ## ๐Ÿค Contributing
1279
+ ### ๐Ÿ—‚๏ธ Advanced Partitioning
1012
1280
 
1013
- We'd love your help making s3db.js even better! Here's how you can contribute:
1281
+ Organize data efficiently with complex partitioning strategies:
1014
1282
 
1015
- ### ๐Ÿ› ๏ธ Development Setup
1283
+ #### Composite Partitions
1016
1284
 
1017
- ```bash
1018
- # Clone the repository
1019
- git clone https://github.com/forattini-dev/s3db.js.git
1020
- cd s3db.js
1285
+ ```javascript
1286
+ // Partition with multiple fields
1287
+ const analytics = await s3db.createResource({
1288
+ name: "analytics",
1289
+ attributes: {
1290
+ userId: "string",
1291
+ event: "string",
1292
+ timestamp: "date",
1293
+ region: "string",
1294
+ device: "string"
1295
+ },
1296
+ partitions: {
1297
+ // Single field partition
1298
+ byEvent: { fields: { event: "string" } },
1299
+
1300
+ // Two field partition
1301
+ byEventAndRegion: {
1302
+ fields: {
1303
+ event: "string",
1304
+ region: "string"
1305
+ }
1306
+ },
1307
+
1308
+ // Three field partition
1309
+ byEventRegionDevice: {
1310
+ fields: {
1311
+ event: "string",
1312
+ region: "string",
1313
+ device: "string"
1314
+ }
1315
+ }
1316
+ }
1317
+ });
1318
+ ```
1319
+
1320
+ #### Nested Field Partitions
1321
+
1322
+ ```javascript
1323
+ // Partition by nested object fields
1324
+ const users = await s3db.createResource({
1325
+ name: "users",
1326
+ attributes: {
1327
+ name: "string",
1328
+ profile: {
1329
+ country: "string",
1330
+ city: "string",
1331
+ preferences: {
1332
+ theme: "string"
1333
+ }
1334
+ }
1335
+ },
1336
+ partitions: {
1337
+ byCountry: { fields: { "profile.country": "string" } },
1338
+ byCity: { fields: { "profile.city": "string" } },
1339
+ byTheme: { fields: { "profile.preferences.theme": "string" } }
1340
+ }
1341
+ });
1342
+
1343
+ // Query by nested field
1344
+ const usUsers = await users.list({
1345
+ partition: "byCountry",
1346
+ partitionValues: { "profile.country": "US" }
1347
+ });
1348
+
1349
+ // Note: The system automatically manages partition references internally
1350
+ // Users should use standard list() method with partition parameters
1351
+
1352
+ #### Automatic Timestamp Partitions
1021
1353
 
1022
- # Install dependencies
1023
- npm install
1354
+ ```javascript
1355
+ // Enable automatic timestamp partitions
1356
+ const events = await s3db.createResource({
1357
+ name: "events",
1358
+ attributes: {
1359
+ name: "string",
1360
+ data: "object"
1361
+ },
1362
+ timestamps: true // Automatically adds byCreatedDate and byUpdatedDate
1363
+ });
1024
1364
 
1025
- # Run tests
1026
- npm test
1365
+ // Query by creation date
1366
+ const todayEvents = await events.list({
1367
+ partition: "byCreatedDate",
1368
+ partitionValues: { createdAt: "2024-01-15" }
1369
+ });
1027
1370
 
1028
- # Start development server
1029
- npm run dev
1371
+ // Query by update date
1372
+ const recentlyUpdated = await events.list({
1373
+ partition: "byUpdatedDate",
1374
+ partitionValues: { updatedAt: "2024-01-15" }
1375
+ });
1030
1376
  ```
1031
1377
 
1032
- ### ๐Ÿ“‹ Contribution Guidelines
1378
+ #### Partition Validation
1033
1379
 
1034
- 1. **๐Ÿด Fork** the repository
1035
- 2. **๐ŸŒฟ Create** a feature branch (`git checkout -b feature/amazing-feature`)
1036
- 3. **โœจ Make** your changes with tests
1037
- 4. **โœ… Ensure** all tests pass (`npm test`)
1038
- 5. **๐Ÿ“ Commit** your changes (`git commit -m 'Add amazing feature'`)
1039
- 6. **๐Ÿš€ Push** to your branch (`git push origin feature/amazing-feature`)
1040
- 7. **๐Ÿ”„ Open** a Pull Request
1380
+ ```javascript
1381
+ // Partitions are automatically validated against attributes
1382
+ const users = await s3db.createResource({
1383
+ name: "users",
1384
+ attributes: {
1385
+ name: "string",
1386
+ email: "string",
1387
+ status: "string"
1388
+ },
1389
+ partitions: {
1390
+ byStatus: { fields: { status: "string" } }, // โœ… Valid
1391
+ byEmail: { fields: { email: "string" } } // โœ… Valid
1392
+ // byInvalid: { fields: { invalid: "string" } } // โŒ Would throw error
1393
+ }
1394
+ });
1395
+ ```
1041
1396
 
1042
- ### ๐Ÿ› Bug Reports
1397
+ ### ๐ŸŽฃ Advanced Hooks System
1043
1398
 
1044
- Found a bug? Please open an issue with:
1045
- - Clear description of the problem
1046
- - Steps to reproduce
1047
- - Expected vs actual behavior
1048
- - Your environment details
1399
+ Extend functionality with comprehensive hook system:
1049
1400
 
1050
- ### ๐Ÿ’ก Feature Requests
1401
+ #### Hook Execution Order
1051
1402
 
1052
- Have an idea? We'd love to hear it! Open an issue describing:
1053
- - The problem you're trying to solve
1054
- - Your proposed solution
1055
- - Any alternatives you've considered
1403
+ ```javascript
1404
+ const users = await s3db.createResource({
1405
+ name: "users",
1406
+ attributes: { name: "string", email: "string" },
1407
+ hooks: {
1408
+ preInsert: [
1409
+ async (data) => {
1410
+ console.log('1. Pre-insert hook 1');
1411
+ data.timestamp = new Date().toISOString();
1412
+ return data;
1413
+ },
1414
+ async (data) => {
1415
+ console.log('2. Pre-insert hook 2');
1416
+ data.processed = true;
1417
+ return data;
1418
+ }
1419
+ ],
1420
+ afterInsert: [
1421
+ async (data) => {
1422
+ console.log('3. After-insert hook 1');
1423
+ await sendWelcomeEmail(data.email);
1424
+ },
1425
+ async (data) => {
1426
+ console.log('4. After-insert hook 2');
1427
+ await updateAnalytics(data);
1428
+ }
1429
+ ]
1430
+ }
1431
+ });
1056
1432
 
1057
- ---
1433
+ // Execution order: preInsert hooks โ†’ insert โ†’ afterInsert hooks
1434
+ ```
1058
1435
 
1059
- ## ๐Ÿ“„ License
1436
+ #### Version-Specific Hooks
1060
1437
 
1061
- This project is licensed under the **Unlicense** - see the [LICENSE](LICENSE) file for details.
1438
+ ```javascript
1439
+ // Hooks that respond to version changes
1440
+ const users = await s3db.createResource({
1441
+ name: "users",
1442
+ attributes: { name: "string", email: "string" },
1443
+ versioningEnabled: true,
1444
+ hooks: {
1445
+ preInsert: [
1446
+ async (data) => {
1447
+ // Access resource context
1448
+ console.log('Current version:', this.version);
1449
+ return data;
1450
+ }
1451
+ ]
1452
+ }
1453
+ });
1062
1454
 
1063
- This means you can use, modify, and distribute this software for any purpose without any restrictions. It's truly free and open source! ๐ŸŽ‰
1455
+ // Listen for version updates
1456
+ users.on('versionUpdated', ({ oldVersion, newVersion }) => {
1457
+ console.log(`Resource updated from ${oldVersion} to ${newVersion}`);
1458
+ });
1459
+ ```
1460
+
1461
+ #### Error Handling in Hooks
1462
+
1463
+ ```javascript
1464
+ const users = await s3db.createResource({
1465
+ name: "users",
1466
+ attributes: { name: "string", email: "string" },
1467
+ hooks: {
1468
+ preInsert: [
1469
+ async (data) => {
1470
+ try {
1471
+ // Validate external service
1472
+ await validateEmail(data.email);
1473
+ return data;
1474
+ } catch (error) {
1475
+ // Transform error or add context
1476
+ throw new Error(`Email validation failed: ${error.message}`);
1477
+ }
1478
+ }
1479
+ ],
1480
+ afterInsert: [
1481
+ async (data) => {
1482
+ try {
1483
+ await sendWelcomeEmail(data.email);
1484
+ } catch (error) {
1485
+ // Log but don't fail the operation
1486
+ console.error('Failed to send welcome email:', error);
1487
+ }
1488
+ }
1489
+ ]
1490
+ }
1491
+ });
1492
+ ```
1493
+
1494
+ #### Hook Context and Binding
1495
+
1496
+ ```javascript
1497
+ const users = await s3db.createResource({
1498
+ name: "users",
1499
+ attributes: { name: "string", email: "string" },
1500
+ hooks: {
1501
+ preInsert: [
1502
+ async function(data) {
1503
+ // 'this' is bound to the resource instance
1504
+ console.log('Resource name:', this.name);
1505
+ console.log('Resource version:', this.version);
1506
+
1507
+ // Access resource methods
1508
+ const exists = await this.exists(data.id);
1509
+ if (exists) {
1510
+ throw new Error('User already exists');
1511
+ }
1512
+
1513
+ return data;
1514
+ }
1515
+ ]
1516
+ }
1517
+ });
1518
+ ```
1064
1519
 
1065
1520
  ---
1066
1521
 
1067
- <p align="center">
1068
- <strong>Made with โค๏ธ by developers, for developers</strong><br>
1069
- <a href="https://github.com/forattini-dev/s3db.js">โญ Star us on GitHub</a> โ€ข
1070
- <a href="https://www.npmjs.com/package/s3db.js">๐Ÿ“ฆ View on NPM</a> โ€ข
1071
- <a href="https://github.com/forattini-dev/s3db.js/issues">๐Ÿ› Report Issues</a>
1072
- </p>
1522
+ ## ๐Ÿ“– API Reference
1073
1523
 
1074
- <p align="center">
1075
- <sub>Built with Node.js โ€ข Powered by AWS S3 โ€ข Streaming Ready</sub>
1076
- </p>
1524
+ ### ๐Ÿ”Œ Database Operations
1525
+
1526
+ | Method | Description | Example |
1527
+ |--------|-------------|---------|
1528
+ | `connect()` | Connect to database | `await s3db.connect()` |
1529
+ | `createResource(config)` | Create new resource | `await s3db.createResource({...})` |
1530
+ | `resource(name)` | Get resource reference | `const users = s3db.resource("users")` |
1531
+ | `resourceExists(name)` | Check if resource exists | `s3db.resourceExists("users")` |
1532
+
1533
+ ### ๐Ÿ“ Resource Operations
1534
+
1535
+ | Method | Description | Example |
1536
+ |--------|-------------|---------|
1537
+ | `insert(data)` | Create document | `await users.insert({name: "John"})` |
1538
+ | `get(id)` | Retrieve document | `await users.get("user-123")` |
1539
+ | `update(id, data)` | Update document | `await users.update("user-123", {age: 31})` |
1540
+ | `upsert(id, data)` | Insert or update | `await users.upsert("user-123", {...})` |
1541
+ | `delete(id)` | Delete document | `await users.delete("user-123")` |
1542
+ | `exists(id)` | Check existence | `await users.exists("user-123")` |
1543
+ | `setContent({id, buffer, contentType})` | Set binary content | `await users.setContent({id: "123", buffer: imageBuffer})` |
1544
+ | `content(id)` | Get binary content | `await users.content("user-123")` |
1545
+ | `hasContent(id)` | Check if has content | `await users.hasContent("user-123")` |
1546
+ | `deleteContent(id)` | Remove content | `await users.deleteContent("user-123")` |
1547
+
1548
+ ### ๐Ÿ“Š Query Operations
1549
+
1550
+ | Method | Description | Example |
1551
+ |--------|-------------|---------|
1552
+ | `list(options?)` | List documents with pagination & partitions | `await users.list({limit: 10, offset: 0})` |
1553
+ | `listIds(options?)` | List document IDs | `await users.listIds()` |
1554
+ | `count(options?)` | Count documents | `await users.count()` |
1555
+ | `page(options)` | Paginate results | `await users.page({offset: 0, size: 10})` |
1556
+ | `query(filter, options?)` | Filter documents | `await users.query({isActive: true})` |
1557
+
1558
+ #### ๐Ÿ“‹ List vs GetAll - When to Use Each
1559
+
1560
+ **`list(options?)`** - Advanced listing with full control:
1561
+ ```javascript
1562
+ // Simple listing (equivalent to getAll)
1563
+ const allUsers = await users.list();
1564
+
1565
+ // With pagination
1566
+ const first10 = await users.list({ limit: 10, offset: 0 });
1567
+
1568
+ // With partitions
1569
+ const usUsers = await users.list({
1570
+ partition: "byCountry",
1571
+ partitionValues: { "profile.country": "US" }
1572
+ });
1573
+ ```
1574
+
1575
+ **`getAll()`** - Simple listing for all documents:
1576
+ ```javascript
1577
+ // Get all documents (no options, no pagination)
1578
+ const allUsers = await users.getAll();
1579
+ console.log(`Total users: ${allUsers.length}`);
1580
+ ```
1581
+
1582
+ **Choose `getAll()` when:**
1583
+ - โœ… You want all documents without pagination
1584
+ - โœ… You don't need partition filtering
1585
+ - โœ… You prefer simplicity over flexibility
1586
+
1587
+ **Choose `list()` when:**
1588
+ - โœ… You need pagination control
1589
+ - โœ… You want to filter by partitions
1590
+ - โœ… You need more control over the query
1591
+
1592
+ ### ๐Ÿš€ Bulk Operations
1593
+
1594
+ | Method | Description | Example |
1595
+ |--------|-------------|---------|
1596
+ | `insertMany(docs)` | Insert multiple | `await users.insertMany([{...}, {...}])` |
1597
+ | `getMany(ids)` | Get multiple | `await users.getMany(["id1", "id2"])` |
1598
+ | `deleteMany(ids)` | Delete multiple | `await users.deleteMany(["id1", "id2"])` |
1599
+ | `getAll()` | Get all documents | `await users.getAll()` |
1600
+ | `deleteAll()` | Delete all documents | `await users.deleteAll()` |
1601
+
1602
+ ### ๐Ÿ”„ Streaming Operations
1603
+
1604
+ | Method | Description | Example |
1605
+ |--------|-------------|---------|
1606
+ | `