s3db.js 13.3.1 → 13.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -10
- package/dist/s3db.cjs.js +102 -23
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +102 -23
- package/dist/s3db.es.js.map +1 -1
- package/package.json +15 -2
- package/src/plugins/audit.plugin.js +427 -0
- package/src/plugins/costs.plugin.js +524 -0
- package/src/plugins/fulltext.plugin.js +484 -0
- package/src/plugins/metrics.plugin.js +575 -0
- package/src/plugins/queue-consumer.plugin.js +607 -19
- package/src/plugins/state-machine.plugin.js +132 -26
package/README.md
CHANGED
|
@@ -137,6 +137,8 @@ console.log("🎉 Connected to S3 database!");
|
|
|
137
137
|
|
|
138
138
|
### 3. Create your first resource
|
|
139
139
|
|
|
140
|
+
Schema validation powered by **[fastest-validator](https://github.com/icebob/fastest-validator)** ⚡
|
|
141
|
+
|
|
140
142
|
```javascript
|
|
141
143
|
const users = await s3db.createResource({
|
|
142
144
|
name: "users",
|
|
@@ -718,7 +720,7 @@ await users.insert({ email: 'john@example.com', password: 'secret123', age: 25 }
|
|
|
718
720
|
|
|
719
721
|
### Schema & Field Types
|
|
720
722
|
|
|
721
|
-
Define your data structure with powerful validation:
|
|
723
|
+
Define your data structure with powerful validation using **[fastest-validator](https://github.com/icebob/fastest-validator)** - a blazing-fast validation library with comprehensive type support:
|
|
722
724
|
|
|
723
725
|
#### Basic Types
|
|
724
726
|
|
|
@@ -749,6 +751,9 @@ Define your data structure with powerful validation:
|
|
|
749
751
|
|
|
750
752
|
#### Schema Examples
|
|
751
753
|
|
|
754
|
+
> **📖 Validation powered by [fastest-validator](https://github.com/icebob/fastest-validator)**
|
|
755
|
+
> All schemas use fastest-validator's syntax with full support for shorthand notation.
|
|
756
|
+
|
|
752
757
|
```javascript
|
|
753
758
|
// Simple schema
|
|
754
759
|
{
|
|
@@ -757,21 +762,40 @@ Define your data structure with powerful validation:
|
|
|
757
762
|
age: 'number|integer|min:0|max:150'
|
|
758
763
|
}
|
|
759
764
|
|
|
760
|
-
// Nested objects
|
|
765
|
+
// Nested objects - MAGIC AUTO-DETECT! ✨ (recommended)
|
|
766
|
+
// Just write your object structure - s3db detects it automatically!
|
|
767
|
+
{
|
|
768
|
+
name: 'string|required',
|
|
769
|
+
profile: { // ← No $$type needed! Auto-detected as optional object
|
|
770
|
+
bio: 'string|max:500',
|
|
771
|
+
avatar: 'url|optional',
|
|
772
|
+
social: { // ← Deeply nested also works!
|
|
773
|
+
twitter: 'string|optional',
|
|
774
|
+
github: 'string|optional'
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Need validation control? Use $$type (when you need required/optional)
|
|
780
|
+
{
|
|
781
|
+
name: 'string|required',
|
|
782
|
+
profile: {
|
|
783
|
+
$$type: 'object|required', // ← Add required validation
|
|
784
|
+
bio: 'string|max:500',
|
|
785
|
+
avatar: 'url|optional'
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Advanced: Full control (rare cases - strict mode, etc)
|
|
761
790
|
{
|
|
762
791
|
name: 'string|required',
|
|
763
792
|
profile: {
|
|
764
793
|
type: 'object',
|
|
794
|
+
optional: false,
|
|
795
|
+
strict: true, // ← Enable strict validation
|
|
765
796
|
props: {
|
|
766
797
|
bio: 'string|max:500',
|
|
767
|
-
avatar: 'url|optional'
|
|
768
|
-
social: {
|
|
769
|
-
type: 'object',
|
|
770
|
-
props: {
|
|
771
|
-
twitter: 'string|optional',
|
|
772
|
-
github: 'string|optional'
|
|
773
|
-
}
|
|
774
|
-
}
|
|
798
|
+
avatar: 'url|optional'
|
|
775
799
|
}
|
|
776
800
|
}
|
|
777
801
|
}
|
package/dist/s3db.cjs.js
CHANGED
|
@@ -26182,7 +26182,7 @@ class Database extends EventEmitter {
|
|
|
26182
26182
|
})();
|
|
26183
26183
|
this.version = "1";
|
|
26184
26184
|
this.s3dbVersion = (() => {
|
|
26185
|
-
const [ok, err, version] = tryFn(() => true ? "13.
|
|
26185
|
+
const [ok, err, version] = tryFn(() => true ? "13.4.0" : "latest");
|
|
26186
26186
|
return ok ? version : "latest";
|
|
26187
26187
|
})();
|
|
26188
26188
|
this._resourcesMap = {};
|
|
@@ -31511,14 +31511,36 @@ class StateMachinePlugin extends Plugin {
|
|
|
31511
31511
|
}
|
|
31512
31512
|
/**
|
|
31513
31513
|
* Setup an event-based trigger
|
|
31514
|
+
* Supports both old API (trigger.event) and new API (trigger.eventName + eventSource)
|
|
31514
31515
|
* @private
|
|
31515
31516
|
*/
|
|
31516
31517
|
async _setupEventTrigger(machineId, stateName, trigger, triggerName) {
|
|
31517
|
-
const
|
|
31518
|
+
const baseEventName = trigger.eventName || trigger.event;
|
|
31519
|
+
const eventSource = trigger.eventSource;
|
|
31520
|
+
if (!baseEventName) {
|
|
31521
|
+
throw new StateMachineError(`Event trigger '${triggerName}' must have either 'event' or 'eventName' property`, {
|
|
31522
|
+
operation: "_setupEventTrigger",
|
|
31523
|
+
machineId,
|
|
31524
|
+
stateName,
|
|
31525
|
+
triggerName
|
|
31526
|
+
});
|
|
31527
|
+
}
|
|
31518
31528
|
const eventHandler = async (eventData) => {
|
|
31519
31529
|
const entities = await this._getEntitiesInState(machineId, stateName);
|
|
31520
31530
|
for (const entity of entities) {
|
|
31521
31531
|
try {
|
|
31532
|
+
let resolvedEventName;
|
|
31533
|
+
if (typeof baseEventName === "function") {
|
|
31534
|
+
resolvedEventName = baseEventName(entity.context);
|
|
31535
|
+
} else {
|
|
31536
|
+
resolvedEventName = baseEventName;
|
|
31537
|
+
}
|
|
31538
|
+
if (eventSource && typeof baseEventName === "function") {
|
|
31539
|
+
const eventIdMatch = eventData?.id || eventData?.entityId;
|
|
31540
|
+
if (eventIdMatch && entity.entityId !== eventIdMatch) {
|
|
31541
|
+
continue;
|
|
31542
|
+
}
|
|
31543
|
+
}
|
|
31522
31544
|
if (trigger.condition) {
|
|
31523
31545
|
const shouldTrigger = await trigger.condition(entity.context, entity.entityId, eventData);
|
|
31524
31546
|
if (!shouldTrigger) continue;
|
|
@@ -31532,28 +31554,76 @@ class StateMachinePlugin extends Plugin {
|
|
|
31532
31554
|
continue;
|
|
31533
31555
|
}
|
|
31534
31556
|
}
|
|
31535
|
-
|
|
31536
|
-
|
|
31537
|
-
|
|
31538
|
-
|
|
31539
|
-
|
|
31540
|
-
|
|
31541
|
-
|
|
31542
|
-
|
|
31543
|
-
|
|
31544
|
-
|
|
31545
|
-
|
|
31546
|
-
|
|
31547
|
-
|
|
31557
|
+
if (trigger.targetState) {
|
|
31558
|
+
await this._transition(
|
|
31559
|
+
machineId,
|
|
31560
|
+
entity.entityId,
|
|
31561
|
+
stateName,
|
|
31562
|
+
trigger.targetState,
|
|
31563
|
+
"TRIGGER",
|
|
31564
|
+
{ ...entity.context, eventData, triggerName }
|
|
31565
|
+
);
|
|
31566
|
+
const machine = this.machines.get(machineId);
|
|
31567
|
+
const resourceConfig = machine.config;
|
|
31568
|
+
if (resourceConfig.resource && resourceConfig.stateField) {
|
|
31569
|
+
let resource;
|
|
31570
|
+
if (typeof resourceConfig.resource === "string") {
|
|
31571
|
+
resource = await this.database.getResource(resourceConfig.resource);
|
|
31572
|
+
} else {
|
|
31573
|
+
resource = resourceConfig.resource;
|
|
31574
|
+
}
|
|
31575
|
+
if (resource) {
|
|
31576
|
+
const [ok] = await tryFn(
|
|
31577
|
+
() => resource.patch(entity.entityId, { [resourceConfig.stateField]: trigger.targetState })
|
|
31578
|
+
);
|
|
31579
|
+
if (!ok && this.config.verbose) {
|
|
31580
|
+
console.warn(`[StateMachinePlugin] Failed to update resource stateField for entity ${entity.entityId}`);
|
|
31581
|
+
}
|
|
31582
|
+
}
|
|
31583
|
+
}
|
|
31584
|
+
const targetStateConfig = machine.config.states[trigger.targetState];
|
|
31585
|
+
if (targetStateConfig?.entry) {
|
|
31586
|
+
await this._executeAction(
|
|
31587
|
+
targetStateConfig.entry,
|
|
31588
|
+
{ ...entity.context, eventData },
|
|
31589
|
+
"TRIGGER",
|
|
31590
|
+
machineId,
|
|
31591
|
+
entity.entityId
|
|
31592
|
+
);
|
|
31593
|
+
}
|
|
31594
|
+
this.emit("plg:state-machine:transition", {
|
|
31595
|
+
machineId,
|
|
31596
|
+
entityId: entity.entityId,
|
|
31597
|
+
from: stateName,
|
|
31598
|
+
to: trigger.targetState,
|
|
31599
|
+
event: "TRIGGER",
|
|
31600
|
+
context: { ...entity.context, eventData, triggerName }
|
|
31548
31601
|
});
|
|
31602
|
+
} else if (trigger.action) {
|
|
31603
|
+
const result = await this._executeAction(
|
|
31604
|
+
trigger.action,
|
|
31605
|
+
{ ...entity.context, eventData },
|
|
31606
|
+
"TRIGGER",
|
|
31607
|
+
machineId,
|
|
31608
|
+
entity.entityId
|
|
31609
|
+
);
|
|
31610
|
+
if (trigger.sendEvent) {
|
|
31611
|
+
await this.send(machineId, entity.entityId, trigger.sendEvent, {
|
|
31612
|
+
...entity.context,
|
|
31613
|
+
triggerResult: result,
|
|
31614
|
+
eventData
|
|
31615
|
+
});
|
|
31616
|
+
}
|
|
31549
31617
|
}
|
|
31618
|
+
await this._incrementTriggerCount(machineId, entity.entityId, triggerName);
|
|
31550
31619
|
this.emit("plg:state-machine:trigger-executed", {
|
|
31551
31620
|
machineId,
|
|
31552
31621
|
entityId: entity.entityId,
|
|
31553
31622
|
state: stateName,
|
|
31554
31623
|
trigger: triggerName,
|
|
31555
31624
|
type: "event",
|
|
31556
|
-
eventName
|
|
31625
|
+
eventName: resolvedEventName,
|
|
31626
|
+
targetState: trigger.targetState
|
|
31557
31627
|
});
|
|
31558
31628
|
} catch (error) {
|
|
31559
31629
|
if (this.config.verbose) {
|
|
@@ -31562,16 +31632,25 @@ class StateMachinePlugin extends Plugin {
|
|
|
31562
31632
|
}
|
|
31563
31633
|
}
|
|
31564
31634
|
};
|
|
31565
|
-
if (
|
|
31566
|
-
const
|
|
31567
|
-
|
|
31635
|
+
if (eventSource) {
|
|
31636
|
+
const baseEvent = typeof baseEventName === "function" ? "updated" : baseEventName;
|
|
31637
|
+
eventSource.on(baseEvent, eventHandler);
|
|
31568
31638
|
if (this.config.verbose) {
|
|
31569
|
-
console.log(`[StateMachinePlugin] Listening to
|
|
31639
|
+
console.log(`[StateMachinePlugin] Listening to resource event '${baseEvent}' from '${eventSource.name}' for trigger '${triggerName}'`);
|
|
31570
31640
|
}
|
|
31571
31641
|
} else {
|
|
31572
|
-
|
|
31573
|
-
if (
|
|
31574
|
-
|
|
31642
|
+
const staticEventName = typeof baseEventName === "function" ? "updated" : baseEventName;
|
|
31643
|
+
if (staticEventName.startsWith("db:")) {
|
|
31644
|
+
const dbEventName = staticEventName.substring(3);
|
|
31645
|
+
this.database.on(dbEventName, eventHandler);
|
|
31646
|
+
if (this.config.verbose) {
|
|
31647
|
+
console.log(`[StateMachinePlugin] Listening to database event '${dbEventName}' for trigger '${triggerName}'`);
|
|
31648
|
+
}
|
|
31649
|
+
} else {
|
|
31650
|
+
this.on(staticEventName, eventHandler);
|
|
31651
|
+
if (this.config.verbose) {
|
|
31652
|
+
console.log(`[StateMachinePlugin] Listening to plugin event '${staticEventName}' for trigger '${triggerName}'`);
|
|
31653
|
+
}
|
|
31575
31654
|
}
|
|
31576
31655
|
}
|
|
31577
31656
|
}
|