s3db.js 7.2.1 → 7.3.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/dist/s3db.cjs.js +149 -1489
- package/dist/s3db.cjs.min.js +1 -1
- package/dist/s3db.es.js +150 -1490
- package/dist/s3db.es.min.js +1 -1
- package/dist/s3db.iife.js +149 -1489
- package/dist/s3db.iife.min.js +1 -1
- package/package.json +15 -8
- package/src/behaviors/body-only.js +2 -2
- package/src/behaviors/truncate-data.js +2 -2
- package/src/client.class.js +1 -1
- package/src/database.class.js +1 -1
- package/src/errors.js +1 -1
- package/src/plugins/audit.plugin.js +5 -5
- package/src/plugins/cache/filesystem-cache.class.js +661 -0
- package/src/plugins/cache/index.js +4 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +480 -0
- package/src/plugins/cache.plugin.js +159 -9
- package/src/plugins/consumers/index.js +3 -3
- package/src/plugins/consumers/sqs-consumer.js +2 -2
- package/src/plugins/fulltext.plugin.js +5 -5
- package/src/plugins/metrics.plugin.js +2 -2
- package/src/plugins/queue-consumer.plugin.js +3 -3
- package/src/plugins/replicator.plugin.js +259 -362
- package/src/plugins/replicators/s3db-replicator.class.js +35 -19
- package/src/plugins/replicators/sqs-replicator.class.js +17 -5
- package/src/resource.class.js +14 -14
- package/src/schema.class.js +3 -3
|
@@ -48,7 +48,7 @@ class S3dbReplicator extends BaseReplicator {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
_normalizeResources(resources) {
|
|
51
|
-
//
|
|
51
|
+
// Supports array, object, function, string
|
|
52
52
|
if (!resources) return {};
|
|
53
53
|
if (Array.isArray(resources)) {
|
|
54
54
|
const map = {};
|
|
@@ -135,24 +135,40 @@ class S3dbReplicator extends BaseReplicator {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
//
|
|
139
|
-
async replicate(
|
|
138
|
+
// Support both object and parameter signatures for flexibility
|
|
139
|
+
async replicate(resourceOrObj, operation, data, recordId, beforeData) {
|
|
140
|
+
let resource, op, payload, id;
|
|
141
|
+
|
|
142
|
+
// Handle object signature: { resource, operation, data, id }
|
|
143
|
+
if (typeof resourceOrObj === 'object' && resourceOrObj.resource) {
|
|
144
|
+
resource = resourceOrObj.resource;
|
|
145
|
+
op = resourceOrObj.operation;
|
|
146
|
+
payload = resourceOrObj.data;
|
|
147
|
+
id = resourceOrObj.id;
|
|
148
|
+
} else {
|
|
149
|
+
// Handle parameter signature: (resource, operation, data, recordId, beforeData)
|
|
150
|
+
resource = resourceOrObj;
|
|
151
|
+
op = operation;
|
|
152
|
+
payload = data;
|
|
153
|
+
id = recordId;
|
|
154
|
+
}
|
|
155
|
+
|
|
140
156
|
const normResource = normalizeResourceName(resource);
|
|
141
|
-
const destResource = this._resolveDestResource(normResource,
|
|
157
|
+
const destResource = this._resolveDestResource(normResource, payload);
|
|
142
158
|
const destResourceObj = this._getDestResourceObj(destResource);
|
|
143
159
|
|
|
144
160
|
// Apply transformer before replicating
|
|
145
|
-
const transformedData = this._applyTransformer(normResource,
|
|
161
|
+
const transformedData = this._applyTransformer(normResource, payload);
|
|
146
162
|
|
|
147
163
|
let result;
|
|
148
|
-
if (
|
|
164
|
+
if (op === 'insert') {
|
|
149
165
|
result = await destResourceObj.insert(transformedData);
|
|
150
|
-
} else if (
|
|
151
|
-
result = await destResourceObj.update(
|
|
152
|
-
} else if (
|
|
153
|
-
result = await destResourceObj.delete(
|
|
166
|
+
} else if (op === 'update') {
|
|
167
|
+
result = await destResourceObj.update(id, transformedData);
|
|
168
|
+
} else if (op === 'delete') {
|
|
169
|
+
result = await destResourceObj.delete(id);
|
|
154
170
|
} else {
|
|
155
|
-
throw new Error(`Invalid operation: ${
|
|
171
|
+
throw new Error(`Invalid operation: ${op}. Supported operations are: insert, update, delete`);
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
return result;
|
|
@@ -174,7 +190,7 @@ class S3dbReplicator extends BaseReplicator {
|
|
|
174
190
|
} else {
|
|
175
191
|
result = data;
|
|
176
192
|
}
|
|
177
|
-
|
|
193
|
+
// Ensure that id is always present
|
|
178
194
|
if (result && data && data.id && !result.id) result.id = data.id;
|
|
179
195
|
// Fallback: if transformer returns undefined/null, use original data
|
|
180
196
|
if (!result && data) result = data;
|
|
@@ -193,12 +209,12 @@ class S3dbReplicator extends BaseReplicator {
|
|
|
193
209
|
}
|
|
194
210
|
// String mapping
|
|
195
211
|
if (typeof entry === 'string') return entry;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
212
|
+
// Mapping function - when there's only transformer, use original resource
|
|
213
|
+
if (resource && !targetResourceName) targetResourceName = resource;
|
|
214
|
+
// Object: { resource, transform }
|
|
199
215
|
if (typeof entry === 'object' && entry.resource) return entry.resource;
|
|
200
216
|
return resource;
|
|
201
|
-
|
|
217
|
+
}
|
|
202
218
|
|
|
203
219
|
_getDestResourceObj(resource) {
|
|
204
220
|
if (!this.client || !this.client.resources) return null;
|
|
@@ -292,15 +308,15 @@ class S3dbReplicator extends BaseReplicator {
|
|
|
292
308
|
// If no action is specified, just check if resource is configured
|
|
293
309
|
if (!action) return true;
|
|
294
310
|
|
|
295
|
-
//
|
|
296
|
-
|
|
311
|
+
// Support for all configuration styles
|
|
312
|
+
// If it's an array of objects, check actions
|
|
297
313
|
if (Array.isArray(entry)) {
|
|
298
314
|
for (const item of entry) {
|
|
299
315
|
if (typeof item === 'object' && item.resource) {
|
|
300
316
|
if (item.actions && Array.isArray(item.actions)) {
|
|
301
317
|
if (item.actions.includes(action)) return true;
|
|
302
318
|
} else {
|
|
303
|
-
return true; //
|
|
319
|
+
return true; // If no actions, accept all
|
|
304
320
|
}
|
|
305
321
|
} else if (typeof item === 'string' || typeof item === 'function') {
|
|
306
322
|
return true;
|
|
@@ -29,7 +29,6 @@ import BaseReplicator from './base-replicator.class.js';
|
|
|
29
29
|
class SqsReplicator extends BaseReplicator {
|
|
30
30
|
constructor(config = {}, resources = [], client = null) {
|
|
31
31
|
super(config);
|
|
32
|
-
this.resources = resources;
|
|
33
32
|
this.client = client;
|
|
34
33
|
this.queueUrl = config.queueUrl;
|
|
35
34
|
this.queues = config.queues || {};
|
|
@@ -39,13 +38,26 @@ class SqsReplicator extends BaseReplicator {
|
|
|
39
38
|
this.messageGroupId = config.messageGroupId;
|
|
40
39
|
this.deduplicationId = config.deduplicationId;
|
|
41
40
|
|
|
42
|
-
//
|
|
43
|
-
if (resources
|
|
41
|
+
// Normalize resources to object format
|
|
42
|
+
if (Array.isArray(resources)) {
|
|
43
|
+
this.resources = {};
|
|
44
|
+
for (const resource of resources) {
|
|
45
|
+
if (typeof resource === 'string') {
|
|
46
|
+
this.resources[resource] = true;
|
|
47
|
+
} else if (typeof resource === 'object' && resource.name) {
|
|
48
|
+
this.resources[resource.name] = resource;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} else if (typeof resources === 'object') {
|
|
52
|
+
this.resources = resources;
|
|
53
|
+
// Build queues from resources configuration
|
|
44
54
|
for (const [resourceName, resourceConfig] of Object.entries(resources)) {
|
|
45
|
-
if (resourceConfig.queueUrl) {
|
|
55
|
+
if (resourceConfig && resourceConfig.queueUrl) {
|
|
46
56
|
this.queues[resourceName] = resourceConfig.queueUrl;
|
|
47
57
|
}
|
|
48
58
|
}
|
|
59
|
+
} else {
|
|
60
|
+
this.resources = {};
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
@@ -297,7 +309,7 @@ class SqsReplicator extends BaseReplicator {
|
|
|
297
309
|
connected: !!this.sqsClient,
|
|
298
310
|
queueUrl: this.queueUrl,
|
|
299
311
|
region: this.region,
|
|
300
|
-
resources: this.resources,
|
|
312
|
+
resources: Object.keys(this.resources || {}),
|
|
301
313
|
totalreplicators: this.listenerCount('replicated'),
|
|
302
314
|
totalErrors: this.listenerCount('replicator_error')
|
|
303
315
|
};
|
package/src/resource.class.js
CHANGED
|
@@ -171,7 +171,7 @@ export class Resource extends EventEmitter {
|
|
|
171
171
|
if (typeof fn === 'function') {
|
|
172
172
|
this.hooks[event].push(fn.bind(this));
|
|
173
173
|
}
|
|
174
|
-
//
|
|
174
|
+
// If not a function, ignore silently
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
}
|
|
@@ -704,7 +704,7 @@ export class Resource extends EventEmitter {
|
|
|
704
704
|
// LOG: body e contentType antes do putObject
|
|
705
705
|
// Only throw if behavior is 'body-only' and body is empty
|
|
706
706
|
if (this.behavior === 'body-only' && (!body || body === "")) {
|
|
707
|
-
throw new Error(`[Resource.insert]
|
|
707
|
+
throw new Error(`[Resource.insert] Attempt to save object without body! Data: id=${finalId}, resource=${this.name}`);
|
|
708
708
|
}
|
|
709
709
|
// For other behaviors, allow empty body (all data in metadata)
|
|
710
710
|
// Before putObject in insert
|
|
@@ -753,7 +753,7 @@ export class Resource extends EventEmitter {
|
|
|
753
753
|
|
|
754
754
|
// Execute afterInsert hooks
|
|
755
755
|
const finalResult = await this.executeHooks('afterInsert', insertedData);
|
|
756
|
-
// Emit event
|
|
756
|
+
// Emit event with data before afterInsert hooks
|
|
757
757
|
this.emit("insert", {
|
|
758
758
|
...insertedData,
|
|
759
759
|
$before: { ...completeData },
|
|
@@ -774,7 +774,7 @@ export class Resource extends EventEmitter {
|
|
|
774
774
|
if (isEmpty(id)) throw new Error('id cannot be empty');
|
|
775
775
|
|
|
776
776
|
const key = this.getResourceKey(id);
|
|
777
|
-
// LOG:
|
|
777
|
+
// LOG: start of get
|
|
778
778
|
// eslint-disable-next-line no-console
|
|
779
779
|
const [ok, err, request] = await tryFn(() => this.client.getObject(key));
|
|
780
780
|
// LOG: resultado do headObject
|
|
@@ -788,7 +788,7 @@ export class Resource extends EventEmitter {
|
|
|
788
788
|
id
|
|
789
789
|
});
|
|
790
790
|
}
|
|
791
|
-
//
|
|
791
|
+
// If object exists but has no content, throw NoSuchKey error
|
|
792
792
|
if (request.ContentLength === 0) {
|
|
793
793
|
const noContentErr = new Error(`No such key: ${key} [bucket:${this.client.config.bucket}]`);
|
|
794
794
|
noContentErr.name = 'NoSuchKey';
|
|
@@ -1346,7 +1346,7 @@ export class Resource extends EventEmitter {
|
|
|
1346
1346
|
prefix = `resource=${this.name}/partition=${partition}`;
|
|
1347
1347
|
}
|
|
1348
1348
|
} else {
|
|
1349
|
-
// List from main resource (
|
|
1349
|
+
// List from main resource (without version in path)
|
|
1350
1350
|
prefix = `resource=${this.name}/data`;
|
|
1351
1351
|
}
|
|
1352
1352
|
// Use getKeysPage for real pagination support
|
|
@@ -1892,7 +1892,7 @@ export class Resource extends EventEmitter {
|
|
|
1892
1892
|
for (const [partitionName, partition] of Object.entries(partitions)) {
|
|
1893
1893
|
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
1894
1894
|
if (partitionKey) {
|
|
1895
|
-
//
|
|
1895
|
+
// Save only version as metadata, never object attributes
|
|
1896
1896
|
const partitionMetadata = {
|
|
1897
1897
|
_v: String(this.version)
|
|
1898
1898
|
};
|
|
@@ -2082,7 +2082,7 @@ export class Resource extends EventEmitter {
|
|
|
2082
2082
|
// Create new partition reference if new key exists
|
|
2083
2083
|
if (newPartitionKey) {
|
|
2084
2084
|
const [ok, err] = await tryFn(async () => {
|
|
2085
|
-
//
|
|
2085
|
+
// Save only version as metadata
|
|
2086
2086
|
const partitionMetadata = {
|
|
2087
2087
|
_v: String(this.version)
|
|
2088
2088
|
};
|
|
@@ -2101,7 +2101,7 @@ export class Resource extends EventEmitter {
|
|
|
2101
2101
|
} else if (newPartitionKey) {
|
|
2102
2102
|
// If partition keys are the same, just update the existing reference
|
|
2103
2103
|
const [ok, err] = await tryFn(async () => {
|
|
2104
|
-
//
|
|
2104
|
+
// Save only version as metadata
|
|
2105
2105
|
const partitionMetadata = {
|
|
2106
2106
|
_v: String(this.version)
|
|
2107
2107
|
};
|
|
@@ -2138,7 +2138,7 @@ export class Resource extends EventEmitter {
|
|
|
2138
2138
|
}
|
|
2139
2139
|
const partitionKey = this.getPartitionKey({ partitionName, id: data.id, data });
|
|
2140
2140
|
if (partitionKey) {
|
|
2141
|
-
//
|
|
2141
|
+
// Save only version as metadata
|
|
2142
2142
|
const partitionMetadata = {
|
|
2143
2143
|
_v: String(this.version)
|
|
2144
2144
|
};
|
|
@@ -2306,8 +2306,8 @@ export class Resource extends EventEmitter {
|
|
|
2306
2306
|
}
|
|
2307
2307
|
|
|
2308
2308
|
/**
|
|
2309
|
-
* Compose the full object (metadata + body) as
|
|
2310
|
-
*
|
|
2309
|
+
* Compose the full object (metadata + body) as returned by .get(),
|
|
2310
|
+
* using in-memory data after insert/update, according to behavior
|
|
2311
2311
|
*/
|
|
2312
2312
|
async composeFullObjectFromWrite({ id, metadata, body, behavior }) {
|
|
2313
2313
|
// Preserve behavior flags before unmapping
|
|
@@ -2464,7 +2464,7 @@ export class Resource extends EventEmitter {
|
|
|
2464
2464
|
this._middlewares.get(method).push(fn);
|
|
2465
2465
|
}
|
|
2466
2466
|
|
|
2467
|
-
//
|
|
2467
|
+
// Utility to apply schema default values
|
|
2468
2468
|
applyDefaults(data) {
|
|
2469
2469
|
const out = { ...data };
|
|
2470
2470
|
for (const [key, def] of Object.entries(this.attributes)) {
|
|
@@ -2473,7 +2473,7 @@ export class Resource extends EventEmitter {
|
|
|
2473
2473
|
const match = def.match(/default:([^|]+)/);
|
|
2474
2474
|
if (match) {
|
|
2475
2475
|
let val = match[1];
|
|
2476
|
-
//
|
|
2476
|
+
// Convert to boolean/number if necessary
|
|
2477
2477
|
if (def.includes('boolean')) val = val === 'true';
|
|
2478
2478
|
else if (def.includes('number')) val = Number(val);
|
|
2479
2479
|
out[key] = val;
|
package/src/schema.class.js
CHANGED
|
@@ -484,7 +484,7 @@ export class Schema {
|
|
|
484
484
|
*/
|
|
485
485
|
static _importAttributes(attrs) {
|
|
486
486
|
if (typeof attrs === 'string') {
|
|
487
|
-
//
|
|
487
|
+
// Try to detect if it's an object serialized as JSON string
|
|
488
488
|
const [ok, err, parsed] = tryFnSync(() => JSON.parse(attrs));
|
|
489
489
|
if (ok && typeof parsed === 'object' && parsed !== null) {
|
|
490
490
|
const [okNested, errNested, nested] = tryFnSync(() => Schema._importAttributes(parsed));
|
|
@@ -687,9 +687,9 @@ export class Schema {
|
|
|
687
687
|
properties: this.preprocessAttributesForValidation(value),
|
|
688
688
|
strict: false
|
|
689
689
|
};
|
|
690
|
-
//
|
|
690
|
+
// If explicitly required, don't mark as optional
|
|
691
691
|
if (isExplicitRequired) {
|
|
692
|
-
//
|
|
692
|
+
// nothing
|
|
693
693
|
} else if (isExplicitOptional || this.allNestedObjectsOptional) {
|
|
694
694
|
objectConfig.optional = true;
|
|
695
695
|
}
|