convex-cms 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/admin.d.ts +16 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +88 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/detectConvexUrl.d.ts +13 -0
- package/dist/cli/utils/detectConvexUrl.d.ts.map +1 -0
- package/dist/cli/utils/detectConvexUrl.js +48 -0
- package/dist/cli/utils/detectConvexUrl.js.map +1 -0
- package/dist/cli/utils/openBrowser.d.ts +7 -0
- package/dist/cli/utils/openBrowser.d.ts.map +1 -0
- package/dist/cli/utils/openBrowser.js +17 -0
- package/dist/cli/utils/openBrowser.js.map +1 -0
- package/dist/client/admin-config.d.ts +126 -0
- package/dist/client/admin-config.d.ts.map +1 -0
- package/dist/client/admin-config.js +117 -0
- package/dist/client/admin-config.js.map +1 -0
- package/dist/client/adminApi.d.ts +2273 -0
- package/dist/client/adminApi.d.ts.map +1 -0
- package/dist/client/adminApi.js +716 -0
- package/dist/client/adminApi.js.map +1 -0
- package/dist/client/agentTools.d.ts +933 -0
- package/dist/client/agentTools.d.ts.map +1 -0
- package/dist/client/agentTools.js +1004 -0
- package/dist/client/agentTools.js.map +1 -0
- package/dist/client/argTypes.d.ts +212 -0
- package/dist/client/argTypes.d.ts.map +1 -0
- package/dist/client/argTypes.js +5 -0
- package/dist/client/argTypes.js.map +1 -0
- package/dist/client/field-types.d.ts +55 -0
- package/dist/client/field-types.d.ts.map +1 -0
- package/dist/client/field-types.js +152 -0
- package/dist/client/field-types.js.map +1 -0
- package/dist/client/index.d.ts +189 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +668 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/queryBuilder.d.ts +765 -0
- package/dist/client/queryBuilder.d.ts.map +1 -0
- package/dist/client/queryBuilder.js +970 -0
- package/dist/client/queryBuilder.js.map +1 -0
- package/dist/client/schema/codegen.d.ts +128 -0
- package/dist/client/schema/codegen.d.ts.map +1 -0
- package/dist/client/schema/codegen.js +318 -0
- package/dist/client/schema/codegen.js.map +1 -0
- package/dist/client/schema/defineContentType.d.ts +221 -0
- package/dist/client/schema/defineContentType.d.ts.map +1 -0
- package/dist/client/schema/defineContentType.js +380 -0
- package/dist/client/schema/defineContentType.js.map +1 -0
- package/dist/client/schema/index.d.ts +85 -0
- package/dist/client/schema/index.d.ts.map +1 -0
- package/dist/client/schema/index.js +92 -0
- package/dist/client/schema/index.js.map +1 -0
- package/dist/client/schema/schemaDrift.d.ts +199 -0
- package/dist/client/schema/schemaDrift.d.ts.map +1 -0
- package/dist/client/schema/schemaDrift.js +340 -0
- package/dist/client/schema/schemaDrift.js.map +1 -0
- package/dist/client/schema/typedClient.d.ts +401 -0
- package/dist/client/schema/typedClient.d.ts.map +1 -0
- package/dist/client/schema/typedClient.js +269 -0
- package/dist/client/schema/typedClient.js.map +1 -0
- package/dist/client/schema/types.d.ts +477 -0
- package/dist/client/schema/types.d.ts.map +1 -0
- package/dist/client/schema/types.js +39 -0
- package/dist/client/schema/types.js.map +1 -0
- package/dist/client/types.d.ts +449 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +149 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/workflows.d.ts +51 -0
- package/dist/client/workflows.d.ts.map +1 -0
- package/dist/client/workflows.js +103 -0
- package/dist/client/workflows.js.map +1 -0
- package/dist/client/wrapper.d.ts +2198 -0
- package/dist/client/wrapper.d.ts.map +1 -0
- package/dist/client/wrapper.js +2651 -0
- package/dist/client/wrapper.js.map +1 -0
- package/dist/component/_generated/api.d.ts +124 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +4321 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/auditLog.d.ts +410 -0
- package/dist/component/auditLog.d.ts.map +1 -0
- package/dist/component/auditLog.js +607 -0
- package/dist/component/auditLog.js.map +1 -0
- package/dist/component/authorization.d.ts +323 -0
- package/dist/component/authorization.d.ts.map +1 -0
- package/dist/component/authorization.js +464 -0
- package/dist/component/authorization.js.map +1 -0
- package/dist/component/authorizationHooks.d.ts +184 -0
- package/dist/component/authorizationHooks.d.ts.map +1 -0
- package/dist/component/authorizationHooks.js +521 -0
- package/dist/component/authorizationHooks.js.map +1 -0
- package/dist/component/bulkOperations.d.ts +200 -0
- package/dist/component/bulkOperations.d.ts.map +1 -0
- package/dist/component/bulkOperations.js +568 -0
- package/dist/component/bulkOperations.js.map +1 -0
- package/dist/component/contentEntries.d.ts +719 -0
- package/dist/component/contentEntries.d.ts.map +1 -0
- package/dist/component/contentEntries.js +1617 -0
- package/dist/component/contentEntries.js.map +1 -0
- package/dist/component/contentEntryMutations.d.ts +505 -0
- package/dist/component/contentEntryMutations.d.ts.map +1 -0
- package/dist/component/contentEntryMutations.js +1009 -0
- package/dist/component/contentEntryMutations.js.map +1 -0
- package/dist/component/contentEntryValidation.d.ts +115 -0
- package/dist/component/contentEntryValidation.d.ts.map +1 -0
- package/dist/component/contentEntryValidation.js +546 -0
- package/dist/component/contentEntryValidation.js.map +1 -0
- package/dist/component/contentLock.d.ts +328 -0
- package/dist/component/contentLock.d.ts.map +1 -0
- package/dist/component/contentLock.js +471 -0
- package/dist/component/contentLock.js.map +1 -0
- package/dist/component/contentTypeMigration.d.ts +411 -0
- package/dist/component/contentTypeMigration.d.ts.map +1 -0
- package/dist/component/contentTypeMigration.js +805 -0
- package/dist/component/contentTypeMigration.js.map +1 -0
- package/dist/component/contentTypeMutations.d.ts +975 -0
- package/dist/component/contentTypeMutations.d.ts.map +1 -0
- package/dist/component/contentTypeMutations.js +768 -0
- package/dist/component/contentTypeMutations.js.map +1 -0
- package/dist/component/contentTypes.d.ts +538 -0
- package/dist/component/contentTypes.d.ts.map +1 -0
- package/dist/component/contentTypes.js +304 -0
- package/dist/component/contentTypes.js.map +1 -0
- package/dist/component/convex.config.d.ts +42 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +43 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/documentTypes.d.ts +186 -0
- package/dist/component/documentTypes.d.ts.map +1 -0
- package/dist/component/documentTypes.js +23 -0
- package/dist/component/documentTypes.js.map +1 -0
- package/dist/component/eventEmitter.d.ts +281 -0
- package/dist/component/eventEmitter.d.ts.map +1 -0
- package/dist/component/eventEmitter.js +300 -0
- package/dist/component/eventEmitter.js.map +1 -0
- package/dist/component/exportImport.d.ts +1120 -0
- package/dist/component/exportImport.d.ts.map +1 -0
- package/dist/component/exportImport.js +931 -0
- package/dist/component/exportImport.js.map +1 -0
- package/dist/component/index.d.ts +28 -0
- package/dist/component/index.d.ts.map +1 -0
- package/dist/component/index.js +142 -0
- package/dist/component/index.js.map +1 -0
- package/dist/component/lib/deepReferenceResolver.d.ts +252 -0
- package/dist/component/lib/deepReferenceResolver.d.ts.map +1 -0
- package/dist/component/lib/deepReferenceResolver.js +601 -0
- package/dist/component/lib/deepReferenceResolver.js.map +1 -0
- package/dist/component/lib/errors.d.ts +306 -0
- package/dist/component/lib/errors.d.ts.map +1 -0
- package/dist/component/lib/errors.js +407 -0
- package/dist/component/lib/errors.js.map +1 -0
- package/dist/component/lib/index.d.ts +10 -0
- package/dist/component/lib/index.d.ts.map +1 -0
- package/dist/component/lib/index.js +33 -0
- package/dist/component/lib/index.js.map +1 -0
- package/dist/component/lib/mediaReferenceResolver.d.ts +217 -0
- package/dist/component/lib/mediaReferenceResolver.d.ts.map +1 -0
- package/dist/component/lib/mediaReferenceResolver.js +326 -0
- package/dist/component/lib/mediaReferenceResolver.js.map +1 -0
- package/dist/component/lib/metadataExtractor.d.ts +245 -0
- package/dist/component/lib/metadataExtractor.d.ts.map +1 -0
- package/dist/component/lib/metadataExtractor.js +548 -0
- package/dist/component/lib/metadataExtractor.js.map +1 -0
- package/dist/component/lib/mutationAuth.d.ts +95 -0
- package/dist/component/lib/mutationAuth.d.ts.map +1 -0
- package/dist/component/lib/mutationAuth.js +146 -0
- package/dist/component/lib/mutationAuth.js.map +1 -0
- package/dist/component/lib/queries.d.ts +17 -0
- package/dist/component/lib/queries.d.ts.map +1 -0
- package/dist/component/lib/queries.js +49 -0
- package/dist/component/lib/queries.js.map +1 -0
- package/dist/component/lib/ragContentChunker.d.ts +423 -0
- package/dist/component/lib/ragContentChunker.d.ts.map +1 -0
- package/dist/component/lib/ragContentChunker.js +897 -0
- package/dist/component/lib/ragContentChunker.js.map +1 -0
- package/dist/component/lib/referenceResolver.d.ts +175 -0
- package/dist/component/lib/referenceResolver.d.ts.map +1 -0
- package/dist/component/lib/referenceResolver.js +293 -0
- package/dist/component/lib/referenceResolver.js.map +1 -0
- package/dist/component/lib/slugGenerator.d.ts +71 -0
- package/dist/component/lib/slugGenerator.d.ts.map +1 -0
- package/dist/component/lib/slugGenerator.js +207 -0
- package/dist/component/lib/slugGenerator.js.map +1 -0
- package/dist/component/lib/slugUniqueness.d.ts +131 -0
- package/dist/component/lib/slugUniqueness.d.ts.map +1 -0
- package/dist/component/lib/slugUniqueness.js +229 -0
- package/dist/component/lib/slugUniqueness.js.map +1 -0
- package/dist/component/lib/softDelete.d.ts +18 -0
- package/dist/component/lib/softDelete.d.ts.map +1 -0
- package/dist/component/lib/softDelete.js +29 -0
- package/dist/component/lib/softDelete.js.map +1 -0
- package/dist/component/localeFallbackChain.d.ts +410 -0
- package/dist/component/localeFallbackChain.d.ts.map +1 -0
- package/dist/component/localeFallbackChain.js +467 -0
- package/dist/component/localeFallbackChain.js.map +1 -0
- package/dist/component/localeFields.d.ts +508 -0
- package/dist/component/localeFields.d.ts.map +1 -0
- package/dist/component/localeFields.js +592 -0
- package/dist/component/localeFields.js.map +1 -0
- package/dist/component/mediaAssetMutations.d.ts +235 -0
- package/dist/component/mediaAssetMutations.d.ts.map +1 -0
- package/dist/component/mediaAssetMutations.js +558 -0
- package/dist/component/mediaAssetMutations.js.map +1 -0
- package/dist/component/mediaAssets.d.ts +168 -0
- package/dist/component/mediaAssets.d.ts.map +1 -0
- package/dist/component/mediaAssets.js +618 -0
- package/dist/component/mediaAssets.js.map +1 -0
- package/dist/component/mediaFolderMutations.d.ts +642 -0
- package/dist/component/mediaFolderMutations.d.ts.map +1 -0
- package/dist/component/mediaFolderMutations.js +849 -0
- package/dist/component/mediaFolderMutations.js.map +1 -0
- package/dist/component/mediaUploadMutations.d.ts +136 -0
- package/dist/component/mediaUploadMutations.d.ts.map +1 -0
- package/dist/component/mediaUploadMutations.js +205 -0
- package/dist/component/mediaUploadMutations.js.map +1 -0
- package/dist/component/mediaVariantMutations.d.ts +468 -0
- package/dist/component/mediaVariantMutations.d.ts.map +1 -0
- package/dist/component/mediaVariantMutations.js +737 -0
- package/dist/component/mediaVariantMutations.js.map +1 -0
- package/dist/component/mediaVariants.d.ts +525 -0
- package/dist/component/mediaVariants.d.ts.map +1 -0
- package/dist/component/mediaVariants.js +661 -0
- package/dist/component/mediaVariants.js.map +1 -0
- package/dist/component/ragContentIndexer.d.ts +595 -0
- package/dist/component/ragContentIndexer.d.ts.map +1 -0
- package/dist/component/ragContentIndexer.js +794 -0
- package/dist/component/ragContentIndexer.js.map +1 -0
- package/dist/component/rateLimitHooks.d.ts +266 -0
- package/dist/component/rateLimitHooks.d.ts.map +1 -0
- package/dist/component/rateLimitHooks.js +412 -0
- package/dist/component/rateLimitHooks.js.map +1 -0
- package/dist/component/roles.d.ts +649 -0
- package/dist/component/roles.d.ts.map +1 -0
- package/dist/component/roles.js +884 -0
- package/dist/component/roles.js.map +1 -0
- package/dist/component/scheduledPublish.d.ts +182 -0
- package/dist/component/scheduledPublish.d.ts.map +1 -0
- package/dist/component/scheduledPublish.js +304 -0
- package/dist/component/scheduledPublish.js.map +1 -0
- package/dist/component/schema.d.ts +4114 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +469 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/taxonomies.d.ts +476 -0
- package/dist/component/taxonomies.d.ts.map +1 -0
- package/dist/component/taxonomies.js +785 -0
- package/dist/component/taxonomies.js.map +1 -0
- package/dist/component/taxonomyMutations.d.ts +206 -0
- package/dist/component/taxonomyMutations.d.ts.map +1 -0
- package/dist/component/taxonomyMutations.js +1001 -0
- package/dist/component/taxonomyMutations.js.map +1 -0
- package/dist/component/trash.d.ts +265 -0
- package/dist/component/trash.d.ts.map +1 -0
- package/dist/component/trash.js +621 -0
- package/dist/component/trash.js.map +1 -0
- package/dist/component/types.d.ts +4 -0
- package/dist/component/types.d.ts.map +1 -0
- package/dist/component/types.js +2 -0
- package/dist/component/types.js.map +1 -0
- package/dist/component/userContext.d.ts +508 -0
- package/dist/component/userContext.d.ts.map +1 -0
- package/dist/component/userContext.js +615 -0
- package/dist/component/userContext.js.map +1 -0
- package/dist/component/validation.d.ts +387 -0
- package/dist/component/validation.d.ts.map +1 -0
- package/dist/component/validation.js +1052 -0
- package/dist/component/validation.js.map +1 -0
- package/dist/component/validators.d.ts +4645 -0
- package/dist/component/validators.d.ts.map +1 -0
- package/dist/component/validators.js +641 -0
- package/dist/component/validators.js.map +1 -0
- package/dist/component/versionMutations.d.ts +216 -0
- package/dist/component/versionMutations.d.ts.map +1 -0
- package/dist/component/versionMutations.js +321 -0
- package/dist/component/versionMutations.js.map +1 -0
- package/dist/component/webhookTrigger.d.ts +770 -0
- package/dist/component/webhookTrigger.d.ts.map +1 -0
- package/dist/component/webhookTrigger.js +1413 -0
- package/dist/component/webhookTrigger.js.map +1 -0
- package/dist/react/index.d.ts +316 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +558 -0
- package/dist/react/index.js.map +1 -0
- package/dist/test.d.ts +2230 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +1107 -0
- package/dist/test.js.map +1 -0
- package/package.json +95 -0
- package/src/cli/commands/admin.ts +104 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/utils/detectConvexUrl.ts +54 -0
- package/src/cli/utils/openBrowser.ts +16 -0
- package/src/client/admin-config.ts +138 -0
- package/src/client/adminApi.ts +942 -0
- package/src/client/agentTools.ts +1311 -0
- package/src/client/argTypes.ts +316 -0
- package/src/client/field-types.ts +187 -0
- package/src/client/index.ts +1301 -0
- package/src/client/queryBuilder.ts +1100 -0
- package/src/client/schema/codegen.ts +500 -0
- package/src/client/schema/defineContentType.ts +501 -0
- package/src/client/schema/index.ts +169 -0
- package/src/client/schema/schemaDrift.ts +574 -0
- package/src/client/schema/typedClient.ts +688 -0
- package/src/client/schema/types.ts +666 -0
- package/src/client/types.ts +723 -0
- package/src/client/workflows.ts +141 -0
- package/src/client/wrapper.ts +4304 -0
- package/src/component/_generated/api.ts +140 -0
- package/src/component/_generated/component.ts +5029 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/authorization.ts +647 -0
- package/src/component/authorizationHooks.ts +668 -0
- package/src/component/bulkOperations.ts +687 -0
- package/src/component/contentEntries.ts +1976 -0
- package/src/component/contentEntryMutations.ts +1223 -0
- package/src/component/contentEntryValidation.ts +707 -0
- package/src/component/contentLock.ts +550 -0
- package/src/component/contentTypeMigration.ts +1064 -0
- package/src/component/contentTypeMutations.ts +969 -0
- package/src/component/contentTypes.ts +346 -0
- package/src/component/convex.config.ts +44 -0
- package/src/component/documentTypes.ts +240 -0
- package/src/component/eventEmitter.ts +485 -0
- package/src/component/exportImport.ts +1169 -0
- package/src/component/index.ts +491 -0
- package/src/component/lib/deepReferenceResolver.ts +999 -0
- package/src/component/lib/errors.ts +816 -0
- package/src/component/lib/index.ts +145 -0
- package/src/component/lib/mediaReferenceResolver.ts +495 -0
- package/src/component/lib/metadataExtractor.ts +792 -0
- package/src/component/lib/mutationAuth.ts +199 -0
- package/src/component/lib/queries.ts +79 -0
- package/src/component/lib/ragContentChunker.ts +1371 -0
- package/src/component/lib/referenceResolver.ts +430 -0
- package/src/component/lib/slugGenerator.ts +262 -0
- package/src/component/lib/slugUniqueness.ts +333 -0
- package/src/component/lib/softDelete.ts +44 -0
- package/src/component/localeFallbackChain.ts +673 -0
- package/src/component/localeFields.ts +896 -0
- package/src/component/mediaAssetMutations.ts +725 -0
- package/src/component/mediaAssets.ts +932 -0
- package/src/component/mediaFolderMutations.ts +1046 -0
- package/src/component/mediaUploadMutations.ts +224 -0
- package/src/component/mediaVariantMutations.ts +900 -0
- package/src/component/mediaVariants.ts +793 -0
- package/src/component/ragContentIndexer.ts +1067 -0
- package/src/component/rateLimitHooks.ts +572 -0
- package/src/component/roles.ts +1360 -0
- package/src/component/scheduledPublish.ts +358 -0
- package/src/component/schema.ts +617 -0
- package/src/component/taxonomies.ts +949 -0
- package/src/component/taxonomyMutations.ts +1210 -0
- package/src/component/trash.ts +724 -0
- package/src/component/userContext.ts +898 -0
- package/src/component/validation.ts +1388 -0
- package/src/component/validators.ts +949 -0
- package/src/component/versionMutations.ts +392 -0
- package/src/component/webhookTrigger.ts +1922 -0
- package/src/react/index.ts +898 -0
- package/src/test.ts +1580 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slug Generator Utility
|
|
3
|
+
*
|
|
4
|
+
* Generates URL-friendly slugs from content titles.
|
|
5
|
+
* Handles special characters, unicode, and ensures slug format consistency.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Default character replacements for common special characters and unicode
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_REPLACEMENTS = {
|
|
11
|
+
// German
|
|
12
|
+
ä: "ae",
|
|
13
|
+
ö: "oe",
|
|
14
|
+
ü: "ue",
|
|
15
|
+
ß: "ss",
|
|
16
|
+
Ä: "ae",
|
|
17
|
+
Ö: "oe",
|
|
18
|
+
Ü: "ue",
|
|
19
|
+
// French
|
|
20
|
+
à: "a",
|
|
21
|
+
â: "a",
|
|
22
|
+
ç: "c",
|
|
23
|
+
é: "e",
|
|
24
|
+
è: "e",
|
|
25
|
+
ê: "e",
|
|
26
|
+
ë: "e",
|
|
27
|
+
î: "i",
|
|
28
|
+
ï: "i",
|
|
29
|
+
ô: "o",
|
|
30
|
+
ù: "u",
|
|
31
|
+
û: "u",
|
|
32
|
+
ÿ: "y",
|
|
33
|
+
œ: "oe",
|
|
34
|
+
æ: "ae",
|
|
35
|
+
// Spanish
|
|
36
|
+
ñ: "n",
|
|
37
|
+
Ñ: "n",
|
|
38
|
+
// Polish
|
|
39
|
+
ą: "a",
|
|
40
|
+
Ą: "a",
|
|
41
|
+
ć: "c",
|
|
42
|
+
Ć: "c",
|
|
43
|
+
ę: "e",
|
|
44
|
+
Ę: "e",
|
|
45
|
+
ł: "l",
|
|
46
|
+
Ł: "l",
|
|
47
|
+
ń: "n",
|
|
48
|
+
Ń: "n",
|
|
49
|
+
ó: "o",
|
|
50
|
+
Ó: "o",
|
|
51
|
+
ś: "s",
|
|
52
|
+
Ś: "s",
|
|
53
|
+
ź: "z",
|
|
54
|
+
Ź: "z",
|
|
55
|
+
ż: "z",
|
|
56
|
+
Ż: "z",
|
|
57
|
+
// Nordic
|
|
58
|
+
å: "a",
|
|
59
|
+
Å: "a",
|
|
60
|
+
ø: "o",
|
|
61
|
+
Ø: "o",
|
|
62
|
+
// Other common
|
|
63
|
+
ð: "d",
|
|
64
|
+
þ: "th",
|
|
65
|
+
// Symbols
|
|
66
|
+
"&": "and",
|
|
67
|
+
"@": "at",
|
|
68
|
+
"#": "hash",
|
|
69
|
+
"%": "percent",
|
|
70
|
+
"+": "plus",
|
|
71
|
+
"=": "equals",
|
|
72
|
+
// Punctuation to remove (replaced with empty string to prevent separator)
|
|
73
|
+
"'": "",
|
|
74
|
+
"\u2018": "", // left single quote '
|
|
75
|
+
"\u2019": "", // right single quote '
|
|
76
|
+
"\u201C": "", // left double quote "
|
|
77
|
+
"\u201D": "", // right double quote "
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Generates a URL-friendly slug from a given title string.
|
|
81
|
+
*
|
|
82
|
+
* @param title - The input string to convert to a slug
|
|
83
|
+
* @param options - Optional configuration for slug generation
|
|
84
|
+
* @returns A URL-friendly slug string
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* generateSlug("Hello World!") // "hello-world"
|
|
89
|
+
* generateSlug("Café & Restaurant") // "cafe-and-restaurant"
|
|
90
|
+
* generateSlug("日本語タイトル") // "ri-ben-yu-taitoru"
|
|
91
|
+
* generateSlug(" Multiple Spaces ") // "multiple-spaces"
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function generateSlug(title, options = {}) {
|
|
95
|
+
const { maxLength = 100, separator = "-", lowercase = true, customReplacements = {}, } = options;
|
|
96
|
+
if (!title || typeof title !== "string") {
|
|
97
|
+
return "";
|
|
98
|
+
}
|
|
99
|
+
// Merge custom replacements with defaults (custom takes precedence)
|
|
100
|
+
const replacements = { ...DEFAULT_REPLACEMENTS, ...customReplacements };
|
|
101
|
+
let slug = title.trim();
|
|
102
|
+
// Apply character replacements
|
|
103
|
+
for (const [char, replacement] of Object.entries(replacements)) {
|
|
104
|
+
slug = slug.split(char).join(replacement);
|
|
105
|
+
}
|
|
106
|
+
// Normalize unicode to decomposed form, then remove combining diacritical marks
|
|
107
|
+
slug = slug.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
108
|
+
// Convert to lowercase if requested
|
|
109
|
+
if (lowercase) {
|
|
110
|
+
slug = slug.toLowerCase();
|
|
111
|
+
}
|
|
112
|
+
// Replace any non-alphanumeric characters (except separator) with separator
|
|
113
|
+
// This regex keeps letters (including unicode letters after normalization), numbers
|
|
114
|
+
const separatorRegex = new RegExp(`[^a-z0-9${escapeRegex(separator)}]`, "gi");
|
|
115
|
+
slug = slug.replace(separatorRegex, separator);
|
|
116
|
+
// Collapse multiple consecutive separators into one
|
|
117
|
+
const multipleSeparatorRegex = new RegExp(`${escapeRegex(separator)}+`, "g");
|
|
118
|
+
slug = slug.replace(multipleSeparatorRegex, separator);
|
|
119
|
+
// Remove leading and trailing separators
|
|
120
|
+
const trimSeparatorRegex = new RegExp(`^${escapeRegex(separator)}|${escapeRegex(separator)}$`, "g");
|
|
121
|
+
slug = slug.replace(trimSeparatorRegex, "");
|
|
122
|
+
// Truncate to max length, but don't cut words in the middle
|
|
123
|
+
if (slug.length > maxLength) {
|
|
124
|
+
slug = truncateSlug(slug, maxLength, separator);
|
|
125
|
+
}
|
|
126
|
+
return slug;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Escapes special regex characters in a string
|
|
130
|
+
*/
|
|
131
|
+
function escapeRegex(str) {
|
|
132
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Truncates a slug to a maximum length without cutting words
|
|
136
|
+
*/
|
|
137
|
+
function truncateSlug(slug, maxLength, separator) {
|
|
138
|
+
if (slug.length <= maxLength) {
|
|
139
|
+
return slug;
|
|
140
|
+
}
|
|
141
|
+
// Find the last separator before the max length
|
|
142
|
+
const truncated = slug.substring(0, maxLength);
|
|
143
|
+
const lastSeparatorIndex = truncated.lastIndexOf(separator);
|
|
144
|
+
if (lastSeparatorIndex > 0) {
|
|
145
|
+
return truncated.substring(0, lastSeparatorIndex);
|
|
146
|
+
}
|
|
147
|
+
// If no separator found, just truncate at maxLength
|
|
148
|
+
return truncated;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Validates if a string is a valid slug format
|
|
152
|
+
*
|
|
153
|
+
* @param slug - The string to validate
|
|
154
|
+
* @param separator - The separator character (default: '-')
|
|
155
|
+
* @returns True if the string is a valid slug
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* isValidSlug("hello-world") // true
|
|
160
|
+
* isValidSlug("Hello World") // false
|
|
161
|
+
* isValidSlug("hello--world") // false
|
|
162
|
+
* isValidSlug("-hello-world") // false
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export function isValidSlug(slug, separator = "-") {
|
|
166
|
+
if (!slug || typeof slug !== "string") {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
// Must be lowercase alphanumeric with single separators
|
|
170
|
+
const validSlugRegex = new RegExp(`^[a-z0-9]+(?:${escapeRegex(separator)}[a-z0-9]+)*$`);
|
|
171
|
+
return validSlugRegex.test(slug);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Generates a unique slug by appending a numeric suffix if needed.
|
|
175
|
+
* This is a helper that can be used with a uniqueness check function.
|
|
176
|
+
*
|
|
177
|
+
* @param baseSlug - The base slug to make unique
|
|
178
|
+
* @param isUnique - Async function that checks if a slug is unique
|
|
179
|
+
* @param maxAttempts - Maximum number of suffix attempts (default: 100)
|
|
180
|
+
* @returns A unique slug
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const checkUnique = async (slug: string) => {
|
|
185
|
+
* return !(await db.query("entries").withSlug(slug).first());
|
|
186
|
+
* };
|
|
187
|
+
* const uniqueSlug = await generateUniqueSlug("hello-world", checkUnique);
|
|
188
|
+
* // Returns "hello-world" if unique, or "hello-world-1", "hello-world-2", etc.
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export async function generateUniqueSlug(baseSlug, isUnique, maxAttempts = 100) {
|
|
192
|
+
// Check if base slug is already unique
|
|
193
|
+
if (await isUnique(baseSlug)) {
|
|
194
|
+
return baseSlug;
|
|
195
|
+
}
|
|
196
|
+
// Try appending numeric suffixes
|
|
197
|
+
for (let i = 1; i <= maxAttempts; i++) {
|
|
198
|
+
const candidateSlug = `${baseSlug}-${i}`;
|
|
199
|
+
if (await isUnique(candidateSlug)) {
|
|
200
|
+
return candidateSlug;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Fallback: append timestamp if all numeric suffixes are taken
|
|
204
|
+
const timestamp = Date.now().toString(36);
|
|
205
|
+
return `${baseSlug}-${timestamp}`;
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=slugGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slugGenerator.js","sourceRoot":"","sources":["../../../src/component/lib/slugGenerator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slug Uniqueness Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for checking slug uniqueness within content type scope
|
|
5
|
+
* and generating unique slugs with incremental suffixes when conflicts exist.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Options for slug uniqueness checking
|
|
9
|
+
*/
|
|
10
|
+
export interface SlugUniquenessOptions {
|
|
11
|
+
/** Maximum number of suffix attempts before falling back to timestamp (default: 100) */
|
|
12
|
+
maxAttempts?: number;
|
|
13
|
+
/** Separator character used in slugs (default: '-') */
|
|
14
|
+
separator?: string;
|
|
15
|
+
/** ID of the current entry to exclude from uniqueness check (for updates) */
|
|
16
|
+
excludeEntryId?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Result of a slug uniqueness check
|
|
20
|
+
*/
|
|
21
|
+
export interface SlugCheckResult {
|
|
22
|
+
/** Whether the slug is unique */
|
|
23
|
+
isUnique: boolean;
|
|
24
|
+
/** The existing entry ID that has this slug (if not unique) */
|
|
25
|
+
existingEntryId?: string;
|
|
26
|
+
/** Suggested alternative slug if not unique */
|
|
27
|
+
suggestedSlug?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Entry data structure for uniqueness checking
|
|
31
|
+
*/
|
|
32
|
+
export interface SlugEntry {
|
|
33
|
+
/** The entry's unique identifier */
|
|
34
|
+
_id: string;
|
|
35
|
+
/** The entry's slug */
|
|
36
|
+
slug: string;
|
|
37
|
+
/** Soft delete timestamp (null/undefined if not deleted) */
|
|
38
|
+
deletedAt?: number | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Function type for querying existing entries by slug within a content type
|
|
42
|
+
*/
|
|
43
|
+
export type SlugQueryFn = (slug: string) => Promise<SlugEntry | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Function type for querying all entries with a slug prefix within a content type
|
|
46
|
+
* Used for finding the next available suffix number
|
|
47
|
+
*/
|
|
48
|
+
export type SlugPrefixQueryFn = (slugPrefix: string) => Promise<SlugEntry[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Checks if a slug is unique within a content type scope.
|
|
51
|
+
*
|
|
52
|
+
* @param slug - The slug to check
|
|
53
|
+
* @param queryFn - Function that queries the database for entries with the given slug
|
|
54
|
+
* @param options - Configuration options
|
|
55
|
+
* @returns Result indicating uniqueness and suggestions
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // In a Convex mutation
|
|
60
|
+
* const queryFn = async (slug: string) => {
|
|
61
|
+
* return await ctx.db
|
|
62
|
+
* .query("contentEntries")
|
|
63
|
+
* .withIndex("by_content_type_and_slug", (q) =>
|
|
64
|
+
* q.eq("contentTypeId", contentTypeId).eq("slug", slug)
|
|
65
|
+
* )
|
|
66
|
+
* .filter((q) => q.eq(q.field("deletedAt"), undefined))
|
|
67
|
+
* .first();
|
|
68
|
+
* };
|
|
69
|
+
*
|
|
70
|
+
* const result = await checkSlugUniqueness("my-post", queryFn);
|
|
71
|
+
* if (!result.isUnique) {
|
|
72
|
+
* console.log(`Slug conflict with entry ${result.existingEntryId}`);
|
|
73
|
+
* console.log(`Suggested alternative: ${result.suggestedSlug}`);
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export declare function checkSlugUniqueness(slug: string, queryFn: SlugQueryFn, options?: SlugUniquenessOptions): Promise<SlugCheckResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Ensures a slug is unique by generating incremental suffixes if needed.
|
|
80
|
+
* This is the main function to use when creating or updating content entries.
|
|
81
|
+
*
|
|
82
|
+
* @param baseSlug - The desired slug (or title to generate slug from)
|
|
83
|
+
* @param queryFn - Function that queries the database for entries with a given slug
|
|
84
|
+
* @param options - Configuration options
|
|
85
|
+
* @returns A unique slug (either the original or with a numeric suffix)
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* // In a Convex mutation for creating a new entry
|
|
90
|
+
* const queryFn = async (slug: string) => {
|
|
91
|
+
* return await ctx.db
|
|
92
|
+
* .query("contentEntries")
|
|
93
|
+
* .withIndex("by_content_type_and_slug", (q) =>
|
|
94
|
+
* q.eq("contentTypeId", contentTypeId).eq("slug", slug)
|
|
95
|
+
* )
|
|
96
|
+
* .filter((q) => q.eq(q.field("deletedAt"), undefined))
|
|
97
|
+
* .first();
|
|
98
|
+
* };
|
|
99
|
+
*
|
|
100
|
+
* const uniqueSlug = await ensureUniqueSlug("my-post", queryFn);
|
|
101
|
+
* // Returns "my-post" if unique, or "my-post-1", "my-post-2", etc.
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export declare function ensureUniqueSlug(baseSlug: string, queryFn: SlugQueryFn, options?: SlugUniquenessOptions): Promise<string>;
|
|
105
|
+
/**
|
|
106
|
+
* Finds the next available slug suffix by analyzing existing slugs.
|
|
107
|
+
* This is useful when you want to pre-compute the next suffix without
|
|
108
|
+
* iterating through each number.
|
|
109
|
+
*
|
|
110
|
+
* @param baseSlug - The base slug to find the next suffix for
|
|
111
|
+
* @param prefixQueryFn - Function that returns all entries with slugs starting with the base
|
|
112
|
+
* @param options - Configuration options
|
|
113
|
+
* @returns The next available slug with the appropriate suffix
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* // If entries exist with slugs: "post", "post-1", "post-2", "post-5"
|
|
118
|
+
* const nextSlug = await findNextAvailableSlug("post", queryPrefixFn);
|
|
119
|
+
* // Returns "post-3" (fills the gap)
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function findNextAvailableSlug(baseSlug: string, prefixQueryFn: SlugPrefixQueryFn, options?: SlugUniquenessOptions): Promise<string>;
|
|
123
|
+
/**
|
|
124
|
+
* Validates a slug and returns validation errors if any.
|
|
125
|
+
*
|
|
126
|
+
* @param slug - The slug to validate
|
|
127
|
+
* @param separator - The separator character (default: '-')
|
|
128
|
+
* @returns Array of validation error messages (empty if valid)
|
|
129
|
+
*/
|
|
130
|
+
export declare function validateSlugFormat(slug: string, separator?: string): string[];
|
|
131
|
+
//# sourceMappingURL=slugUniqueness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slugUniqueness.d.ts","sourceRoot":"","sources":["../../../src/component/lib/slugUniqueness.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B,iCAAiC;IACjC,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACzB,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;AAEtE;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,mBAAmB,CACxC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,qBAA0B,GACjC,OAAO,CAAC,eAAe,CAAC,CA2C1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,gBAAgB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,qBAA0B,GACjC,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,qBAAqB,CAC1C,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,iBAAiB,EAChC,OAAO,GAAE,qBAA0B,GACjC,OAAO,CAAC,MAAM,CAAC,CA0DjB;AASD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAAY,GACrB,MAAM,EAAE,CA0CV"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slug Uniqueness Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for checking slug uniqueness within content type scope
|
|
5
|
+
* and generating unique slugs with incremental suffixes when conflicts exist.
|
|
6
|
+
*/
|
|
7
|
+
import { generateSlug, generateUniqueSlug, isValidSlug, } from "./slugGenerator.js";
|
|
8
|
+
/**
|
|
9
|
+
* Checks if a slug is unique within a content type scope.
|
|
10
|
+
*
|
|
11
|
+
* @param slug - The slug to check
|
|
12
|
+
* @param queryFn - Function that queries the database for entries with the given slug
|
|
13
|
+
* @param options - Configuration options
|
|
14
|
+
* @returns Result indicating uniqueness and suggestions
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // In a Convex mutation
|
|
19
|
+
* const queryFn = async (slug: string) => {
|
|
20
|
+
* return await ctx.db
|
|
21
|
+
* .query("contentEntries")
|
|
22
|
+
* .withIndex("by_content_type_and_slug", (q) =>
|
|
23
|
+
* q.eq("contentTypeId", contentTypeId).eq("slug", slug)
|
|
24
|
+
* )
|
|
25
|
+
* .filter((q) => q.eq(q.field("deletedAt"), undefined))
|
|
26
|
+
* .first();
|
|
27
|
+
* };
|
|
28
|
+
*
|
|
29
|
+
* const result = await checkSlugUniqueness("my-post", queryFn);
|
|
30
|
+
* if (!result.isUnique) {
|
|
31
|
+
* console.log(`Slug conflict with entry ${result.existingEntryId}`);
|
|
32
|
+
* console.log(`Suggested alternative: ${result.suggestedSlug}`);
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export async function checkSlugUniqueness(slug, queryFn, options = {}) {
|
|
37
|
+
const { excludeEntryId } = options;
|
|
38
|
+
// Validate the slug format
|
|
39
|
+
if (!isValidSlug(slug, options.separator)) {
|
|
40
|
+
return {
|
|
41
|
+
isUnique: false,
|
|
42
|
+
suggestedSlug: generateSlug(slug, { separator: options.separator }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Query for existing entry with this slug
|
|
46
|
+
const existingEntry = await queryFn(slug);
|
|
47
|
+
// Check if the slug is available
|
|
48
|
+
if (!existingEntry) {
|
|
49
|
+
return { isUnique: true };
|
|
50
|
+
}
|
|
51
|
+
// If we're updating an entry, exclude it from the check
|
|
52
|
+
if (excludeEntryId && existingEntry._id === excludeEntryId) {
|
|
53
|
+
return { isUnique: true };
|
|
54
|
+
}
|
|
55
|
+
// Slug is taken - generate a suggestion
|
|
56
|
+
const isUniqueFn = async (candidateSlug) => {
|
|
57
|
+
const entry = await queryFn(candidateSlug);
|
|
58
|
+
if (!entry)
|
|
59
|
+
return true;
|
|
60
|
+
if (excludeEntryId && entry._id === excludeEntryId)
|
|
61
|
+
return true;
|
|
62
|
+
return false;
|
|
63
|
+
};
|
|
64
|
+
const suggestedSlug = await generateUniqueSlug(slug, isUniqueFn, options.maxAttempts);
|
|
65
|
+
return {
|
|
66
|
+
isUnique: false,
|
|
67
|
+
existingEntryId: existingEntry._id,
|
|
68
|
+
suggestedSlug,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Ensures a slug is unique by generating incremental suffixes if needed.
|
|
73
|
+
* This is the main function to use when creating or updating content entries.
|
|
74
|
+
*
|
|
75
|
+
* @param baseSlug - The desired slug (or title to generate slug from)
|
|
76
|
+
* @param queryFn - Function that queries the database for entries with a given slug
|
|
77
|
+
* @param options - Configuration options
|
|
78
|
+
* @returns A unique slug (either the original or with a numeric suffix)
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // In a Convex mutation for creating a new entry
|
|
83
|
+
* const queryFn = async (slug: string) => {
|
|
84
|
+
* return await ctx.db
|
|
85
|
+
* .query("contentEntries")
|
|
86
|
+
* .withIndex("by_content_type_and_slug", (q) =>
|
|
87
|
+
* q.eq("contentTypeId", contentTypeId).eq("slug", slug)
|
|
88
|
+
* )
|
|
89
|
+
* .filter((q) => q.eq(q.field("deletedAt"), undefined))
|
|
90
|
+
* .first();
|
|
91
|
+
* };
|
|
92
|
+
*
|
|
93
|
+
* const uniqueSlug = await ensureUniqueSlug("my-post", queryFn);
|
|
94
|
+
* // Returns "my-post" if unique, or "my-post-1", "my-post-2", etc.
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export async function ensureUniqueSlug(baseSlug, queryFn, options = {}) {
|
|
98
|
+
const { excludeEntryId, maxAttempts = 100 } = options;
|
|
99
|
+
// Validate and normalize the base slug
|
|
100
|
+
let slug = baseSlug;
|
|
101
|
+
if (!isValidSlug(slug, options.separator)) {
|
|
102
|
+
slug = generateSlug(slug, { separator: options.separator });
|
|
103
|
+
}
|
|
104
|
+
// If slug is empty after normalization, use a fallback
|
|
105
|
+
if (!slug) {
|
|
106
|
+
slug = "untitled";
|
|
107
|
+
}
|
|
108
|
+
// Check if the base slug is available
|
|
109
|
+
const isUniqueFn = async (candidateSlug) => {
|
|
110
|
+
const entry = await queryFn(candidateSlug);
|
|
111
|
+
if (!entry)
|
|
112
|
+
return true;
|
|
113
|
+
if (excludeEntryId && entry._id === excludeEntryId)
|
|
114
|
+
return true;
|
|
115
|
+
return false;
|
|
116
|
+
};
|
|
117
|
+
return generateUniqueSlug(slug, isUniqueFn, maxAttempts);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Finds the next available slug suffix by analyzing existing slugs.
|
|
121
|
+
* This is useful when you want to pre-compute the next suffix without
|
|
122
|
+
* iterating through each number.
|
|
123
|
+
*
|
|
124
|
+
* @param baseSlug - The base slug to find the next suffix for
|
|
125
|
+
* @param prefixQueryFn - Function that returns all entries with slugs starting with the base
|
|
126
|
+
* @param options - Configuration options
|
|
127
|
+
* @returns The next available slug with the appropriate suffix
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* // If entries exist with slugs: "post", "post-1", "post-2", "post-5"
|
|
132
|
+
* const nextSlug = await findNextAvailableSlug("post", queryPrefixFn);
|
|
133
|
+
* // Returns "post-3" (fills the gap)
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export async function findNextAvailableSlug(baseSlug, prefixQueryFn, options = {}) {
|
|
137
|
+
const { excludeEntryId, separator = "-" } = options;
|
|
138
|
+
// Validate the base slug
|
|
139
|
+
let slug = baseSlug;
|
|
140
|
+
if (!isValidSlug(slug, separator)) {
|
|
141
|
+
slug = generateSlug(slug, { separator });
|
|
142
|
+
}
|
|
143
|
+
if (!slug) {
|
|
144
|
+
slug = "untitled";
|
|
145
|
+
}
|
|
146
|
+
// Get all entries with this prefix
|
|
147
|
+
const existingEntries = await prefixQueryFn(slug);
|
|
148
|
+
// Filter out the excluded entry and soft-deleted entries
|
|
149
|
+
const activeEntries = existingEntries.filter((entry) => {
|
|
150
|
+
if (excludeEntryId && entry._id === excludeEntryId)
|
|
151
|
+
return false;
|
|
152
|
+
if (entry.deletedAt)
|
|
153
|
+
return false;
|
|
154
|
+
return true;
|
|
155
|
+
});
|
|
156
|
+
// If no entries exist with this slug, the base is available
|
|
157
|
+
const hasSlugsToCheck = activeEntries.some((entry) => {
|
|
158
|
+
return entry.slug === slug || entry.slug.startsWith(`${slug}${separator}`);
|
|
159
|
+
});
|
|
160
|
+
if (!hasSlugsToCheck) {
|
|
161
|
+
return slug;
|
|
162
|
+
}
|
|
163
|
+
// Check if base slug itself is taken
|
|
164
|
+
const baseIsTaken = activeEntries.some((entry) => entry.slug === slug);
|
|
165
|
+
if (!baseIsTaken) {
|
|
166
|
+
return slug;
|
|
167
|
+
}
|
|
168
|
+
// Extract existing suffix numbers
|
|
169
|
+
const suffixPattern = new RegExp(`^${escapeRegex(slug)}${escapeRegex(separator)}(\\d+)$`);
|
|
170
|
+
const usedNumbers = new Set();
|
|
171
|
+
for (const entry of activeEntries) {
|
|
172
|
+
const match = entry.slug.match(suffixPattern);
|
|
173
|
+
if (match) {
|
|
174
|
+
usedNumbers.add(parseInt(match[1], 10));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Find the smallest available number
|
|
178
|
+
let nextNumber = 1;
|
|
179
|
+
while (usedNumbers.has(nextNumber)) {
|
|
180
|
+
nextNumber++;
|
|
181
|
+
}
|
|
182
|
+
return `${slug}${separator}${nextNumber}`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Escapes special regex characters in a string
|
|
186
|
+
*/
|
|
187
|
+
function escapeRegex(str) {
|
|
188
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Validates a slug and returns validation errors if any.
|
|
192
|
+
*
|
|
193
|
+
* @param slug - The slug to validate
|
|
194
|
+
* @param separator - The separator character (default: '-')
|
|
195
|
+
* @returns Array of validation error messages (empty if valid)
|
|
196
|
+
*/
|
|
197
|
+
export function validateSlugFormat(slug, separator = "-") {
|
|
198
|
+
const errors = [];
|
|
199
|
+
if (!slug || typeof slug !== "string") {
|
|
200
|
+
errors.push("Slug is required");
|
|
201
|
+
return errors;
|
|
202
|
+
}
|
|
203
|
+
if (slug.length === 0) {
|
|
204
|
+
errors.push("Slug cannot be empty");
|
|
205
|
+
return errors;
|
|
206
|
+
}
|
|
207
|
+
if (slug.length > 100) {
|
|
208
|
+
errors.push("Slug must be 100 characters or less");
|
|
209
|
+
}
|
|
210
|
+
if (slug !== slug.toLowerCase()) {
|
|
211
|
+
errors.push("Slug must be lowercase");
|
|
212
|
+
}
|
|
213
|
+
if (slug.startsWith(separator)) {
|
|
214
|
+
errors.push(`Slug cannot start with '${separator}'`);
|
|
215
|
+
}
|
|
216
|
+
if (slug.endsWith(separator)) {
|
|
217
|
+
errors.push(`Slug cannot end with '${separator}'`);
|
|
218
|
+
}
|
|
219
|
+
const doubleSeparatorRegex = new RegExp(`${escapeRegex(separator)}{2,}`);
|
|
220
|
+
if (doubleSeparatorRegex.test(slug)) {
|
|
221
|
+
errors.push(`Slug cannot contain consecutive '${separator}' characters`);
|
|
222
|
+
}
|
|
223
|
+
const invalidCharsRegex = new RegExp(`[^a-z0-9${escapeRegex(separator)}]`);
|
|
224
|
+
if (invalidCharsRegex.test(slug)) {
|
|
225
|
+
errors.push("Slug can only contain lowercase letters, numbers, and hyphens");
|
|
226
|
+
}
|
|
227
|
+
return errors;
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=slugUniqueness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slugUniqueness.js","sourceRoot":"","sources":["../../../src/component/lib/slugUniqueness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soft-delete utilities for CMS documents.
|
|
3
|
+
*
|
|
4
|
+
* Provides type-safe helpers for working with documents that use
|
|
5
|
+
* the soft-delete pattern (deletedAt timestamp).
|
|
6
|
+
*/
|
|
7
|
+
export interface SoftDeletable {
|
|
8
|
+
deletedAt?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function isDeleted<T extends SoftDeletable>(doc: T): boolean;
|
|
11
|
+
export declare function isActive<T extends SoftDeletable>(doc: T): boolean;
|
|
12
|
+
export declare function filterActive<T extends SoftDeletable>(docs: T[]): T[];
|
|
13
|
+
export declare function filterDeleted<T extends SoftDeletable>(docs: T[]): T[];
|
|
14
|
+
export declare function requireNotDeleted<T extends SoftDeletable>(doc: T, errorFactory: () => Error): asserts doc is T & {
|
|
15
|
+
deletedAt: undefined;
|
|
16
|
+
};
|
|
17
|
+
export declare function requireDeleted<T extends SoftDeletable>(doc: T, errorFactory: () => Error): void;
|
|
18
|
+
//# sourceMappingURL=softDelete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"softDelete.d.ts","sourceRoot":"","sources":["../../../src/component/lib/softDelete.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAElE;AAED,wBAAgB,QAAQ,CAAC,CAAC,SAAS,aAAa,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAEjE;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAEpE;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAErE;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,aAAa,EACvD,GAAG,EAAE,CAAC,EACN,YAAY,EAAE,MAAM,KAAK,GACxB,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG;IAAE,SAAS,EAAE,SAAS,CAAA;CAAE,CAI7C;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,EACpD,GAAG,EAAE,CAAC,EACN,YAAY,EAAE,MAAM,KAAK,GACxB,IAAI,CAIN"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Soft-delete utilities for CMS documents.
|
|
3
|
+
*
|
|
4
|
+
* Provides type-safe helpers for working with documents that use
|
|
5
|
+
* the soft-delete pattern (deletedAt timestamp).
|
|
6
|
+
*/
|
|
7
|
+
export function isDeleted(doc) {
|
|
8
|
+
return doc.deletedAt !== undefined;
|
|
9
|
+
}
|
|
10
|
+
export function isActive(doc) {
|
|
11
|
+
return doc.deletedAt === undefined;
|
|
12
|
+
}
|
|
13
|
+
export function filterActive(docs) {
|
|
14
|
+
return docs.filter(isActive);
|
|
15
|
+
}
|
|
16
|
+
export function filterDeleted(docs) {
|
|
17
|
+
return docs.filter(isDeleted);
|
|
18
|
+
}
|
|
19
|
+
export function requireNotDeleted(doc, errorFactory) {
|
|
20
|
+
if (isDeleted(doc)) {
|
|
21
|
+
throw errorFactory();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function requireDeleted(doc, errorFactory) {
|
|
25
|
+
if (!isDeleted(doc)) {
|
|
26
|
+
throw errorFactory();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=softDelete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"softDelete.js","sourceRoot":"","sources":["../../../src/component/lib/softDelete.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,UAAU,SAAS,CAA0B,GAAM;IACvD,OAAO,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,QAAQ,CAA0B,GAAM;IACtD,OAAO,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAA0B,IAAS;IAC7D,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,aAAa,CAA0B,IAAS;IAC9D,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,GAAM,EACN,YAAyB;IAEzB,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,YAAY,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,GAAM,EACN,YAAyB;IAEzB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,YAAY,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
|