@qrvey/tags
v0.0.1-1139-beta
Published

Readme
@qrvey/tags
A TypeScript data-access package for managing tags in Qrvey. Provides a repository abstraction over the admin.tags database table with built-in validation, normalized tag names, and cursor-based pagination.
Installation
npm install @qrvey/tagsOr with yarn:
yarn add @qrvey/tagsFeatures
- Repository pattern - Clean
ITagsRepositoryinterface with read and write operations - Auto-normalized tag names - Tag names are trimmed, lowercased, and spaces replaced with underscores automatically
- Scoped tags - Tags carry an
internalorglobalscope - Cursor-based pagination - Opaque, base64-encoded pagination tokens
- Flexible filtering - 14 comparison operators for querying tags
- Validation - Input validated via
class-validatorbefore every write - TypeScript - Full type safety and IntelliSense support
- Dual module output - Ships both CJS and ESM builds
Usage
Basic Setup
import { TagsRepository } from '@qrvey/tags';
const repo = new TagsRepository();Find a Tag by ID
Tags are identified by the composite key { tag_name, org_id }.
const tag = await repo.findById({ tag_name: 'my_tag', org_id: 'org_123' });
if (tag) {
console.log(tag.scope); // 'internal' | 'global'
}Find Tags with Filters and Pagination
import { TagsRepository, QueryOptions } from '@qrvey/tags';
const repo = new TagsRepository();
const options: QueryOptions = {
filters: [
{ column: 'org_id', operator: 'EQUALS', value: 'org_123' },
{ column: 'scope', operator: 'EQUALS', value: 'global' },
],
loadAll: false,
pagination: undefined, // pass the token from a previous response to get the next page
};
const { results, pagination } = await repo.find(options);
console.log(results); // TagsModel[]
console.log(pagination); // opaque token for the next page, or undefined if no more pagesTo load all results without pagination:
const { results } = await repo.find({ filters: [], loadAll: true });Create a Tag
import { TagsRepository, TagsModel } from '@qrvey/tags';
const repo = new TagsRepository();
const newTag = Object.assign(new TagsModel(), {
tag_name: 'My New Tag', // normalized to 'my_new_tag' automatically
org_id: 'org_123',
scope: 'internal',
created_by: 'user_456',
});
const created = await repo.create(newTag);
console.log(created.tag_name); // 'my_new_tag'Update a Tag
const updated = await repo.update(
{ tag_name: 'my_new_tag', org_id: 'org_123' },
{ ...existingTag, scope: 'global' },
);Delete a Tag
await repo.delete({ tag_name: 'my_new_tag', org_id: 'org_123' });Data Model
TagsModel
| Field | Type | Required | Description |
| ------------ | ---------- | -------- | ------------------------------------------------------------------- |
| tag_name | string | Yes | 1–100 chars. Auto-normalized: trimmed, lowercased, spaces → _ |
| org_id | string | Yes | Organization identifier (composite PK) |
| scope | 'internal' \| 'global' | Yes | Visibility scope of the tag |
| created_by | string | Yes | Identifier of the user or process that created the tag |
| created_at | Date | Yes | Creation timestamp (defaults to new Date() if omitted) |
| updated_at | Date | Yes | Last-updated timestamp (defaults to new Date() if omitted) |
The composite primary key is (tag_name, org_id).
API Reference
TagsRepository
Implements ITagsRepository, which extends both IReadableRepository and IWritableRepository.
findById(id: { tag_name: string; org_id: string }): Promise<TagsModel | null>
Returns the tag matching the composite key, or null if not found.
find(options: QueryOptions): Promise<PaginatedResults<TagsModel>>
Returns a paginated list of tags matching the given filters.
create(item: TagsModel): Promise<TagsModel>
Creates a new tag. Throws if validation fails.
update(id: { tag_name: string; org_id: string }, item: TagsModel): Promise<TagsModel | null>
Updates an existing tag. Returns null if no matching record exists. Throws if validation fails.
delete(id: { tag_name: string; org_id: string }): Promise<void>
Deletes the tag with the given composite key.
QueryOptions
type QueryOptions = {
filters: {
column: string;
operator: DatabaseOperator;
value: unknown;
}[];
loadAll: boolean;
pagination?: string; // opaque base64 token from a previous PaginatedResults response
};Supported Filter Operators
| Operator | Description |
| ------------------------- | ---------------------------------- |
| EQUALS | Exact match |
| NOT_EQUALS | Not equal |
| GREATER_THAN | Greater than |
| GREATER_THAN_OR_EQUAL | Greater than or equal |
| LESS_THAN | Less than |
| LESS_THAN_OR_EQUAL | Less than or equal |
| IN | Value is in a list |
| NOT_IN | Value is not in a list |
| CONTAINS | String contains substring |
| NOT_CONTAINS | String does not contain substring |
| STARTS_WITH | String starts with prefix |
| EXISTS | Field exists |
| NOT_EXISTS | Field does not exist |
| BETWEEN | Value is between two bounds |
PaginatedResults<T>
type PaginatedResults<T> = {
results: T[];
pagination?: string; // pass this token to the next find() call to get the next page
};License
ISC
