s3db.js 4.1.10 โ†’ 4.1.11

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
@@ -1,28 +1,139 @@
1
- # s3db.js
1
+ # ๐Ÿ—ƒ๏ธ s3db.js
2
+
3
+ <p align="center">
4
+ <img width="200" src="https://img.icons8.com/fluency/200/database.png" alt="s3db.js">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <strong>Transform AWS S3 into a powerful document database</strong><br>
9
+ <em>Zero-cost storage โ€ข Automatic encryption โ€ข ORM-like interface โ€ข Streaming API</em>
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/s3db.js"><img src="https://img.shields.io/npm/v/s3db.js.svg?style=flat&color=brightgreen" alt="npm version"></a>
14
+ &nbsp;
15
+ <a href="https://github.com/forattini-dev/s3db.js"><img src="https://img.shields.io/github/stars/forattini-dev/s3db.js?style=flat&color=yellow" alt="GitHub stars"></a>
16
+ &nbsp;
17
+ <a href="https://github.com/forattini-dev/s3db.js/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Unlicense-blue.svg?style=flat" alt="License"></a>
18
+ &nbsp;
19
+ <a href="https://api.codeclimate.com/v1/badges/26e3dc46c42367d44f18/maintainability"><img src="https://api.codeclimate.com/v1/badges/26e3dc46c42367d44f18/maintainability" alt="Maintainability"></a>
20
+ &nbsp;
21
+ <a href="https://coveralls.io/github/forattini-dev/s3db.js?branch=main"><img src="https://coveralls.io/repos/github/forattini-dev/s3db.js/badge.svg?branch=main&style=flat" alt="Coverage Status"></a>
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="https://github.com/forattini-dev/s3db.js"><img src="https://img.shields.io/badge/Built_with-Node.js-339933.svg?style=flat&logo=node.js" alt="Built with Node.js"></a>
26
+ &nbsp;
27
+ <a href="https://aws.amazon.com/s3/"><img src="https://img.shields.io/badge/Powered_by-AWS_S3-FF9900.svg?style=flat&logo=amazon-aws" alt="Powered by AWS S3"></a>
28
+ &nbsp;
29
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Runtime-Node.js-339933.svg?style=flat&logo=node.js" alt="Node.js Runtime"></a>
30
+ </p>
31
+
32
+ <br>
33
+
34
+ ## ๐Ÿš€ What is s3db.js?
35
+
36
+ **s3db.js** is a revolutionary document database that transforms AWS S3 into a fully functional database using S3's metadata capabilities. Instead of traditional storage methods, it stores document data in S3's metadata fields (up to 2KB), making it incredibly cost-effective while providing a familiar ORM-like interface.
37
+
38
+ **Perfect for:**
39
+ - ๐ŸŒ **Serverless applications** - No database servers to manage
40
+ - ๐Ÿ’ฐ **Cost-conscious projects** - Pay only for what you use
41
+ - ๐Ÿ”’ **Secure applications** - Built-in encryption and validation
42
+ - ๐Ÿ“Š **Analytics platforms** - Efficient data streaming and processing
43
+ - ๐Ÿš€ **Rapid prototyping** - Get started in minutes, not hours
44
+
45
+ ---
46
+
47
+ ## โœจ Key Features
48
+
49
+ <table>
50
+ <tr>
51
+ <td width="50%">
52
+
53
+ ### ๐ŸŽฏ **Database Operations**
54
+ - **ORM-like Interface** - Familiar CRUD operations
55
+ - **Schema Validation** - Automatic data validation
56
+ - **Streaming API** - Handle large datasets efficiently
57
+ - **Event System** - Real-time notifications
58
+
59
+ </td>
60
+ <td width="50%">
61
+
62
+ ### ๐Ÿ” **Security & Performance**
63
+ - **Field-level Encryption** - Secure sensitive data
64
+ - **Intelligent Caching** - Reduce API calls
65
+ - **Auto-generated Passwords** - Secure by default
66
+ - **Cost Tracking** - Monitor AWS expenses
67
+
68
+ </td>
69
+ </tr>
70
+ <tr>
71
+ <td width="50%">
72
+
73
+ ### ๐Ÿ“ฆ **Data Management**
74
+ - **Partitions** - Organize data efficiently
75
+ - **Bulk Operations** - Handle multiple records
76
+ - **Nested Objects** - Complex data structures
77
+ - **Automatic Timestamps** - Track changes
78
+
79
+ </td>
80
+ <td width="50%">
81
+
82
+ ### ๐Ÿ”ง **Extensibility**
83
+ - **Custom Behaviors** - Handle large documents
84
+ - **Hooks System** - Custom business logic
85
+ - **Plugin Architecture** - Extend functionality
86
+ - **Event System** - Real-time notifications
87
+
88
+ </td>
89
+ </tr>
90
+ </table>
91
+
92
+ ---
2
93
 
