@okf/ootils 1.28.0 → 1.28.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.
@@ -1260,11 +1260,30 @@ declare const UI_CONTENT: {
1260
1260
  };
1261
1261
  };
1262
1262
 
1263
- declare const genCleanCamelCaseId: (id: string) => string;
1263
+ interface GenCleanCamelCaseIdOptions {
1264
+ /** Strip all non-alphanumeric chars (A-Z, a-z, 0-9 only). Default: false */
1265
+ stripNonAlphanumeric?: boolean;
1266
+ /** Truncate result to this max length. Default: no limit */
1267
+ maxLength?: number;
1268
+ }
1269
+ declare const genCleanCamelCaseId: (id: string, options?: GenCleanCamelCaseIdOptions) => string;
1270
+
1271
+ /**
1272
+ * Generates a clean, deterministic contentType ID from text.
1273
+ * Wraps genCleanCamelCaseId with stripNonAlphanumeric + maxLength 40.
1274
+ *
1275
+ * Idempotent: running twice produces the same output.
1276
+ *
1277
+ * @example
1278
+ * genCleanContentTypeId("My Articles!") => "myArticles"
1279
+ * genCleanContentTypeId("myArticles") => "myArticles" (idempotent)
1280
+ * genCleanContentTypeId("123 Test") => "a123Test"
1281
+ */
1282
+ declare const genCleanContentTypeId: (text: string) => string;
1264
1283
 
1265
1284
  /**
1266
1285
  * Generates a clean, short, unique ID from text input.
1267
- * Used for both contentType IDs and block valuePaths.
1286
+ * Used for block valuePaths in templates.
1268
1287
  *
1269
1288
  * 1. Strips all characters except A-Z, a-z, 0-9
1270
1289
  * 2. Lowercases the result
@@ -1272,15 +1291,18 @@ declare const genCleanCamelCaseId: (id: string) => string;
1272
1291
  * 4. Truncates to maxLength (default 10)
1273
1292
  * 5. Appends _{random id} (default 4 chars)
1274
1293
  *
1294
+ * Idempotent: if input already matches the output pattern
1295
+ * (lowercase alphanumeric + _hash suffix), returns as-is.
1296
+ *
1275
1297
  * @param text - The input text to convert
1276
- * @param maxLength - Max characters before the random suffix (default 10, use 20 for contentType)
1298
+ * @param maxLength - Max characters before the random suffix (default 10)
1277
1299
  * @param shortIdLength - Length of the random suffix (default 4)
1278
1300
  *
1279
1301
  * @example
1280
- * genCleanIdForContentTypeAndValuePaths("Author Name") => "authorname_x7k2"
1281
- * genCleanIdForContentTypeAndValuePaths("My Articles!", 20) => "myarticles_m3p1"
1282
- * genCleanIdForContentTypeAndValuePaths("123 Test") => "a123test_ab4d"
1302
+ * genCleanValuePath("Author Name") => "authorname_x7k2"
1303
+ * genCleanValuePath("authorname_x7k2") => "authorname_x7k2" (idempotent)
1304
+ * genCleanValuePath("123 Test") => "a123test_ab4d"
1283
1305
  */
1284
- declare const genCleanIdForContentTypeAndValuePaths: (text: string, maxLength?: number, shortIdLength?: number) => string;
1306
+ declare const genCleanValuePath: (text: string) => string;
1285
1307
 
