s3db.js 4.0.2 → 4.1.1
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 +463 -7
- package/dist/s3db.cjs.js +127 -15
- package/dist/s3db.cjs.min.js +7 -7
- package/dist/s3db.es.js +128 -16
- package/dist/s3db.es.min.js +7 -7
- package/dist/s3db.iife.js +127 -15
- package/dist/s3db.iife.min.js +10 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -263,6 +263,75 @@ const users = await s3db.createResource({
|
|
|
263
263
|
});
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
+
#### Nested Attributes
|
|
267
|
+
|
|
268
|
+
`s3db.js` fully supports nested object attributes, allowing you to create complex document structures:
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
const users = await s3db.createResource({
|
|
272
|
+
name: "users",
|
|
273
|
+
attributes: {
|
|
274
|
+
name: "string|required",
|
|
275
|
+
email: "email|required",
|
|
276
|
+
utm: {
|
|
277
|
+
source: "string|required",
|
|
278
|
+
medium: "string|required",
|
|
279
|
+
campaign: "string|required",
|
|
280
|
+
term: "string|optional",
|
|
281
|
+
content: "string|optional"
|
|
282
|
+
},
|
|
283
|
+
address: {
|
|
284
|
+
street: "string|required",
|
|
285
|
+
city: "string|required",
|
|
286
|
+
state: "string|required",
|
|
287
|
+
country: "string|required",
|
|
288
|
+
zipCode: "string|optional"
|
|
289
|
+
},
|
|
290
|
+
metadata: {
|
|
291
|
+
category: "string|required",
|
|
292
|
+
priority: "string|required",
|
|
293
|
+
settings: "object|optional"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Insert data with nested objects
|
|
299
|
+
const user = await users.insert({
|
|
300
|
+
name: "John Doe",
|
|
301
|
+
email: "john@example.com",
|
|
302
|
+
utm: {
|
|
303
|
+
source: "google",
|
|
304
|
+
medium: "cpc",
|
|
305
|
+
campaign: "brand_awareness",
|
|
306
|
+
term: "search term"
|
|
307
|
+
},
|
|
308
|
+
address: {
|
|
309
|
+
street: "123 Main St",
|
|
310
|
+
city: "San Francisco",
|
|
311
|
+
state: "California",
|
|
312
|
+
country: "US",
|
|
313
|
+
zipCode: "94105"
|
|
314
|
+
},
|
|
315
|
+
metadata: {
|
|
316
|
+
category: "premium",
|
|
317
|
+
priority: "high",
|
|
318
|
+
settings: { theme: "dark", notifications: true }
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Access nested data
|
|
323
|
+
console.log(user.utm.source); // "google"
|
|
324
|
+
console.log(user.address.city); // "San Francisco"
|
|
325
|
+
console.log(user.metadata.category); // "premium"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Key features of nested attributes:**
|
|
329
|
+
- **Deep nesting**: Support for multiple levels of nested objects
|
|
330
|
+
- **Validation**: Each nested field can have its own validation rules
|
|
331
|
+
- **Optional fields**: Nested objects can contain optional fields
|
|
332
|
+
- **Mixed types**: Combine simple types, arrays, and nested objects
|
|
333
|
+
- **Partition support**: Use dot notation for partitions on nested fields (e.g., `"utm.source"`, `"address.country"`)
|
|
334
|
+
|
|
266
335
|
#### Automatic Timestamps
|
|
267
336
|
|
|
268
337
|
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.
|
|
@@ -456,6 +525,31 @@ const resource = await s3db.createResource({
|
|
|
456
525
|
zipCode: "string|optional"
|
|
457
526
|
},
|
|
458
527
|
|
|
528
|
+
// Complex nested structures
|
|
529
|
+
profile: {
|
|
530
|
+
bio: "string|max:500|optional",
|
|
531
|
+
avatar: "url|optional",
|
|
532
|
+
birthDate: "date|optional",
|
|
533
|
+
preferences: {
|
|
534
|
+
theme: "string|enum:light,dark|default:light",
|
|
535
|
+
language: "string|enum:en,es,fr|default:en",
|
|
536
|
+
notifications: "boolean|default:true"
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
// Nested objects with validation
|
|
541
|
+
contact: {
|
|
542
|
+
phone: {
|
|
543
|
+
mobile: "string|pattern:^\\+?[1-9]\\d{1,14}$|optional",
|
|
544
|
+
work: "string|pattern:^\\+?[1-9]\\d{1,14}$|optional"
|
|
545
|
+
},
|
|
546
|
+
social: {
|
|
547
|
+
twitter: "string|optional",
|
|
548
|
+
linkedin: "url|optional",
|
|
549
|
+
github: "url|optional"
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
|
|
459
553
|
// Arrays
|
|
460
554
|
tags: "array|items:string|unique",
|
|
461
555
|
scores: "array|items:number|min:1",
|
|
@@ -467,6 +561,19 @@ const resource = await s3db.createResource({
|
|
|
467
561
|
metadata: {
|
|
468
562
|
settings: "object|optional",
|
|
469
563
|
preferences: "object|optional"
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
// Analytics and tracking
|
|
567
|
+
analytics: {
|
|
568
|
+
utm: {
|
|
569
|
+
source: "string|optional",
|
|
570
|
+
medium: "string|optional",
|
|
571
|
+
campaign: "string|optional",
|
|
572
|
+
term: "string|optional",
|
|
573
|
+
content: "string|optional"
|
|
574
|
+
},
|
|
575
|
+
events: "array|items:object|optional",
|
|
576
|
+
lastVisit: "date|optional"
|
|
470
577
|
}
|
|
471
578
|
},
|
|
472
579
|
|
|
@@ -582,7 +689,104 @@ const attributes = {
|
|
|
582
689
|
};
|
|
583
690
|
```
|
|
584
691
|
|
|
585
|
-
|
|
692
|
+
#### Nested Object Validation
|
|
693
|
+
|
|
694
|
+
Nested objects support comprehensive validation rules at each level:
|
|
695
|
+
|
|
696
|
+
```javascript
|
|
697
|
+
const users = await s3db.createResource({
|
|
698
|
+
name: "users",
|
|
699
|
+
attributes: {
|
|
700
|
+
name: "string|min:2|max:100",
|
|
701
|
+
email: "email|unique",
|
|
702
|
+
|
|
703
|
+
// Simple nested object
|
|
704
|
+
profile: {
|
|
705
|
+
bio: "string|max:500|optional",
|
|
706
|
+
avatar: "url|optional",
|
|
707
|
+
birthDate: "date|optional"
|
|
708
|
+
},
|
|
709
|
+
|
|
710
|
+
// Complex nested structure with validation
|
|
711
|
+
contact: {
|
|
712
|
+
phone: {
|
|
713
|
+
mobile: "string|pattern:^\\+?[1-9]\\d{1,14}$|optional",
|
|
714
|
+
work: "string|pattern:^\\+?[1-9]\\d{1,14}$|optional"
|
|
715
|
+
},
|
|
716
|
+
social: {
|
|
717
|
+
twitter: "string|optional",
|
|
718
|
+
linkedin: "url|optional"
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
|
|
722
|
+
// Nested object with arrays
|
|
723
|
+
preferences: {
|
|
724
|
+
categories: "array|items:string|unique|optional",
|
|
725
|
+
notifications: {
|
|
726
|
+
email: "boolean|default:true",
|
|
727
|
+
sms: "boolean|default:false",
|
|
728
|
+
push: "boolean|default:true"
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
|
|
732
|
+
// Deep nesting with validation
|
|
733
|
+
analytics: {
|
|
734
|
+
tracking: {
|
|
735
|
+
utm: {
|
|
736
|
+
source: "string|optional",
|
|
737
|
+
medium: "string|optional",
|
|
738
|
+
campaign: "string|optional"
|
|
739
|
+
},
|
|
740
|
+
events: "array|items:object|optional"
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// Insert data with complex nested structure
|
|
747
|
+
const user = await users.insert({
|
|
748
|
+
name: "John Doe",
|
|
749
|
+
email: "john@example.com",
|
|
750
|
+
profile: {
|
|
751
|
+
bio: "Software developer with 10+ years of experience",
|
|
752
|
+
avatar: "https://example.com/avatar.jpg",
|
|
753
|
+
birthDate: new Date("1990-01-15")
|
|
754
|
+
},
|
|
755
|
+
contact: {
|
|
756
|
+
phone: {
|
|
757
|
+
mobile: "+1234567890",
|
|
758
|
+
work: "+1987654321"
|
|
759
|
+
},
|
|
760
|
+
social: {
|
|
761
|
+
twitter: "@johndoe",
|
|
762
|
+
linkedin: "https://linkedin.com/in/johndoe"
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
preferences: {
|
|
766
|
+
categories: ["technology", "programming", "web-development"],
|
|
767
|
+
notifications: {
|
|
768
|
+
email: true,
|
|
769
|
+
sms: false,
|
|
770
|
+
push: true
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
analytics: {
|
|
774
|
+
tracking: {
|
|
775
|
+
utm: {
|
|
776
|
+
source: "google",
|
|
777
|
+
medium: "organic",
|
|
778
|
+
campaign: "brand"
|
|
779
|
+
},
|
|
780
|
+
events: [
|
|
781
|
+
{ type: "page_view", timestamp: new Date() },
|
|
782
|
+
{ type: "signup", timestamp: new Date() }
|
|
783
|
+
]
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
#### Enhanced Array and Object Handling
|
|
586
790
|
|
|
587
791
|
s3db.js now provides robust serialization for complex data structures:
|
|
588
792
|
|
|
@@ -1119,7 +1323,8 @@ const users = await s3db.createResource({
|
|
|
1119
1323
|
name: "string",
|
|
1120
1324
|
email: "email",
|
|
1121
1325
|
region: "string",
|
|
1122
|
-
ageGroup: "string"
|
|
1326
|
+
ageGroup: "string",
|
|
1327
|
+
createdAt: "date"
|
|
1123
1328
|
},
|
|
1124
1329
|
options: {
|
|
1125
1330
|
partitions: {
|
|
@@ -1128,6 +1333,9 @@ const users = await s3db.createResource({
|
|
|
1128
1333
|
},
|
|
1129
1334
|
byAgeGroup: {
|
|
1130
1335
|
fields: { ageGroup: "string" }
|
|
1336
|
+
},
|
|
1337
|
+
byDate: {
|
|
1338
|
+
fields: { createdAt: "date|maxlength:10" }
|
|
1131
1339
|
}
|
|
1132
1340
|
}
|
|
1133
1341
|
}
|
|
@@ -1136,12 +1344,68 @@ const users = await s3db.createResource({
|
|
|
1136
1344
|
|
|
1137
1345
|
### Querying by partition
|
|
1138
1346
|
|
|
1347
|
+
Partitions are automatically created when you insert documents, and you can query them using specific methods that accept partition parameters:
|
|
1348
|
+
|
|
1349
|
+
#### List IDs by partition
|
|
1350
|
+
|
|
1351
|
+
```js
|
|
1352
|
+
// Get all user IDs in the 'south' region
|
|
1353
|
+
const userIds = await users.listIds({
|
|
1354
|
+
partition: "byRegion",
|
|
1355
|
+
partitionValues: { region: "south" }
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
// Get all user IDs in the 'adult' age group
|
|
1359
|
+
const adultIds = await users.listIds({
|
|
1360
|
+
partition: "byAgeGroup",
|
|
1361
|
+
partitionValues: { ageGroup: "adult" }
|
|
1362
|
+
});
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
#### Count documents by partition
|
|
1366
|
+
|
|
1139
1367
|
```js
|
|
1140
|
-
//
|
|
1141
|
-
const
|
|
1368
|
+
// Count users in the 'south' region
|
|
1369
|
+
const count = await users.count({
|
|
1370
|
+
partition: "byRegion",
|
|
1371
|
+
partitionValues: { region: "south" }
|
|
1372
|
+
});
|
|
1142
1373
|
|
|
1143
|
-
//
|
|
1144
|
-
const
|
|
1374
|
+
// Count adult users
|
|
1375
|
+
const adultCount = await users.count({
|
|
1376
|
+
partition: "byAgeGroup",
|
|
1377
|
+
partitionValues: { ageGroup: "adult" }
|
|
1378
|
+
});
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
#### List objects by partition
|
|
1382
|
+
|
|
1383
|
+
```js
|
|
1384
|
+
// Get all users in the 'south' region
|
|
1385
|
+
const usersSouth = await users.listByPartition({
|
|
1386
|
+
partition: "byRegion",
|
|
1387
|
+
partitionValues: { region: "south" }
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
// Get all adult users with pagination
|
|
1391
|
+
const adultUsers = await users.listByPartition(
|
|
1392
|
+
{ partition: "byAgeGroup", partitionValues: { ageGroup: "adult" } },
|
|
1393
|
+
{ limit: 10, offset: 0 }
|
|
1394
|
+
);
|
|
1395
|
+
```
|
|
1396
|
+
|
|
1397
|
+
#### Page through partition data
|
|
1398
|
+
|
|
1399
|
+
```js
|
|
1400
|
+
// Get first page of users in 'south' region
|
|
1401
|
+
const page = await users.page(0, 10, {
|
|
1402
|
+
partition: "byRegion",
|
|
1403
|
+
partitionValues: { region: "south" }
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
console.log(page.items); // Array of user objects
|
|
1407
|
+
console.log(page.totalItems); // Total count in this partition
|
|
1408
|
+
console.log(page.totalPages); // Total pages available
|
|
1145
1409
|
```
|
|
1146
1410
|
|
|
1147
1411
|
### Example: Time-based partition
|
|
@@ -1163,10 +1427,202 @@ const logs = await s3db.createResource({
|
|
|
1163
1427
|
}
|
|
1164
1428
|
});
|
|
1165
1429
|
|
|
1430
|
+
// Insert logs (partitions are created automatically)
|
|
1431
|
+
await logs.insert({
|
|
1432
|
+
message: "User login",
|
|
1433
|
+
level: "info",
|
|
1434
|
+
createdAt: new Date("2024-06-27")
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1166
1437
|
// Query logs for a specific day
|
|
1167
|
-
const logsToday = await logs.
|
|
1438
|
+
const logsToday = await logs.listByPartition({
|
|
1439
|
+
partition: "byDate",
|
|
1440
|
+
partitionValues: { createdAt: "2024-06-27" }
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
// Count logs for a specific day
|
|
1444
|
+
const count = await logs.count({
|
|
1445
|
+
partition: "byDate",
|
|
1446
|
+
partitionValues: { createdAt: "2024-06-27" }
|
|
1447
|
+
});
|
|
1168
1448
|
```
|
|
1169
1449
|
|
|
1450
|
+
### Partitions with Nested Fields
|
|
1451
|
+
|
|
1452
|
+
`s3db.js` supports partitions using nested object fields using dot notation, just like the schema mapper:
|
|
1453
|
+
|
|
1454
|
+
```js
|
|
1455
|
+
const users = await s3db.createResource({
|
|
1456
|
+
name: "users",
|
|
1457
|
+
attributes: {
|
|
1458
|
+
name: "string|required",
|
|
1459
|
+
utm: {
|
|
1460
|
+
source: "string|required",
|
|
1461
|
+
medium: "string|required",
|
|
1462
|
+
campaign: "string|required"
|
|
1463
|
+
},
|
|
1464
|
+
address: {
|
|
1465
|
+
country: "string|required",
|
|
1466
|
+
state: "string|required",
|
|
1467
|
+
city: "string|required"
|
|
1468
|
+
},
|
|
1469
|
+
metadata: {
|
|
1470
|
+
category: "string|required",
|
|
1471
|
+
priority: "string|required"
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
options: {
|
|
1475
|
+
partitions: {
|
|
1476
|
+
byUtmSource: {
|
|
1477
|
+
fields: {
|
|
1478
|
+
"utm.source": "string"
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
byAddressCountry: {
|
|
1482
|
+
fields: {
|
|
1483
|
+
"address.country": "string|maxlength:2"
|
|
1484
|
+
}
|
|
1485
|
+
},
|
|
1486
|
+
byAddressState: {
|
|
1487
|
+
fields: {
|
|
1488
|
+
"address.country": "string|maxlength:2",
|
|
1489
|
+
"address.state": "string"
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
byUtmAndAddress: {
|
|
1493
|
+
fields: {
|
|
1494
|
+
"utm.source": "string",
|
|
1495
|
+
"utm.medium": "string",
|
|
1496
|
+
"address.country": "string|maxlength:2"
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
// Insert user with nested data
|
|
1504
|
+
await users.insert({
|
|
1505
|
+
name: "John Doe",
|
|
1506
|
+
utm: {
|
|
1507
|
+
source: "google",
|
|
1508
|
+
medium: "cpc",
|
|
1509
|
+
campaign: "brand"
|
|
1510
|
+
},
|
|
1511
|
+
address: {
|
|
1512
|
+
country: "US",
|
|
1513
|
+
state: "California",
|
|
1514
|
+
city: "San Francisco"
|
|
1515
|
+
},
|
|
1516
|
+
metadata: {
|
|
1517
|
+
category: "premium",
|
|
1518
|
+
priority: "high"
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
// Query by nested UTM source
|
|
1523
|
+
const googleUsers = await users.listIds({
|
|
1524
|
+
partition: "byUtmSource",
|
|
1525
|
+
partitionValues: { "utm.source": "google" }
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
// Query by nested address country
|
|
1529
|
+
const usUsers = await users.listIds({
|
|
1530
|
+
partition: "byAddressCountry",
|
|
1531
|
+
partitionValues: { "address.country": "US" }
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
// Query by multiple nested fields
|
|
1535
|
+
const usCaliforniaUsers = await users.listIds({
|
|
1536
|
+
partition: "byAddressState",
|
|
1537
|
+
partitionValues: {
|
|
1538
|
+
"address.country": "US",
|
|
1539
|
+
"address.state": "California"
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
// Complex query with UTM and address
|
|
1544
|
+
const googleCpcUsUsers = await users.listIds({
|
|
1545
|
+
partition: "byUtmAndAddress",
|
|
1546
|
+
partitionValues: {
|
|
1547
|
+
"utm.source": "google",
|
|
1548
|
+
"utm.medium": "cpc",
|
|
1549
|
+
"address.country": "US"
|
|
1550
|
+
}
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
// Count and list operations work the same way
|
|
1554
|
+
const googleCount = await users.count({
|
|
1555
|
+
partition: "byUtmSource",
|
|
1556
|
+
partitionValues: { "utm.source": "google" }
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
const googleUsersData = await users.listByPartition({
|
|
1560
|
+
partition: "byUtmSource",
|
|
1561
|
+
partitionValues: { "utm.source": "google" }
|
|
1562
|
+
});
|
|
1563
|
+
```
|
|
1564
|
+
|
|
1565
|
+
**Key features of nested field partitions:**
|
|
1566
|
+
|
|
1567
|
+
- **Dot notation**: Use `"parent.child"` to access nested fields
|
|
1568
|
+
- **Multiple levels**: Support for deeply nested objects like `"address.country.state"`
|
|
1569
|
+
- **Mixed partitions**: Combine nested and flat fields in the same partition
|
|
1570
|
+
- **Rules support**: Apply maxlength, date formatting, etc. to nested fields
|
|
1571
|
+
- **Automatic flattening**: Uses the same flattening logic as the schema mapper
|
|
1572
|
+
|
|
1573
|
+
### Partition rules and transformations
|
|
1574
|
+
|
|
1575
|
+
Partitions support various field rules that automatically transform values:
|
|
1576
|
+
|
|
1577
|
+
```js
|
|
1578
|
+
const products = await s3db.createResource({
|
|
1579
|
+
name: "products",
|
|
1580
|
+
attributes: {
|
|
1581
|
+
name: "string",
|
|
1582
|
+
category: "string",
|
|
1583
|
+
price: "number",
|
|
1584
|
+
createdAt: "date"
|
|
1585
|
+
},
|
|
1586
|
+
options: {
|
|
1587
|
+
partitions: {
|
|
1588
|
+
byCategory: {
|
|
1589
|
+
fields: { category: "string" }
|
|
1590
|
+
},
|
|
1591
|
+
byDate: {
|
|
1592
|
+
fields: { createdAt: "date|maxlength:10" } // Truncates to YYYY-MM-DD
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
// Date values are automatically formatted
|
|
1599
|
+
await products.insert({
|
|
1600
|
+
name: "Widget",
|
|
1601
|
+
category: "electronics",
|
|
1602
|
+
price: 99.99,
|
|
1603
|
+
createdAt: new Date("2024-06-27T15:30:00Z") // Will be stored as "2024-06-27"
|
|
1604
|
+
});
|
|
1605
|
+
```
|
|
1606
|
+
|
|
1607
|
+
### Important notes about partitions
|
|
1608
|
+
|
|
1609
|
+
1. **Automatic creation**: Partitions are automatically created when you insert documents
|
|
1610
|
+
2. **Performance**: Partition queries are more efficient than filtering all documents
|
|
1611
|
+
3. **Storage**: Each partition creates additional S3 objects, increasing storage costs
|
|
1612
|
+
4. **Consistency**: Partition data is automatically kept in sync with main resource data
|
|
1613
|
+
5. **Field requirements**: All partition fields must exist in your resource attributes
|
|
1614
|
+
|
|
1615
|
+
### Available partition-aware methods
|
|
1616
|
+
|
|
1617
|
+
| Method | Description | Partition Support |
|
|
1618
|
+
|--------|-------------|-------------------|
|
|
1619
|
+
| `listIds()` | Get array of document IDs | ✅ `{ partition, partitionValues }` |
|
|
1620
|
+
| `count()` | Count documents | ✅ `{ partition, partitionValues }` |
|
|
1621
|
+
| `listByPartition()` | List documents by partition | ✅ `{ partition, partitionValues }` |
|
|
1622
|
+
| `page()` | Paginate documents | ✅ `{ partition, partitionValues }` |
|
|
1623
|
+
| `getFromPartition()` | Get single document from partition | ✅ Direct partition access |
|
|
1624
|
+
| `query()` | Filter documents in memory | ❌ No partition support |
|
|
1625
|
+
|
|
1170
1626
|
## Hooks
|
|
1171
1627
|
|
|
1172
1628
|
`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.
|