@cldmv/slothlet 2.10.0 → 2.11.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.
@@ -161,7 +161,8 @@ export async function create(dir, maxDepth = Infinity, currentDepth = 0) {
161
161
  depth,
162
162
  maxDepth,
163
163
  pathParts: [key],
164
- runWithCtx
164
+ runWithCtx,
165
+ existingContent: parent[key]
165
166
  });
166
167
  parent[key] = proxy;
167
168
  }
@@ -173,6 +174,28 @@ export async function create(dir, maxDepth = Infinity, currentDepth = 0) {
173
174
 
174
175
  function replacePlaceholder(parent, key, placeholder, value, instance, depth) {
175
176
  if (!parent || !key) return;
177
+
178
+
179
+ if (
180
+ parent[key] !== placeholder &&
181
+ parent[key] &&
182
+ typeof parent[key] === "object" &&
183
+ typeof value === "object" &&
184
+ !Array.isArray(parent[key]) &&
185
+ !Array.isArray(value)
186
+ ) {
187
+ if (instance?.config?.debug) {
188
+ console.log(`[lazy] MERGE CASE DETECTED for '${key}' - existing:`, Object.keys(parent[key]), "new:", Object.keys(value));
189
+ console.log(`[lazy] Before merge - parent[${key}]:`, parent[key]);
190
+ }
191
+
192
+ Object.assign(parent[key], value);
193
+ if (instance?.config?.debug) {
194
+ console.log(`[lazy] After merge - parent[${key}]:`, Object.keys(parent[key]));
195
+ }
196
+ return;
197
+ }
198
+
176
199
  if (parent[key] !== placeholder) return;
177
200
 
178
201
 
@@ -215,9 +238,43 @@ function replacePlaceholder(parent, key, placeholder, value, instance, depth) {
215
238
  }
216
239
 
217
240
  try {
218
- Object.defineProperty(parent, finalKey, { value, writable: true, enumerable: true, configurable: true });
241
+
242
+ if (
243
+ parent[finalKey] &&
244
+ typeof parent[finalKey] === "object" &&
245
+ typeof value === "object" &&
246
+ !Array.isArray(parent[finalKey]) &&
247
+ !Array.isArray(value)
248
+ ) {
249
+ if (instance?.config?.debug) {
250
+ console.log(`[lazy] Merging subdirectory '${finalKey}' - existing:`, Object.keys(parent[finalKey]), "new:", Object.keys(value));
251
+ }
252
+
253
+ Object.assign(parent[finalKey], value);
254
+ } else {
255
+ Object.defineProperty(parent, finalKey, { value, writable: true, enumerable: true, configurable: true });
256
+ }
219
257
  } catch {
220
- parent[finalKey] = value;
258
+
259
+ if (
260
+ parent[finalKey] &&
261
+ typeof parent[finalKey] === "object" &&
262
+ typeof value === "object" &&
263
+ !Array.isArray(parent[finalKey]) &&
264
+ !Array.isArray(value)
265
+ ) {
266
+ if (instance?.config?.debug) {
267
+ console.log(
268
+ `[lazy] Merging subdirectory '${finalKey}' (fallback) - existing:`,
269
+ Object.keys(parent[finalKey]),
270
+ "new:",
271
+ Object.keys(value)
272
+ );
273
+ }
274
+ Object.assign(parent[finalKey], value);
275
+ } else {
276
+ parent[finalKey] = value;
277
+ }
221
278
  }
222
279
  if (instance?.config?.debug) {
223
280
  console.log(`[lazy][materialize] replaced ${key}${finalKey !== key ? ` -> ${finalKey}` : ""} (${typeof value})`);
@@ -228,7 +285,7 @@ function replacePlaceholder(parent, key, placeholder, value, instance, depth) {
228
285
  }
229
286
 
230
287
 
231
- function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth, pathParts, runWithCtx }) {
288
+ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth, pathParts, runWithCtx, existingContent }) {
232
289
  let materialized = null;
233
290
  let inFlight = null;
234
291
 
@@ -243,6 +300,7 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
243
300
  currentDepth: depth,
244
301
  maxDepth,
245
302
  mode: "lazy",
303
+ existingApi: parent,
246
304
  subdirHandler: ({ subDirPath: nestedPath, key: nestedKey, categoryModules, currentDepth: cd, maxDepth: md }) =>
247
305
  createFolderProxy({
248
306
  subDirPath: nestedPath,
@@ -256,6 +314,29 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
256
314
  })
257
315
  });