1286
- export { BASE_BULLMQ_CONFIG, FILTER_IDS, TEMP_removeDuplicateFilters, UI_CONTENT, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanIdForContentTypeAndValuePaths, genTagId, generateFilterKey, getFilterKeyForBlock, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
1308
+ export { BASE_BULLMQ_CONFIG, FILTER_IDS, TEMP_removeDuplicateFilters, UI_CONTENT, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanContentTypeId, genCleanValuePath, genTagId, generateFilterKey, getFilterKeyForBlock, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
package/dist/browser.d.ts CHANGED
@@ -1260,11 +1260,30 @@ declare const UI_CONTENT: {
1260
1260
  };
1261
1261
  };
1262
1262
 
1263
- declare const genCleanCamelCaseId: (id: string) => string;
1263
+ interface GenCleanCamelCaseIdOptions {
1264
+ /** Strip all non-alphanumeric chars (A-Z, a-z, 0-9 only). Default: false */
1265
+ stripNonAlphanumeric?: boolean;
1266
+ /** Truncate result to this max length. Default: no limit */
1267
+ maxLength?: number;
1268
+ }
1269
+ declare const genCleanCamelCaseId: (id: string, options?: GenCleanCamelCaseIdOptions) => string;
1270
+
1271
+ /**
1272
+ * Generates a clean, deterministic contentType ID from text.
1273
+ * Wraps genCleanCamelCaseId with stripNonAlphanumeric + maxLength 40.
1274
+ *
1275
+ * Idempotent: running twice produces the same output.
1276
+ *
1277
+ * @example
1278
+ * genCleanContentTypeId("My Articles!") => "myArticles"
1279
+ * genCleanContentTypeId("myArticles") => "myArticles" (idempotent)
1280
+ * genCleanContentTypeId("123 Test") => "a123Test"
1281
+ */
1282
+ declare const genCleanContentTypeId: (text: string) => string;
1264
1283
 
1265
1284
  /**
1266
1285
  * Generates a clean, short, unique ID from text input.
1267
- * Used for both contentType IDs and block valuePaths.
1286
+ * Used for block valuePaths in templates.
1268
1287
  *
1269
1288
  * 1. Strips all characters except A-Z, a-z, 0-9
1270
1289
  * 2. Lowercases the result
@@ -1272,15 +1291,18 @@ declare const genCleanCamelCaseId: (id: string) => string;
1272
1291
  * 4. Truncates to maxLength (default 10)
1273
1292
  * 5. Appends _{random id} (default 4 chars)
1274
1293
  *
1294
+ * Idempotent: if input already matches the output pattern
1295
+ * (lowercase alphanumeric + _hash suffix), returns as-is.
1296
+ *
1275
1297
  * @param text - The input text to convert
1276
- * @param maxLength - Max characters before the random suffix (default 10, use 20 for contentType)
1298
+ * @param maxLength - Max characters before the random suffix (default 10)
1277
1299
  * @param shortIdLength - Length of the random suffix (default 4)
1278
1300
  *
1279
1301
  * @example
1280
- * genCleanIdForContentTypeAndValuePaths("Author Name") => "authorname_x7k2"
1281
- * genCleanIdForContentTypeAndValuePaths("My Articles!", 20) => "myarticles_m3p1"
1282
- * genCleanIdForContentTypeAndValuePaths("123 Test") => "a123test_ab4d"
1302
+ * genCleanValuePath("Author Name") => "authorname_x7k2"
1303
+ * genCleanValuePath("authorname_x7k2") => "authorname_x7k2" (idempotent)
1304
+ * genCleanValuePath("123 Test") => "a123test_ab4d"
1283
1305
  */
1284
- declare const genCleanIdForContentTypeAndValuePaths: (text: string, maxLength?: number, shortIdLength?: number) => string;
1306
+ declare const genCleanValuePath: (text: string) => string;
1285
1307
 
1286
- export { BASE_BULLMQ_CONFIG, FILTER_IDS, TEMP_removeDuplicateFilters, UI_CONTENT, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanIdForContentTypeAndValuePaths, genTagId, generateFilterKey, getFilterKeyForBlock, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
1308
+ export { BASE_BULLMQ_CONFIG, FILTER_IDS, TEMP_removeDuplicateFilters, UI_CONTENT, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanContentTypeId, genCleanValuePath, genTagId, generateFilterKey, getFilterKeyForBlock, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
package/dist/browser.js CHANGED
@@ -35,7 +35,8 @@ __export(browser_exports, {
35
35
  extractAllBlocksFromTpl: () => extractAllBlocksFromTpl,
36
36
  extractAndOrganizeBlocks: () => extractAndOrganizeBlocks,
37
37
  genCleanCamelCaseId: () => genCleanCamelCaseId,
38
- genCleanIdForContentTypeAndValuePaths: () => genCleanIdForContentTypeAndValuePaths,
38
+ genCleanContentTypeId: () => genCleanContentTypeId,
39
+ genCleanValuePath: () => genCleanValuePath,
39
40
  genTagId: () => genTagId,
40
41
  generateFilterKey: () => generateFilterKey,
41
42
  getFilterKeyForBlock: () => getFilterKeyForBlock,
@@ -2127,20 +2128,33 @@ var autoGenFilterConfigsFromTpl = ({
2127
2128
  };
2128
2129
 
2129
2130
  // src/utils/genCleanCamelCaseId.ts
2130
- var genCleanCamelCaseId = (id) => {
2131
+ var genCleanCamelCaseId = (id, options) => {
2131
2132
  if (!id || typeof id !== "string") return id;
2132
- const isAlreadyClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
2133
- if (isAlreadyClean) return id;
2133
+ const { stripNonAlphanumeric = false, maxLength } = options || {};
2134
+ const baseClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
2135
+ const withinMaxLength = maxLength ? id.length <= maxLength : true;
2136
+ const hasNoNonAlpha = stripNonAlphanumeric ? /^[a-zA-Z0-9]+$/.test(id) : true;
2137
+ if (baseClean && withinMaxLength && hasNoNonAlpha) return id;
2134
2138
  const words = id.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
2135
2139
  if (words.length === 0) return "a";
2136
- const result = words.map((word, i) => {
2140
+ let result = words.map((word, i) => {
2137
2141
  const lower = word.toLowerCase();
2138
2142
  return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
2139
2143
  }).join("");
2140
- return /^\d/.test(result) ? "a" + result : result;
2144
+ if (/^\d/.test(result)) result = "a" + result;
2145
+ if (stripNonAlphanumeric) {
2146
+ result = result.replace(/[^a-zA-Z0-9]/g, "");
2147
+ }
2148
+ if (maxLength) {
2149
+ result = result.slice(0, maxLength);
2150
+ }
2151
+ return result;
2141
2152
  };
2142
2153
 
2143
- // src/utils/genCleanIdForContentTypeAndValuePaths.ts
2154
+ // src/utils/genCleanContentTypeId.ts
2155
+ var genCleanContentTypeId = (text) => genCleanCamelCaseId(text, { stripNonAlphanumeric: true, maxLength: 40 });
2156
+
2157
+ // src/utils/genCleanValuePath.ts
2144
2158
  var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
2145
2159
  function genShortId(length = 4) {
2146
2160
  let result = "";
@@ -2149,11 +2163,15 @@ function genShortId(length = 4) {
2149
2163
  }
2150
2164
  return result;
2151
2165
  }
2152
- var genCleanIdForContentTypeAndValuePaths = (text, maxLength = 10, shortIdLength = 4) => {
2166
+ var genCleanValuePath = (text) => {
2167
+ const maxLength = 10;
2168
+ const shortIdLength = 4;
2153
2169
  if (!text || typeof text !== "string") return text;
2170
+ const idempotencyPattern = new RegExp(`^[a-z][a-z0-9]*_[a-z0-9]{${shortIdLength}}$`);
2171
+ if (idempotencyPattern.test(text)) return text;
2154
2172
  let cleaned = text.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
2155
2173
  if (!cleaned) {
2156
- throw new Error(`[genCleanIdForContentTypeAndValuePaths] Input "${text}" contains no alphanumeric characters`);
2174
+ throw new Error(`[genCleanValuePath] Input "${text}" contains no alphanumeric characters`);
2157
2175
  }
2158
2176
  if (/^\d/.test(cleaned)) cleaned = "a" + cleaned;
2159
2177
  const truncated = cleaned.slice(0, maxLength);
@@ -2176,7 +2194,8 @@ var genCleanIdForContentTypeAndValuePaths = (text, maxLength = 10, shortIdLength
2176
2194
  extractAllBlocksFromTpl,
2177
2195
  extractAndOrganizeBlocks,
2178
2196
  genCleanCamelCaseId,
2179
- genCleanIdForContentTypeAndValuePaths,
2197
+ genCleanContentTypeId,
2198
+ genCleanValuePath,
2180
2199
  genTagId,
2181
2200
  generateFilterKey,
2182
2201
  getFilterKeyForBlock,
package/dist/browser.mjs CHANGED
@@ -2070,20 +2070,33 @@ var autoGenFilterConfigsFromTpl = ({
2070
2070
  };
2071
2071
 
2072
2072
  // src/utils/genCleanCamelCaseId.ts
2073
- var genCleanCamelCaseId = (id) => {
2073
+ var genCleanCamelCaseId = (id, options) => {
2074
2074
  if (!id || typeof id !== "string") return id;
2075
- const isAlreadyClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
2076
- if (isAlreadyClean) return id;
2075
+ const { stripNonAlphanumeric = false, maxLength } = options || {};
2076
+ const baseClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
2077
+ const withinMaxLength = maxLength ? id.length <= maxLength : true;
2078
+ const hasNoNonAlpha = stripNonAlphanumeric ? /^[a-zA-Z0-9]+$/.test(id) : true;
2079
+ if (baseClean && withinMaxLength && hasNoNonAlpha) return id;
2077
2080
  const words = id.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
2078
2081
  if (words.length === 0) return "a";
2079
- const result = words.map((word, i) => {
2082
+ let result = words.map((word, i) => {
2080
2083
  const lower = word.toLowerCase();
2081
2084
  return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
2082
2085
  }).join("");
2083
- return /^\d/.test(result) ? "a" + result : result;
2086
+ if (/^\d/.test(result)) result = "a" + result;
2087
+ if (stripNonAlphanumeric) {
2088
+ result = result.replace(/[^a-zA-Z0-9]/g, "");
2089
+ }
2090
+ if (maxLength) {
2091
+ result = result.slice(0, maxLength);
2092
+ }
2093
+ return result;
2084
2094
  };
2085
2095
 
2086
- // src/utils/genCleanIdForContentTypeAndValuePaths.ts
2096
+ // src/utils/genCleanContentTypeId.ts
2097
+ var genCleanContentTypeId = (text) => genCleanCamelCaseId(text, { stripNonAlphanumeric: true, maxLength: 40 });
2098
+
2099
+ // src/utils/genCleanValuePath.ts
2087
2100
  var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
2088
2101
  function genShortId(length = 4) {
2089
2102
  let result = "";
@@ -2092,11 +2105,15 @@ function genShortId(length = 4) {
2092
2105
  }
2093
2106
  return result;
2094
2107
  }
2095
- var genCleanIdForContentTypeAndValuePaths = (text, maxLength = 10, shortIdLength = 4) => {
2108
+ var genCleanValuePath = (text) => {
2109
+ const maxLength = 10;
2110
+ const shortIdLength = 4;
2096
2111
  if (!text || typeof text !== "string") return text;
2112
+ const idempotencyPattern = new RegExp(`^[a-z][a-z0-9]*_[a-z0-9]{${shortIdLength}}$`);
2113
+ if (idempotencyPattern.test(text)) return text;
2097
2114
  let cleaned = text.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
2098
2115
  if (!cleaned) {
2099
- throw new Error(`[genCleanIdForContentTypeAndValuePaths] Input "${text}" contains no alphanumeric characters`);
2116
+ throw new Error(`[genCleanValuePath] Input "${text}" contains no alphanumeric characters`);
2100
2117
  }
2101
2118
  if (/^\d/.test(cleaned)) cleaned = "a" + cleaned;
2102
2119
  const truncated = cleaned.slice(0, maxLength);
@@ -2118,7 +2135,8 @@ export {
2118
2135
  extractAllBlocksFromTpl,
2119
2136
  extractAndOrganizeBlocks,
2120
2137
  genCleanCamelCaseId,
2121
- genCleanIdForContentTypeAndValuePaths,
2138
+ genCleanContentTypeId,
2139
+ genCleanValuePath,
2122
2140
  genTagId,
2123
2141
  generateFilterKey,
2124
2142
  getFilterKeyForBlock,
package/dist/node.d.mts CHANGED
@@ -1267,11 +1267,30 @@ declare const UI_CONTENT: {
1267
1267
  };
1268
1268
  };
1269
1269
 
1270
- declare const genCleanCamelCaseId: (id: string) => string;
1270
+ interface GenCleanCamelCaseIdOptions {
1271
+ /** Strip all non-alphanumeric chars (A-Z, a-z, 0-9 only). Default: false */
1272
+ stripNonAlphanumeric?: boolean;
1273
+ /** Truncate result to this max length. Default: no limit */
1274
+ maxLength?: number;
1275
+ }
1276
+ declare const genCleanCamelCaseId: (id: string, options?: GenCleanCamelCaseIdOptions) => string;
1277
+
1278
+ /**
1279
+ * Generates a clean, deterministic contentType ID from text.
1280
+ * Wraps genCleanCamelCaseId with stripNonAlphanumeric + maxLength 40.
1281
+ *
1282
+ * Idempotent: running twice produces the same output.
1283
+ *
1284
+ * @example
1285
+ * genCleanContentTypeId("My Articles!") => "myArticles"
1286
+ * genCleanContentTypeId("myArticles") => "myArticles" (idempotent)
1287
+ * genCleanContentTypeId("123 Test") => "a123Test"
1288
+ */
1289
+ declare const genCleanContentTypeId: (text: string) => string;
1271
1290
 
1272
1291
  /**
1273
1292
  * Generates a clean, short, unique ID from text input.
1274
- * Used for both contentType IDs and block valuePaths.
1293
+ * Used for block valuePaths in templates.
1275
1294
  *
1276
1295
  * 1. Strips all characters except A-Z, a-z, 0-9
1277
1296
  * 2. Lowercases the result
@@ -1279,16 +1298,19 @@ declare const genCleanCamelCaseId: (id: string) => string;
1279
1298
  * 4. Truncates to maxLength (default 10)
1280
1299
  * 5. Appends _{random id} (default 4 chars)
1281
1300
  *
1301
+ * Idempotent: if input already matches the output pattern
1302
+ * (lowercase alphanumeric + _hash suffix), returns as-is.
1303
+ *
1282
1304
  * @param text - The input text to convert
1283
- * @param maxLength - Max characters before the random suffix (default 10, use 20 for contentType)
1305
+ * @param maxLength - Max characters before the random suffix (default 10)
1284
1306
  * @param shortIdLength - Length of the random suffix (default 4)
1285
1307
  *
1286
1308
  * @example
1287
- * genCleanIdForContentTypeAndValuePaths("Author Name") => "authorname_x7k2"
1288
- * genCleanIdForContentTypeAndValuePaths("My Articles!", 20) => "myarticles_m3p1"
1289
- * genCleanIdForContentTypeAndValuePaths("123 Test") => "a123test_ab4d"
1309
+ * genCleanValuePath("Author Name") => "authorname_x7k2"
1310
+ * genCleanValuePath("authorname_x7k2") => "authorname_x7k2" (idempotent)
1311
+ * genCleanValuePath("123 Test") => "a123test_ab4d"
1290
1312
  */
1291
- declare const genCleanIdForContentTypeAndValuePaths: (text: string, maxLength?: number, shortIdLength?: number) => string;
1313
+ declare const genCleanValuePath: (text: string) => string;
1292
1314
 
1293
1315
  declare class MongoConnector {
1294
1316
  static getInstance(): any;
@@ -2020,4 +2042,4 @@ declare function GET_GLOBAL_BULLMQ_CONFIG({ env, redisCredentials }: {
2020
2042
  };
2021
2043
  }): Object;
2022
2044
 
2023
- export { AIChatSchema, AnnosElasticSyncProducer, AnnotationSchema, BASE_BULLMQ_CONFIG, BaseProducer, BaseWorker, ChunksElasticSyncProducer, ElasticSearchConnector, FILTER_IDS, GET_GLOBAL_BULLMQ_CONFIG, GeneratedEntitiesSchema, GeneratedTopicsSchema, MongoConnector, PlatformConfigsSchema, ProducerManager, RedisCacheConnector, SecretManagerConnector, TEMP_removeDuplicateFilters, TplSchema, UI_CONTENT, WorkerManager, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanIdForContentTypeAndValuePaths, genTagId, generateFilterKey, getAIChatModelByTenant, getAnnotationsModelByTenant, getDbByTenant, getFilterKeyForBlock, getGeneratedEntitiesModelByTenant, getGeneratedTopicsModelByTenant, getModelByTenant, getPlatformConfigsModelByTenant, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getTplModelByTenant, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
2045
+ export { AIChatSchema, AnnosElasticSyncProducer, AnnotationSchema, BASE_BULLMQ_CONFIG, BaseProducer, BaseWorker, ChunksElasticSyncProducer, ElasticSearchConnector, FILTER_IDS, GET_GLOBAL_BULLMQ_CONFIG, GeneratedEntitiesSchema, GeneratedTopicsSchema, MongoConnector, PlatformConfigsSchema, ProducerManager, RedisCacheConnector, SecretManagerConnector, TEMP_removeDuplicateFilters, TplSchema, UI_CONTENT, WorkerManager, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanContentTypeId, genCleanValuePath, genTagId, generateFilterKey, getAIChatModelByTenant, getAnnotationsModelByTenant, getDbByTenant, getFilterKeyForBlock, getGeneratedEntitiesModelByTenant, getGeneratedTopicsModelByTenant, getModelByTenant, getPlatformConfigsModelByTenant, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getTplModelByTenant, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
package/dist/node.d.ts CHANGED
@@ -1267,11 +1267,30 @@ declare const UI_CONTENT: {
1267
1267
  };
1268
1268
  };
1269
1269
 
1270
- declare const genCleanCamelCaseId: (id: string) => string;
1270
+ interface GenCleanCamelCaseIdOptions {
1271
+ /** Strip all non-alphanumeric chars (A-Z, a-z, 0-9 only). Default: false */
1272
+ stripNonAlphanumeric?: boolean;
1273
+ /** Truncate result to this max length. Default: no limit */
1274
+ maxLength?: number;
1275
+ }
1276
+ declare const genCleanCamelCaseId: (id: string, options?: GenCleanCamelCaseIdOptions) => string;
1277
+
1278
+ /**
1279
+ * Generates a clean, deterministic contentType ID from text.
1280
+ * Wraps genCleanCamelCaseId with stripNonAlphanumeric + maxLength 40.
1281
+ *
1282
+ * Idempotent: running twice produces the same output.
1283
+ *
1284
+ * @example
1285
+ * genCleanContentTypeId("My Articles!") => "myArticles"
1286
+ * genCleanContentTypeId("myArticles") => "myArticles" (idempotent)
1287
+ * genCleanContentTypeId("123 Test") => "a123Test"
1288
+ */
1289
+ declare const genCleanContentTypeId: (text: string) => string;
1271
1290
 
1272
1291
  /**
1273
1292
  * Generates a clean, short, unique ID from text input.
1274
- * Used for both contentType IDs and block valuePaths.
1293
+ * Used for block valuePaths in templates.
1275
1294
  *
1276
1295
  * 1. Strips all characters except A-Z, a-z, 0-9
1277
1296
  * 2. Lowercases the result
@@ -1279,16 +1298,19 @@ declare const genCleanCamelCaseId: (id: string) => string;
1279
1298
  * 4. Truncates to maxLength (default 10)
1280
1299
  * 5. Appends _{random id} (default 4 chars)
1281
1300
  *
1301
+ * Idempotent: if input already matches the output pattern
1302
+ * (lowercase alphanumeric + _hash suffix), returns as-is.
1303
+ *
1282
1304
  * @param text - The input text to convert
1283
- * @param maxLength - Max characters before the random suffix (default 10, use 20 for contentType)
1305
+ * @param maxLength - Max characters before the random suffix (default 10)
1284
1306
  * @param shortIdLength - Length of the random suffix (default 4)
1285
1307
  *
1286
1308
  * @example
1287
- * genCleanIdForContentTypeAndValuePaths("Author Name") => "authorname_x7k2"
1288
- * genCleanIdForContentTypeAndValuePaths("My Articles!", 20) => "myarticles_m3p1"
1289
- * genCleanIdForContentTypeAndValuePaths("123 Test") => "a123test_ab4d"
1309
+ * genCleanValuePath("Author Name") => "authorname_x7k2"
1310
+ * genCleanValuePath("authorname_x7k2") => "authorname_x7k2" (idempotent)
1311
+ * genCleanValuePath("123 Test") => "a123test_ab4d"
1290
1312
  */
1291
- declare const genCleanIdForContentTypeAndValuePaths: (text: string, maxLength?: number, shortIdLength?: number) => string;
1313
+ declare const genCleanValuePath: (text: string) => string;
1292
1314
 
1293
1315
  declare class MongoConnector {
1294
1316
  static getInstance(): any;
@@ -2020,4 +2042,4 @@ declare function GET_GLOBAL_BULLMQ_CONFIG({ env, redisCredentials }: {
2020
2042
  };
2021
2043
  }): Object;
2022
2044
 
2023
- export { AIChatSchema, AnnosElasticSyncProducer, AnnotationSchema, BASE_BULLMQ_CONFIG, BaseProducer, BaseWorker, ChunksElasticSyncProducer, ElasticSearchConnector, FILTER_IDS, GET_GLOBAL_BULLMQ_CONFIG, GeneratedEntitiesSchema, GeneratedTopicsSchema, MongoConnector, PlatformConfigsSchema, ProducerManager, RedisCacheConnector, SecretManagerConnector, TEMP_removeDuplicateFilters, TplSchema, UI_CONTENT, WorkerManager, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanIdForContentTypeAndValuePaths, genTagId, generateFilterKey, getAIChatModelByTenant, getAnnotationsModelByTenant, getDbByTenant, getFilterKeyForBlock, getGeneratedEntitiesModelByTenant, getGeneratedTopicsModelByTenant, getModelByTenant, getPlatformConfigsModelByTenant, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getTplModelByTenant, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
2045
+ export { AIChatSchema, AnnosElasticSyncProducer, AnnotationSchema, BASE_BULLMQ_CONFIG, BaseProducer, BaseWorker, ChunksElasticSyncProducer, ElasticSearchConnector, FILTER_IDS, GET_GLOBAL_BULLMQ_CONFIG, GeneratedEntitiesSchema, GeneratedTopicsSchema, MongoConnector, PlatformConfigsSchema, ProducerManager, RedisCacheConnector, SecretManagerConnector, TEMP_removeDuplicateFilters, TplSchema, UI_CONTENT, WorkerManager, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanContentTypeId, genCleanValuePath, genTagId, generateFilterKey, getAIChatModelByTenant, getAnnotationsModelByTenant, getDbByTenant, getFilterKeyForBlock, getGeneratedEntitiesModelByTenant, getGeneratedTopicsModelByTenant, getModelByTenant, getPlatformConfigsModelByTenant, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getTplModelByTenant, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
package/dist/node.js CHANGED
@@ -1949,7 +1949,8 @@ __export(node_exports, {
1949
1949
  extractAllBlocksFromTpl: () => extractAllBlocksFromTpl,
1950
1950
  extractAndOrganizeBlocks: () => extractAndOrganizeBlocks,
1951
1951
  genCleanCamelCaseId: () => genCleanCamelCaseId,
1952
- genCleanIdForContentTypeAndValuePaths: () => genCleanIdForContentTypeAndValuePaths,
1952
+ genCleanContentTypeId: () => genCleanContentTypeId,
1953
+ genCleanValuePath: () => genCleanValuePath,
1953
1954
  genTagId: () => genTagId,
1954
1955
  generateFilterKey: () => generateFilterKey,
1955
1956
  getAIChatModelByTenant: () => import_getModelByTenant2.getAIChatModelByTenant,
@@ -3721,20 +3722,33 @@ var autoGenFilterConfigsFromTpl = ({
3721
3722
  };
3722
3723
 
3723
3724
  // src/utils/genCleanCamelCaseId.ts
3724
- var genCleanCamelCaseId = (id) => {
3725
+ var genCleanCamelCaseId = (id, options) => {
3725
3726
  if (!id || typeof id !== "string") return id;
3726
- const isAlreadyClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
3727
- if (isAlreadyClean) return id;
3727
+ const { stripNonAlphanumeric = false, maxLength } = options || {};
3728
+ const baseClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
3729
+ const withinMaxLength = maxLength ? id.length <= maxLength : true;
3730
+ const hasNoNonAlpha = stripNonAlphanumeric ? /^[a-zA-Z0-9]+$/.test(id) : true;
3731
+ if (baseClean && withinMaxLength && hasNoNonAlpha) return id;
3728
3732
  const words = id.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
3729
3733
  if (words.length === 0) return "a";
3730
- const result = words.map((word, i) => {
3734
+ let result = words.map((word, i) => {
3731
3735
  const lower = word.toLowerCase();
3732
3736
  return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
3733
3737
  }).join("");
3734
- return /^\d/.test(result) ? "a" + result : result;
3738
+ if (/^\d/.test(result)) result = "a" + result;
3739
+ if (stripNonAlphanumeric) {
3740
+ result = result.replace(/[^a-zA-Z0-9]/g, "");
3741
+ }
3742
+ if (maxLength) {
3743
+ result = result.slice(0, maxLength);
3744
+ }
3745
+ return result;
3735
3746
  };
3736
3747
 
3737
- // src/utils/genCleanIdForContentTypeAndValuePaths.ts
3748
+ // src/utils/genCleanContentTypeId.ts
3749
+ var genCleanContentTypeId = (text) => genCleanCamelCaseId(text, { stripNonAlphanumeric: true, maxLength: 40 });
3750
+
3751
+ // src/utils/genCleanValuePath.ts
3738
3752
  var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
3739
3753
  function genShortId(length = 4) {
3740
3754
  let result = "";
@@ -3743,11 +3757,15 @@ function genShortId(length = 4) {
3743
3757
  }
3744
3758
  return result;
3745
3759
  }
3746
- var genCleanIdForContentTypeAndValuePaths = (text, maxLength = 10, shortIdLength = 4) => {
3760
+ var genCleanValuePath = (text) => {
3761
+ const maxLength = 10;
3762
+ const shortIdLength = 4;
3747
3763
  if (!text || typeof text !== "string") return text;
3764
+ const idempotencyPattern = new RegExp(`^[a-z][a-z0-9]*_[a-z0-9]{${shortIdLength}}$`);
3765
+ if (idempotencyPattern.test(text)) return text;
3748
3766
  let cleaned = text.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
3749
3767
  if (!cleaned) {
3750
- throw new Error(`[genCleanIdForContentTypeAndValuePaths] Input "${text}" contains no alphanumeric characters`);
3768
+ throw new Error(`[genCleanValuePath] Input "${text}" contains no alphanumeric characters`);
3751
3769
  }
3752
3770
  if (/^\d/.test(cleaned)) cleaned = "a" + cleaned;
3753
3771
  const truncated = cleaned.slice(0, maxLength);
@@ -4171,7 +4189,8 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
4171
4189
  extractAllBlocksFromTpl,
4172
4190
  extractAndOrganizeBlocks,
4173
4191
  genCleanCamelCaseId,
4174
- genCleanIdForContentTypeAndValuePaths,
4192
+ genCleanContentTypeId,
4193
+ genCleanValuePath,
4175
4194
  genTagId,
4176
4195
  generateFilterKey,
4177
4196
  getAIChatModelByTenant,
package/dist/node.mjs CHANGED
@@ -3663,20 +3663,33 @@ var autoGenFilterConfigsFromTpl = ({
3663
3663
  };
3664
3664
 
3665
3665
  // src/utils/genCleanCamelCaseId.ts
3666
- var genCleanCamelCaseId = (id) => {
3666
+ var genCleanCamelCaseId = (id, options) => {
3667
3667
  if (!id || typeof id !== "string") return id;
3668
- const isAlreadyClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
3669
- if (isAlreadyClean) return id;
3668
+ const { stripNonAlphanumeric = false, maxLength } = options || {};
3669
+ const baseClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
3670
+ const withinMaxLength = maxLength ? id.length <= maxLength : true;
3671
+ const hasNoNonAlpha = stripNonAlphanumeric ? /^[a-zA-Z0-9]+$/.test(id) : true;
3672
+ if (baseClean && withinMaxLength && hasNoNonAlpha) return id;
3670
3673
  const words = id.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
3671
3674
  if (words.length === 0) return "a";
3672
- const result = words.map((word, i) => {
3675
+ let result = words.map((word, i) => {
3673
3676
  const lower = word.toLowerCase();
3674
3677
  return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
3675
3678
  }).join("");
3676
- return /^\d/.test(result) ? "a" + result : result;
3679
+ if (/^\d/.test(result)) result = "a" + result;
3680
+ if (stripNonAlphanumeric) {
3681
+ result = result.replace(/[^a-zA-Z0-9]/g, "");
3682
+ }
3683
+ if (maxLength) {
3684
+ result = result.slice(0, maxLength);
3685
+ }
3686
+ return result;
3677
3687
  };
3678
3688
 
3679
- // src/utils/genCleanIdForContentTypeAndValuePaths.ts
3689
+ // src/utils/genCleanContentTypeId.ts
3690
+ var genCleanContentTypeId = (text) => genCleanCamelCaseId(text, { stripNonAlphanumeric: true, maxLength: 40 });
3691
+
3692
+ // src/utils/genCleanValuePath.ts
3680
3693
  var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
3681
3694
  function genShortId(length = 4) {
3682
3695
  let result = "";
@@ -3685,11 +3698,15 @@ function genShortId(length = 4) {
3685
3698
  }
3686
3699
  return result;
3687
3700
  }
3688
- var genCleanIdForContentTypeAndValuePaths = (text, maxLength = 10, shortIdLength = 4) => {
3701
+ var genCleanValuePath = (text) => {
3702
+ const maxLength = 10;
3703
+ const shortIdLength = 4;
3689
3704
  if (!text || typeof text !== "string") return text;
3705
+ const idempotencyPattern = new RegExp(`^[a-z][a-z0-9]*_[a-z0-9]{${shortIdLength}}$`);
3706
+ if (idempotencyPattern.test(text)) return text;
3690
3707
  let cleaned = text.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
3691
3708
  if (!cleaned) {
3692
- throw new Error(`[genCleanIdForContentTypeAndValuePaths] Input "${text}" contains no alphanumeric characters`);
3709
+ throw new Error(`[genCleanValuePath] Input "${text}" contains no alphanumeric characters`);
3693
3710
  }
3694
3711
  if (/^\d/.test(cleaned)) cleaned = "a" + cleaned;
3695
3712
  const truncated = cleaned.slice(0, maxLength);
@@ -4128,7 +4145,8 @@ export {
4128
4145
  extractAllBlocksFromTpl,
4129
4146
  extractAndOrganizeBlocks,
4130
4147
  genCleanCamelCaseId,
4131
- genCleanIdForContentTypeAndValuePaths,
4148
+ genCleanContentTypeId,
4149
+ genCleanValuePath,
4132
4150
  genTagId,
4133
4151
  generateFilterKey,
4134
4152
  export_getAIChatModelByTenant as getAIChatModelByTenant,
@@ -1260,11 +1260,30 @@ declare const UI_CONTENT: {
1260
1260
  };
1261
1261
  };
1262
1262
 
1263
- declare const genCleanCamelCaseId: (id: string) => string;
1263
+ interface GenCleanCamelCaseIdOptions {
1264
+ /** Strip all non-alphanumeric chars (A-Z, a-z, 0-9 only). Default: false */
1265
+ stripNonAlphanumeric?: boolean;
1266
+ /** Truncate result to this max length. Default: no limit */
1267
+ maxLength?: number;
1268
+ }
1269
+ declare const genCleanCamelCaseId: (id: string, options?: GenCleanCamelCaseIdOptions) => string;
1270
+
1271
+ /**
1272
+ * Generates a clean, deterministic contentType ID from text.
1273
+ * Wraps genCleanCamelCaseId with stripNonAlphanumeric + maxLength 40.
1274
+ *
1275
+ * Idempotent: running twice produces the same output.
1276
+ *
1277
+ * @example
1278
+ * genCleanContentTypeId("My Articles!") => "myArticles"
1279
+ * genCleanContentTypeId("myArticles") => "myArticles" (idempotent)
1280
+ * genCleanContentTypeId("123 Test") => "a123Test"
1281
+ */
1282
+ declare const genCleanContentTypeId: (text: string) => string;
1264
1283
 
1265
1284
  /**
1266
1285
  * Generates a clean, short, unique ID from text input.
1267
- * Used for both contentType IDs and block valuePaths.
1286
+ * Used for block valuePaths in templates.
1268
1287
  *
1269
1288
  * 1. Strips all characters except A-Z, a-z, 0-9
1270
1289
  * 2. Lowercases the result
@@ -1272,15 +1291,18 @@ declare const genCleanCamelCaseId: (id: string) => string;
1272
1291
  * 4. Truncates to maxLength (default 10)
1273
1292
  * 5. Appends _{random id} (default 4 chars)
1274
1293
  *
1294
+ * Idempotent: if input already matches the output pattern
1295
+ * (lowercase alphanumeric + _hash suffix), returns as-is.
1296
+ *
1275
1297
  * @param text - The input text to convert
1276
- * @param maxLength - Max characters before the random suffix (default 10, use 20 for contentType)
1298
+ * @param maxLength - Max characters before the random suffix (default 10)
1277
1299
  * @param shortIdLength - Length of the random suffix (default 4)
1278
1300
  *
1279
1301
  * @example
1280
- * genCleanIdForContentTypeAndValuePaths("Author Name") => "authorname_x7k2"
1281
- * genCleanIdForContentTypeAndValuePaths("My Articles!", 20) => "myarticles_m3p1"
1282
- * genCleanIdForContentTypeAndValuePaths("123 Test") => "a123test_ab4d"
1302
+ * genCleanValuePath("Author Name") => "authorname_x7k2"
1303
+ * genCleanValuePath("authorname_x7k2") => "authorname_x7k2" (idempotent)
1304
+ * genCleanValuePath("123 Test") => "a123test_ab4d"
1283
1305
  */
1284
- declare const genCleanIdForContentTypeAndValuePaths: (text: string, maxLength?: number, shortIdLength?: number) => string;
1306
+ declare const genCleanValuePath: (text: string) => string;
1285
1307
 
1286
- export { BASE_BULLMQ_CONFIG, FILTER_IDS, TEMP_removeDuplicateFilters, UI_CONTENT, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanIdForContentTypeAndValuePaths, genTagId, generateFilterKey, getFilterKeyForBlock, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
1308
+ export { BASE_BULLMQ_CONFIG, FILTER_IDS, TEMP_removeDuplicateFilters, UI_CONTENT, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanContentTypeId, genCleanValuePath, genTagId, generateFilterKey, getFilterKeyForBlock, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
@@ -1260,11 +1260,30 @@ declare const UI_CONTENT: {
1260
1260
  };
1261
1261
  };
1262
1262
 
1263
- declare const genCleanCamelCaseId: (id: string) => string;
1263
+ interface GenCleanCamelCaseIdOptions {
1264
+ /** Strip all non-alphanumeric chars (A-Z, a-z, 0-9 only). Default: false */
1265
+ stripNonAlphanumeric?: boolean;
1266
+ /** Truncate result to this max length. Default: no limit */
1267
+ maxLength?: number;
1268
+ }
1269
+ declare const genCleanCamelCaseId: (id: string, options?: GenCleanCamelCaseIdOptions) => string;
1270
+
1271
+ /**
1272
+ * Generates a clean, deterministic contentType ID from text.
1273
+ * Wraps genCleanCamelCaseId with stripNonAlphanumeric + maxLength 40.
1274
+ *
1275
+ * Idempotent: running twice produces the same output.
1276
+ *
1277
+ * @example
1278
+ * genCleanContentTypeId("My Articles!") => "myArticles"
1279
+ * genCleanContentTypeId("myArticles") => "myArticles" (idempotent)
1280
+ * genCleanContentTypeId("123 Test") => "a123Test"
1281
+ */
1282
+ declare const genCleanContentTypeId: (text: string) => string;
1264
1283
 
1265
1284
  /**
1266
1285
  * Generates a clean, short, unique ID from text input.
1267
- * Used for both contentType IDs and block valuePaths.
1286
+ * Used for block valuePaths in templates.
1268
1287
  *
1269
1288
  * 1. Strips all characters except A-Z, a-z, 0-9
1270
1289
  * 2. Lowercases the result
@@ -1272,15 +1291,18 @@ declare const genCleanCamelCaseId: (id: string) => string;
1272
1291
  * 4. Truncates to maxLength (default 10)
1273
1292
  * 5. Appends _{random id} (default 4 chars)
1274
1293
  *
1294
+ * Idempotent: if input already matches the output pattern
1295
+ * (lowercase alphanumeric + _hash suffix), returns as-is.
1296
+ *
1275
1297
  * @param text - The input text to convert
1276
- * @param maxLength - Max characters before the random suffix (default 10, use 20 for contentType)
1298
+ * @param maxLength - Max characters before the random suffix (default 10)
1277
1299
  * @param shortIdLength - Length of the random suffix (default 4)
1278
1300
  *
1279
1301
  * @example
1280
- * genCleanIdForContentTypeAndValuePaths("Author Name") => "authorname_x7k2"
1281
- * genCleanIdForContentTypeAndValuePaths("My Articles!", 20) => "myarticles_m3p1"
1282
- * genCleanIdForContentTypeAndValuePaths("123 Test") => "a123test_ab4d"
1302
+ * genCleanValuePath("Author Name") => "authorname_x7k2"
1303
+ * genCleanValuePath("authorname_x7k2") => "authorname_x7k2" (idempotent)
1304
+ * genCleanValuePath("123 Test") => "a123test_ab4d"
1283
1305
  */
1284
- declare const genCleanIdForContentTypeAndValuePaths: (text: string, maxLength?: number, shortIdLength?: number) => string;
1306
+ declare const genCleanValuePath: (text: string) => string;
1285
1307
 
1286
- export { BASE_BULLMQ_CONFIG, FILTER_IDS, TEMP_removeDuplicateFilters, UI_CONTENT, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanIdForContentTypeAndValuePaths, genTagId, generateFilterKey, getFilterKeyForBlock, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
1308
+ export { BASE_BULLMQ_CONFIG, FILTER_IDS, TEMP_removeDuplicateFilters, UI_CONTENT, _self_managed_buildAnnoHierarchyConfig, _self_managed_buildDocHierarchyConfig, _self_managed_getFixedAnnoRollupBlocks, _self_managed_getFixedAnnoTagBlock, autoGenFilterConfigsFromTpl, buildFilterConfigurations, compareAndGroupBlocks, deleteVal, extractAllBlocksFromTpl, extractAndOrganizeBlocks, genCleanCamelCaseId, genCleanContentTypeId, genCleanValuePath, genTagId, generateFilterKey, getFilterKeyForBlock, getPlatformContextContent, getRollupPossibilities, getRoutePathToEditContent, getRoutePathToModerateContent, getRoutePathToPublishedContent, getVal, mergeAnnoDataIntoAnnotationsTags, parseSpecialConfigSyntax, processAuthorAndCommonFilters, _recursExtractBlocks as recursivelyExtractBlocks, segrigateDocs, setVal, toArray };
package/dist/universal.js CHANGED
@@ -35,7 +35,8 @@ __export(universal_exports, {
35
35
  extractAllBlocksFromTpl: () => extractAllBlocksFromTpl,
36
36
  extractAndOrganizeBlocks: () => extractAndOrganizeBlocks,
37
37
  genCleanCamelCaseId: () => genCleanCamelCaseId,
38
- genCleanIdForContentTypeAndValuePaths: () => genCleanIdForContentTypeAndValuePaths,
38
+ genCleanContentTypeId: () => genCleanContentTypeId,
39
+ genCleanValuePath: () => genCleanValuePath,
39
40
  genTagId: () => genTagId,
40
41
  generateFilterKey: () => generateFilterKey,
41
42
  getFilterKeyForBlock: () => getFilterKeyForBlock,
@@ -2127,20 +2128,33 @@ var autoGenFilterConfigsFromTpl = ({
2127
2128
  };
2128
2129
 
2129
2130
  // src/utils/genCleanCamelCaseId.ts
2130
- var genCleanCamelCaseId = (id) => {
2131
+ var genCleanCamelCaseId = (id, options) => {
2131
2132
  if (!id || typeof id !== "string") return id;
2132
- const isAlreadyClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
2133
- if (isAlreadyClean) return id;
2133
+ const { stripNonAlphanumeric = false, maxLength } = options || {};
2134
+ const baseClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
2135
+ const withinMaxLength = maxLength ? id.length <= maxLength : true;
2136
+ const hasNoNonAlpha = stripNonAlphanumeric ? /^[a-zA-Z0-9]+$/.test(id) : true;
2137
+ if (baseClean && withinMaxLength && hasNoNonAlpha) return id;
2134
2138
  const words = id.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
2135
2139
  if (words.length === 0) return "a";
2136
- const result = words.map((word, i) => {
2140
+ let result = words.map((word, i) => {
2137
2141
  const lower = word.toLowerCase();
2138
2142
  return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
2139
2143
  }).join("");
2140
- return /^\d/.test(result) ? "a" + result : result;
2144
+ if (/^\d/.test(result)) result = "a" + result;
2145
+ if (stripNonAlphanumeric) {
2146
+ result = result.replace(/[^a-zA-Z0-9]/g, "");
2147
+ }
2148
+ if (maxLength) {
2149
+ result = result.slice(0, maxLength);
2150
+ }
2151
+ return result;
2141
2152
  };
2142
2153
 
2143
- // src/utils/genCleanIdForContentTypeAndValuePaths.ts
2154
+ // src/utils/genCleanContentTypeId.ts
2155
+ var genCleanContentTypeId = (text) => genCleanCamelCaseId(text, { stripNonAlphanumeric: true, maxLength: 40 });
2156
+
2157
+ // src/utils/genCleanValuePath.ts
2144
2158
  var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
2145
2159
  function genShortId(length = 4) {
2146
2160
  let result = "";
@@ -2149,11 +2163,15 @@ function genShortId(length = 4) {
2149
2163
  }
2150
2164
  return result;
2151
2165
  }
2152
- var genCleanIdForContentTypeAndValuePaths = (text, maxLength = 10, shortIdLength = 4) => {
2166
+ var genCleanValuePath = (text) => {
2167
+ const maxLength = 10;
2168
+ const shortIdLength = 4;
2153
2169
  if (!text || typeof text !== "string") return text;
2170
+ const idempotencyPattern = new RegExp(`^[a-z][a-z0-9]*_[a-z0-9]{${shortIdLength}}$`);
2171
+ if (idempotencyPattern.test(text)) return text;
2154
2172
  let cleaned = text.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
2155
2173
  if (!cleaned) {
2156
- throw new Error(`[genCleanIdForContentTypeAndValuePaths] Input "${text}" contains no alphanumeric characters`);
2174
+ throw new Error(`[genCleanValuePath] Input "${text}" contains no alphanumeric characters`);
2157
2175
  }
2158
2176
  if (/^\d/.test(cleaned)) cleaned = "a" + cleaned;
2159
2177
  const truncated = cleaned.slice(0, maxLength);
@@ -2176,7 +2194,8 @@ var genCleanIdForContentTypeAndValuePaths = (text, maxLength = 10, shortIdLength
2176
2194
  extractAllBlocksFromTpl,
2177
2195
  extractAndOrganizeBlocks,
2178
2196
  genCleanCamelCaseId,
2179
- genCleanIdForContentTypeAndValuePaths,
2197
+ genCleanContentTypeId,
2198
+ genCleanValuePath,
2180
2199
  genTagId,
2181
2200
  generateFilterKey,
2182
2201
  getFilterKeyForBlock,
@@ -2070,20 +2070,33 @@ var autoGenFilterConfigsFromTpl = ({
2070
2070
  };
2071
2071
 
2072
2072
  // src/utils/genCleanCamelCaseId.ts
2073
- var genCleanCamelCaseId = (id) => {
2073
+ var genCleanCamelCaseId = (id, options) => {
2074
2074
  if (!id || typeof id !== "string") return id;
2075
- const isAlreadyClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
2076
- if (isAlreadyClean) return id;
2075
+ const { stripNonAlphanumeric = false, maxLength } = options || {};
2076
+ const baseClean = /^\p{Ll}[\p{L}\p{N}]*$/u.test(id) || /^a\d[\p{L}\p{N}]*$/u.test(id);
2077
+ const withinMaxLength = maxLength ? id.length <= maxLength : true;
2078
+ const hasNoNonAlpha = stripNonAlphanumeric ? /^[a-zA-Z0-9]+$/.test(id) : true;
2079
+ if (baseClean && withinMaxLength && hasNoNonAlpha) return id;
2077
2080
  const words = id.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
2078
2081
  if (words.length === 0) return "a";
2079
- const result = words.map((word, i) => {
2082
+ let result = words.map((word, i) => {
2080
2083
  const lower = word.toLowerCase();
2081
2084
  return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
2082
2085
  }).join("");
2083
- return /^\d/.test(result) ? "a" + result : result;
2086
+ if (/^\d/.test(result)) result = "a" + result;
2087
+ if (stripNonAlphanumeric) {
2088
+ result = result.replace(/[^a-zA-Z0-9]/g, "");
2089
+ }
2090
+ if (maxLength) {
2091
+ result = result.slice(0, maxLength);
2092
+ }
2093
+ return result;
2084
2094
  };
2085
2095
 
2086
- // src/utils/genCleanIdForContentTypeAndValuePaths.ts
2096
+ // src/utils/genCleanContentTypeId.ts
2097
+ var genCleanContentTypeId = (text) => genCleanCamelCaseId(text, { stripNonAlphanumeric: true, maxLength: 40 });
2098
+
2099
+ // src/utils/genCleanValuePath.ts
2087
2100
  var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
2088
2101
  function genShortId(length = 4) {
2089
2102
  let result = "";
@@ -2092,11 +2105,15 @@ function genShortId(length = 4) {
2092
2105
  }
2093
2106
  return result;
2094
2107
  }
2095
- var genCleanIdForContentTypeAndValuePaths = (text, maxLength = 10, shortIdLength = 4) => {
2108
+ var genCleanValuePath = (text) => {
2109
+ const maxLength = 10;
2110
+ const shortIdLength = 4;
2096
2111
  if (!text || typeof text !== "string") return text;
2112
+ const idempotencyPattern = new RegExp(`^[a-z][a-z0-9]*_[a-z0-9]{${shortIdLength}}$`);
2113
+ if (idempotencyPattern.test(text)) return text;
2097
2114
  let cleaned = text.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
2098
2115
  if (!cleaned) {
2099
- throw new Error(`[genCleanIdForContentTypeAndValuePaths] Input "${text}" contains no alphanumeric characters`);
2116
+ throw new Error(`[genCleanValuePath] Input "${text}" contains no alphanumeric characters`);
2100
2117
  }
2101
2118
  if (/^\d/.test(cleaned)) cleaned = "a" + cleaned;
2102
2119
  const truncated = cleaned.slice(0, maxLength);
@@ -2118,7 +2135,8 @@ export {
2118
2135
  extractAllBlocksFromTpl,
2119
2136
  extractAndOrganizeBlocks,
2120
2137
  genCleanCamelCaseId,
2121
- genCleanIdForContentTypeAndValuePaths,
2138
+ genCleanContentTypeId,
2139
+ genCleanValuePath,
2122
2140
  genTagId,
2123
2141
  generateFilterKey,
2124
2142
  getFilterKeyForBlock,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.28.0",
6
+ "version": "1.28.1",
7
7
  "description": "Utility functions for both browser and Node.js",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.mjs",