@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 +22 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +111 -0
- package/dist/index.d.ts +450 -0
- package/dist/index.js +739 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +1976 -0
- package/dist/smrt-knowledge.json +726 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +319 -0
- package/dist/utils.js +52 -0
- package/dist/utils.js.map +1 -0
- package/package.json +71 -0
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 |
|
package/dist/index.d.ts
ADDED
|
@@ -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 { }
|