258
316
  materialized = value;
317
+
318
+
319
+ if (
320
+ existingContent &&
321
+ typeof existingContent === "object" &&
322
+ typeof materialized === "object" &&
323
+ !Array.isArray(existingContent) &&
324
+ !Array.isArray(materialized)
325
+ ) {
326
+ if (instance?.config?.debug) {
327
+ console.log(
328
+ `[lazy] Merging existing content with materialized subdirectory '${key}' - existing:`,
329
+ Object.keys(existingContent),
330
+ "new:",
331
+ Object.keys(materialized)
332
+ );
333
+ }
334
+
335
+ materialized = Object.assign({}, existingContent, materialized);
336
+ } else if (existingContent && !materialized) {
337
+
338
+ materialized = existingContent;
339
+ }
259
340
  if (instance?.config?.debug) {
260
341
  try {
261
342
  const infoKeys = materialized && typeof materialized === "object" ? Object.keys(materialized) : [];
package/dist/slothlet.mjs CHANGED
@@ -148,7 +148,17 @@ const slothletObject = {
148
148
  reference: {},
149
149
  mode: "singleton",
150
150
  loaded: false,
151
- config: { lazy: false, apiDepth: Infinity, debug: DEBUG, dir: null, sanitize: null, allowApiOverwrite: true },
151
+ config: {
152
+ lazy: false,
153
+ apiDepth: Infinity,
154
+ debug: DEBUG,
155
+ dir: null,
156
+ sanitize: null,
157
+ allowApiOverwrite: true,
158
+ enableModuleOwnership: false
159
+ },
160
+
161
+ _moduleOwnership: new Map(),
152
162
  _dispose: null,
153
163
  _boundAPIShutdown: null,
154
164
  instanceId: null,
@@ -396,7 +406,7 @@ const slothletObject = {
396
406
 
397
407
 
398
408
  async _buildCategory(categoryPath, options = {}) {
399
- const { currentDepth = 0, maxDepth = Infinity, mode = "eager", subdirHandler } = options;
409
+ const { currentDepth = 0, maxDepth = Infinity, mode = "eager", subdirHandler, existingApi } = options;
400
410
 
401
411
 
402
412
  return buildCategoryStructure(categoryPath, {
@@ -404,7 +414,8 @@ const slothletObject = {
404
414
  maxDepth,
405
415
  mode,
406
416
  subdirHandler,
407
- instance: this
417
+ instance: this,
418
+ existingApi
408
419
  });
409
420
  },
410
421
 
@@ -694,8 +705,45 @@ const slothletObject = {
694
705
  },
695
706
 
696
707
 
697
- async addApi(apiPath, folderPath, metadata = {}) {
698
- return addApiFromFolder({ apiPath, folderPath, instance: this, metadata });
708
+ _registerApiOwnership(apiPath, moduleId) {
709
+ if (!this.config.enableModuleOwnership) return;
710
+ this._moduleOwnership.set(apiPath, moduleId);
711
+ if (this.config.debug) {
712
+ console.log(`[DEBUG] Registered ownership: ${apiPath} -> ${moduleId}`);
713
+ }
714
+ },
715
+
716
+
717
+ _getApiOwnership(apiPath) {
718
+ if (!this.config.enableModuleOwnership) return null;
719
+ return this._moduleOwnership.get(apiPath) || null;
720
+ },
721
+
722
+
723
+ _validateModuleOwnership(apiPath, moduleId, forceOverwrite) {
724
+ if (!this.config.enableModuleOwnership || !forceOverwrite) return false;
725
+
726
+ const existingOwner = this._getApiOwnership(apiPath);
727
+ if (!existingOwner) {
728
+
729
+ return true;
730
+ }
731
+
732
+
733
+ if (existingOwner === moduleId) {
734
+ return true;
735
+ }
736
+
737
+
738
+ if (this.config.debug) {
739
+ console.log(`[DEBUG] Ownership conflict: ${apiPath} owned by ${existingOwner}, attempted by ${moduleId}`);
740
+ }
741
+ return false;
742
+ },
743
+
744
+
745
+ async addApi(apiPath, folderPath, metadata = {}, options = {}) {
746
+ return addApiFromFolder({ apiPath, folderPath, instance: this, metadata, options });
699
747
  },
700
748
 
701
749