3
- [![license: unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](http://unlicense.org/) [![npm version](https://img.shields.io/npm/v/s3db.js.svg?style=flat)](https://www.npmjs.com/package/s3db.js) [![Maintainability](https://api.codeclimate.com/v1/badges/26e3dc46c42367d44f18/maintainability)](https://codeclimate.com/github/forattini-dev/s3db.js/maintainability) [![Coverage Status](https://coveralls.io/repos/github/forattini-dev/s3db.js/badge.svg?branch=main)](https://coveralls.io/github/forattini-dev/s3db.js?branch=main)
94
+ ## ๐Ÿ“‹ Table of Contents
4
95
 
5
- **A document-based database built on AWS S3 with a powerful ORM-like interface**
96
+ - [๐Ÿš€ Quick Start](#-quick-start)
97
+ - [๐Ÿ’พ Installation](#-installation)
98
+ - [๐ŸŽฏ Core Concepts](#-core-concepts)
99
+ - [โšก Advanced Features](#-advanced-features)
100
+ - [๐Ÿ“– API Reference](#-api-reference)
101
+ - [๐ŸŽจ Examples](#-examples)
102
+ - [๐Ÿ” Security](#-security)
103
+ - [๐Ÿ’ฐ Cost Analysis](#-cost-analysis)
104
+ - [๐Ÿšจ Best Practices](#-best-practices)
105
+ - [๐Ÿงช Testing](#-testing)
106
+ - [๐Ÿค Contributing](#-contributing)
107
+ - [๐Ÿ“„ License](#-license)
6
108
 
7
- Transform AWS S3 into a fully functional document database with automatic validation, encryption, caching, and streaming capabilities.
109
+ ---
8
110
 
9
111
  ## ๐Ÿš€ Quick Start
10
112
 
113
+ Get up and running in less than 5 minutes!
114
+
115
+ ### 1. Install s3db.js
116
+
11
117
  ```bash
12
- npm i s3db.js
118
+ npm install s3db.js
13
119
  ```
14
120
 
121
+ ### 2. Connect to your S3 database
122
+
15
123
  ```javascript
16
124
  import { S3db } from "s3db.js";
17
125
 
18
- // Connect to your S3 database
19
126
  const s3db = new S3db({
20
127
  uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
21
128
  });
22
129
 
23
130
  await s3db.connect();
131
+ console.log("๐ŸŽ‰ Connected to S3 database!");
132
+ ```
133
+
134
+ ### 3. Create your first resource
24
135
 
25
- // Create a resource (collection)
136
+ ```javascript
26
137
  const users = await s3db.createResource({
27
138
  name: "users",
28
139
  attributes: {
@@ -31,10 +142,29 @@ const users = await s3db.createResource({
31
142
  age: "number|integer|positive",
32
143
  isActive: "boolean",
33
144
  createdAt: "date"
145
+ },
146
+ timestamps: true,
147
+ behavior: "user-management",
148
+ partitions: {
149
+ byRegion: { fields: { region: "string" } }
150
+ },
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
+ }]
34
160
  }
35
161
  });
162
+ ```
36
163
 
37
- // Insert data
164
+ ### 4. Start storing data
165
+
166
+ ```javascript
167
+ // Insert a user
38
168
  const user = await users.insert({
39
169
  name: "John Doe",
40
170
  email: "john@example.com",
@@ -43,144 +173,74 @@ const user = await users.insert({
43
173
  createdAt: new Date()
44
174
  });
45
175
 
46
- // Query data
176
+ // Query the user
47
177
  const foundUser = await users.get(user.id);
48
- console.log(foundUser.name); // "John Doe"
49
- ```
50
-
51
- ## ๐Ÿ“‹ Table of Contents
52
-
53
- - [๐ŸŽฏ What is s3db.js?](#-what-is-s3dbjs)
54
- - [๐Ÿ’ก How it Works](#-how-it-works)
55
- - [โšก Installation & Setup](#-installation--setup)
56
- - [๐Ÿ”ง Configuration](#-configuration)
57
- - [๐Ÿ“š Core Concepts](#-core-concepts)
58
- - [๐Ÿ› ๏ธ API Reference](#๏ธ-api-reference)
59
- - [๐Ÿ“Š Examples](#-examples)
60
- - [๐Ÿ”„ Streaming](#-streaming)
61
- - [๐Ÿ” Security & Encryption](#-security--encryption)
62
- - [๐Ÿ’ฐ Cost Analysis](#-cost-analysis)
63
- - [๐ŸŽ›๏ธ Advanced Features](#๏ธ-advanced-features)
64
- - [๐Ÿšจ Limitations & Best Practices](#-limitations--best-practices)
65
- - [๐Ÿงช Testing](#-testing)
66
- - [๐Ÿ“… Version Compatibility](#-version-compatibility)
178
+ console.log(`Hello, ${foundUser.name}! ๐Ÿ‘‹`);
67
179
 
68
- ## ๐ŸŽฏ What is s3db.js?
180
+ // Update the user
181
+ await users.update(user.id, { age: 31 });
69
182
 
70
- `s3db.js` is a document database that leverages AWS S3's metadata capabilities to store structured data. Instead of storing data in file bodies, it uses S3's metadata fields (up to 2KB) to store document data, making it extremely cost-effective for document storage.
71
-
72
- ### Key Features
73
-
74
- - **๐Ÿ”„ ORM-like Interface**: Familiar database operations (insert, get, update, delete)
75
- - **โœ… Automatic Validation**: Built-in schema validation using fastest-validator
76
- - **๐Ÿ” Encryption**: Optional field-level encryption for sensitive data
77
- - **โšก Streaming**: Handle large datasets with readable/writable streams
78
- - **๐Ÿ’พ Caching**: Reduce API calls with intelligent caching
79
- - **๐Ÿ“Š Cost Tracking**: Monitor AWS costs with built-in plugins
80
- - **๐Ÿ›ก๏ธ Type Safety**: Full TypeScript support
81
- - **๐Ÿ”ง Robust Serialization**: Advanced handling of arrays and objects with edge cases
82
- - **๐Ÿ“ Comprehensive Testing**: Complete test suite with journey-based scenarios
83
- - **๐Ÿ•’ Automatic Timestamps**: Optional createdAt/updatedAt fields
84
- - **๐Ÿ“ฆ Partitions**: Organize data by fields for efficient queries
85
- - **๐ŸŽฃ Hooks**: Custom logic before/after operations
86
- - **๐Ÿ”Œ Plugins**: Extensible architecture
183
+ // List all users
184
+ const allUsers = await users.list();
185
+ console.log(`Total users: ${allUsers.length}`);
186
+ ```
87
187
 
88
- ## ๐Ÿ’ก How it Works
188
+ **That's it!** You now have a fully functional document database running on AWS S3. ๐ŸŽ‰
89
189
 
90
- ### The Magic Behind s3db.js
190
+ ---
91
191
 
92
- AWS S3 allows you to store metadata with each object:
93
- - **Metadata**: Up to 2KB of UTF-8 encoded data
192
+ ## ๐Ÿ’พ Installation
94
193
 
95
- `s3db.js` cleverly uses these fields to store document data instead of file contents, making each S3 object act as a database record.
194
+ ### Package Manager
96
195
 
97
- ### Data Storage Strategy
196
+ ```bash
197
+ # npm
198
+ npm install s3db.js
98
199
 
99
- ```javascript
100
- // Your document
101
- {
102
- id: "user-123",
103
- name: "John Doe",
104
- email: "john@example.com",
105
- age: 30
106
- }
200
+ # pnpm
201
+ pnpm add s3db.js
107
202
 
108
- // Stored in S3 as:
109
- // Key: users/user-123
110
- // Metadata: { "name": "John Doe", "email": "john@example.com", "age": "30", "id": "user-123" }
203
+ # yarn
204
+ yarn add s3db.js
111
205
  ```
112
206
 
113
- ## โšก Installation & Setup
207
+ ### Environment Setup
114
208
 
115
- ### Install
209
+ Create a `.env` file with your AWS credentials:
116
210
 
117
- ```bash
118
- npm i s3db.js
119
- # or
120
- pnpm add s3db.js
121
- # or
122
- yarn add s3db.js
211
+ ```env
212
+ AWS_ACCESS_KEY_ID=your_access_key
213
+ AWS_SECRET_ACCESS_KEY=your_secret_key
214
+ AWS_BUCKET=your_bucket_name
215
+ DATABASE_NAME=myapp
123
216
  ```
124
217
 
125
- ### Basic Setup
218
+ Then initialize s3db.js:
126
219
 
127
220
  ```javascript
128
221
  import { S3db } from "s3db.js";
222
+ import dotenv from "dotenv";
129
223
 
130
- const s3db = new S3db({
131
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
132
- });
133
-
134
- await s3db.connect();
135
- console.log("Connected to S3 database!");
136
- ```
137
-
138
- ### Environment Variables Setup
139
-
140
- ```javascript
141
- import * as dotenv from "dotenv";
142
224
  dotenv.config();
143
225
 
144
- import { S3db } from "s3db.js";
145
-
146
226
  const s3db = new S3db({
147
227
  uri: `s3://${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY}@${process.env.AWS_BUCKET}/databases/${process.env.DATABASE_NAME}`
148
228
  });
149
229
  ```
150
230
 
151
- ## ๐Ÿ”ง Configuration
152
-
153
- ### Connection Options
154
-
155
- | Option | Type | Default | Description |
156
- |--------|------|---------|-------------|
157
- | `uri` | `string` | **required** | S3 connection string |
158
- | `parallelism` | `number` | `10` | Concurrent operations |
159
- | `passphrase` | `string` | `"secret"` | Encryption key |
160
- | `cache` | `boolean` | `false` | Enable caching |
161
- | `ttl` | `number` | `86400` | Cache TTL in seconds |
162
- | `plugins` | `array` | `[]` | Custom plugins |
163
-
164
- ### ๐Ÿ” Authentication & Connectivity
231
+ ### Authentication Methods
165
232
 
166
- `s3db.js` supports multiple authentication methods and can connect to various S3-compatible services:
167
-
168
- #### Connection String Format
169
-
170
- ```
171
- s3://[ACCESS_KEY:SECRET_KEY@]BUCKET_NAME[/PREFIX]
172
- ```
173
-
174
- #### 1. AWS S3 with Access Keys
233
+ <details>
234
+ <summary><strong>๐Ÿ”‘ Multiple authentication options</strong></summary>
175
235
 
236
+ #### 1. Access Keys (Development)
176
237
  ```javascript
177
238
  const s3db = new S3db({
178
239
  uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
179
240
  });
180
241
  ```
181
242
 
182
- #### 2. AWS S3 with IAM Roles (EC2/EKS)
183
-
243
+ #### 2. IAM Roles (Production - Recommended)
184
244
  ```javascript
185
245
  // No credentials needed - uses IAM role permissions
186
246
  const s3db = new S3db({
@@ -188,1157 +248,556 @@ const s3db = new S3db({
188
248
  });
189
249
  ```
190
250
 
191
- #### 3. MinIO or S3-Compatible Services
192
-
251
+ #### 3. S3-Compatible Services (MinIO, etc.)
193
252
  ```javascript
194
253
  const s3db = new S3db({
195
254
  uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
196
- endpoint: "http://localhost:9000" // MinIO default endpoint
197
- });
198
- ```
199
-
200
- #### 4. Environment-Based Configuration
201
-
202
- ```javascript
203
- const s3db = new S3db({
204
- uri: `s3://${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY}@${process.env.AWS_BUCKET}/databases/${process.env.DATABASE_NAME}`,
205
- endpoint: process.env.S3_ENDPOINT
255
+ endpoint: "http://localhost:9000"
206
256
  });
207
257
  ```
208
258
 
209
- #### Security Best Practices
210
-
211
- - **IAM Roles**: Use IAM roles instead of access keys when possible (EC2, EKS, Lambda)
212
- - **Environment Variables**: Store credentials in environment variables, not in code
213
- - **Bucket Permissions**: Ensure your IAM role/user has the necessary S3 permissions:
214
- - `s3:GetObject`, `s3:PutObject`, `s3:DeleteObject`, `s3:ListBucket`, `s3:GetBucketLocation`
215
-
216
- ### Advanced Configuration
217
-
218
- ```javascript
219
- import fs from "fs";
220
-
221
- const s3db = new S3db({
222
- uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp",
223
- parallelism: 25, // Handle 25 concurrent operations
224
- passphrase: fs.readFileSync("./cert.pem"), // Custom encryption key
225
- cache: true, // Enable caching
226
- ttl: 3600, // 1 hour cache TTL
227
- plugins: [CostsPlugin] // Enable cost tracking
228
- });
229
- ```
259
+ </details>
230
260
 
231
- ## ๐Ÿ“š Core Concepts
261
+ ---
232
262
 
233
- ### 1. Database
263
+ ## ๐ŸŽฏ Core Concepts
234
264
 
235
- A database is a logical container for your resources, stored in a specific S3 prefix.
265
+ ### ๐Ÿ—„๏ธ Database
266
+ A logical container for your resources, stored in a specific S3 prefix.
236
267
 
237
268
  ```javascript
238
- // This creates/connects to a database at:
239
- // s3://bucket/databases/myapp/
240
269
  const s3db = new S3db({
241
270
  uri: "s3://ACCESS_KEY:SECRET_KEY@BUCKET_NAME/databases/myapp"
242
271
  });
272
+ // Creates/connects to: s3://bucket/databases/myapp/
243
273
  ```
244
274
 
245
- ### 2. Resources (Collections)
275
+ ### ๐Ÿ“‹ Resources (Collections)
276
+ Resources define the structure of your documents, similar to tables in traditional databases.
277
+
278
+ #### New Configuration Structure
246
279
 
247
- Resources are like tables in traditional databases - they define the structure of your documents.
280
+ The Resource class now uses a unified configuration object where all options are passed directly in the config object:
248
281
 
249
282
  ```javascript
250
283
  const users = await s3db.createResource({
251
284
  name: "users",
252
285
  attributes: {
286
+ // Basic types
253
287
  name: "string|min:2|max:100",
254
288
  email: "email|unique",
255
289
  age: "number|integer|positive",
290
+ isActive: "boolean",
291
+
292
+ // Nested objects
256
293
  profile: {
257
294
  bio: "string|optional",
258
- avatar: "url|optional"
295
+ avatar: "url|optional",
296
+ preferences: {
297
+ theme: "string|enum:light,dark|default:light",
298
+ notifications: "boolean|default:true"
299
+ }
259
300
  },
260
- tags: "array|items:string",
261
- metadata: "object|optional"
301
+
302
+ // Arrays
303
+ tags: "array|items:string|unique",
304
+
305
+ // Encrypted fields
306
+ password: "secret"
307
+ },
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
+ 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
+ }
324
+ });
325
+ ```
326
+
327
+ ### ๐Ÿ” 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"
262
339
  }
263
340
  });
264
341
  ```
265
342
 
266
- #### List, ListIds, Count, Page, Query (Novo Formato)
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
267
349
 
268
- Todos os mรฉtodos de listagem, paginaรงรฃo e contagem agora recebem um รบnico objeto de parรขmetros:
350
+ ---
269
351
 
270
- ```js
271
- // Listar todos os usuรกrios
272
- const allUsers = await users.list();
352
+ ## โšก Advanced Features
273
353
 
274
- // Listar usuรกrios de uma partiรงรฃo
275
- const googleUsers = await users.list({
276
- partition: 'byUtmSource',
277
- partitionValues: { 'utm.source': 'google' }
278
- });
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.
279
355
 
280
- // Listar IDs de uma partiรงรฃo
281
- const googleUserIds = await users.listIds({
282
- partition: 'byUtmSource',
283
- partitionValues: { 'utm.source': 'google' }
284
- });
356
+ ### ๐Ÿ“ฆ Partitions
285
357
 
286
- // Paginar resultados com contagem total
287
- const page = await users.page({
288
- partition: 'byUtmSource',
289
- partitionValues: { 'utm.source': 'google' },
290
- offset: 0,
291
- size: 10
292
- });
293
- console.log(page.items); // Array de usuรกrios
294
- console.log(page.totalItems, page.totalPages); // Contagem total e pรกginas
295
- console.log(page.page, page.pageSize); // Pรกgina atual e tamanho
296
-
297
- // Paginar resultados sem contagem total (mais rรกpido para grandes coleรงรตes)
298
- const fastPage = await users.page({
299
- partition: 'byUtmSource',
300
- partitionValues: { 'utm.source': 'google' },
301
- offset: 0,
302
- size: 10,
303
- skipCount: true // Pula a contagem total para melhor performance
358
+ Organize data efficiently with partitions for faster queries:
359
+
360
+ ```javascript
361
+ const analytics = await s3db.createResource({
362
+ name: "analytics",
363
+ attributes: {
364
+ userId: "string",
365
+ event: "string",
366
+ timestamp: "date",
367
+ utm: {
368
+ source: "string",
369
+ medium: "string",
370
+ campaign: "string"
371
+ }
372
+ },
373
+ partitions: {
374
+ 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
+ }
382
+ }
304
383
  });
305
- console.log(fastPage.items); // Array de usuรกrios
306
- console.log(fastPage.totalItems); // null (nรฃo contado)
307
- console.log(fastPage._debug); // Informaรงรตes de debug
308
-
309
- // Contar documentos em uma partiรงรฃo
310
- const count = await users.count({
311
- partition: 'byUtmSource',
312
- partitionValues: { 'utm.source': 'google' }
384
+
385
+ // Query by partition for better performance
386
+ const googleEvents = await analytics.list({
387
+ partition: "byUtmSource",
388
+ partitionValues: { "utm.source": "google" }
313
389
  });
314
390
 
315
- // Query com filtro e paginaรงรฃo
316
- const filtered = await users.query(
317
- { isActive: true },
318
- { partition: 'byUtmSource', partitionValues: { 'utm.source': 'google' }, limit: 5, offset: 0 }
319
- );
391
+ const todayEvents = await analytics.count({
392
+ partition: "byDate",
393
+ partitionValues: { timestamp: "2024-01-15" }
394
+ });
320
395
  ```
321
396
 
322
- #### Partiรงรตes com Campos Aninhados
397
+ ### ๐ŸŽฃ Hooks System
323
398
 
324
- Vocรช pode usar dot notation para acessar campos aninhados em partiรงรตes:
399
+ Add custom logic with pre/post operation hooks:
325
400
 
326
- ```js
327
- const users = await s3db.createResource({
328
- name: 'users',
401
+ ```javascript
402
+ const products = await s3db.createResource({
403
+ name: "products",
329
404
  attributes: {
330
- name: 'string|required',
331
- utm: { source: 'string|required', medium: 'string|required' },
332
- address: { country: 'string|required', city: 'string|required' }
405
+ name: "string",
406
+ price: "number",
407
+ category: "string"
333
408
  },
334
- options: {
335
- partitions: {
336
- byUtmSource: { fields: { 'utm.source': 'string' } },
337
- byCountry: { fields: { 'address.country': 'string' } }
338
- }
339
- }
409
+ 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
340
445
  });
446
+ ```
341
447
 
342
- // Listar por campo aninhado
343
- const usUsers = await users.list({
344
- partition: 'byCountry',
345
- partitionValues: { 'address.country': 'US' }
448
+ ### ๐Ÿ”„ Streaming API
449
+
450
+ Handle large datasets efficiently with streams:
451
+
452
+ ```javascript
453
+ // Export all users to CSV
454
+ 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
+ ]
346
462
  });
347
- ```
348
463
 
349
- #### getPartitionKey e getFromPartition
464
+ const records = [];
465
+ readableStream.on("data", (user) => {
466
+ records.push(user);
467
+ });
350
468
 
351
- ```js
352
- // Gerar chave de partiรงรฃo
353
- const key = users.getPartitionKey({
354
- partitionName: 'byUtmSource',
355
- id: 'user-123',
356
- data: { utm: { source: 'google' } }
469
+ readableStream.on("end", async () => {
470
+ await csvWriter.writeRecords(records);
471
+ console.log("โœ… Export completed: users_export.csv");
357
472
  });
358
473
 
359
- // Buscar diretamente de uma partiรงรฃo
360
- const user = await users.getFromPartition({
361
- id: 'user-123',
362
- partitionName: 'byUtmSource',
363
- partitionValues: { 'utm.source': 'google' }
474
+ // Bulk import from stream
475
+ const writableStream = await users.writable();
476
+ importData.forEach(userData => {
477
+ writableStream.write(userData);
364
478
  });
479
+ writableStream.end();
365
480
  ```
366
481
 
367
- #### Automatic Timestamps
482
+ ### ๐Ÿ›ก๏ธ Document Behaviors
368
483
 
369
- If you enable the `timestamps` option, `s3db.js` will automatically add `createdAt` and `updatedAt` fields to your resource, and keep them updated on insert and update operations.
484
+ Handle documents that exceed S3's 2KB metadata limit:
370
485
 
371
- ```js
372
- const users = await s3db.createResource({
373
- name: "users",
374
- attributes: { name: "string", email: "email" },
375
- options: { timestamps: true }
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
376
496
  });
377
497
 
378
- const user = await users.insert({ name: "John", email: "john@example.com" });
379
- console.log(user.createdAt); // e.g. "2024-06-27T12:34:56.789Z"
380
- console.log(user.updatedAt); // same as createdAt on insert
498
+ // Strict validation - throws error if limit exceeded
499
+ const settings = await s3db.createResource({
500
+ name: "settings",
501
+ attributes: {
502
+ key: "string",
503
+ value: "string"
504
+ },
505
+ behavior: "enforce-limits" // Ensures data stays within 2KB
506
+ });
507
+
508
+ // Smart truncation - preserves structure, truncates content
509
+ const summaries = await s3db.createResource({
510
+ name: "summaries",
511
+ attributes: {
512
+ title: "string",
513
+ description: "string"
514
+ },
515
+ behavior: "data-truncate" // Truncates to fit within limits
516
+ });
381
517
  ```
382
518
 
383
- #### Resource Behaviors
519
+ ---
520
+
521
+ ## ๐Ÿ“– API Reference
384
522
 
385
- `s3db.js` provides a powerful behavior system to handle how your data is managed when it approaches or exceeds S3's 2KB metadata limit. Each behavior implements different strategies for handling large documents.
523
+ ### ๐Ÿ”Œ Database Operations
386
524
 
387
- ##### Available Behaviors
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")` |
388
531
 
389
- | Behavior | Description | Use Case |
390
- |----------|-------------|----------|
391
- | `user-management` | **Default** - Emits warnings but allows operations | Development and testing |
392
- | `enforce-limits` | Throws errors when limit is exceeded | Strict data size control |
393
- | `data-truncate` | Truncates data to fit within limits | Preserve structure, lose data |
394
- | `body-overflow` | Stores excess data in S3 object body | Preserve all data |
532
+ ### ๐Ÿ“ Resource Operations
395
533
 
396
- ##### Behavior Configuration
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
566
+
567
+ ### ๐Ÿ“ Blog Platform
397
568
 
398
569
  ```javascript
399
- const users = await s3db.createResource({
400
- name: "users",
570
+ // Create blog posts with body-overflow behavior for long content
571
+ const posts = await s3db.createResource({
572
+ name: "posts",
401
573
  attributes: {
402
- name: "string|min:2|max:100",
403
- email: "email|unique",
404
- bio: "string|optional",
405
- preferences: "object|optional"
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"
406
580
  },
407
- options: {
408
- behavior: "body-overflow", // Choose behavior strategy
409
- timestamps: true, // Enable automatic timestamps
410
- partitions: { // Define data partitions
411
- byRegion: {
412
- fields: { region: "string" }
413
- }
414
- },
415
- hooks: { // Custom operation hooks
416
- preInsert: [async (data) => {
417
- // Custom validation logic
418
- return data;
419
- }],
420
- afterInsert: [async (data) => {
421
- console.log("User created:", data.id);
422
- }]
423
- }
581
+ behavior: "body-overflow", // Handle long content
582
+ timestamps: true,
583
+ partitions: {
584
+ byAuthor: { fields: { author: "string" } },
585
+ byTag: { fields: { "tags.0": "string" } }
424
586
  }
425
587
  });
426
- ```
427
588
 
428
- ##### 1. User Management Behavior (Default)
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()
597
+ });
598
+
599
+ // Query posts by author
600
+ const johnsPosts = await posts.list({
601
+ partition: "byAuthor",
602
+ partitionValues: { author: "john_doe" }
603
+ });
604
+ ```
429
605
 
430
- The default behavior that gives you full control over data size management:
606
+ ### ๐Ÿ›’ E-commerce Store
431
607
 
432
608
  ```javascript
433
- const users = await s3db.createResource({
434
- name: "users",
435
- attributes: { name: "string", email: "email" },
436
- options: { behavior: "user-management" }
609
+ // Products with detailed specifications
610
+ const products = await s3db.createResource({
611
+ name: "products",
612
+ 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"
623
+ },
624
+ behavior: "body-overflow",
625
+ timestamps: true,
626
+ partitions: {
627
+ byCategory: { fields: { category: "string" } }
628
+ }
437
629
  });
438
630
 
439
- // Listen for limit warnings
440
- users.on("exceedsLimit", (info) => {
441
- console.log(`Document ${info.operation} exceeds 2KB limit:`, {
442
- totalSize: info.totalSize,
443
- limit: info.limit,
444
- excess: info.excess
445
- });
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
446
648
  });
447
649
 
448
- // Operations continue normally even if limit is exceeded
449
- const user = await users.insert({
450
- name: "John Doe",
451
- email: "john@example.com",
452
- largeBio: "Very long bio...".repeat(100) // Will trigger warning but succeed
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"]
453
663
  });
454
- ```
455
664
 
456
- ##### 2. Enforce Limits Behavior
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
+ }
679
+ });
680
+ ```
457
681
 
458
- Strict behavior that prevents operations when data exceeds the limit:
682
+ ### ๐Ÿ‘ฅ User Management System
459
683
 
460
684
  ```javascript
685
+ // Users with authentication
461
686
  const users = await s3db.createResource({
462
687
  name: "users",
463
- attributes: { name: "string", email: "email" },
464
- options: { behavior: "enforce-limits" }
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
+ }
465
720
  });
466
721
 
467
- try {
468
- const user = await users.insert({
469
- name: "John Doe",
470
- email: "john@example.com",
471
- largeBio: "Very long bio...".repeat(100)
472
- });
473
- } catch (error) {
474
- console.error("Operation failed:", error.message);
475
- // Error: S3 metadata size exceeds 2KB limit. Current size: 2500 bytes, limit: 2000 bytes
476
- }
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
+ }
734
+ });
735
+
736
+ // Password was auto-generated and encrypted
737
+ console.log("Generated password:", user.password);
477
738
  ```
478
739
 
479
- ##### 3. Data Truncate Behavior
740
+ ---
741
+
742
+ ## ๐Ÿ” Security
480
743
 
481
- Intelligently truncates data to fit within limits while preserving structure:
744
+ ### ๐Ÿ”’ Field-Level Encryption
745
+
746
+ Sensitive data is automatically encrypted using the `"secret"` type:
482
747
 
483
748
  ```javascript
484
749
  const users = await s3db.createResource({
485
750
  name: "users",
486
- attributes: { name: "string", email: "email", bio: "string" },
487
- options: { behavior: "data-truncate" }
751
+ attributes: {
752
+ email: "email",
753
+ password: "secret", // ๐Ÿ” Encrypted
754
+ apiKey: "secret", // ๐Ÿ” Encrypted
755
+ creditCard: "secret" // ๐Ÿ” Encrypted
756
+ }
488
757
  });
489
758
 
490
759
  const user = await users.insert({
491
- name: "John Doe",
492
760
  email: "john@example.com",
493
- bio: "This is a very long biography that will be truncated to fit within the 2KB metadata limit..."
761
+ password: "my_secure_password",
762
+ apiKey: "sk_live_123456789",
763
+ creditCard: "4111111111111111"
494
764
  });
495
765
 
496
- console.log(user.bio); // "This is a very long biography that will be truncated to fit within the 2KB metadata limit..."
497
- // Note: The bio will be truncated with "..." suffix if it exceeds available space
766
+ // Data is automatically decrypted when retrieved
767
+ const retrieved = await users.get(user.id);
768
+ console.log(retrieved.password); // "my_secure_password" โœ…
498
769
  ```
499
770
 
500
- ##### 4. Body Overflow Behavior
771
+ ### ๐ŸŽฒ Auto-Generated Secure Passwords
501
772
 
502
- Stores excess data in the S3 object body, preserving all information:
773
+ s3db.js automatically generates secure passwords for `secret` fields when not provided:
503
774
 
504
775
  ```javascript
505
- const users = await s3db.createResource({
506
- name: "users",
507
- attributes: { name: "string", email: "email", bio: "string" },
508
- options: { behavior: "body-overflow" }
509
- });
510
-
511
- const user = await users.insert({
512
- name: "John Doe",
513
- email: "john@example.com",
514
- bio: "This is a very long biography that will be stored in the S3 object body..."
515
- });
516
-
517
- // All data is preserved and automatically merged when retrieved
518
- console.log(user.bio); // Full biography preserved
519
- ```
520
-
521
- **How Body Overflow Works:**
522
- - Small attributes stay in metadata for fast access
523
- - Large attributes are moved to S3 object body
524
- - Data is automatically merged when retrieved
525
- - Maintains full data integrity
526
-
527
- ##### Complete Resource Configuration Reference
528
-
529
- ```javascript
530
- const resource = await s3db.createResource({
531
- // Required: Resource name (unique within database)
532
- name: "users",
533
-
534
- // Required: Schema definition
535
- attributes: {
536
- // Basic types
537
- name: "string|min:2|max:100",
538
- email: "email|unique",
539
- age: "number|integer|positive",
540
- isActive: "boolean",
541
-
542
- // Advanced types
543
- website: "url",
544
- uuid: "uuid",
545
- createdAt: "date",
546
- price: "currency|symbol:$",
547
-
548
- // Encrypted fields
549
- password: "secret",
550
- apiKey: "secret",
551
-
552
- // Nested objects
553
- address: {
554
- street: "string",
555
- city: "string",
556
- country: "string",
557
- zipCode: "string|optional"
558
- },
559
-
560
- // Complex nested structures
561
- profile: {
562
- bio: "string|max:500|optional",
563
- avatar: "url|optional",
564
- birthDate: "date|optional",
565
- preferences: {
566
- theme: "string|enum:light,dark|default:light",
567
- language: "string|enum:en,es,fr|default:en",
568
- notifications: "boolean|default:true"
569
- }
570
- },
571
-
572
- // Nested objects with validation
573
- contact: {
574
- phone: {
575
- mobile: "string|pattern:^\\+?[1-9]\\d{1,14}$|optional",
576
- work: "string|pattern:^\\+?[1-9]\\d{1,14}$|optional"
577
- },
578
- social: {
579
- twitter: "string|optional",
580
- linkedin: "url|optional",
581
- github: "url|optional"
582
- }
583
- },
584
-
585
- // Arrays
586
- tags: "array|items:string|unique",
587
- scores: "array|items:number|min:1",
588
-
589
- // Multiple types
590
- id: ["string", "number"],
591
-
592
- // Complex nested structures
593
- metadata: {
594
- settings: "object|optional",
595
- preferences: "object|optional"
596
- },
597
-
598
- // Analytics and tracking
599
- analytics: {
600
- utm: {
601
- source: "string|optional",
602
- medium: "string|optional",
603
- campaign: "string|optional",
604
- term: "string|optional",
605
- content: "string|optional"
606
- },
607
- events: "array|items:object|optional",
608
- lastVisit: "date|optional"
609
- }
610
- },
611
-
612
- // Optional: Resource configuration
613
- options: {
614
- // Behavior strategy for handling 2KB metadata limits
615
- behavior: "user-management", // "user-management" | "enforce-limits" | "data-truncate" | "body-overflow"
616
-
617
- // Enable automatic timestamps
618
- timestamps: true, // Adds createdAt and updatedAt fields
619
-
620
- // Define data partitions for efficient querying
621
- partitions: {
622
- byRegion: {
623
- fields: { region: "string" }
624
- },
625
- byAgeGroup: {
626
- fields: { ageGroup: "string" }
627
- },
628
- byDate: {
629
- fields: { createdAt: "date|maxlength:10" }
630
- }
631
- },
632
-
633
- // Custom operation hooks
634
- hooks: {
635
- // Pre-operation hooks (can modify data)
636
- preInsert: [
637
- async (data) => {
638
- // Validate or transform data before insert
639
- if (!data.email.includes("@")) {
640
- throw new Error("Invalid email format");
641
- }
642
- return data;
643
- }
644
- ],
645
- preUpdate: [
646
- async (id, data) => {
647
- // Validate or transform data before update
648
- return data;
649
- }
650
- ],
651
- preDelete: [
652
- async (id) => {
653
- // Validate before deletion
654
- return true; // Return false to abort
655
- }
656
- ],
657
-
658
- // Post-operation hooks (cannot modify data)
659
- afterInsert: [
660
- async (data) => {
661
- console.log("User created:", data.id);
662
- }
663
- ],
664
- afterUpdate: [
665
- async (id, data) => {
666
- console.log("User updated:", id);
667
- }
668
- ],
669
- afterDelete: [
670
- async (id) => {
671
- console.log("User deleted:", id);
672
- }
673
- ]
674
- }
675
- }
676
- });
677
- ```
678
-
679
- ### 3. Schema Validation
680
-
681
- `s3db.js` uses [fastest-validator](https://github.com/icebob/fastest-validator) for schema validation with robust handling of edge cases:
682
-
683
- ```javascript
684
- const attributes = {
685
- // Basic types
686
- name: "string|min:2|max:100|trim",
687
- email: "email|nullable",
688
- age: "number|integer|positive",
689
- isActive: "boolean",
690
-
691
- // Advanced types
692
- website: "url",
693
- uuid: "uuid",
694
- createdAt: "date",
695
- price: "currency|symbol:$",
696
-
697
- // Custom s3db types
698
- password: "secret", // Encrypted field
699
-
700
- // Nested objects (supports empty objects and null values)
701
- address: {
702
- street: "string",
703
- city: "string",
704
- country: "string",
705
- zipCode: "string|optional"
706
- },
707
-
708
- // Arrays (robust serialization with special character handling)
709
- tags: "array|items:string|unique", // Handles empty arrays: []
710
- scores: "array|items:number|min:1", // Handles null arrays
711
- categories: "array|items:string", // Handles arrays with pipe characters: ['tag|special', 'normal']
712
-
713
- // Multiple types
714
- id: ["string", "number"],
715
-
716
- // Complex nested structures
717
- metadata: {
718
- settings: "object|optional", // Can be empty: {}
719
- preferences: "object|optional" // Can be null
720
- }
721
- };
722
- ```
723
-
724
- #### Nested Object Validation
725
-
726
- Nested objects support comprehensive validation rules at each level:
727
-
728
- ```javascript
729
- const users = await s3db.createResource({
730
- name: "users",
731
- attributes: {
732
- name: "string|min:2|max:100",
733
- email: "email|unique",
734
-
735
- // Simple nested object
736
- profile: {
737
- bio: "string|max:500|optional",
738
- avatar: "url|optional",
739
- birthDate: "date|optional"
740
- },
741
-
742
- // Complex nested structure with validation
743
- contact: {
744
- phone: {
745
- mobile: "string|pattern:^\\+?[1-9]\\d{1,14}$|optional",
746
- work: "string|pattern:^\\+?[1-9]\\d{1,14}$|optional"
747
- },
748
- social: {
749
- twitter: "string|optional",
750
- linkedin: "url|optional"
751
- }
752
- },
753
-
754
- // Nested object with arrays
755
- preferences: {
756
- categories: "array|items:string|unique|optional",
757
- notifications: {
758
- email: "boolean|default:true",
759
- sms: "boolean|default:false",
760
- push: "boolean|default:true"
761
- }
762
- },
763
-
764
- // Deep nesting with validation
765
- analytics: {
766
- tracking: {
767
- utm: {
768
- source: "string|optional",
769
- medium: "string|optional",
770
- campaign: "string|optional"
771
- },
772
- events: "array|items:object|optional"
773
- }
774
- }
775
- }
776
- });
777
-
778
- // Insert data with complex nested structure
779
- const user = await users.insert({
780
- name: "John Doe",
781
- email: "john@example.com",
782
- profile: {
783
- bio: "Software developer with 10+ years of experience",
784
- avatar: "https://example.com/avatar.jpg",
785
- birthDate: new Date("1990-01-15")
786
- },
787
- contact: {
788
- phone: {
789
- mobile: "+1234567890",
790
- work: "+1987654321"
791
- },
792
- social: {
793
- twitter: "@johndoe",
794
- linkedin: "https://linkedin.com/in/johndoe"
795
- }
796
- },
797
- preferences: {
798
- categories: ["technology", "programming", "web-development"],
799
- notifications: {
800
- email: true,
801
- sms: false,
802
- push: true
803
- }
804
- },
805
- analytics: {
806
- tracking: {
807
- utm: {
808
- source: "google",
809
- medium: "organic",
810
- campaign: "brand"
811
- },
812
- events: [
813
- { type: "page_view", timestamp: new Date() },
814
- { type: "signup", timestamp: new Date() }
815
- ]
816
- }
817
- }
818
- });
819
- ```
820
-
821
- #### Enhanced Array and Object Handling
822
-
823
- s3db.js now provides robust serialization for complex data structures:
824
-
825
- ```javascript
826
- // โœ… Supported: Empty arrays and objects
827
- const user = await users.insert({
828
- name: "John Doe",
829
- tags: [], // Empty array - properly serialized
830
- metadata: {}, // Empty object - properly handled
831
- preferences: null // Null object - correctly preserved
832
- });
833
-
834
- // โœ… Supported: Arrays with special characters
835
- const product = await products.insert({
836
- name: "Widget",
837
- categories: ["electronics|gadgets", "home|office"], // Pipe characters escaped
838
- tags: ["tag|with|pipes", "normal-tag"] // Multiple pipes handled
839
- });
840
- ```
841
-
842
- ## ๐Ÿ› ๏ธ API Reference
843
-
844
- ### Database Operations
845
-
846
- #### Connect to Database
847
-
848
- ```javascript
849
- await s3db.connect();
850
- // Emits 'connected' event when ready
851
- ```
852
-
853
- #### Create Resource
854
-
855
- ```javascript
856
- const resource = await s3db.createResource({
857
- name: "users",
858
- attributes: {
859
- name: "string",
860
- email: "email"
861
- }
862
- });
863
- ```
864
-
865
- #### Check Resource Existence
866
-
867
- ```javascript
868
- // Check if a resource exists by name
869
- const exists = s3db.resourceExists("users");
870
- console.log(exists); // true or false
871
- ```
872
-
873
- #### Create Resource If Not Exists
874
-
875
- ```javascript
876
- // Create a resource only if it doesn't exist with the same definition hash
877
- const result = await s3db.createResourceIfNotExists({
878
- name: "users",
879
- attributes: {
880
- name: "string|required",
881
- email: "email|required"
882
- },
883
- options: { timestamps: true },
884
- behavior: "user-management"
885
- });
886
-
887
- console.log(result);
888
- // {
889
- // resource: Resource,
890
- // created: true, // or false if already existed
891
- // reason: "New resource created" // or "Resource already exists with same definition hash"
892
- // }
893
-
894
- // If the resource already exists with the same hash, it returns the existing resource
895
- const result2 = await s3db.createResourceIfNotExists({
896
- name: "users",
897
- attributes: {
898
- name: "string|required",
899
- email: "email|required"
900
- }
901
- });
902
-
903
- console.log(result2.created); // false
904
- console.log(result2.reason); // "Resource already exists with same definition hash"
905
- ```
906
-
907
- #### Get Resource Reference
908
-
909
- ```javascript
910
- const users = s3db.resource("users");
911
- // or
912
- const users = s3db.resources.users
913
- ```
914
-
915
- ### Resource Operations
916
-
917
- #### Insert Document
918
-
919
- ```javascript
920
- // With custom ID
921
- const user = await users.insert({
922
- id: "user-123",
923
- name: "John Doe",
924
- email: "john@example.com"
925
- });
926
-
927
- // Auto-generated ID
928
- const user = await users.insert({
929
- name: "Jane Doe",
930
- email: "jane@example.com"
931
- });
932
- // ID will be auto-generated using nanoid
933
- ```
934
-
935
- #### Get Document
936
-
937
- ```javascript
938
- const user = await users.get("user-123");
939
- console.log(user.name); // "John Doe"
940
- ```
941
-
942
- #### Update Document
943
-
944
- ```javascript
945
- const updatedUser = await users.update("user-123", {
946
- name: "John Smith",
947
- age: 31
948
- });
949
- // Only specified fields are updated
950
- ```
951
-
952
- #### Upsert Document
953
-
954
- ```javascript
955
- // Insert if doesn't exist, update if exists
956
- const user = await users.upsert("user-123", {
957
- name: "John Doe",
958
- email: "john@example.com",
959
- age: 30
960
- });
961
- ```
962
-
963
- #### Delete Document
964
-
965
- ```javascript
966
- await users.delete("user-123");
967
- ```
968
-
969
- #### Count Documents
970
-
971
- ```javascript
972
- const count = await users.count();
973
- console.log(`Total users: ${count}`);
974
- ```
975
-
976
- #### Page Documents
977
-
978
- The `page()` method provides efficient pagination with optional total count for performance optimization.
979
-
980
- ```javascript
981
- // Basic pagination with total count
982
- const page = await users.page({
983
- offset: 0,
984
- size: 10
985
- });
986
-
987
- console.log(page.items); // Array of user objects
988
- console.log(page.totalItems); // Total number of items
989
- console.log(page.totalPages); // Total number of pages
990
- console.log(page.page); // Current page number (0-based)
991
- console.log(page.pageSize); // Items per page
992
- console.log(page._debug); // Debug information
993
-
994
- // Pagination with partition filtering
995
- const googleUsersPage = await users.page({
996
- partition: 'byUtmSource',
997
- partitionValues: { 'utm.source': 'google' },
998
- offset: 0,
999
- size: 5
1000
- });
1001
-
1002
- // Skip total count for better performance on large collections
1003
- const fastPage = await users.page({
1004
- offset: 0,
1005
- size: 100,
1006
- skipCount: true // Skips counting total items
1007
- });
1008
-
1009
- console.log(fastPage.totalItems); // null (not counted)
1010
- console.log(fastPage.totalPages); // null (not calculated)
1011
- console.log(fastPage._debug.skipCount); // true
1012
- ```
1013
-
1014
- **Page Response Structure:**
1015
-
1016
- ```javascript
1017
- {
1018
- items: Array, // Array of document objects
1019
- totalItems: number, // Total count (null if skipCount: true)
1020
- page: number, // Current page number (0-based)
1021
- pageSize: number, // Number of items per page
1022
- totalPages: number, // Total pages (null if skipCount: true)
1023
- _debug: { // Debug information
1024
- requestedSize: number,
1025
- requestedOffset: number,
1026
- actualItemsReturned: number,
1027
- skipCount: boolean,
1028
- hasTotalItems: boolean
1029
- }
1030
- }
1031
- ```
1032
-
1033
- **Parameters:**
1034
-
1035
- | Parameter | Type | Default | Description |
1036
- |-----------|------|---------|-------------|
1037
- | `offset` | `number` | `0` | Number of items to skip |
1038
- | `size` | `number` | `100` | Number of items per page |
1039
- | `partition` | `string` | `null` | Partition name to filter by |
1040
- | `partitionValues` | `object` | `{}` | Partition field values |
1041
- | `skipCount` | `boolean` | `false` | Skip total count for performance |
1042
-
1043
- ### Bulk Operations
1044
-
1045
- #### Insert Many
1046
-
1047
- ```javascript
1048
- const users = [
1049
- { name: "User 1", email: "user1@example.com" },
1050
- { name: "User 2", email: "user2@example.com" },
1051
- { name: "User 3", email: "user3@example.com" }
1052
- ];
1053
-
1054
- await users.insertMany(users);
1055
- ```
1056
-
1057
- #### Get Many
1058
-
1059
- ```javascript
1060
- const userList = await users.getMany(["user-1", "user-2", "user-3"]);
1061
- ```
1062
-
1063
- #### Delete Many
1064
-
1065
- ```javascript
1066
- await users.deleteMany(["user-1", "user-2", "user-3"]);
1067
- ```
1068
-
1069
- #### Get All
1070
-
1071
- ```javascript
1072
- const allUsers = await users.getAll();
1073
- // Returns all documents in the resource
1074
- ```
1075
-
1076
- #### List IDs
1077
-
1078
- ```javascript
1079
- const userIds = await users.listIds();
1080
- // Returns array of all document IDs
1081
- ```
1082
-
1083
- #### Delete All
1084
-
1085
- ```javascript
1086
- await users.deleteAll();
1087
- // โš ๏ธ Destructive operation - removes all documents
1088
- ```
1089
-
1090
- ## ๐Ÿ“Š Examples
1091
-
1092
- ### E-commerce Application
1093
-
1094
- ```javascript
1095
- // Create product resource with body-overflow behavior for long descriptions
1096
- const products = await s3db.createResource({
1097
- name: "products",
1098
- attributes: {
1099
- name: "string|min:2|max:200",
1100
- description: "string|optional",
1101
- price: "number|positive",
1102
- category: "string",
1103
- tags: "array|items:string",
1104
- inStock: "boolean",
1105
- images: "array|items:url",
1106
- metadata: "object|optional"
1107
- },
1108
- options: {
1109
- behavior: "body-overflow", // Handle long product descriptions
1110
- timestamps: true // Track creation and update times
1111
- }
1112
- });
1113
-
1114
- // Create order resource with enforce-limits for strict data control
1115
- const orders = await s3db.createResource({
1116
- name: "orders",
1117
- attributes: {
1118
- customerId: "string",
1119
- products: "array|items:string",
1120
- total: "number|positive",
1121
- status: "string|enum:pending,paid,shipped,delivered",
1122
- shippingAddress: {
1123
- street: "string",
1124
- city: "string",
1125
- country: "string",
1126
- zipCode: "string"
1127
- },
1128
- createdAt: "date"
1129
- },
1130
- options: {
1131
- behavior: "enforce-limits", // Strict validation for order data
1132
- timestamps: true
1133
- }
1134
- });
1135
-
1136
- // Insert products (long descriptions will be handled by body-overflow)
1137
- const product = await products.insert({
1138
- name: "Wireless Headphones",
1139
- description: "High-quality wireless headphones with noise cancellation, 30-hour battery life, premium comfort design, and crystal-clear audio quality. Perfect for music lovers, professionals, and gamers alike. Features include Bluetooth 5.0, active noise cancellation, touch controls, and a premium carrying case.",
1140
- price: 99.99,
1141
- category: "electronics",
1142
- tags: ["wireless", "bluetooth", "audio", "noise-cancellation"],
1143
- inStock: true,
1144
- images: ["https://example.com/headphones.jpg"]
1145
- });
1146
-
1147
- // Create order (enforce-limits ensures data integrity)
1148
- const order = await orders.insert({
1149
- customerId: "customer-123",
1150
- products: [product.id],
1151
- total: 99.99,
1152
- status: "pending",
1153
- shippingAddress: {
1154
- street: "123 Main St",
1155
- city: "New York",
1156
- country: "USA",
1157
- zipCode: "10001"
1158
- },
1159
- createdAt: new Date()
1160
- });
1161
- ```
1162
-
1163
- ### User Authentication System
1164
-
1165
- ```javascript
1166
- // Create users resource with encrypted password and strict validation
1167
- const users = await s3db.createResource({
1168
- name: "users",
1169
- attributes: {
1170
- username: "string|min:3|max:50|unique",
1171
- email: "email|unique",
1172
- password: "secret", // Encrypted field
1173
- role: "string|enum:user,admin,moderator",
1174
- isActive: "boolean",
1175
- lastLogin: "date|optional",
1176
- profile: {
1177
- firstName: "string",
1178
- lastName: "string",
1179
- avatar: "url|optional",
1180
- bio: "string|optional"
1181
- }
1182
- },
1183
- options: {
1184
- behavior: "enforce-limits", // Strict validation for user data
1185
- timestamps: true // Track account creation and updates
1186
- }
1187
- });
1188
-
1189
- // Create sessions resource with body-overflow for session data
1190
- const sessions = await s3db.createResource({
1191
- name: "sessions",
1192
- attributes: {
1193
- userId: "string",
1194
- token: "secret", // Encrypted session token
1195
- expiresAt: "date",
1196
- userAgent: "string|optional",
1197
- ipAddress: "string|optional",
1198
- sessionData: "object|optional" // Additional session metadata
1199
- },
1200
- options: {
1201
- behavior: "body-overflow", // Handle large session data
1202
- timestamps: true
1203
- }
1204
- });
1205
-
1206
- // Register user (enforce-limits ensures data integrity)
1207
- const user = await users.insert({
1208
- username: "john_doe",
1209
- email: "john@example.com",
1210
- password: "secure_password_123",
1211
- role: "user",
1212
- isActive: true,
1213
- profile: {
1214
- firstName: "John",
1215
- lastName: "Doe"
1216
- }
1217
- });
1218
-
1219
- // Create session (body-overflow preserves all session data)
1220
- const session = await sessions.insert({
1221
- userId: user.id,
1222
- token: "jwt_token_here",
1223
- expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
1224
- userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
1225
- ipAddress: "192.168.1.1",
1226
- sessionData: {
1227
- preferences: { theme: "dark", language: "en" },
1228
- lastActivity: new Date(),
1229
- deviceInfo: { type: "desktop", os: "Windows" }
1230
- }
1231
- });
1232
- ```
1233
-
1234
- ## ๐Ÿ”„ Streaming
1235
-
1236
- For large datasets, use streams to process data efficiently:
1237
-
1238
- ### Readable Stream
1239
-
1240
- ```javascript
1241
- const readableStream = await users.readable();
1242
-
1243
- readableStream.on("id", (id) => {
1244
- console.log("Processing user ID:", id);
1245
- });
1246
-
1247
- readableStream.on("data", (user) => {
1248
- console.log("User:", user.name);
1249
- // Process each user
1250
- });
1251
-
1252
- readableStream.on("end", () => {
1253
- console.log("Finished processing all users");
1254
- });
1255
-
1256
- readableStream.on("error", (error) => {
1257
- console.error("Stream error:", error);
1258
- });
1259
- ```
1260
-
1261
- ### Writable Stream
1262
-
1263
- ```javascript
1264
- const writableStream = await users.writable();
1265
-
1266
- // Write data to stream
1267
- writableStream.write({
1268
- name: "User 1",
1269
- email: "user1@example.com"
1270
- });
1271
-
1272
- writableStream.write({
1273
- name: "User 2",
1274
- email: "user2@example.com"
1275
- });
1276
-
1277
- // End stream
1278
- writableStream.end();
1279
- ```
1280
-
1281
- ### Stream to CSV
1282
-
1283
- ```javascript
1284
- import fs from "fs";
1285
- import { createObjectCsvWriter } from "csv-writer";
1286
-
1287
- const csvWriter = createObjectCsvWriter({
1288
- path: "users.csv",
1289
- header: [
1290
- { id: "id", title: "ID" },
1291
- { id: "name", title: "Name" },
1292
- { id: "email", title: "Email" }
1293
- ]
1294
- });
1295
-
1296
- const readableStream = await users.readable();
1297
- const records = [];
1298
-
1299
- readableStream.on("data", (user) => {
1300
- records.push(user);
1301
- });
1302
-
1303
- readableStream.on("end", async () => {
1304
- await csvWriter.writeRecords(records);
1305
- console.log("CSV file created successfully");
1306
- });
1307
- ```
1308
-
1309
- ## ๐Ÿ” Security & Encryption
1310
-
1311
- ### Field-Level Encryption
1312
-
1313
- Use the `"secret"` type for sensitive data:
1314
-
1315
- ```javascript
1316
- const users = await s3db.createResource({
1317
- name: "users",
776
+ const accounts = await s3db.createResource({
777
+ name: "accounts",
1318
778
  attributes: {
1319
- username: "string",
1320
- email: "email",
1321
- password: "secret", // Encrypted
1322
- apiKey: "secret", // Encrypted
1323
- creditCard: "secret" // Encrypted
779
+ name: "string",
780
+ password: "secret", // Auto-generated if not provided
781
+ apiKey: "secret" // Auto-generated if not provided
1324
782
  }
1325
783
  });
1326
784
 
1327
- // Data is automatically encrypted/decrypted
1328
- const user = await users.insert({
1329
- username: "john_doe",
1330
- email: "john@example.com",
1331
- password: "my_secure_password", // Stored encrypted
1332
- apiKey: "sk_live_123456789", // Stored encrypted
1333
- creditCard: "4111111111111111" // Stored encrypted
785
+ const account = await accounts.insert({
786
+ name: "Service Account"
787
+ // password and apiKey will be auto-generated
1334
788
  });
1335
789
 
1336
- // Retrieved data is automatically decrypted
1337
- const retrieved = await users.get(user.id);
1338
- console.log(retrieved.password); // "my_secure_password" (decrypted)
790
+ console.log(account.password); // "Ax7Kp9mN2qR3" (12-char secure password)
791
+ console.log(account.apiKey); // "Bc8Lq0nO3sS4" (12-char secure key)
1339
792
  ```
1340
793
 
1341
- ### Custom Encryption Key
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
1342
801
 
1343
802
  ```javascript
1344
803
  import fs from "fs";
@@ -1349,44 +808,60 @@ const s3db = new S3db({
1349
808
  });
1350
809
  ```
1351
810
 
811
+ ---
812
+
1352
813
  ## ๐Ÿ’ฐ Cost Analysis
1353
814
 
1354
- ### Understanding S3 Costs
815
+ ### ๐Ÿ“Š Understanding S3 Costs
1355
816
 
1356
- - **PUT Requests**: $0.000005 per 1,000 requests
1357
- - **GET Requests**: $0.0000004 per 1,000 requests
1358
- - **Data Transfer**: $0.09 per GB
1359
- - **Storage**: $0.023 per GB (but s3db.js uses 0-byte files)
817
+ s3db.js is incredibly cost-effective because it uses S3 metadata instead of file storage:
1360
818
 
1361
- ### Cost Examples
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) |
1362
825
 
1363
- #### Small Application (1,000 users)
826
+ ### ๐Ÿ’ก Cost Examples
827
+
828
+ <details>
829
+ <summary><strong>๐Ÿ“ˆ Small Application (1,000 users)</strong></summary>
1364
830
 
1365
831
  ```javascript
1366
- // Setup cost (one-time)
1367
- const setupCost = 0.005; // 1,000 PUT requests
832
+ // One-time setup cost
833
+ const setupCost = 0.0005; // 1,000 PUT requests = $0.0005
1368
834
 
1369
- // Monthly read cost
1370
- const monthlyReadCost = 0.0004; // 1,000 GET requests
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
1371
838
 
1372
- console.log(`Setup: $${setupCost}`);
1373
- console.log(`Monthly reads: $${monthlyReadCost}`);
839
+ const totalMonthlyCost = monthlyReads + monthlyUpdates;
840
+ console.log(`Monthly cost: $${totalMonthlyCost.toFixed(4)}`); // $0.0045
1374
841
  ```
1375
842
 
1376
- #### Large Application (1,000,000 users)
843
+ </details>
844
+
845
+ <details>
846
+ <summary><strong>๐Ÿš€ Large Application (1,000,000 users)</strong></summary>
1377
847
 
1378
848
  ```javascript
1379
- // Setup cost (one-time)
1380
- const setupCost = 5.00; // 1,000,000 PUT requests
849
+ // One-time setup cost
850
+ const setupCost = 0.50; // 1,000,000 PUT requests = $0.50
1381
851
 
1382
- // Monthly read cost
1383
- const monthlyReadCost = 0.40; // 1,000,000 GET requests
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
1384
855
 
1385
- console.log(`Setup: $${setupCost}`);
1386
- console.log(`Monthly reads: $${monthlyReadCost}`);
856
+ const totalMonthlyCost = monthlyReads + monthlyUpdates;
857
+ console.log(`Monthly cost: $${totalMonthlyCost.toFixed(2)}`); // $4.50
1387
858
  ```
1388
859
 
1389
- ### Cost Tracking Plugin
860
+ </details>
861
+
862
+ ### ๐Ÿ“ˆ Cost Tracking
863
+
864
+ Monitor your expenses with the built-in cost tracking plugin:
1390
865
 
1391
866
  ```javascript
1392
867
  import { CostsPlugin } from "s3db.js";
@@ -1397,600 +872,205 @@ const s3db = new S3db({
1397
872
  });
1398
873
 
1399
874
  // After operations
1400
- console.log("Total cost:", s3db.client.costs.total.toFixed(4), "USD");
1401
- console.log("Requests made:", s3db.client.costs.requests.total);
1402
- ```
1403
-
1404
- ## ๐ŸŽ›๏ธ Advanced Features
1405
-
1406
- ### AutoEncrypt / AutoDecrypt
1407
-
1408
- Fields with the type `secret` are automatically encrypted and decrypted using the resource's passphrase. This ensures sensitive data is protected at rest.
1409
-
1410
- ```js
1411
- const users = await s3db.createResource({
1412
- name: "users",
1413
- attributes: {
1414
- username: "string",
1415
- password: "secret" // Will be encrypted
1416
- }
1417
- });
1418
-
1419
- const user = await users.insert({
1420
- username: "john_doe",
1421
- password: "my_secret_password"
1422
- });
1423
-
1424
- // The password is stored encrypted in S3, but automatically decrypted when retrieved
1425
- const retrieved = await users.get(user.id);
1426
- console.log(retrieved.password); // "my_secret_password"
1427
- ```
1428
-
1429
- ### Resource Events
1430
-
1431
- All resources emit events for key operations. You can listen to these events for logging, analytics, or custom workflows.
1432
-
1433
- ```js
1434
- users.on("insert", (data) => console.log("User inserted:", data.id));
1435
- users.on("get", (data) => console.log("User retrieved:", data.id));
1436
- users.on("update", (attrs, data) => console.log("User updated:", data.id));
1437
- users.on("delete", (id) => console.log("User deleted:", id));
1438
- ```
1439
-
1440
- ### Resource Schema Export/Import
1441
-
1442
- You can export and import resource schemas for backup, migration, or versioning purposes.
1443
-
1444
- ```js
1445
- // Export schema
1446
- const schemaData = users.schema.export();
1447
-
1448
- // Import schema
1449
- const importedSchema = Schema.import(schemaData);
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);
1450
878
  ```
1451
879
 
1452
- ## Partitions
880
+ ---
1453
881
 
1454
- `s3db.js` supports **partitions** to organize and query your data efficiently. Partitions allow you to group documents by one or more fields, making it easy to filter, archive, or manage large datasets.
882
+ ## ๐Ÿšจ Best Practices
1455
883
 
1456
- ### Defining partitions
884
+ ### โœ… Do's
1457
885
 
1458
- You can define partitions when creating a resource using the `options.partitions` property:
1459
-
1460
- ```js
1461
- const users = await s3db.createResource({
1462
- name: "users",
1463
- attributes: {
1464
- name: "string",
1465
- email: "email",
1466
- region: "string",
1467
- ageGroup: "string",
1468
- createdAt: "date"
1469
- },
1470
- options: {
1471
- partitions: {
1472
- byRegion: {
1473
- fields: { region: "string" }
1474
- },
1475
- byAgeGroup: {
1476
- fields: { ageGroup: "string" }
1477
- },
1478
- byDate: {
1479
- fields: { createdAt: "date|maxlength:10" }
1480
- }
1481
- }
886
+ #### **๐ŸŽฏ Design for Document Storage**
887
+ ```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" }
1482
895
  }
1483
- });
1484
- ```
1485
-
1486
- ### Querying by partition
1487
-
1488
- Partitions are automatically created when you insert documents, and you can query them using specific methods that accept partition parameters:
1489
-
1490
- #### List IDs by partition
1491
-
1492
- ```js
1493
- // Get all user IDs in the 'south' region
1494
- const userIds = await users.listIds({
1495
- partition: "byRegion",
1496
- partitionValues: { region: "south" }
1497
- });
1498
-
1499
- // Get all user IDs in the 'adult' age group
1500
- const adultIds = await users.listIds({
1501
- partition: "byAgeGroup",
1502
- partitionValues: { ageGroup: "adult" }
1503
- });
1504
- ```
1505
-
1506
- #### Count documents by partition
1507
-
1508
- ```js
1509
- // Count users in the 'south' region
1510
- const count = await users.count({
1511
- partition: "byRegion",
1512
- partitionValues: { region: "south" }
1513
- });
1514
-
1515
- // Count adult users
1516
- const adultCount = await users.count({
1517
- partition: "byAgeGroup",
1518
- partitionValues: { ageGroup: "adult" }
1519
- });
896
+ };
1520
897
  ```
1521
898
 
1522
- #### List objects by partition
1523
-
1524
- ```js
1525
- // Get all users in the 'south' region
1526
- const usersSouth = await users.listByPartition({
1527
- partition: "byRegion",
1528
- partitionValues: { region: "south" }
1529
- });
899
+ #### **๐Ÿ“ˆ Use Sequential IDs for Performance**
900
+ ```javascript
901
+ // โœ… Best: Sequential IDs
902
+ const productIds = ["00001", "00002", "00003"];
1530
903
 
1531
- // Get all adult users with pagination
1532
- const adultUsers = await users.listByPartition(
1533
- { partition: "byAgeGroup", partitionValues: { ageGroup: "adult" } },
1534
- { limit: 10, offset: 0 }
1535
- );
904
+ // โœ… Good: UUIDs with sufficient entropy
905
+ const userIds = ["a1b2c3d4", "e5f6g7h8", "i9j0k1l2"];
1536
906
  ```
1537
907
 
1538
- #### Page through partition data
1539
-
1540
- ```js
1541
- // Get first page of users in 'south' region
1542
- const page = await users.page(0, 10, {
1543
- partition: "byRegion",
1544
- partitionValues: { region: "south" }
908
+ #### **๐Ÿ”„ Leverage Streaming for Large Operations**
909
+ ```javascript
910
+ // โœ… Good: Process large datasets with streams
911
+ const stream = await users.readable();
912
+ stream.on("data", (user) => {
913
+ // Process each user individually
1545
914
  });
1546
-
1547
- console.log(page.items); // Array of user objects
1548
- console.log(page.totalItems); // Total count in this partition
1549
- console.log(page.totalPages); // Total pages available
1550
915
  ```
1551
916
 
1552
- ### Example: Time-based partition
1553
-
1554
- ```js
1555
- const logs = await s3db.createResource({
1556
- name: "logs",
1557
- attributes: {
1558
- message: "string",
1559
- level: "string",
1560
- createdAt: "date"
1561
- },
1562
- options: {
1563
- partitions: {
1564
- byDate: {
1565
- fields: { createdAt: "date|maxlength:10" }
1566
- }
1567
- }
1568
- }
1569
- });
1570
-
1571
- // Insert logs (partitions are created automatically)
1572
- await logs.insert({
1573
- message: "User login",
1574
- level: "info",
1575
- createdAt: new Date("2024-06-27")
1576
- });
917
+ #### **๐ŸŽ›๏ธ Choose the Right Behavior Strategy**
918
+ ```javascript
919
+ // โœ… Development: Flexible with warnings
920
+ { behavior: "user-management" }
1577
921
 
1578
- // Query logs for a specific day
1579
- const logsToday = await logs.listByPartition({
1580
- partition: "byDate",
1581
- partitionValues: { createdAt: "2024-06-27" }
1582
- });
922
+ // โœ… Production: Strict validation
923
+ { behavior: "enforce-limits" }
1583
924
 
1584
- // Count logs for a specific day
1585
- const count = await logs.count({
1586
- partition: "byDate",
1587
- partitionValues: { createdAt: "2024-06-27" }
1588
- });
925
+ // โœ… Content: Preserve all data
926
+ { behavior: "body-overflow" }
1589
927
  ```
1590
928
 
1591
- ### Partitions with Nested Fields
1592
-
1593
- `s3db.js` supports partitions using nested object fields using dot notation, just like the schema mapper:
1594
-
1595
- ```js
1596
- const users = await s3db.createResource({
1597
- name: "users",
1598
- attributes: {
1599
- name: "string|required",
1600
- utm: {
1601
- source: "string|required",
1602
- medium: "string|required",
1603
- campaign: "string|required"
1604
- },
1605
- address: {
1606
- country: "string|required",
1607
- state: "string|required",
1608
- city: "string|required"
1609
- },
1610
- metadata: {
1611
- category: "string|required",
1612
- priority: "string|required"
1613
- }
1614
- },
1615
- options: {
1616
- partitions: {
1617
- byUtmSource: {
1618
- fields: {
1619
- "utm.source": "string"
1620
- }
1621
- },
1622
- byAddressCountry: {
1623
- fields: {
1624
- "address.country": "string|maxlength:2"
1625
- }
1626
- },
1627
- byAddressState: {
1628
- fields: {
1629
- "address.country": "string|maxlength:2",
1630
- "address.state": "string"
1631
- }
1632
- },
1633
- byUtmAndAddress: {
1634
- fields: {
1635
- "utm.source": "string",
1636
- "utm.medium": "string",
1637
- "address.country": "string|maxlength:2"
1638
- }
1639
- }
1640
- }
1641
- }
1642
- });
1643
-
1644
- // Insert user with nested data
1645
- await users.insert({
1646
- name: "John Doe",
1647
- utm: {
1648
- source: "google",
1649
- medium: "cpc",
1650
- campaign: "brand"
1651
- },
1652
- address: {
1653
- country: "US",
1654
- state: "California",
1655
- city: "San Francisco"
1656
- },
1657
- metadata: {
1658
- category: "premium",
1659
- priority: "high"
1660
- }
1661
- });
1662
-
1663
- // Query by nested UTM source
1664
- const googleUsers = await users.listIds({
1665
- partition: "byUtmSource",
1666
- partitionValues: { "utm.source": "google" }
1667
- });
1668
-
1669
- // Query by nested address country
1670
- const usUsers = await users.listIds({
1671
- partition: "byAddressCountry",
1672
- partitionValues: { "address.country": "US" }
1673
- });
1674
-
1675
- // Query by multiple nested fields
1676
- const usCaliforniaUsers = await users.listIds({
1677
- partition: "byAddressState",
1678
- partitionValues: {
1679
- "address.country": "US",
1680
- "address.state": "California"
1681
- }
1682
- });
1683
-
1684
- // Complex query with UTM and address
1685
- const googleCpcUsUsers = await users.listIds({
1686
- partition: "byUtmAndAddress",
1687
- partitionValues: {
1688
- "utm.source": "google",
1689
- "utm.medium": "cpc",
1690
- "address.country": "US"
1691
- }
1692
- });
929
+ ### โŒ Don'ts
1693
930
 
1694
- // Count and list operations work the same way
1695
- const googleCount = await users.count({
1696
- partition: "byUtmSource",
1697
- partitionValues: { "utm.source": "google" }
1698
- });
931
+ #### **๐Ÿšซ Avoid Large Arrays in Documents**
932
+ ```javascript
933
+ // โŒ Bad: Large arrays can exceed 2KB limit
934
+ const user = {
935
+ name: "John",
936
+ purchaseHistory: [/* hundreds of orders */]
937
+ };
1699
938
 
1700
- const googleUsersData = await users.listByPartition({
1701
- partition: "byUtmSource",
1702
- partitionValues: { "utm.source": "google" }
1703
- });
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
944
+ ];
1704
945
  ```
1705
946
 
1706
- **Key features of nested field partitions:**
1707
-
1708
- - **Dot notation**: Use `"parent.child"` to access nested fields
1709
- - **Multiple levels**: Support for deeply nested objects like `"address.country.state"`
1710
- - **Mixed partitions**: Combine nested and flat fields in the same partition
1711
- - **Rules support**: Apply maxlength, date formatting, etc. to nested fields
1712
- - **Automatic flattening**: Uses the same flattening logic as the schema mapper
1713
-
1714
- ### Partition rules and transformations
1715
-
1716
- Partitions support various field rules that automatically transform values:
1717
-
1718
- ```js
1719
- const products = await s3db.createResource({
1720
- name: "products",
1721
- attributes: {
1722
- name: "string",
1723
- category: "string",
1724
- price: "number",
1725
- createdAt: "date"
1726
- },
1727
- options: {
1728
- partitions: {
1729
- byCategory: {
1730
- fields: { category: "string" }
1731
- },
1732
- byDate: {
1733
- fields: { createdAt: "date|maxlength:10" } // Truncates to YYYY-MM-DD
1734
- }
1735
- }
1736
- }
1737
- });
947
+ #### **๐Ÿšซ Don't Load Everything at Once**
948
+ ```javascript
949
+ // โŒ Bad: Memory intensive
950
+ const allUsers = await users.getAll();
1738
951
 
1739
- // Date values are automatically formatted
1740
- await products.insert({
1741
- name: "Widget",
1742
- category: "electronics",
1743
- price: 99.99,
1744
- createdAt: new Date("2024-06-27T15:30:00Z") // Will be stored as "2024-06-27"
1745
- });
952
+ // โœ… Better: Use pagination or streaming
953
+ const page = await users.page({ offset: 0, size: 100 });
1746
954
  ```
1747
955
 
1748
- ### Important notes about partitions
1749
-
1750
- 1. **Automatic creation**: Partitions are automatically created when you insert documents
1751
- 2. **Performance**: Partition queries are more efficient than filtering all documents
1752
- 3. **Storage**: Each partition creates additional S3 objects, increasing storage costs
1753
- 4. **Consistency**: Partition data is automatically kept in sync with main resource data
1754
- 5. **Field requirements**: All partition fields must exist in your resource attributes
1755
-
1756
- ### Available partition-aware methods
1757
-
1758
- | Method | Description | Partition Support |
1759
- |--------|-------------|-------------------|
1760
- | `listIds()` | Get array of document IDs | โœ… `{ partition, partitionValues }` |
1761
- | `count()` | Count documents | โœ… `{ partition, partitionValues }` |
1762
- | `listByPartition()` | List documents by partition | โœ… `{ partition, partitionValues }` |
1763
- | `page()` | Paginate documents | โœ… `{ partition, partitionValues, skipCount }` |
1764
- | `getFromPartition()` | Get single document from partition | โœ… Direct partition access |
1765
- | `query()` | Filter documents in memory | โŒ No partition support |
1766
-
1767
- ## Hooks
1768
-
1769
- `s3db.js` provides a powerful hooks system to let you run custom logic before and after key operations on your resources. Hooks can be used for validation, transformation, logging, or any custom workflow.
956
+ ### ๐ŸŽฏ Performance Tips
1770
957
 
1771
- ### Supported hooks
1772
- - `preInsert` / `afterInsert`
1773
- - `preUpdate` / `afterUpdate`
1774
- - `preDelete` / `afterDelete`
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
+ ```
1775
966
 
1776
- ### Registering hooks
1777
- You can register hooks when creating a resource or dynamically:
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
+ ```
1778
974
 
1779
- ```js
1780
- const users = await s3db.createResource({
1781
- name: "users",
1782
- attributes: { name: "string", email: "email" },
1783
- options: {
1784
- hooks: {
1785
- preInsert: [async (data) => {
1786
- if (!data.email.includes("@")) throw new Error("Invalid email");
1787
- return data;
1788
- }],
1789
- afterInsert: [async (data) => {
1790
- console.log("User inserted:", data.id);
1791
- }]
1792
- }
1793
- }
1794
- });
1795
-
1796
- // Or dynamically:
1797
- users.addHook('preInsert', async (data) => {
1798
- // Custom logic
1799
- return data;
1800
- });
1801
- ```
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
+ ```
1802
983
 
1803
- ### Hook execution order
1804
- - Internal hooks run first, user hooks run last (in the order they were added).
1805
- - Hooks can be async and can modify the data (for `pre*` hooks).
1806
- - If a hook throws, the operation is aborted.
984
+ ---
1807
985
 
1808
- ## Plugins
986
+ ## ๐Ÿงช Testing
1809
987
 
1810
- `s3db.js` supports plugins to extend or customize its behavior. Plugins can hook into lifecycle events, add new methods, or integrate with external systems.
988
+ s3db.js includes a comprehensive test suite. Run tests with:
1811
989
 
1812
- ### Example: Custom plugin
990
+ ```bash
991
+ # Run all tests
992
+ npm test
1813
993
 
1814
- ```js
1815
- const MyPlugin = {
1816
- setup(s3db) {
1817
- console.log("Plugin setup");
1818
- },
1819
- start() {
1820
- console.log("Plugin started");
1821
- },
1822
- onUserCreated(user) {
1823
- console.log("New user created:", user.id);
1824
- }
1825
- };
994
+ # Run specific test file
995
+ npm test -- --testNamePattern="Resource"
1826
996
 
1827
- const s3db = new S3db({
1828
- uri: "s3://...",
1829
- plugins: [MyPlugin]
1830
- });
997
+ # Run with coverage
998
+ npm run test:coverage
1831
999
  ```
1832
1000
 
1833
- ## ๐Ÿšจ Limitations & Best Practices
1001
+ ### Test Coverage
1834
1002
 
1835
- ### Limitations
1836
-
1837
- 1. **Document Size**: Maximum ~2KB per document (metadata only) - **๐Ÿ’ก Use behaviors to handle larger documents**
1838
- 2. **No Complex Queries**: No SQL-like WHERE clauses or joins
1839
- 3. **No Indexes**: No automatic indexing for fast lookups
1840
- 4. **Sequential IDs**: Best performance with sequential IDs (00001, 00002, etc.)
1841
- 5. **No Transactions**: No ACID transactions across multiple operations
1842
- 6. **S3 Pagination**: S3 lists objects in pages of 1000 items maximum, and these operations are not parallelizable, which can make listing large datasets slow
1843
-
1844
- **๐Ÿ’ก Overcoming the 2KB Limit**: Use resource behaviors to handle documents that exceed the 2KB metadata limit:
1845
- - **`body-overflow`**: Stores excess data in S3 object body (preserves all data)
1846
- - **`data-truncate`**: Intelligently truncates data to fit within limits
1847
- - **`enforce-limits`**: Strict validation to prevent oversized documents
1848
- - **`user-management`**: Default behavior with warnings and monitoring
1849
-
1850
- ### Best Practices
1851
-
1852
- #### 1. Design for Document Storage
1853
-
1854
- ```javascript
1855
- // โœ… Good: Nested structure is fine
1856
- const user = {
1857
- id: "user-123",
1858
- name: "John Doe",
1859
- email: "john@example.com",
1860
- profile: {
1861
- bio: "Software developer",
1862
- avatar: "https://example.com/avatar.jpg",
1863
- preferences: {
1864
- theme: "dark",
1865
- notifications: true
1866
- }
1867
- }
1868
- };
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
1869
1008
 
1870
- // โŒ Avoid: Large arrays in documents
1871
- const user = {
1872
- id: "user-123",
1873
- name: "John Doe",
1874
- // This could exceed metadata limits
1875
- purchaseHistory: [
1876
- { id: "order-1", date: "2023-01-01", total: 99.99 },
1877
- { id: "order-2", date: "2023-01-15", total: 149.99 },
1878
- // ... many more items
1879
- ]
1880
- };
1881
- ```
1009
+ ---
1882
1010
 
1883
- #### 2. Use Sequential IDs
1011
+ ## ๐Ÿค Contributing
1884
1012
 
1885
- ```javascript
1886
- // โœ… Good: Sequential IDs for better performance
1887
- const users = ["00001", "00002", "00003", "00004"];
1013
+ We'd love your help making s3db.js even better! Here's how you can contribute:
1888
1014
 
1889
- // โš ๏ธ Acceptable: Random IDs (but ensure sufficient uniqueness)
1890
- const users = ["abc123", "def456", "ghi789", "jkl012"];
1015
+ ### ๐Ÿ› ๏ธ Development Setup
1891
1016
 
1892
- // โŒ Avoid: Random IDs with low combinations (risk of collisions)
1893
- const users = ["a1", "b2", "c3", "d4"]; // Only 26*10 = 260 combinations
1894
- ```
1017
+ ```bash
1018
+ # Clone the repository
1019
+ git clone https://github.com/forattini-dev/s3db.js.git
1020
+ cd s3db.js
1895
1021
 
1896
- #### 3. Optimize for Read Patterns
1022
+ # Install dependencies
1023
+ npm install
1897
1024
 
1898
- ```javascript
1899
- // โœ… Good: Store frequently accessed data together
1900
- const order = {
1901
- id: "order-123",
1902
- customerId: "customer-456",
1903
- customerName: "John Doe", // Denormalized for quick access
1904
- items: ["product-1", "product-2"],
1905
- total: 99.99
1906
- };
1025
+ # Run tests
1026
+ npm test
1907
1027
 
1908
- // โŒ Avoid: Requiring multiple lookups
1909
- const order = {
1910
- id: "order-123",
1911
- customerId: "customer-456", // Requires separate lookup
1912
- items: ["product-1", "product-2"]
1913
- };
1028
+ # Start development server
1029
+ npm run dev
1914
1030
  ```
1915
1031
 
1916
- #### 4. Use Streaming for Large Datasets
1917
-
1918
- ```javascript
1919
- // โœ… Good: Use streams for large operations
1920
- const readableStream = await users.readable();
1921
- readableStream.on("data", (user) => {
1922
- // Process each user individually
1923
- });
1032
+ ### ๐Ÿ“‹ Contribution Guidelines
1924
1033
 
1925
- // โŒ Avoid: Loading all data at once
1926
- const allUsers = await users.getAll(); // May timeout with large datasets
1927
- ```
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
1928
1041
 
1929
- #### 5. Implement Proper Error Handling
1042
+ ### ๐Ÿ› Bug Reports
1930
1043
 
1931
- ```javascript
1932
- // Method 1: Try-catch with get()
1933
- try {
1934
- const user = await users.get("non-existent-id");
1935
- } catch (error) {
1936
- if (error.message.includes("does not exist")) {
1937
- console.log("User not found");
1938
- } else {
1939
- console.error("Unexpected error:", error);
1940
- }
1941
- }
1942
-
1943
- // Method 2: Check existence first (โš ๏ธ Additional request cost)
1944
- const userId = "user-123";
1945
- if (await users.exists(userId)) {
1946
- const user = await users.get(userId);
1947
- console.log("User found:", user.name);
1948
- } else {
1949
- console.log("User not found");
1950
- }
1951
- ```
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
1952
1049
 
1953
- **โš ๏ธ Cost Warning**: Using `exists()` creates an additional S3 request. For high-volume operations, prefer the try-catch approach to minimize costs.
1050
+ ### ๐Ÿ’ก Feature Requests
1954
1051
 
1955
- #### 6. Choose the Right Behavior Strategy
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
1956
1056
 
1957
- ```javascript
1958
- // โœ… For development and testing - allows flexibility
1959
- const devUsers = await s3db.createResource({
1960
- name: "users",
1961
- attributes: { name: "string", email: "email" },
1962
- options: { behavior: "user-management" }
1963
- });
1057
+ ---
1964
1058
 
1965
- // โœ… For production with strict data control
1966
- const prodUsers = await s3db.createResource({
1967
- name: "users",
1968
- attributes: { name: "string", email: "email" },
1969
- options: { behavior: "enforce-limits" }
1970
- });
1059
+ ## ๐Ÿ“„ License
1971
1060
 
1972
- // โœ… For preserving all data with larger documents
1973
- const blogPosts = await s3db.createResource({
1974
- name: "posts",
1975
- attributes: { title: "string", content: "string", author: "string" },
1976
- options: { behavior: "body-overflow" }
1977
- });
1061
+ This project is licensed under the **Unlicense** - see the [LICENSE](LICENSE) file for details.
1978
1062
 
1979
- // โœ… For structured data where truncation is acceptable
1980
- const productDescriptions = await s3db.createResource({
1981
- name: "products",
1982
- attributes: { name: "string", description: "string", price: "number" },
1983
- options: { behavior: "data-truncate" }
1984
- });
1985
- ```
1063
+ This means you can use, modify, and distribute this software for any purpose without any restrictions. It's truly free and open source! ๐ŸŽ‰
1986
1064
 
1987
- **Behavior Selection Guide:**
1988
- - **`user-management`**: Development, testing, or when you want full control
1989
- - **`enforce-limits`**: Production systems requiring strict data validation
1990
- - **`body-overflow`**: When data integrity is critical and you need to preserve all information
1991
- - **`data-truncate`**: When you can afford to lose some data but want to maintain structure
1065
+ ---
1992
1066
 
1993
- ### Performance Tips
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>
1994
1073
 
1995
- 1. **Enable Caching**: Use `cache: true` for frequently accessed data
1996
- 2. **Adjust Parallelism**: Increase `parallelism`
1074
+ <p align="center">
1075
+ <sub>Built with Node.js โ€ข Powered by AWS S3 โ€ข Streaming Ready</sub>
1076
+ </p>