@happyvertical/smrt-tags 0.30.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/AGENTS.md ADDED
@@ -0,0 +1,22 @@
1
+ # @happyvertical/smrt-tags
2
+
3
+ Hierarchical tagging with context-scoped slugs and multi-language aliases.
4
+
5
+ ## Models
6
+
7
+ - **Tag** (STI, extends `SmrtHierarchical`): public identifier is `slug` (not UUID) + `context` (default: 'global'), but the hierarchy FK is `parentId` (UUID) since R3-B. `TagCollection.moveTag` / `mergeTag` / `getChildren` keep slug-string signatures and resolve to UUIDs internally. `level` is a denormalised depth recalculated on `moveTag`. Metadata JSON.
8
+ - **TagAlias**: language-specific translations/aliases (ISO 639-1 `language` code, nullable). Optional context scoping.
9
+
10
+ ## Key Collection Methods
11
+
12
+ - `getOrCreate(slug, options)`: auto-generates name from slug
13
+ - `moveTag(slug, newParentSlug)`: circular reference detection, level recalculation
14
+ - `mergeTag(sourceSlug, targetSlug)`: moves children + aliases from source to target, then deletes source
15
+ - `cleanupUnused()`: only deletes tags with no children AND no aliases
16
+ - `findWithGlobals(tenantId)`: tenant + global tags
17
+
18
+ ## Gotchas
19
+
20
+ - **Slug stored in protected `_slug`**: has override getter/setter (not standard SmrtObject slug behavior)
21
+ - **Context defaults to 'global'**: if not specified
22
+ - **Optional tenancy** with nullable tenantId
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Happy Vertical Corporation>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # @happyvertical/smrt-tags
2
+
3
+ Hierarchical tagging with context-scoped slugs, multi-language aliases, and slug utilities.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @happyvertical/smrt-tags
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { Tag, TagCollection, TagAlias, TagAliasCollection } from '@happyvertical/smrt-tags';
15
+ import { sanitizeSlug, validateSlug, generateUniqueSlug } from '@happyvertical/smrt-tags';
16
+
17
+ // Create a tag hierarchy
18
+ const collection = await TagCollection.create();
19
+
20
+ // `collection.create(...)` returns the saved row, so use it directly
21
+ // instead of `new Tag()` + a separate `.create(...)` step. The returned
22
+ // instance has its persisted `id` set for use as a parent reference.
23
+ const tech = await collection.create({
24
+ slug: 'technology',
25
+ name: 'Technology',
26
+ context: 'blog', // defaults to 'global' if omitted
27
+ });
28
+
29
+ // Children reference their parent by UUID (`parentId`). With `tech`
30
+ // already saved, pass `tech.id` directly. `level` is recalculated by
31
+ // `TagCollection.moveTag` / `mergeTag` for later moves; you can pass it
32
+ // explicitly on create or let it stay at 0 and call moveTag later.
33
+ const ai = await collection.create({
34
+ slug: 'artificial-intelligence',
35
+ name: 'Artificial Intelligence',
36
+ context: 'blog',
37
+ parentId: tech.id,
38
+ level: 1,
39
+ });
40
+
41
+ // Traverse hierarchy
42
+ const children = await tech.getChildren();
43
+ const ancestors = await ai.getAncestors();
44
+
45
+ // Context scoping -- same slug, different contexts
46
+ const blogNews = new Tag({ slug: 'news', name: 'News', context: 'blog' });
47
+ const forumNews = new Tag({ slug: 'news', name: 'Forum News', context: 'forum' });
48
+
49
+ // Multi-language aliases
50
+ const aliasCollection = await TagAliasCollection.create();
51
+ const alias = new TagAlias({
52
+ tagSlug: 'technology',
53
+ alias: 'Tecnologia',
54
+ language: 'es',
55
+ context: 'blog',
56
+ });
57
+ await aliasCollection.create(alias);
58
+
59
+ // Collection operations. Slug args resolve through (slug, context) —
60
+ // pass an explicit context when the same slug exists in multiple
61
+ // contexts, otherwise the resolver throws on ambiguity.
62
+ await collection.moveTag('artificial-intelligence', 'technology', 'blog'); // cycle-checked
63
+ await collection.mergeTag('source', 'target', 'blog'); // moves children + aliases, deletes source
64
+ await collection.cleanupUnused(); // deletes tags with no children and no aliases
65
+
66
+ // Slug utilities
67
+ sanitizeSlug('My Cool Tag!'); // 'my-cool-tag'
68
+ validateSlug('my-tag-123'); // true
69
+ ```
70
+
71
+ ## API
72
+
73
+ ### Models (SmrtObject)
74
+
75
+ | Export | Description |
76
+ |--------|------------|
77
+ | `Tag` | Identified by `slug` + `context`. Hierarchical via `parentId` (UUID, inherited from `SmrtHierarchical`). Denormalised `level` recalculated by `TagCollection.moveTag` / `mergeTag`. JSON `metadata`. Hierarchy methods inherited: `getParent()`, `getChildren()`, `getAncestors()`, `getDescendants()`, `getHierarchy()`, `moveTo()`. Own helpers: `getMetadata()`, `setMetadata()`, `updateMetadata()` |
78
+ | `TagAlias` | Language-specific translations: `tagSlug`, `alias`, `language` (ISO 639-1), optional `context` |
79
+
80
+ ### Collections (SmrtCollection)
81
+
82
+ | Export | Description |
83
+ |--------|------------|
84
+ | `TagCollection` | CRUD + `getOrCreate()`, `moveTag()`, `mergeTag()`, `cleanupUnused()`, `findWithGlobals()` |
85
+ | `TagAliasCollection` | CRUD for tag aliases |
86
+
87
+ ### Utilities
88
+
89
+ | Export | Description |
90
+ |--------|------------|
91
+ | `sanitizeSlug(input)` | Clean and format slug strings |
92
+ | `validateSlug(slug)` | Validate slug format (lowercase, alphanumeric + hyphens) |
93
+ | `generateUniqueSlug(name, context, collection)` | Auto-numbered unique slug generation |
94
+ | `calculateLevel(parentSlug, collection)` | Determine hierarchy depth from parent |
95
+ | `hasCircularReference(slug, parentSlug, collection)` | Detect circular parent references |
96
+
97
+ ### Types
98
+
99
+ | Export | Description |
100
+ |--------|------------|
101
+ | `TagOptions` | Options for `Tag` constructor |
102
+ | `TagAliasOptions` | Options for `TagAlias` constructor |
103
+ | `TagMetadata` | Flexible metadata structure (colors, icons, statistics) |
104
+ | `TagHierarchy` | Complete hierarchy result (ancestors, current, descendants) |
105
+
106
+ ## Dependencies
107
+
108
+ | Package | Purpose |
109
+ |---------|---------|
110
+ | `@happyvertical/smrt-core` | ORM base (SmrtObject, SmrtCollection) |
111
+ | `@happyvertical/smrt-tenancy` | Optional tenant scoping |
@@ -0,0 +1,450 @@
1
+ import { SmrtCollection } from '@happyvertical/smrt-core';
2
+ import { SmrtHierarchical } from '@happyvertical/smrt-core';
3
+ import { SmrtObject } from '@happyvertical/smrt-core';
4
+ import { SmrtObjectOptions } from '@happyvertical/smrt-core';
5
+
6
+ /**
7
+ * Calculate hierarchy level
8
+ *
9
+ * Determines the level (depth) of a tag based on its parent.
10
+ * Root tags have level 0, their children have level 1, etc.
11
+ *
12
+ * @param parentSlug - The parent tag slug (null for root)
13
+ * @param tagCollection - TagCollection instance for queries
14
+ * @returns The calculated level
15
+ */
16
+ export declare function calculateLevel(parentSlug: string | null, tagCollection: TagCollection): Promise<number>;
17
+
18
+ /**
19
+ * Generate a unique slug from a name
20
+ *
21
+ * Creates a slug and ensures uniqueness by appending a number if needed.
22
+ *
23
+ * @param name - The name to convert to slug
24
+ * @param context - The context for uniqueness checking
25
+ * @param tagCollection - TagCollection instance for queries
26
+ * @returns Unique slug
27
+ */
28
+ export declare function generateUniqueSlug(name: string, context: string, tagCollection: TagCollection): Promise<string>;
29
+
30
+ /**
31
+ * Validate hierarchy for circular references
32
+ *
33
+ * Checks if setting a parent would create a circular reference
34
+ * (e.g., making a tag its own ancestor). The actual move call in
35
+ * `TagCollection.moveTag` also runs `SmrtHierarchical.moveTo`'s
36
+ * descendant-cycle check; this helper remains exported for callers that
37
+ * want to pre-validate a candidate parent without attempting the move.
38
+ *
39
+ * Tags are identified by `(slug, context)`. If `context` is omitted,
40
+ * slug-only lookups are used — fine when slugs are unique across all
41
+ * contexts, but the walk can traverse the wrong chain when the same
42
+ * slug exists in multiple contexts. Pass the candidate parent's
43
+ * `context` for accurate cross-context-safe validation.
44
+ *
45
+ * @param slug - The tag being moved
46
+ * @param parentSlug - The proposed new parent
47
+ * @param tagCollection - TagCollection instance for queries
48
+ * @param context - Optional context to scope every slug lookup to
49
+ * @returns True if circular reference detected
50
+ */
51
+ export declare function hasCircularReference(slug: string, parentSlug: string, tagCollection: TagCollection, context?: string): Promise<boolean>;
52
+
53
+ /**
54
+ * Sanitize slug input
55
+ *
56
+ * Converts to lowercase, replaces spaces with hyphens,
57
+ * removes invalid characters, and ensures proper format.
58
+ *
59
+ * @param input - The input string to sanitize
60
+ * @returns Sanitized slug
61
+ */
62
+ export declare function sanitizeSlug(input: string): string;
63
+
64
+ export declare class Tag extends SmrtHierarchical {
65
+ protected _slug: string;
66
+ protected _context: string;
67
+ get slug(): string;
68
+ set slug(value: string);
69
+ get context(): string;
70
+ set context(value: string);
71
+ name: string;
72
+ level: number;
73
+ description: string;
74
+ metadata: string;
75
+ tenantId: string | null;
76
+ createdAt: Date;
77
+ updatedAt: Date;
78
+ constructor(options?: TagOptions);
79
+ /**
80
+ * Get metadata as parsed object
81
+ *
82
+ * @returns Parsed metadata object or empty object if no metadata
83
+ */
84
+ getMetadata(): TagMetadata;
85
+ /**
86
+ * Set metadata from object
87
+ *
88
+ * @param data - Metadata object to store
89
+ */
90
+ setMetadata(data: TagMetadata): void;
91
+ /**
92
+ * Update metadata by merging with existing values
93
+ *
94
+ * @param updates - Partial metadata to merge
95
+ */
96
+ updateMetadata(updates: Partial<TagMetadata>): void;
97
+ /**
98
+ * Convenience method for slug-based lookup
99
+ *
100
+ * @param slug - The slug to search for
101
+ * @param context - Optional context filter
102
+ * @returns Tag instance or null if not found
103
+ */
104
+ static getBySlug(_slug: string, _context?: string): Promise<Tag | null>;
105
+ /**
106
+ * Get root tags (no parent) for a context
107
+ *
108
+ * @param context - The context to filter by
109
+ * @returns Array of root tags
110
+ */
111
+ static getRootTags(_context?: string): Promise<Tag[]>;
112
+ }
113
+
114
+ export declare class TagAlias extends SmrtObject {
115
+ tagSlug: string;
116
+ alias: string;
117
+ language: string;
118
+ protected _context: string;
119
+ get context(): string;
120
+ set context(value: string);
121
+ tenantId: string | null;
122
+ createdAt: Date;
123
+ constructor(options?: TagAliasOptions);
124
+ /**
125
+ * Get the tag this alias belongs to
126
+ *
127
+ * @returns Tag instance or null if not found
128
+ */
129
+ getTag(): Promise<Tag | null>;
130
+ /**
131
+ * Search tags by alias
132
+ *
133
+ * @param alias - The alias to search for
134
+ * @param language - Optional language filter
135
+ * @returns Array of matching tags
136
+ */
137
+ static searchByAlias(_alias: string, _language?: string): Promise<Tag[]>;
138
+ /**
139
+ * Get all aliases for a tag
140
+ *
141
+ * @param tagSlug - The tag slug to get aliases for
142
+ * @returns Array of TagAlias instances
143
+ */
144
+ static getAliasesForTag(_tagSlug: string): Promise<TagAlias[]>;
145
+ }
146
+
147
+ export declare class TagAliasCollection extends SmrtCollection<TagAlias> {
148
+ static readonly _itemClass: typeof TagAlias;
149
+ /**
150
+ * Add an alias to a tag (get or create)
151
+ *
152
+ * @param tagSlug - The tag slug
153
+ * @param alias - The alias text
154
+ * @param language - Optional language code
155
+ * @param context - Optional context
156
+ * @returns TagAlias instance
157
+ */
158
+ addAlias(tagSlug: string, alias: string, language?: string, context?: string): Promise<TagAlias>;
159
+ /**
160
+ * Search tags by alias
161
+ *
162
+ * @param alias - The alias to search for
163
+ * @param language - Optional language filter
164
+ * @returns Array of matching tags
165
+ */
166
+ searchByAlias(alias: string, language?: string): Promise<Tag[]>;
167
+ /**
168
+ * Get all aliases for a tag
169
+ *
170
+ * @param tagSlug - The tag slug
171
+ * @param language - Optional language filter
172
+ * @returns Array of TagAlias instances
173
+ */
174
+ getAliasesForTag(tagSlug: string, language?: string): Promise<TagAlias[]>;
175
+ /**
176
+ * Remove an alias by ID
177
+ *
178
+ * @param aliasId - The alias UUID
179
+ */
180
+ removeAlias(aliasId: string): Promise<void>;
181
+ /**
182
+ * Bulk add aliases to a tag
183
+ *
184
+ * @param tagSlug - The tag slug
185
+ * @param aliases - Array of alias configurations
186
+ * @returns Array of created TagAlias instances
187
+ */
188
+ bulkAddAliases(tagSlug: string, aliases: Array<{
189
+ alias: string;
190
+ language?: string;
191
+ context?: string;
192
+ }>): Promise<TagAlias[]>;
193
+ /**
194
+ * Get aliases grouped by language
195
+ *
196
+ * @param tagSlug - The tag slug
197
+ * @returns Map of language code to array of aliases
198
+ */
199
+ getAliasesByLanguage(tagSlug: string): Promise<Map<string, string[]>>;
200
+ /**
201
+ * Find matching aliases (case-insensitive partial match)
202
+ *
203
+ * Note: This is a simple implementation. For production use,
204
+ * consider using full-text search or fuzzy matching.
205
+ *
206
+ * @param query - The search query
207
+ * @param language - Optional language filter
208
+ * @returns Array of matching TagAlias instances
209
+ */
210
+ findMatchingAliases(query: string, language?: string): Promise<TagAlias[]>;
211
+ /**
212
+ * Find all tag aliases belonging to a specific tenant
213
+ *
214
+ * @param tenantId - The tenant ID to filter by
215
+ * @returns Array of tag aliases for the specified tenant
216
+ */
217
+ findByTenant(tenantId: string): Promise<TagAlias[]>;
218
+ /**
219
+ * Find all global (tenant-less) tag aliases
220
+ *
221
+ * @returns Array of global tag aliases with null tenantId
222
+ */
223
+ findGlobal(): Promise<TagAlias[]>;
224
+ /**
225
+ * Find tag aliases for a tenant including global aliases
226
+ *
227
+ * @param tenantId - The tenant ID to filter by
228
+ * @returns Array of tag aliases for the tenant plus all global aliases
229
+ */
230
+ findWithGlobals(tenantId: string): Promise<TagAlias[]>;
231
+ }
232
+
233
+ /**
234
+ * Options for creating a TagAlias instance
235
+ */
236
+ export declare interface TagAliasOptions extends SmrtObjectOptions {
237
+ tagSlug?: string;
238
+ alias?: string;
239
+ language?: string;
240
+ context?: string;
241
+ tenantId?: string | null;
242
+ }
243
+
244
+ export declare class TagCollection extends SmrtCollection<Tag> {
245
+ static readonly _itemClass: typeof Tag;
246
+ /**
247
+ * Get or create a tag with context
248
+ *
249
+ * @param slug - Tag slug
250
+ * @param context - Tag context (default: 'global')
251
+ * @returns Tag instance
252
+ */
253
+ getOrCreate(slug: string, context?: string): Promise<Tag>;
254
+ /**
255
+ * Resolve a tag by slug, optionally scoped to a context.
256
+ *
257
+ * Tags are identified by `(slug, context)`. When `context` is omitted
258
+ * and the slug exists in more than one context, this throws a clear
259
+ * ambiguity error rather than silently picking the first matching row.
260
+ * Callers that know their context should pass it; callers that work in
261
+ * a single-context world can leave it off.
262
+ *
263
+ * @returns The matching Tag, or `null` if nothing matches.
264
+ * @throws Error if `context` is omitted and the slug is ambiguous.
265
+ */
266
+ private resolveBySlug;
267
+ /**
268
+ * List tags by context with optional parent filtering by slug.
269
+ *
270
+ * @param context - The context to filter by
271
+ * @param parentSlug - Optional parent slug to filter children. Pass an
272
+ * empty string or `null` to find root tags; pass a slug to find that
273
+ * tag's immediate children. Typed as `string | null` so TypeScript
274
+ * callers can pass `null` without a cast — the `null` and `''` paths
275
+ * are both treated as "roots only".
276
+ * @returns Array of matching tags
277
+ */
278
+ listByContext(context: string, parentSlug?: string | null): Promise<Tag[]>;
279
+ /**
280
+ * Get root tags (no parent) for a context
281
+ *
282
+ * @param context - The context to filter by (default: 'global')
283
+ * @returns Array of root tags
284
+ */
285
+ getRootTags(context?: string): Promise<Tag[]>;
286
+ /**
287
+ * Get immediate children of a parent tag, looked up by slug.
288
+ *
289
+ * @param parentSlug - The parent tag slug
290
+ * @param context - Optional context for the parent lookup. When omitted,
291
+ * the parent slug must be unambiguous across contexts (throws if not).
292
+ * @returns Array of child tags, or `[]` if the parent slug doesn't
293
+ * resolve. Children are filtered to the resolved parent's context so
294
+ * cross-context children don't leak in.
295
+ */
296
+ getChildren(parentSlug: string, context?: string): Promise<Tag[]>;
297
+ /**
298
+ * Get tag hierarchy (all ancestors and descendants)
299
+ *
300
+ * @param slug - The tag slug
301
+ * @param context - Optional context for the slug lookup. When omitted,
302
+ * the slug must be unambiguous across contexts.
303
+ * @returns Object with ancestors, current tag, and descendants
304
+ */
305
+ getHierarchy(slug: string, context?: string): Promise<TagHierarchy>;
306
+ /**
307
+ * Move a tag to a new parent. Slug-based API; UUIDs resolved internally.
308
+ *
309
+ * Cycle detection is inlined here (mirroring `SmrtHierarchical.moveTo`'s
310
+ * self-loop + descendant checks) so that both `parentId` and the
311
+ * denormalised `level` field can be persisted in a single `save()`.
312
+ * Delegating to `moveTo` would write `parentId` first and `level` in a
313
+ * second save — if the second save failed, the tag would be left with
314
+ * the new parent but a stale level, breaking the depth cache.
315
+ *
316
+ * After the moved tag persists, descendant levels are recalculated
317
+ * recursively via `updateDescendantLevels`.
318
+ *
319
+ * @param slug - The tag to move
320
+ * @param newParentSlug - The new parent slug (null for root)
321
+ * @param context - Optional context. When provided, both source and new
322
+ * parent are resolved within it. When omitted, both slugs must be
323
+ * unambiguous across contexts; the resolver throws otherwise.
324
+ * @throws Error if either slug fails to resolve, if either slug is
325
+ * ambiguous across contexts (no context provided), if source and new
326
+ * parent live in different contexts, or if the move would create a
327
+ * cycle.
328
+ */
329
+ moveTag(slug: string, newParentSlug: string | null, context?: string): Promise<void>;
330
+ /**
331
+ * Merge one tag into another (updates all references)
332
+ *
333
+ * Reparents `fromTag`'s direct children onto `toTag` and recalculates
334
+ * their `level` field plus the level of every descendant — without
335
+ * this, children moved from a different depth would carry stale
336
+ * levels relative to their new parent. `TagAlias.tagSlug` references
337
+ * are also rewritten, then `fromTag` is deleted.
338
+ *
339
+ * Note: Consuming packages are responsible for updating their own
340
+ * join tables (e.g. `asset_tags`).
341
+ *
342
+ * @param fromSlug - The tag to merge from
343
+ * @param toSlug - The tag to merge into
344
+ * @param context - Optional context. When provided, both tags are
345
+ * resolved within it. When omitted, both slugs must be unambiguous
346
+ * across contexts.
347
+ * @throws Error if either slug fails to resolve, if either slug is
348
+ * ambiguous, or if the two tags live in different contexts.
349
+ */
350
+ mergeTag(fromSlug: string, toSlug: string, context?: string): Promise<void>;
351
+ /**
352
+ * Remove tags with no references (cleanup unused tags)
353
+ *
354
+ * Note: This requires consuming packages to provide usage information.
355
+ * By default, only removes tags with no children and no aliases.
356
+ *
357
+ * @param context - Optional context to filter cleanup
358
+ */
359
+ cleanupUnused(context?: string): Promise<number>;
360
+ /**
361
+ * Calculate hierarchy level for a tag, looking the parent up by slug.
362
+ *
363
+ * @param parentSlug - The parent tag slug (null/empty for root)
364
+ * @param context - Optional context for the parent lookup. When
365
+ * omitted, the parent slug must be unambiguous across contexts.
366
+ * @returns The calculated level (root parent → 1, missing parent → 0)
367
+ */
368
+ calculateLevel(parentSlug: string | null, context?: string): Promise<number>;
369
+ /**
370
+ * Update levels for all descendants after moving a tag
371
+ *
372
+ * @param tag - The tag that was moved
373
+ */
374
+ private updateDescendantLevels;
375
+ /**
376
+ * Find all tags belonging to a specific tenant
377
+ *
378
+ * @param tenantId - The tenant ID to filter by
379
+ * @returns Array of tags for the specified tenant
380
+ */
381
+ findByTenant(tenantId: string): Promise<Tag[]>;
382
+ /**
383
+ * Find all global (tenant-less) tags
384
+ *
385
+ * @returns Array of global tags with null tenantId
386
+ */
387
+ findGlobal(): Promise<Tag[]>;
388
+ /**
389
+ * Find tags for a tenant including global tags
390
+ *
391
+ * @param tenantId - The tenant ID to filter by
392
+ * @returns Array of tags for the tenant plus all global tags
393
+ */
394
+ findWithGlobals(tenantId: string): Promise<Tag[]>;
395
+ }
396
+
397
+ /**
398
+ * Tag hierarchy result structure
399
+ */
400
+ export declare interface TagHierarchy {
401
+ ancestors: Tag[];
402
+ current: Tag;
403
+ descendants: Tag[];
404
+ }
405
+
406
+ /**
407
+ * Tag metadata structure (flexible, application-specific)
408
+ */
409
+ export declare interface TagMetadata {
410
+ color?: string;
411
+ backgroundColor?: string;
412
+ icon?: string;
413
+ emoji?: string;
414
+ usageCount?: number;
415
+ lastUsed?: string;
416
+ trending?: boolean;
417
+ featured?: boolean;
418
+ sortOrder?: number;
419
+ showInNav?: boolean;
420
+ displayFormat?: string;
421
+ aiGenerated?: boolean;
422
+ confidence?: number;
423
+ source?: string;
424
+ reviewStatus?: string;
425
+ [key: string]: any;
426
+ }
427
+
428
+ /**
429
+ * Options for creating a Tag instance
430
+ */
431
+ export declare interface TagOptions extends SmrtObjectOptions {
432
+ slug?: string;
433
+ name?: string;
434
+ context?: string;
435
+ parentId?: string | null;
436
+ level?: number;
437
+ description?: string;
438
+ metadata?: string | Record<string, any>;
439
+ tenantId?: string | null;
440
+ }
441
+
442
+ /**
443
+ * Validate slug format (lowercase, alphanumeric + hyphens)
444
+ *
445
+ * @param slug - The slug to validate
446
+ * @returns True if slug is valid
447
+ */
448
+ export declare function validateSlug(slug: string): boolean;
449
+
450
+ export { }