s3db.js 7.4.1 → 7.5.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/mcp/server.js CHANGED
@@ -747,7 +747,7 @@ class S3dbMCPServer {
747
747
  });
748
748
  } else {
749
749
  // Memory cache configuration (default)
750
- cacheConfig.driverType = 'memory';
750
+ cacheConfig.driver = 'memory';
751
751
  cacheConfig.memoryOptions = {
752
752
  maxSize: cacheMaxSizeEnv,
753
753
  ttl: cacheTtlEnv,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "s3db.js",
3
- "version": "7.4.1",
3
+ "version": "7.5.0",
4
4
  "description": "Use AWS S3, the world's most reliable document storage, as a database with this ORM.",
5
5
  "main": "dist/s3db.cjs.js",
6
6
  "module": "dist/s3db.es.js",
@@ -113,6 +113,23 @@ export class Database extends EventEmitter {
113
113
 
114
114
  if (versionData) {
115
115
  // Extract configuration from version data at root level
116
+ // Restore ID generator configuration
117
+ let restoredIdGenerator, restoredIdSize;
118
+ if (versionData.idGenerator !== undefined) {
119
+ if (versionData.idGenerator === 'custom_function') {
120
+ // Custom function was used but can't be restored - use default
121
+ restoredIdGenerator = undefined;
122
+ restoredIdSize = versionData.idSize || 22;
123
+ } else if (typeof versionData.idGenerator === 'number') {
124
+ // Size-based generator
125
+ restoredIdGenerator = versionData.idGenerator;
126
+ restoredIdSize = versionData.idSize || versionData.idGenerator;
127
+ }
128
+ } else {
129
+ // Legacy resource without saved ID config
130
+ restoredIdSize = versionData.idSize || 22;
131
+ }
132
+
116
133
  this.resources[name] = new Resource({
117
134
  name,
118
135
  client: this.client,
@@ -131,7 +148,9 @@ export class Database extends EventEmitter {
131
148
  autoDecrypt: versionData.autoDecrypt !== undefined ? versionData.autoDecrypt : true,
132
149
  hooks: versionData.hooks || {},
133
150
  versioningEnabled: this.versioningEnabled,
134
- map: versionData.map
151
+ map: versionData.map,
152
+ idGenerator: restoredIdGenerator,
153
+ idSize: restoredIdSize
135
154
  });
136
155
  }
137
156
  }
@@ -334,6 +353,8 @@ export class Database extends EventEmitter {
334
353
  autoDecrypt: resource.config.autoDecrypt,
335
354
  cache: resource.config.cache,
336
355
  hooks: resource.config.hooks,
356
+ idSize: resource.idSize,
357
+ idGenerator: resource.idGeneratorType,
337
358
  createdAt: isNewVersion ? new Date().toISOString() : existingVersionData?.createdAt
338
359
  }
339
360
  }
@@ -28,12 +28,12 @@ export class CachePlugin extends Plugin {
28
28
 
29
29
  async onSetup() {
30
30
  // Initialize cache driver
31
- if (this.config.driver) {
32
- // Use custom driver if provided
31
+ if (this.config.driver && typeof this.config.driver === 'object') {
32
+ // Use custom driver instance if provided
33
33
  this.driver = this.config.driver;
34
- } else if (this.config.driverType === 'memory') {
34
+ } else if (this.config.driver === 'memory') {
35
35
  this.driver = new MemoryCache(this.config.memoryOptions || {});
36
- } else if (this.config.driverType === 'filesystem') {
36
+ } else if (this.config.driver === 'filesystem') {
37
37
  // Use partition-aware filesystem cache if enabled
38
38
  if (this.config.partitionAware) {
39
39
  this.driver = new PartitionAwareFilesystemCache({
@@ -164,6 +164,13 @@ export class ReplicatorPlugin extends Plugin {
164
164
  return filtered;
165
165
  }
166
166
 
167
+ async getCompleteData(resource, data) {
168
+ // Always get the complete record from the resource to ensure we have all data
169
+ // This handles all behaviors: body-overflow, truncate-data, body-only, etc.
170
+ const [ok, err, completeRecord] = await tryFn(() => resource.get(data.id));
171
+ return ok ? completeRecord : data;
172
+ }
173
+
167
174
  installEventListeners(resource, database, plugin) {
168
175
  if (!resource || this.eventListenersInstalled.has(resource.name) ||
169
176
  resource.name === this.config.replicatorLogResource) {
@@ -186,8 +193,10 @@ export class ReplicatorPlugin extends Plugin {
186
193
 
187
194
  resource.on('update', async (data, beforeData) => {
188
195
  const [ok, error] = await tryFn(async () => {
189
- const completeData = { ...data, updatedAt: new Date().toISOString() };
190
- await plugin.processReplicatorEvent('update', resource.name, completeData.id, completeData, beforeData);
196
+ // For updates, we need to get the complete updated record, not just the changed fields
197
+ const completeData = await plugin.getCompleteData(resource, data);
198
+ const dataWithTimestamp = { ...completeData, updatedAt: new Date().toISOString() };
199
+ await plugin.processReplicatorEvent('update', resource.name, completeData.id, dataWithTimestamp, beforeData);
191
200
  });
192
201
 
193
202
  if (!ok) {
@@ -214,17 +223,6 @@ export class ReplicatorPlugin extends Plugin {
214
223
  this.eventListenersInstalled.add(resource.name);
215
224
  }
216
225
 
217
- /**
218
- * Get complete data by always fetching the full record from the resource
219
- * This ensures we always have the complete data regardless of behavior or data size
220
- */
221
- async getCompleteData(resource, data) {
222
- // Always get the complete record from the resource to ensure we have all data
223
- // This handles all behaviors: body-overflow, truncate-data, body-only, etc.
224
- const [ok, err, completeRecord] = await tryFn(() => resource.get(data.id));
225
- return ok ? completeRecord : data;
226
- }
227
-
228
226
  async setup(database) {
229
227
  this.database = database;
230
228
 
@@ -241,7 +239,7 @@ export class ReplicatorPlugin extends Plugin {
241
239
  }
242
240
 
243
241
  const [logOk, logError] = await tryFn(async () => {
244
- if (this.config.replicatorLogResource) {
242
+ if (this.config.persistReplicatorLog) {
245
243
  const logRes = await database.createResource({
246
244
  name: this.config.replicatorLogResource,
247
245
  behavior: 'body-overflow',
@@ -271,6 +269,13 @@ export class ReplicatorPlugin extends Plugin {
271
269
 
272
270
  await this.uploadMetadataFile(database);
273
271
 
272
+ // Install event listeners for existing resources
273
+ for (const resourceName in database.resources) {
274
+ const resource = database.resources[resourceName];
275
+ this.installEventListeners(resource, database, this);
276
+ }
277
+
278
+ // Override createResource to install listeners for new resources
274
279
  const originalCreateResource = database.createResource.bind(database);
275
280
  database.createResource = async (config) => {
276
281
  const resource = await originalCreateResource(config);
@@ -279,11 +284,6 @@ export class ReplicatorPlugin extends Plugin {
279
284
  }
280
285
  return resource;
281
286
  };
282
-
283
- for (const resourceName in database.resources) {
284
- const resource = database.resources[resourceName];
285
- this.installEventListeners(resource, database, this);
286
- }
287
287
  }
288
288
 
289
289
  createReplicator(driver, config, resources, client) {
@@ -142,6 +142,19 @@ export class Resource extends EventEmitter {
142
142
 
143
143
  // Configure ID generator
144
144
  this.idGenerator = this.configureIdGenerator(customIdGenerator, idSize);
145
+
146
+ // Store ID configuration for persistence
147
+ // If customIdGenerator is a number, use it as idSize
148
+ // Otherwise, use the provided idSize or default to 22
149
+ if (typeof customIdGenerator === 'number' && customIdGenerator > 0) {
150
+ this.idSize = customIdGenerator;
151
+ } else if (typeof idSize === 'number' && idSize > 0) {
152
+ this.idSize = idSize;
153
+ } else {
154
+ this.idSize = 22;
155
+ }
156
+
157
+ this.idGeneratorType = this.getIdGeneratorType(customIdGenerator, this.idSize);
145
158
 
146
159
  // Store configuration - all at root level
147
160
  this.config = {
@@ -220,9 +233,9 @@ export class Resource extends EventEmitter {
220
233
  * @private
221
234
  */
222
235
  configureIdGenerator(customIdGenerator, idSize) {
223
- // If a custom function is provided, use it
236
+ // If a custom function is provided, wrap it to ensure string output
224
237
  if (typeof customIdGenerator === 'function') {
225
- return customIdGenerator;
238
+ return () => String(customIdGenerator());
226
239
  }
227
240
  // If customIdGenerator is a number (size), create a generator with that size
228
241
  if (typeof customIdGenerator === 'number' && customIdGenerator > 0) {
@@ -236,6 +249,22 @@ export class Resource extends EventEmitter {
236
249
  return defaultIdGenerator;
237
250
  }
238
251
 
252
+ /**
253
+ * Get a serializable representation of the ID generator type
254
+ * @param {Function|number} customIdGenerator - Custom ID generator function or size
255
+ * @param {number} idSize - Size for auto-generated IDs
256
+ * @returns {string|number} Serializable ID generator type
257
+ * @private
258
+ */
259
+ getIdGeneratorType(customIdGenerator, idSize) {
260
+ // If a custom function is provided
261
+ if (typeof customIdGenerator === 'function') {
262
+ return 'custom_function';
263
+ }
264
+ // For number generators or default size, return the actual idSize
265
+ return idSize;
266
+ }
267
+
239
268
  /**
240
269
  * Get resource options (for backward compatibility with tests)
241
270
  */