@okf/ootils 1.42.0 → 1.44.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/dist/node.js CHANGED
@@ -935,7 +935,7 @@ var init_Tpl = __esm({
935
935
  var require_MongoConnector = __commonJS({
936
936
  "src/db/MongoConnector.js"(exports2, module2) {
937
937
  "use strict";
938
- var mongoose5 = require("mongoose");
938
+ var mongoose10 = require("mongoose");
939
939
  var { ServerApiVersion } = require("mongodb");
940
940
  var AnnotationsSchema = (init_Annotations(), __toCommonJS(Annotations_exports));
941
941
  var PlatformConfigsSchema2 = (init_PlatformConfigs(), __toCommonJS(PlatformConfigs_exports));
@@ -999,7 +999,7 @@ var require_MongoConnector = __commonJS({
999
999
  `Missing CLUSTER_NAME or CLUSTER_URI in cluster conf: ${JSON.stringify(clusterConf)}`
1000
1000
  );
1001
1001
  }
1002
- this.clusterConnections[CLUSTER_NAME] = mongoose5.createConnection(
1002
+ this.clusterConnections[CLUSTER_NAME] = mongoose10.createConnection(
1003
1003
  CLUSTER_URI,
1004
1004
  this.mongoOptions
1005
1005
  );
@@ -1026,7 +1026,7 @@ var require_MongoConnector = __commonJS({
1026
1026
  this.skippedClusters.add(CLUSTER_NAME);
1027
1027
  return;
1028
1028
  }
1029
- const connection = mongoose5.createConnection(CLUSTER_URI, this.mongoOptions);
1029
+ const connection = mongoose10.createConnection(CLUSTER_URI, this.mongoOptions);
1030
1030
  this.clusterConnections[CLUSTER_NAME] = connection;
1031
1031
  this.initiateConnectionEventListeners(CLUSTER_NAME);
1032
1032
  await new Promise((resolve, reject) => {
@@ -1491,7 +1491,7 @@ var require_getModelByTenant = __commonJS({
1491
1491
  "use strict";
1492
1492
  var { getDbByTenant: getDbByTenant2 } = (init_getDbByTenant(), __toCommonJS(getDbByTenant_exports));
1493
1493
  var { AIChatSchema: AIChatSchema2, AnnotationSchema: AnnotationSchema2, PlatformConfigsSchema: PlatformConfigsSchema2, TplSchema: TplSchema2, GeneratedTopicsSchema: GeneratedTopicsSchema2, GeneratedEntitiesSchema: GeneratedEntitiesSchema2 } = (init_models(), __toCommonJS(models_exports));
1494
- var getModelByTenant3 = ({ tenant, modelName, schema, env }) => {
1494
+ var getModelByTenant4 = ({ tenant, modelName, schema, env }) => {
1495
1495
  if (!tenant) {
1496
1496
  throw new Error("tenant id has not been provided");
1497
1497
  }
@@ -1501,48 +1501,48 @@ var require_getModelByTenant = __commonJS({
1501
1501
  }
1502
1502
  return db.model(modelName);
1503
1503
  };
1504
- var getAnnotationsModelByTenant2 = ({ tenant, env, mongodb, modelName }) => getModelByTenant3({
1504
+ var getAnnotationsModelByTenant2 = ({ tenant, env, mongodb, modelName }) => getModelByTenant4({
1505
1505
  tenant,
1506
1506
  modelName: modelName || "annotations",
1507
1507
  schema: AnnotationSchema2,
1508
1508
  env
1509
1509
  });
1510
- var getPlatformConfigsModelByTenant2 = ({ tenant, env, mongodb }) => getModelByTenant3({
1510
+ var getPlatformConfigsModelByTenant2 = ({ tenant, env, mongodb }) => getModelByTenant4({
1511
1511
  tenant,
1512
1512
  modelName: "platformConfigs",
1513
1513
  schema: PlatformConfigsSchema2,
1514
1514
  env
1515
1515
  });
1516
- var getTplModelByTenant2 = ({ tenant, env, mongodb }) => getModelByTenant3({
1516
+ var getTplModelByTenant3 = ({ tenant, env, mongodb }) => getModelByTenant4({
1517
1517
  tenant,
1518
1518
  modelName: "tpl",
1519
1519
  schema: TplSchema2,
1520
1520
  env
1521
1521
  });
1522
- var getAIChatModelByTenant2 = ({ tenant, env, mongodb }) => getModelByTenant3({
1522
+ var getAIChatModelByTenant2 = ({ tenant, env, mongodb }) => getModelByTenant4({
1523
1523
  tenant,
1524
1524
  modelName: "aiChat",
1525
1525
  schema: AIChatSchema2,
1526
1526
  env
1527
1527
  });
1528
- var getGeneratedTopicsModelByTenant2 = ({ tenant, env }) => getModelByTenant3({
1528
+ var getGeneratedTopicsModelByTenant2 = ({ tenant, env }) => getModelByTenant4({
1529
1529
  tenant,
1530
1530
  modelName: "generatedTopics",
1531
1531
  schema: GeneratedTopicsSchema2,
1532
1532
  env
1533
1533
  });
1534
- var getGeneratedEntitiesModelByTenant2 = ({ tenant, env }) => getModelByTenant3({
1534
+ var getGeneratedEntitiesModelByTenant2 = ({ tenant, env }) => getModelByTenant4({
1535
1535
  tenant,
1536
1536
  modelName: "generatedEntities",
1537
1537
  schema: GeneratedEntitiesSchema2,
1538
1538
  env
1539
1539
  });
1540
1540
  module2.exports = {
1541
- getModelByTenant: getModelByTenant3,
1541
+ getModelByTenant: getModelByTenant4,
1542
1542
  getAIChatModelByTenant: getAIChatModelByTenant2,
1543
1543
  getAnnotationsModelByTenant: getAnnotationsModelByTenant2,
1544
1544
  getPlatformConfigsModelByTenant: getPlatformConfigsModelByTenant2,
1545
- getTplModelByTenant: getTplModelByTenant2,
1545
+ getTplModelByTenant: getTplModelByTenant3,
1546
1546
  getGeneratedTopicsModelByTenant: getGeneratedTopicsModelByTenant2,
1547
1547
  getGeneratedEntitiesModelByTenant: getGeneratedEntitiesModelByTenant2
1548
1548
  };
@@ -2385,6 +2385,7 @@ __export(node_exports, {
2385
2385
  GET_GLOBAL_BULLMQ_CONFIG: () => import_GET_GLOBAL_BULLMQ_CONFIG.GET_GLOBAL_BULLMQ_CONFIG,
2386
2386
  GeneratedEntitiesSchema: () => GeneratedEntities_default,
2387
2387
  GeneratedTopicsSchema: () => GeneratedTopics_default,
2388
+ LIVE_COLLAB_DOC_REFRESH_MS: () => LIVE_COLLAB_DOC_REFRESH_MS,
2388
2389
  MONGO_SCHEMA_PRESETS: () => MONGO_SCHEMA_PRESETS,
2389
2390
  MongoConnector: () => import_MongoConnector3.MongoConnector,
2390
2391
  PlatformConfigsSchema: () => PlatformConfigs_default,
@@ -2392,9 +2393,13 @@ __export(node_exports, {
2392
2393
  RedisCacheConnector: () => RedisCacheConnector,
2393
2394
  SecretManagerConnector: () => SecretManagerConnector,
2394
2395
  TEMP_removeDuplicateFilters: () => TEMP_removeDuplicateFilters,
2396
+ TenantRegistry: () => TenantRegistry,
2395
2397
  TplSchema: () => Tpl_default,
2396
2398
  UI_CONTENT: () => UI_CONTENT,
2397
2399
  WorkerManager: () => import_WorkerManager.WorkerManager,
2400
+ _backOfficeFields__contr: () => _backOfficeFields__contr,
2401
+ _constantMeta__contr: () => _constantMeta__contr,
2402
+ _fixedMainFields__contr: () => _fixedMainFields__contr,
2398
2403
  _self_managed_buildAnnoHierarchyConfig: () => _self_managed_buildAnnoHierarchyConfig,
2399
2404
  _self_managed_buildDocHierarchyConfig: () => _self_managed_buildDocHierarchyConfig,
2400
2405
  _self_managed_getFixedAnnoRollupBlocks: () => _self_managed_getFixedAnnoRollupBlocks,
@@ -2402,23 +2407,28 @@ __export(node_exports, {
2402
2407
  autoGenFilterConfigsFromTpl: () => autoGenFilterConfigsFromTpl,
2403
2408
  blockRegistry: () => blockRegistry,
2404
2409
  buildFilterConfigurations: () => buildFilterConfigurations,
2410
+ clearLiveCollabDoc: () => clearLiveCollabDoc,
2405
2411
  collectMarkIdsInOrder: () => collectMarkIdsInOrder,
2406
2412
  compareAndGroupBlocks: () => compareAndGroupBlocks,
2407
2413
  deleteVal: () => deleteVal,
2408
2414
  extractAllBlocksFromTpl: () => extractAllBlocksFromTpl,
2415
+ extractAllUniqueAnnoTagTypesFromTpl: () => extractAllUniqueAnnoTagTypesFromTpl,
2409
2416
  extractAndOrganizeBlocks: () => extractAndOrganizeBlocks,
2417
+ extractNumberFromValue: () => extractNumberFromValue,
2410
2418
  genCleanCamelCaseId: () => genCleanCamelCaseId,
2419
+ genContentTypeSchema: () => genContentTypeSchema,
2411
2420
  genTagId: () => genTagId,
2412
2421
  generateFilterKey: () => generateFilterKey,
2413
- getAIChatModelByTenant: () => import_getModelByTenant2.getAIChatModelByTenant,
2422
+ getAIChatModelByTenant: () => import_getModelByTenant4.getAIChatModelByTenant,
2414
2423
  getAnnoFilterBucketKey: () => getAnnoFilterBucketKey,
2415
- getAnnotationsModelByTenant: () => import_getModelByTenant2.getAnnotationsModelByTenant,
2424
+ getAnnotationsModelByTenant: () => import_getModelByTenant4.getAnnotationsModelByTenant,
2425
+ getContrModelByTenant: () => getContrModelByTenant,
2416
2426
  getDbByTenant: () => getDbByTenant,
2417
2427
  getFilterKeyForBlock: () => getFilterKeyForBlock,
2418
- getGeneratedEntitiesModelByTenant: () => import_getModelByTenant2.getGeneratedEntitiesModelByTenant,
2419
- getGeneratedTopicsModelByTenant: () => import_getModelByTenant2.getGeneratedTopicsModelByTenant,
2420
- getModelByTenant: () => import_getModelByTenant2.getModelByTenant,
2421
- getPlatformConfigsModelByTenant: () => import_getModelByTenant2.getPlatformConfigsModelByTenant,
2428
+ getGeneratedEntitiesModelByTenant: () => import_getModelByTenant4.getGeneratedEntitiesModelByTenant,
2429
+ getGeneratedTopicsModelByTenant: () => import_getModelByTenant4.getGeneratedTopicsModelByTenant,
2430
+ getModelByTenant: () => import_getModelByTenant4.getModelByTenant,
2431
+ getPlatformConfigsModelByTenant: () => import_getModelByTenant4.getPlatformConfigsModelByTenant,
2422
2432
  getPlatformContextContent: () => getPlatformContextContent,
2423
2433
  getRollupPossibilities: () => getRollupPossibilities,
2424
2434
  getRoutePathToContentTypeLanding: () => getRoutePathToContentTypeLanding,
@@ -2429,9 +2439,13 @@ __export(node_exports, {
2429
2439
  getRoutePathToReviewDashboard: () => getRoutePathToReviewDashboard,
2430
2440
  getRoutePathToTCI: () => getRoutePathToTCI,
2431
2441
  getRoutePathToTagCategoryLanding: () => getRoutePathToTagCategoryLanding,
2432
- getTplModelByTenant: () => import_getModelByTenant2.getTplModelByTenant,
2442
+ getTpl: () => getTpl,
2443
+ getTplModelByTenant: () => import_getModelByTenant4.getTplModelByTenant,
2444
+ getTypesForSchemaFromTpl: () => getTypesForSchemaFromTpl,
2433
2445
  getVal: () => getVal,
2446
+ isLiveCollabDoc: () => isLiveCollabDoc,
2434
2447
  isTplAnnotationEnabled: () => isTplAnnotationEnabled,
2448
+ markLiveCollabDoc: () => markLiveCollabDoc,
2435
2449
  mergeAnnoDataIntoAnnotationsTags: () => mergeAnnoDataIntoAnnotationsTags,
2436
2450
  parseSpecialConfigSyntax: () => parseSpecialConfigSyntax,
2437
2451
  plainTextToLexical: () => plainTextToLexical,
@@ -5092,8 +5106,771 @@ RedisCacheConnector.instance = null;
5092
5106
  // src/node.ts
5093
5107
  init_SecretManagerConnector();
5094
5108
  init_getDbByTenant();
5109
+ var import_getModelByTenant4 = __toESM(require_getModelByTenant());
5110
+
5111
+ // src/contentSchema/genContentTypeSchema.js
5112
+ var import_mongoose12 = __toESM(require("mongoose"));
5113
+
5114
+ // src/contentSchema/components/_backOfficeFields__contr.js
5115
+ var import_mongoose8 = __toESM(require("mongoose"));
5116
+ var _backOfficeFields__contr = (thisContentModelRef) => ({
5117
+ //----- (USUSED AT THE MOMENT) version 2 properties for better tracking & interlinking of document clones ---- //
5118
+ isPrimaryDoc: Boolean,
5119
+ cloneStack: [import_mongoose8.default.Schema.Types.ObjectId],
5120
+ // the last item in the array being the last clone
5121
+ ogStack: [import_mongoose8.default.Schema.Types.ObjectId],
5122
+ // the last item in the array being the og-est og ( the primaryDoc )
5123
+ //----- END version 2 properties ---- //
5124
+ tagId: String,
5125
+ kp_published_status: {
5126
+ type: String,
5127
+ // options : draft | published | editPublished | awaitingModeration | underModeration
5128
+ default: "draft",
5129
+ required: true
5130
+ },
5131
+ warnings: [Object],
5132
+ kp_og_published_doc: {
5133
+ type: import_mongoose8.default.Schema.Types.ObjectId,
5134
+ ref: thisContentModelRef
5135
+ },
5136
+ // ----- mod rel --- //
5137
+ kp_pre_mod_doc: {
5138
+ type: import_mongoose8.default.Schema.Types.ObjectId
5139
+ },
5140
+ kp_is_pbl_taken_into_mod: Boolean,
5141
+ kp_is_mod_clone: Boolean,
5142
+ kp_moderator: {
5143
+ type: import_mongoose8.default.Schema.Types.ObjectId,
5144
+ ref: "user"
5145
+ },
5146
+ kp_date_mod_begin: Date,
5147
+ kp_date_mod_end: Date,
5148
+ kp_date_submitted: Date,
5149
+ // ----- END mod rel --- //
5150
+ kp_date_created: Date,
5151
+ kp_date_last_saved: Date,
5152
+ kp_date_published: {
5153
+ type: Date
5154
+ },
5155
+ editPublishedDocId: {
5156
+ //only comes on og docs that have an editPbl clone
5157
+ type: import_mongoose8.default.Schema.Types.ObjectId,
5158
+ ref: thisContentModelRef
5159
+ },
5160
+ currentEditor: {
5161
+ type: import_mongoose8.default.Schema.Types.ObjectId,
5162
+ //relevant for segments : staticPages & collection, where author dont matter.
5163
+ ref: "user"
5164
+ },
5165
+ lastActivity: {
5166
+ date: {
5167
+ type: Date
5168
+ }
5169
+ }
5170
+ });
5171
+
5172
+ // src/contentSchema/components/_comments__contr.js
5173
+ var import_mongoose9 = __toESM(require("mongoose"));
5174
+ var baseCommentsObj = {
5175
+ user: {
5176
+ userId: { type: import_mongoose9.default.Schema.Types.ObjectId, ref: "user" },
5177
+ profileType: String,
5178
+ name: String,
5179
+ avatar: Object
5180
+ },
5181
+ text: Object,
5182
+ dateCreated: Date,
5183
+ dateModified: [Date]
5184
+ //the start of tracking mod dates on things..experiment...
5185
+ };
5186
+ var _comments__contr = {
5187
+ comments: [
5188
+ {
5189
+ ...baseCommentsObj,
5190
+ responses: [baseCommentsObj]
5191
+ }
5192
+ ]
5193
+ };
5194
+
5195
+ // src/contentSchema/components/_reaction_contr.js
5196
+ var import_mongoose10 = __toESM(require("mongoose"));
5197
+ var baseSchema = {
5198
+ type: {
5199
+ type: String,
5200
+ enum: ["like", "celebrate", "inspiring", "learnings"]
5201
+ },
5202
+ dateCreated: Date,
5203
+ userId: { type: import_mongoose10.default.Schema.Types.ObjectId, ref: "user" }
5204
+ };
5205
+ var _reaction_contr = {
5206
+ reactions: [
5207
+ {
5208
+ ...baseSchema
5209
+ }
5210
+ ]
5211
+ };
5212
+
5213
+ // src/contentSchema/components/_constantMeta__contr.js
5214
+ var import_mongoose11 = __toESM(require("mongoose"));
5215
+ var _constantMeta__contr = {
5216
+ kp_contributed_by: {
5217
+ type: import_mongoose11.default.Schema.Types.ObjectId,
5218
+ ref: "user",
5219
+ required: true
5220
+ },
5221
+ kp_content_type: {
5222
+ type: String,
5223
+ required: true
5224
+ },
5225
+ // "thematic" or "attribute" — distinguishes thematic sub-themes/tags from
5226
+ // attribute classification categories (speech act, emotion, etc.).
5227
+ taxonomyRole: {
5228
+ type: String
5229
+ },
5230
+ // Only meaningful on sub-theme docs. When false, new tags cannot be created under
5231
+ // this sub-theme (by humans via API or by the inductive AI annotator).
5232
+ // Absent/true = allowed (default).
5233
+ allowNewTags: {
5234
+ type: Boolean
5235
+ },
5236
+ // Only meaningful on attribute-role sub-themes. "single" = pick one tag,
5237
+ // "multi" = pick one or more. Default "multi".
5238
+ selectMode: {
5239
+ type: String
5240
+ },
5241
+ // "autoAI" | "humanInTheLoop" — absent means human editor (default).
5242
+ createdVia: {
5243
+ type: String
5244
+ }
5245
+ };
5246
+
5247
+ // src/contentSchema/components/_fixedMainFields__contr.js
5248
+ var _fixedMainFields__contr = {
5249
+ /**
5250
+ * hard coding title & subtitle is required because,
5251
+ * in tpl categories like 'researchNotes' the title comp is never defined in the template.
5252
+ * instead, it is hardcoded in the code, and it expects a 'main.title' value path.
5253
+ * Hence the hardcode below. Besides its not a problem, since, if in the template a
5254
+ * main.title component is indeed defined, then these hardcodes would be overwritten by
5255
+ * mainExtract. Which is what we want.
5256
+ */
5257
+ title: {
5258
+ type: String,
5259
+ cast: (v) => typeof v === "string" ? String(v) : Object(v)
5260
+ },
5261
+ subtitle: {
5262
+ type: String,
5263
+ cast: (v) => typeof v === "string" ? String(v) : Object(v)
5264
+ },
5265
+ titleInfo: String
5266
+ // this is used to show the meta text ("This story is supported by AI-assisted bot") on the published page
5267
+ };
5268
+
5269
+ // src/contentSchema/extractAllUniqueAnnoTagTypesFromTpl.js
5270
+ var extractAllUniqueAnnoTagTypesFromTpl = (extractedBlocks, { smTagTypesConfig } = {}) => {
5271
+ return Array.from(
5272
+ new Set(
5273
+ blockRegistry.getAnnotationEnabledBlocks(extractedBlocks).flatMap((bl) => [
5274
+ ...blockRegistry.getTagTypesConfig(bl, smTagTypesConfig).map((tt) => tt.tagType),
5275
+ ...bl.props?.annotation?.toggleTagTypesConfig?.map((tt) => tt.tagType) || []
5276
+ ])
5277
+ )
5278
+ );
5279
+ };
5280
+
5281
+ // src/contentSchema/getTypesForSchemaFromTpl/compToTypeMap/getConditionalDataType.js
5282
+ var getConditionalDataType = ({ block }) => {
5283
+ if (block.props?.saveValueAsString)
5284
+ return {
5285
+ type: Number,
5286
+ cast: (v) => {
5287
+ if (typeof v === "number" && !isNaN(v)) {
5288
+ return Number(v);
5289
+ } else if (typeof v === "string") {
5290
+ return String(v);
5291
+ } else {
5292
+ return Boolean(v);
5293
+ }
5294
+ }
5295
+ };
5296
+ else {
5297
+ const returnValue = ["DropdownMulti", "CheckboxList"].includes(block.comp) ? { type: Array } : { type: Object };
5298
+ return returnValue;
5299
+ }
5300
+ };
5301
+
5302
+ // src/contentSchema/utils/extractNumberFromValue.js
5303
+ var extractNumberFromValue = (value) => {
5304
+ if (value === null || value === void 0) return void 0;
5305
+ if (typeof value === "number") return isNaN(value) ? void 0 : value;
5306
+ const str = String(value).trim();
5307
+ if (str === "") return void 0;
5308
+ const directParse = Number(str);
5309
+ if (!isNaN(directParse)) return directParse;
5310
+ const commaNumberMatch = str.match(/-?\d[\d,]*\d(?:\.\d+)?/);
5311
+ if (commaNumberMatch && commaNumberMatch[0].includes(",")) {
5312
+ const num = Number(commaNumberMatch[0].replace(/,/g, ""));
5313
+ if (!isNaN(num)) return num;
5314
+ }
5315
+ const numberMatch = str.match(/-?\d+(?:\.\d+)?/);
5316
+ if (numberMatch) {
5317
+ const num = Number(numberMatch[0]);
5318
+ return isNaN(num) ? void 0 : num;
5319
+ }
5320
+ return void 0;
5321
+ };
5322
+
5323
+ // src/contentSchema/getTypesForSchemaFromTpl/compToTypeMap/index.js
5324
+ var compToTypeMap = ({ block }) => {
5325
+ const comp = block.comp;
5326
+ const blockDef = blockRegistry.getBlock(comp);
5327
+ if (blockDef) return blockDef.mongoSchemaType;
5328
+ switch (comp) {
5329
+ case "SummaryInput":
5330
+ case "KPRichInput":
5331
+ //deprecated
5332
+ case "RichTextEditor":
5333
+ //deprecated
5334
+ case "StaticRichText":
5335
+ case "CardRadioInput":
5336
+ case "NumberRangeInput":
5337
+ case "KPLikeContactChannelsBlock":
5338
+ case "TitleInput":
5339
+ case "SubtitleInput":
5340
+ case "PluginSentinel":
5341
+ case "DynamicRefetchDropdown":
5342
+ case "VideoInput":
5343
+ // data type for videoinput should actually be array, but since the old version of the comp used a string data type, we are currently setting it to object to avoid error
5344
+ case "SimpleTable":
5345
+ case "GroupQuestionsInputSingle":
5346
+ return { type: Object };
5347
+ case "ProfileImageInput":
5348
+ case "ResourceInput":
5349
+ case "ImageInput":
5350
+ case "VideoInput":
5351
+ case "Repeater":
5352
+ case "RepeaterTabs":
5353
+ case "PDFInput":
5354
+ case "TextInputToArray":
5355
+ return { type: Array };
5356
+ case "RichTextAsPlainText":
5357
+ case "PasswordInput":
5358
+ case "EmailInput":
5359
+ case "TextInput":
5360
+ case "URLInput":
5361
+ case "PhoneInput":
5362
+ case "KPMetaPrimary":
5363
+ case "IFrameInput":
5364
+ case "JSONInput":
5365
+ return { type: String };
5366
+ case "NumberInput":
5367
+ return {
5368
+ type: Number,
5369
+ // Smart extraction of numbers from messy values (e.g. "18+", "18 years old", "$100")
5370
+ // Uses extractNumberFromValue utility for consistent behavior with direct import
5371
+ set: extractNumberFromValue
5372
+ };
5373
+ case "DropdownSingleLanguage":
5374
+ case "ComboboxSingleLanguage":
5375
+ return {
5376
+ type: [
5377
+ {
5378
+ display: String,
5379
+ value: String
5380
+ }
5381
+ ]
5382
+ };
5383
+ case "DatePicker":
5384
+ return { type: Date };
5385
+ case "DateTimePicker":
5386
+ return { type: Date };
5387
+ case "TimePicker":
5388
+ return {
5389
+ type: String,
5390
+ // Validates time is in 24-hour format (00:00-23:59) with hours 00-23, a colon, and minutes 00-59
5391
+ validate: {
5392
+ validator: function(v) {
5393
+ return /^([01]\d|2[0-3]):([0-5]\d)$/.test(v);
5394
+ },
5395
+ message: (props) => `${props.value} is not a valid time format!`
5396
+ }
5397
+ };
5398
+ case "CheckboxButton":
5399
+ return { type: Boolean };
5400
+ case "TagsInputMulti":
5401
+ case "FragmentsSet":
5402
+ case "FragmentsDict":
5403
+ case "TagsInputSingle":
5404
+ return {
5405
+ type: {
5406
+ collectionId: String,
5407
+ data: [
5408
+ {
5409
+ _id: {
5410
+ type: String,
5411
+ index: block.index === true
5412
+ },
5413
+ display: String,
5414
+ tagId: String
5415
+ }
5416
+ ]
5417
+ }
5418
+ };
5419
+ case "SPECIAL_AnnotationTagsField":
5420
+ return {
5421
+ /**
5422
+ * very important that we wrap the whole thing in type,
5423
+ * because, if the tag name itself is type (like in the case of noora),
5424
+ * then it completely confuses mongoose, since type is a reserved word,
5425
+ * and causes all sorts of bugs.
5426
+ */
5427
+ type: {
5428
+ collectionId: String,
5429
+ data: Array
5430
+ //leaving this weakly typed for now cuz annotations have several other properties in here which i am not aware of..
5431
+ }
5432
+ };
5433
+ case "LinkEmbedWithInput":
5434
+ return {
5435
+ type: {
5436
+ url: String,
5437
+ metadata: Object
5438
+ }
5439
+ };
5440
+ case "CheckboxList":
5441
+ //new
5442
+ case "RadioList":
5443
+ //new
5444
+ case "DropdownSingle":
5445
+ //new
5446
+ case "DropdownMulti":
5447
+ //new
5448
+ case "SwitchDouble":
5449
+ case "SwitchSingle":
5450
+ return getConditionalDataType({ block });
5451
+ case "DateTimeRangePicker":
5452
+ return {
5453
+ type: {
5454
+ date: Date,
5455
+ timeRange: Array
5456
+ }
5457
+ };
5458
+ case "DateRangePicker":
5459
+ return { type: Array };
5460
+ }
5461
+ };
5462
+
5463
+ // src/contentSchema/getTypesForSchemaFromTpl/index.js
5464
+ var getTypesForSchemaFromTpl = (options = {}) => {
5465
+ const { tpl, smTagTypesConfig } = options;
5466
+ const defaultSchemaFieldsByTplLayout = {
5467
+ researchNotes2: {
5468
+ tags: {
5469
+ quotes: {
5470
+ collectionId: String,
5471
+ data: Array
5472
+ },
5473
+ vocabs: {
5474
+ collectionId: String,
5475
+ data: Array
5476
+ }
5477
+ }
5478
+ }
5479
+ };
5480
+ let mainExtract = {};
5481
+ let metaExtract = {};
5482
+ let tagsExtract = {};
5483
+ let rootExtract = {};
5484
+ let annoExtract = {
5485
+ ...defaultSchemaFieldsByTplLayout[tpl?.category] || {}
5486
+ };
5487
+ const extractedBlocks = extractAllBlocksFromTpl({ tpl });
5488
+ const annotationTagTypes = extractAllUniqueAnnoTagTypesFromTpl(extractedBlocks, {
5489
+ smTagTypesConfig
5490
+ });
5491
+ annotationTagTypes.forEach((annoTagType) => {
5492
+ const annotationFieldDef = compToTypeMap({
5493
+ block: { comp: "SPECIAL_AnnotationTagsField" }
5494
+ });
5495
+ annoExtract = setVal(annoExtract, `tags.${annoTagType}`, annotationFieldDef);
5496
+ });
5497
+ extractedBlocks.forEach((block) => {
5498
+ const { valuePath } = block;
5499
+ if (!valuePath) return;
5500
+ const pathSegments = valuePath.split(".");
5501
+ const [prefix, ...remainingPath] = pathSegments;
5502
+ const nestedPath = remainingPath.join(".");
5503
+ const fieldDefinition = compToTypeMap({ block });
5504
+ switch (prefix) {
5505
+ case "meta":
5506
+ metaExtract = setVal(
5507
+ metaExtract,
5508
+ nestedPath,
5509
+ // "author.name" (without "meta" prefix)
5510
+ fieldDefinition
5511
+ );
5512
+ break;
5513
+ case "main":
5514
+ mainExtract = setVal(
5515
+ mainExtract,
5516
+ nestedPath,
5517
+ // "content.title" (without "main" prefix)
5518
+ fieldDefinition
5519
+ );
5520
+ break;
5521
+ case "tags":
5522
+ tagsExtract = setVal(
5523
+ tagsExtract,
5524
+ nestedPath,
5525
+ // "categories.primary" (without "tags" prefix)
5526
+ fieldDefinition
5527
+ );
5528
+ break;
5529
+ default:
5530
+ if (block.comp && block.comp !== "TplMetaBlock" && valuePath !== ".") {
5531
+ rootExtract = setVal(
5532
+ rootExtract,
5533
+ valuePath,
5534
+ // Use full path since it's not prefixed
5535
+ fieldDefinition
5536
+ );
5537
+ }
5538
+ break;
5539
+ }
5540
+ });
5541
+ return {
5542
+ metaExtract,
5543
+ mainExtract,
5544
+ tagsExtract,
5545
+ rootExtract,
5546
+ annoExtract
5547
+ };
5548
+ };
5549
+
5550
+ // src/contentSchema/genContentTypeSchema.js
5551
+ var genContentTypeSchema = ({ contentType, segment, contentTypeConfig, smTagTypesConfig }) => {
5552
+ let SchemaToReturn;
5553
+ let {
5554
+ mainExtract = {},
5555
+ metaExtract = {},
5556
+ tagsExtract = {},
5557
+ rootExtract = {},
5558
+ annoExtract = {}
5559
+ } = getTypesForSchemaFromTpl({ tpl: contentTypeConfig, smTagTypesConfig });
5560
+ const schemaDef = {
5561
+ /**
5562
+ * this metadata is v.important from an optimization point of view.
5563
+ * it helps with getAllContrModelsByTenant, without having to
5564
+ * query the database for content type configurations.
5565
+ */
5566
+ metadata: {
5567
+ isContentType: { type: String, default: true },
5568
+ contentType: { type: String, default: contentType },
5569
+ segment: { type: String, default: segment }
5570
+ },
5571
+ ..._backOfficeFields__contr(contentType),
5572
+ ...rootExtract,
5573
+ tags: {
5574
+ ...tagsExtract
5575
+ },
5576
+ annotations: {
5577
+ ...annoExtract
5578
+ },
5579
+ meta: {
5580
+ ..._constantMeta__contr,
5581
+ ...metaExtract
5582
+ },
5583
+ main: {
5584
+ ..._fixedMainFields__contr,
5585
+ ...mainExtract
5586
+ },
5587
+ archive: Object,
5588
+ //burial ground for past fields that have eventually been removed from the tpl (and therefore the schema)
5589
+ translations: Object,
5590
+ reviewerNotes: Object,
5591
+ ..._reaction_contr,
5592
+ ..._comments__contr,
5593
+ ...segment === "publishing" && {
5594
+ shares: {
5595
+ count: {
5596
+ type: Number,
5597
+ default: 0
5598
+ }
5599
+ },
5600
+ views: {
5601
+ count: {
5602
+ type: Number,
5603
+ default: 0
5604
+ }
5605
+ }
5606
+ },
5607
+ updates: Object
5608
+ // DEPRECATED. DONT USE IT. DONT DELETE EITHER. ELSE WE WILL HAVE TO MAKE DATA UPDATES
5609
+ };
5610
+ SchemaToReturn = new import_mongoose12.default.Schema(schemaDef, {
5611
+ collection: contentType,
5612
+ toJSON: { virtuals: true },
5613
+ // So `res.json()` and other `JSON.stringify()` functions include virtuals
5614
+ toObject: { virtuals: true },
5615
+ // So `toObject()` output includes virtuals
5616
+ strict: "throw"
5617
+ //will throw an error if we try to add properties that arent on this schema
5618
+ });
5619
+ SchemaToReturn.index(
5620
+ { tagId: 1 },
5621
+ ...segment === "collections" ? [
5622
+ {
5623
+ unique: true,
5624
+ partialFilterExpression: {
5625
+ kp_published_status: { $eq: "published" }
5626
+ }
5627
+ }
5628
+ ] : []
5629
+ );
5630
+ SchemaToReturn.index({
5631
+ kp_published_status: 1,
5632
+ kp_date_published: -1,
5633
+ _id: -1
5634
+ });
5635
+ SchemaToReturn.index(
5636
+ { kp_published_status: 1, "main.title": 1, _id: 1 },
5637
+ { collation: { locale: "en", numericOrdering: true } }
5638
+ );
5639
+ SchemaToReturn.index({ kp_published_status: 1, kp_is_mod_clone: 1 });
5640
+ return SchemaToReturn;
5641
+ };
5642
+
5643
+ // src/TenantRegistry.js
5644
+ var import_storage = require("@google-cloud/storage");
5645
+ var BUCKET_NAME = "dots-system-configs";
5646
+ var FILE_NAME = "tenantRegistry.json";
5647
+ var TenantRegistry = class {
5648
+ // ─── Initialization ─────────────────────────────────
5649
+ /**
5650
+ * Must be awaited at startup before any sync getter is called.
5651
+ * Uses Application Default Credentials (ADC) for GCS auth.
5652
+ */
5653
+ static async init() {
5654
+ this._data = await this._load();
5655
+ this._rebuildCaches();
5656
+ this._lastRefreshAt = Date.now();
5657
+ }
5658
+ /**
5659
+ * Reload from GCS when this instance sees an unknown tenant/origin — likely one
5660
+ * provisioned on another instance after startup. Rate-limited; on failure keeps
5661
+ * the existing cache (never throws). Returns true if a reload actually ran.
5662
+ */
5663
+ static async refreshIfStale() {
5664
+ const now = Date.now();
5665
+ if (now - this._lastRefreshAt < this.REFRESH_COOLDOWN_MS) return false;
5666
+ this._lastRefreshAt = now;
5667
+ try {
5668
+ this._data = await this._load();
5669
+ this._rebuildCaches();
5670
+ return true;
5671
+ } catch (err) {
5672
+ console.error(`[TenantRegistry] refresh failed (keeping cache): ${err.message}`);
5673
+ return false;
5674
+ }
5675
+ }
5676
+ // ─── Storage layer (swap only these to change backend) ───
5677
+ static async _load() {
5678
+ const storage = new import_storage.Storage();
5679
+ const file = storage.bucket(BUCKET_NAME).file(FILE_NAME);
5680
+ const [buffer] = await file.download();
5681
+ const { data } = JSON.parse(buffer.toString("utf-8"));
5682
+ console.log(`[TenantRegistry] Loaded ${data.length} tenants from GCS`);
5683
+ return data;
5684
+ }
5685
+ static async _save() {
5686
+ const storage = new import_storage.Storage();
5687
+ const file = storage.bucket(BUCKET_NAME).file(FILE_NAME);
5688
+ await file.save(JSON.stringify({ data: this._data }, null, 2), {
5689
+ contentType: "application/json"
5690
+ });
5691
+ }
5692
+ // ─── End storage layer ──────────────────────────────
5693
+ static _rebuildCaches() {
5694
+ this._byEnv = {};
5695
+ this._selfManagedSet = /* @__PURE__ */ new Set();
5696
+ for (const entry of this._data) {
5697
+ const { tenantId, urls, isSelfManaged } = entry;
5698
+ if (isSelfManaged) this._selfManagedSet.add(tenantId);
5699
+ for (const env of ["dev", "staging", "prod"]) {
5700
+ if (!urls[env]) continue;
5701
+ if (!this._byEnv[env]) this._byEnv[env] = {};
5702
+ this._byEnv[env][tenantId] = urls[env];
5703
+ }
5704
+ }
5705
+ }
5706
+ // ─── Sync getters ───────────────────────────────────
5707
+ /** { tenantId: url } for the given env */
5708
+ static getMappingsForEnv(env) {
5709
+ return this._byEnv[env] || {};
5710
+ }
5711
+ /** [url, url, ...] for the given env */
5712
+ static getUrlsForEnv(env) {
5713
+ return Object.values(this._byEnv[env] || {});
5714
+ }
5715
+ /** [tenantId, tenantId, ...] for the given env */
5716
+ static getTenantIdsForEnv(env) {
5717
+ return Object.keys(this._byEnv[env] || {});
5718
+ }
5719
+ /** Single URL for a tenant+env, or undefined */
5720
+ static getUrl(tenant, env) {
5721
+ return (this._byEnv[env] || {})[tenant];
5722
+ }
5723
+ static isSelfManaged(tenant) {
5724
+ return this._selfManagedSet.has(tenant);
5725
+ }
5726
+ static getSelfManagedTenantIds() {
5727
+ return [...this._selfManagedSet];
5728
+ }
5729
+ /** Full registry data array (read-only snapshot) */
5730
+ static getData() {
5731
+ return this._data;
5732
+ }
5733
+ // ─── Mutator ────────────────────────────────────────
5734
+ /**
5735
+ * Idempotent: removes existing entry for tenantId (if any), then adds.
5736
+ * Persists to GCS and updates in-memory caches.
5737
+ */
5738
+ static async addTenant({ tenantId, urls, isSelfManaged = false }) {
5739
+ this._data = this._data.filter((e) => e.tenantId !== tenantId);
5740
+ this._data.push({ tenantId, urls, isSelfManaged });
5741
+ await this._save();
5742
+ this._rebuildCaches();
5743
+ }
5744
+ /**
5745
+ * Idempotent inverse of addTenant: drops the tenant's entry from the registry,
5746
+ * persists to GCS, and rebuilds in-memory caches (so the tenant stops resolving
5747
+ * on THIS instance immediately). Returns true if an entry was actually removed.
5748
+ * Other running instances pick up the removal via refreshIfStale() on their next
5749
+ * unknown-origin miss.
5750
+ */
5751
+ static async removeTenant(tenantId) {
5752
+ const before = this._data.length;
5753
+ this._data = this._data.filter((e) => e.tenantId !== tenantId);
5754
+ const removed = this._data.length < before;
5755
+ if (removed) {
5756
+ await this._save();
5757
+ this._rebuildCaches();
5758
+ }
5759
+ return removed;
5760
+ }
5761
+ };
5762
+ // ─── In-memory state ────────────────────────────────
5763
+ __publicField(TenantRegistry, "_data", null);
5764
+ // [{ tenantId, urls: { dev, staging, prod }, isSelfManaged }]
5765
+ __publicField(TenantRegistry, "_byEnv", {});
5766
+ // { dev: { tenantId: url }, staging: {...}, prod: {...} }
5767
+ __publicField(TenantRegistry, "_selfManagedSet", /* @__PURE__ */ new Set());
5768
+ // On-miss refresh rate-limit (see refreshIfStale).
5769
+ __publicField(TenantRegistry, "_lastRefreshAt", 0);
5770
+ __publicField(TenantRegistry, "REFRESH_COOLDOWN_MS", 3e4);
5771
+
5772
+ // src/contentSchema/selfManagedAnnotationTagTypesConfig.js
5773
+ var SM_ANNOTATION_TAG_TYPES_CONFIG = [{ tagType: "tags" }];
5774
+ var getSmTagTypesConfigForTenant = async (tenant) => {
5775
+ return TenantRegistry.isSelfManaged(tenant) ? SM_ANNOTATION_TAG_TYPES_CONFIG : null;
5776
+ };
5777
+
5778
+ // src/db/getContrModelByTenant.js
5779
+ var import_getModelByTenant3 = __toESM(require_getModelByTenant());
5780
+ init_getDbByTenant();
5781
+
5782
+ // src/db/getTpl.js
5095
5783
  var import_getModelByTenant2 = __toESM(require_getModelByTenant());
5784
+ var getTpl = async ({ tenant, contentType, env, source = "db" }) => {
5785
+ if (source === "cache") {
5786
+ return RedisCacheConnector.findOneConfigInCache({
5787
+ tenant,
5788
+ modelName: "tpl",
5789
+ type: contentType,
5790
+ env
5791
+ });
5792
+ }
5793
+ return (0, import_getModelByTenant2.getTplModelByTenant)({ tenant, env }).findOne({ kp_content_type: contentType }).lean();
5794
+ };
5795
+
5796
+ // src/db/getContrModelByTenant.js
5797
+ var getContrModelByTenant = async ({
5798
+ tenant,
5799
+ contentType,
5800
+ env = process.env.ENV,
5801
+ // v.imp, else the reuse check below always fails when env isn't passed (env is meant to be optional)
5802
+ tpl,
5803
+ //optional : if passed uses it instead of fetching the tpl for this contenttype from db. important in the case of _createTpl, where, within a transaction we create a tpl and then use this tpl to create some content against it. since this is within a transaction, this newly created tpl wont be available in the db just yet.
5804
+ tplSource = "db",
5805
+ // "db" (okf-be default, original semantics) | "cache" (okf-sub — tpl cache is kept in sync on every tpl write)
5806
+ /**
5807
+ * When true, the registered-model reuse is SKIPPED — the schema is rebuilt
5808
+ * from the current tpl on every call (~5-10ms CPU + the tpl read).
5809
+ *
5810
+ * okf-sub passes this on every collab call: unlike okf-be — which
5811
+ * invalidates registered models on tpl change via
5812
+ * reRegisterModelInLocalCache + the '${env}-tpl-model-recache' Pub/Sub —
5813
+ * okf-sub subscribes to nothing, so a reused model could carry a stale
5814
+ * schema after a tpl edit, and strict:"throw" would reject collab flushes
5815
+ * for newly added fields. Rebuilding every time is correct by
5816
+ * construction: no freshness bookkeeping for tpl write paths (or raw
5817
+ * mongodbscripts that mutate tpls + recache) to ever get wrong.
5818
+ */
5819
+ rebuild = false
5820
+ }) => {
5821
+ try {
5822
+ const db = getDbByTenant({ tenant });
5823
+ if (!rebuild && db.models[contentType] && env === process.env.ENV && !tpl) {
5824
+ const modelToReturn = db.model(contentType);
5825
+ if (modelToReturn.schema.obj.metadata && modelToReturn.schema.obj.metadata.isContentType && modelToReturn.schema.obj.metadata.isContentType.default === true) {
5826
+ return modelToReturn;
5827
+ }
5828
+ return void 0;
5829
+ }
5830
+ const contentTypeConfig = tpl || await getTpl({ tenant, contentType, env, source: tplSource });
5831
+ if (!contentTypeConfig) return void 0;
5832
+ const smTagTypesConfig = await getSmTagTypesConfigForTenant(tenant);
5833
+ const thisSchema = genContentTypeSchema({
5834
+ contentType,
5835
+ segment: contentTypeConfig.general?.segment,
5836
+ contentTypeConfig,
5837
+ smTagTypesConfig
5838
+ });
5839
+ if (rebuild && db.models[contentType]) {
5840
+ db.deleteModel(contentType);
5841
+ }
5842
+ return (0, import_getModelByTenant3.getModelByTenant)({
5843
+ tenant,
5844
+ modelName: contentType,
5845
+ schema: thisSchema,
5846
+ env
5847
+ });
5848
+ } catch (err) {
5849
+ throw err;
5850
+ }
5851
+ };
5852
+
5853
+ // src/node.ts
5096
5854
  init_models();
5855
+
5856
+ // src/collab/liveCollabDocMarker.js
5857
+ var TTL_SECONDS = 300;
5858
+ var LIVE_COLLAB_DOC_REFRESH_MS = 12e4;
5859
+ var buildKey = ({ env, tenant, contentType, docId }) => `${env || RedisCacheConnector.getEnv()}:liveCollabDoc:${tenant}:${contentType}:${docId}`;
5860
+ var markLiveCollabDoc = async ({ tenant, contentType, docId, env }) => {
5861
+ const client = RedisCacheConnector.getClient(env || RedisCacheConnector.getEnv());
5862
+ await client.set(buildKey({ env, tenant, contentType, docId }), Date.now(), "EX", TTL_SECONDS);
5863
+ };
5864
+ var clearLiveCollabDoc = async ({ tenant, contentType, docId, env }) => {
5865
+ const client = RedisCacheConnector.getClient(env || RedisCacheConnector.getEnv());
5866
+ await client.del(buildKey({ env, tenant, contentType, docId }));
5867
+ };
5868
+ var isLiveCollabDoc = async ({ tenant, contentType, docId, env }) => {
5869
+ const client = RedisCacheConnector.getClient(env || RedisCacheConnector.getEnv());
5870
+ return await client.exists(buildKey({ env, tenant, contentType, docId })) === 1;
5871
+ };
5872
+
5873
+ // src/node.ts
5097
5874
  var import_WorkerManager = __toESM(require_WorkerManager());
5098
5875
  var import_ProducerManager = __toESM(require_ProducerManager());
5099
5876
  var import_BaseProducer = __toESM(require_BaseProducer());
@@ -5122,6 +5899,7 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
5122
5899
  GET_GLOBAL_BULLMQ_CONFIG,
5123
5900
  GeneratedEntitiesSchema,
5124
5901
  GeneratedTopicsSchema,
5902
+ LIVE_COLLAB_DOC_REFRESH_MS,
5125
5903
  MONGO_SCHEMA_PRESETS,
5126
5904
  MongoConnector,
5127
5905
  PlatformConfigsSchema,
@@ -5129,9 +5907,13 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
5129
5907
  RedisCacheConnector,
5130
5908
  SecretManagerConnector,
5131
5909
  TEMP_removeDuplicateFilters,
5910
+ TenantRegistry,
5132
5911
  TplSchema,
5133
5912
  UI_CONTENT,
5134
5913
  WorkerManager,
5914
+ _backOfficeFields__contr,
5915
+ _constantMeta__contr,
5916
+ _fixedMainFields__contr,
5135
5917
  _self_managed_buildAnnoHierarchyConfig,
5136
5918
  _self_managed_buildDocHierarchyConfig,
5137
5919
  _self_managed_getFixedAnnoRollupBlocks,
@@ -5139,17 +5921,22 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
5139
5921
  autoGenFilterConfigsFromTpl,
5140
5922
  blockRegistry,
5141
5923
  buildFilterConfigurations,
5924
+ clearLiveCollabDoc,
5142
5925
  collectMarkIdsInOrder,
5143
5926
  compareAndGroupBlocks,
5144
5927
  deleteVal,
5145
5928
  extractAllBlocksFromTpl,
5929
+ extractAllUniqueAnnoTagTypesFromTpl,
5146
5930
  extractAndOrganizeBlocks,
5931
+ extractNumberFromValue,
5147
5932
  genCleanCamelCaseId,
5933
+ genContentTypeSchema,
5148
5934
  genTagId,
5149
5935
  generateFilterKey,
5150
5936
  getAIChatModelByTenant,
5151
5937
  getAnnoFilterBucketKey,
5152
5938
  getAnnotationsModelByTenant,
5939
+ getContrModelByTenant,
5153
5940
  getDbByTenant,
5154
5941
  getFilterKeyForBlock,
5155
5942
  getGeneratedEntitiesModelByTenant,
@@ -5166,9 +5953,13 @@ var import_GET_GLOBAL_BULLMQ_CONFIG = __toESM(require_GET_GLOBAL_BULLMQ_CONFIG()
5166
5953
  getRoutePathToReviewDashboard,
5167
5954
  getRoutePathToTCI,
5168
5955
  getRoutePathToTagCategoryLanding,
5956
+ getTpl,
5169
5957
  getTplModelByTenant,
5958
+ getTypesForSchemaFromTpl,
5170
5959
  getVal,
5960
+ isLiveCollabDoc,
5171
5961
  isTplAnnotationEnabled,
5962
+ markLiveCollabDoc,
5172
5963
  mergeAnnoDataIntoAnnotationsTags,
5173
5964
  parseSpecialConfigSyntax,
5174
5965
  plainTextToLexical,