@utilarium/overcontext 0.0.4-dev.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/LICENSE +65 -0
- package/README.md +286 -0
- package/dist/api/builder.d.ts +26 -0
- package/dist/api/builder.js +24 -0
- package/dist/api/builder.js.map +1 -0
- package/dist/api/context.d.ts +90 -0
- package/dist/api/context.js +94 -0
- package/dist/api/context.js.map +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/query-builder.d.ts +51 -0
- package/dist/api/query-builder.js +95 -0
- package/dist/api/query-builder.js.map +1 -0
- package/dist/api/query.d.ts +32 -0
- package/dist/api/search.d.ts +20 -0
- package/dist/api/search.js +112 -0
- package/dist/api/search.js.map +1 -0
- package/dist/api/slug.d.ts +10 -0
- package/dist/api/slug.js +55 -0
- package/dist/api/slug.js.map +1 -0
- package/dist/cli/builder.d.ts +74 -0
- package/dist/cli/builder.js +42 -0
- package/dist/cli/builder.js.map +1 -0
- package/dist/cli/commands.d.ts +53 -0
- package/dist/cli/commands.js +57 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/formatters.d.ts +15 -0
- package/dist/cli/formatters.js +50 -0
- package/dist/cli/formatters.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/discovery/context-root.d.ts +31 -0
- package/dist/discovery/context-root.js +48 -0
- package/dist/discovery/context-root.js.map +1 -0
- package/dist/discovery/hierarchical-provider.d.ts +13 -0
- package/dist/discovery/hierarchical-provider.js +102 -0
- package/dist/discovery/hierarchical-provider.js.map +1 -0
- package/dist/discovery/index.d.ts +18 -0
- package/dist/discovery/index.js +47 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/walker.d.ts +36 -0
- package/dist/discovery/walker.js +87 -0
- package/dist/discovery/walker.js.map +1 -0
- package/dist/index.cjs +1763 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/namespace/index.d.ts +3 -0
- package/dist/namespace/multi-context.d.ts +40 -0
- package/dist/namespace/multi-context.js +72 -0
- package/dist/namespace/multi-context.js.map +1 -0
- package/dist/namespace/resolver.d.ts +33 -0
- package/dist/namespace/resolver.js +85 -0
- package/dist/namespace/resolver.js.map +1 -0
- package/dist/namespace/types.d.ts +41 -0
- package/dist/overcontext.d.ts +7 -0
- package/dist/schema/base.d.ts +29 -0
- package/dist/schema/base.js +24 -0
- package/dist/schema/base.js.map +1 -0
- package/dist/schema/builder.d.ts +28 -0
- package/dist/schema/builder.js +39 -0
- package/dist/schema/builder.js.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/inference.d.ts +49 -0
- package/dist/schema/inference.js +15 -0
- package/dist/schema/inference.js.map +1 -0
- package/dist/schema/registry.d.ts +74 -0
- package/dist/schema/registry.js +117 -0
- package/dist/schema/registry.js.map +1 -0
- package/dist/schema/validation.d.ts +26 -0
- package/dist/schema/validation.js +51 -0
- package/dist/schema/validation.js.map +1 -0
- package/dist/storage/errors.d.ts +35 -0
- package/dist/storage/errors.js +58 -0
- package/dist/storage/errors.js.map +1 -0
- package/dist/storage/events.d.ts +50 -0
- package/dist/storage/filesystem.d.ts +10 -0
- package/dist/storage/filesystem.js +284 -0
- package/dist/storage/filesystem.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/interface.d.ts +109 -0
- package/dist/storage/memory.d.ts +7 -0
- package/dist/storage/memory.js +128 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/observable.d.ts +6 -0
- package/dist/storage/observable.js +98 -0
- package/dist/storage/observable.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { QueryOptions, SortDirection } from './query';
|
|
2
|
+
/**
|
|
3
|
+
* Fluent query builder for constructing search queries.
|
|
4
|
+
*/
|
|
5
|
+
export declare class QueryBuilder {
|
|
6
|
+
private options;
|
|
7
|
+
/**
|
|
8
|
+
* Filter by entity type(s).
|
|
9
|
+
*/
|
|
10
|
+
type(type: string | string[]): this;
|
|
11
|
+
/**
|
|
12
|
+
* Filter by namespace(s).
|
|
13
|
+
*/
|
|
14
|
+
namespace(ns: string | string[]): this;
|
|
15
|
+
/**
|
|
16
|
+
* Filter by specific IDs.
|
|
17
|
+
*/
|
|
18
|
+
ids(ids: string[]): this;
|
|
19
|
+
/**
|
|
20
|
+
* Add text search.
|
|
21
|
+
*/
|
|
22
|
+
search(query: string, fields?: string[]): this;
|
|
23
|
+
/**
|
|
24
|
+
* Enable case-sensitive search.
|
|
25
|
+
*/
|
|
26
|
+
caseSensitive(enabled?: boolean): this;
|
|
27
|
+
/**
|
|
28
|
+
* Add sort field.
|
|
29
|
+
*/
|
|
30
|
+
sortBy(field: string, direction?: SortDirection): this;
|
|
31
|
+
/**
|
|
32
|
+
* Set result limit.
|
|
33
|
+
*/
|
|
34
|
+
limit(n: number): this;
|
|
35
|
+
/**
|
|
36
|
+
* Set result offset.
|
|
37
|
+
*/
|
|
38
|
+
offset(n: number): this;
|
|
39
|
+
/**
|
|
40
|
+
* Set page (calculates offset from limit).
|
|
41
|
+
*/
|
|
42
|
+
page(pageNum: number, pageSize: number): this;
|
|
43
|
+
/**
|
|
44
|
+
* Build the query options.
|
|
45
|
+
*/
|
|
46
|
+
build(): QueryOptions;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Start building a query.
|
|
50
|
+
*/
|
|
51
|
+
export declare const query: () => QueryBuilder;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) {
|
|
3
|
+
Object.defineProperty(obj, key, {
|
|
4
|
+
value: value,
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true
|
|
8
|
+
});
|
|
9
|
+
} else {
|
|
10
|
+
obj[key] = value;
|
|
11
|
+
}
|
|
12
|
+
return obj;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fluent query builder for constructing search queries.
|
|
16
|
+
*/ class QueryBuilder {
|
|
17
|
+
/**
|
|
18
|
+
* Filter by entity type(s).
|
|
19
|
+
*/ type(type) {
|
|
20
|
+
this.options.type = type;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Filter by namespace(s).
|
|
25
|
+
*/ namespace(ns) {
|
|
26
|
+
this.options.namespace = ns;
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Filter by specific IDs.
|
|
31
|
+
*/ ids(ids) {
|
|
32
|
+
this.options.ids = ids;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Add text search.
|
|
37
|
+
*/ search(query, fields) {
|
|
38
|
+
this.options.search = query;
|
|
39
|
+
if (fields) this.options.searchFields = fields;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Enable case-sensitive search.
|
|
44
|
+
*/ caseSensitive(enabled = true) {
|
|
45
|
+
this.options.caseSensitive = enabled;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Add sort field.
|
|
50
|
+
*/ sortBy(field, direction = 'asc') {
|
|
51
|
+
this.options.sort = [
|
|
52
|
+
...this.options.sort || [],
|
|
53
|
+
{
|
|
54
|
+
field,
|
|
55
|
+
direction
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Set result limit.
|
|
62
|
+
*/ limit(n) {
|
|
63
|
+
this.options.limit = n;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Set result offset.
|
|
68
|
+
*/ offset(n) {
|
|
69
|
+
this.options.offset = n;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Set page (calculates offset from limit).
|
|
74
|
+
*/ page(pageNum, pageSize) {
|
|
75
|
+
this.options.limit = pageSize;
|
|
76
|
+
this.options.offset = (pageNum - 1) * pageSize;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build the query options.
|
|
81
|
+
*/ build() {
|
|
82
|
+
return {
|
|
83
|
+
...this.options
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
constructor(){
|
|
87
|
+
_define_property(this, "options", {});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Start building a query.
|
|
92
|
+
*/ const query = ()=>new QueryBuilder();
|
|
93
|
+
|
|
94
|
+
export { QueryBuilder, query };
|
|
95
|
+
//# sourceMappingURL=query-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-builder.js","sources":["../../src/api/query-builder.ts"],"sourcesContent":["import { QueryOptions, SortDirection } from './query';\n\n/**\n * Fluent query builder for constructing search queries.\n */\nexport class QueryBuilder {\n private options: QueryOptions = {};\n\n /**\n * Filter by entity type(s).\n */\n type(type: string | string[]): this {\n this.options.type = type;\n return this;\n }\n\n /**\n * Filter by namespace(s).\n */\n namespace(ns: string | string[]): this {\n this.options.namespace = ns;\n return this;\n }\n\n /**\n * Filter by specific IDs.\n */\n ids(ids: string[]): this {\n this.options.ids = ids;\n return this;\n }\n\n /**\n * Add text search.\n */\n search(query: string, fields?: string[]): this {\n this.options.search = query;\n if (fields) this.options.searchFields = fields;\n return this;\n }\n\n /**\n * Enable case-sensitive search.\n */\n caseSensitive(enabled: boolean = true): this {\n this.options.caseSensitive = enabled;\n return this;\n }\n\n /**\n * Add sort field.\n */\n sortBy(field: string, direction: SortDirection = 'asc'): this {\n this.options.sort = [...(this.options.sort || []), { field, direction }];\n return this;\n }\n\n /**\n * Set result limit.\n */\n limit(n: number): this {\n this.options.limit = n;\n return this;\n }\n\n /**\n * Set result offset.\n */\n offset(n: number): this {\n this.options.offset = n;\n return this;\n }\n\n /**\n * Set page (calculates offset from limit).\n */\n page(pageNum: number, pageSize: number): this {\n this.options.limit = pageSize;\n this.options.offset = (pageNum - 1) * pageSize;\n return this;\n }\n\n /**\n * Build the query options.\n */\n build(): QueryOptions {\n return { ...this.options };\n }\n}\n\n/**\n * Start building a query.\n */\nexport const query = () => new QueryBuilder();\n"],"names":["QueryBuilder","type","options","namespace","ns","ids","search","query","fields","searchFields","caseSensitive","enabled","sortBy","field","direction","sort","limit","n","offset","page","pageNum","pageSize","build"],"mappings":";;;;;;;;;;;;;AAEA;;AAEC,IACM,MAAMA,YAAAA,CAAAA;AAGT;;QAGAC,IAAAA,CAAKA,IAAuB,EAAQ;AAChC,QAAA,IAAI,CAACC,OAAO,CAACD,IAAI,GAAGA,IAAAA;AACpB,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;QAGAE,SAAAA,CAAUC,EAAqB,EAAQ;AACnC,QAAA,IAAI,CAACF,OAAO,CAACC,SAAS,GAAGC,EAAAA;AACzB,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;QAGAC,GAAAA,CAAIA,GAAa,EAAQ;AACrB,QAAA,IAAI,CAACH,OAAO,CAACG,GAAG,GAAGA,GAAAA;AACnB,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;AAEC,QACDC,MAAAA,CAAOC,KAAa,EAAEC,MAAiB,EAAQ;AAC3C,QAAA,IAAI,CAACN,OAAO,CAACI,MAAM,GAAGC,KAAAA;AACtB,QAAA,IAAIC,QAAQ,IAAI,CAACN,OAAO,CAACO,YAAY,GAAGD,MAAAA;AACxC,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;QAGAE,aAAAA,CAAcC,OAAAA,GAAmB,IAAI,EAAQ;AACzC,QAAA,IAAI,CAACT,OAAO,CAACQ,aAAa,GAAGC,OAAAA;AAC7B,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;AAEC,QACDC,MAAAA,CAAOC,KAAa,EAAEC,SAAAA,GAA2B,KAAK,EAAQ;AAC1D,QAAA,IAAI,CAACZ,OAAO,CAACa,IAAI,GAAG;AAAK,YAAA,GAAA,IAAI,CAACb,OAAO,CAACa,IAAI,IAAI,EAAE;AAAG,YAAA;AAAEF,gBAAAA,KAAAA;AAAOC,gBAAAA;AAAU;AAAE,SAAA;AACxE,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;QAGAE,KAAAA,CAAMC,CAAS,EAAQ;AACnB,QAAA,IAAI,CAACf,OAAO,CAACc,KAAK,GAAGC,CAAAA;AACrB,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;QAGAC,MAAAA,CAAOD,CAAS,EAAQ;AACpB,QAAA,IAAI,CAACf,OAAO,CAACgB,MAAM,GAAGD,CAAAA;AACtB,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;AAEC,QACDE,IAAAA,CAAKC,OAAe,EAAEC,QAAgB,EAAQ;AAC1C,QAAA,IAAI,CAACnB,OAAO,CAACc,KAAK,GAAGK,QAAAA;QACrB,IAAI,CAACnB,OAAO,CAACgB,MAAM,GAAG,CAACE,OAAAA,GAAU,CAAA,IAAKC,QAAAA;AACtC,QAAA,OAAO,IAAI;AACf,IAAA;AAEA;;AAEC,QACDC,KAAAA,GAAsB;QAClB,OAAO;YAAE,GAAG,IAAI,CAACpB;AAAQ,SAAA;AAC7B,IAAA;;AAjFA,QAAA,gBAAA,CAAA,IAAA,EAAQA,WAAwB,EAAC,CAAA;;AAkFrC;AAEA;;AAEC,IACM,MAAMK,KAAAA,GAAQ,IAAM,IAAIP,YAAAA;;;;"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseEntity } from '../schema/base';
|
|
2
|
+
export type SortDirection = 'asc' | 'desc';
|
|
3
|
+
export interface SortOption {
|
|
4
|
+
field: string;
|
|
5
|
+
direction: SortDirection;
|
|
6
|
+
}
|
|
7
|
+
export interface QueryOptions {
|
|
8
|
+
/** Filter by entity type(s) */
|
|
9
|
+
type?: string | string[];
|
|
10
|
+
/** Filter by namespace(s) */
|
|
11
|
+
namespace?: string | string[];
|
|
12
|
+
/** Filter by specific IDs */
|
|
13
|
+
ids?: string[];
|
|
14
|
+
/** Text search across name and searchable fields */
|
|
15
|
+
search?: string;
|
|
16
|
+
/** Additional fields to include in search */
|
|
17
|
+
searchFields?: string[];
|
|
18
|
+
/** Case sensitive search (default: false) */
|
|
19
|
+
caseSensitive?: boolean;
|
|
20
|
+
/** Pagination limit */
|
|
21
|
+
limit?: number;
|
|
22
|
+
/** Pagination offset */
|
|
23
|
+
offset?: number;
|
|
24
|
+
/** Sort options */
|
|
25
|
+
sort?: SortOption[];
|
|
26
|
+
}
|
|
27
|
+
export interface QueryResult<T extends BaseEntity> {
|
|
28
|
+
items: T[];
|
|
29
|
+
total: number;
|
|
30
|
+
hasMore: boolean;
|
|
31
|
+
query: QueryOptions;
|
|
32
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BaseEntity } from '../schema/base';
|
|
2
|
+
import { SchemaRegistry } from '../schema/registry';
|
|
3
|
+
import { StorageProvider } from '../storage/interface';
|
|
4
|
+
import { QueryOptions, QueryResult } from './query';
|
|
5
|
+
export interface SearchEngine {
|
|
6
|
+
/**
|
|
7
|
+
* Search entities with filtering, pagination, and sorting.
|
|
8
|
+
*/
|
|
9
|
+
search<T extends BaseEntity>(options: QueryOptions): Promise<QueryResult<T>>;
|
|
10
|
+
/**
|
|
11
|
+
* Quick search by name across all types.
|
|
12
|
+
*/
|
|
13
|
+
quickSearch<T extends BaseEntity>(query: string, options?: Pick<QueryOptions, 'type' | 'namespace' | 'limit'>): Promise<T[]>;
|
|
14
|
+
}
|
|
15
|
+
export interface SearchEngineOptions {
|
|
16
|
+
provider: StorageProvider;
|
|
17
|
+
registry: SchemaRegistry;
|
|
18
|
+
defaultNamespace?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const createSearchEngine: (options: SearchEngineOptions) => SearchEngine;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const createSearchEngine = (options)=>{
|
|
2
|
+
const { provider, registry, defaultNamespace } = options;
|
|
3
|
+
const textMatch = (text, query, caseSensitive)=>{
|
|
4
|
+
if (!text) return false;
|
|
5
|
+
const a = caseSensitive ? text : text.toLowerCase();
|
|
6
|
+
const b = caseSensitive ? query : query.toLowerCase();
|
|
7
|
+
return a.includes(b);
|
|
8
|
+
};
|
|
9
|
+
const matchesSearch = (entity, search, searchFields, caseSensitive)=>{
|
|
10
|
+
// Always search name
|
|
11
|
+
if (textMatch(entity.name, search, caseSensitive)) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
// Search additional fields
|
|
15
|
+
for (const field of searchFields){
|
|
16
|
+
const value = entity[field];
|
|
17
|
+
if (typeof value === 'string' && textMatch(value, search, caseSensitive)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
// Handle arrays of strings (like sounds_like)
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
for (const item of value){
|
|
23
|
+
if (typeof item === 'string' && textMatch(item, search, caseSensitive)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
const sortEntities = (entities, sort)=>{
|
|
32
|
+
return [
|
|
33
|
+
...entities
|
|
34
|
+
].sort((a, b)=>{
|
|
35
|
+
for (const { field, direction } of sort){
|
|
36
|
+
const aVal = a[field];
|
|
37
|
+
const bVal = b[field];
|
|
38
|
+
if (aVal === bVal) continue;
|
|
39
|
+
if (aVal === undefined || aVal === null) return direction === 'asc' ? 1 : -1;
|
|
40
|
+
if (bVal === undefined || bVal === null) return direction === 'asc' ? -1 : 1;
|
|
41
|
+
let cmp;
|
|
42
|
+
if (typeof aVal === 'string' && typeof bVal === 'string') {
|
|
43
|
+
cmp = aVal.localeCompare(bVal);
|
|
44
|
+
} else if (aVal instanceof Date && bVal instanceof Date) {
|
|
45
|
+
cmp = aVal.getTime() - bVal.getTime();
|
|
46
|
+
} else {
|
|
47
|
+
cmp = aVal < bVal ? -1 : 1;
|
|
48
|
+
}
|
|
49
|
+
return direction === 'asc' ? cmp : -cmp;
|
|
50
|
+
}
|
|
51
|
+
return 0;
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
async search (options) {
|
|
56
|
+
const { type, namespace, ids, search, searchFields = [], caseSensitive = false, limit, offset = 0, sort = [
|
|
57
|
+
{
|
|
58
|
+
field: 'name',
|
|
59
|
+
direction: 'asc'
|
|
60
|
+
}
|
|
61
|
+
] } = options;
|
|
62
|
+
// Determine which types to search
|
|
63
|
+
const types = type ? Array.isArray(type) ? type : [
|
|
64
|
+
type
|
|
65
|
+
] : registry.types();
|
|
66
|
+
// Determine which namespaces to search
|
|
67
|
+
const namespaces = namespace ? Array.isArray(namespace) ? namespace : [
|
|
68
|
+
namespace
|
|
69
|
+
] : [
|
|
70
|
+
defaultNamespace
|
|
71
|
+
];
|
|
72
|
+
// Collect entities
|
|
73
|
+
let allEntities = [];
|
|
74
|
+
for (const t of types){
|
|
75
|
+
for (const ns of namespaces){
|
|
76
|
+
const entities = await provider.getAll(t, ns);
|
|
77
|
+
allEntities = allEntities.concat(entities);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Apply ID filter
|
|
81
|
+
if (ids === null || ids === void 0 ? void 0 : ids.length) {
|
|
82
|
+
allEntities = allEntities.filter((e)=>ids.includes(e.id));
|
|
83
|
+
}
|
|
84
|
+
// Apply search filter
|
|
85
|
+
if (search) {
|
|
86
|
+
allEntities = allEntities.filter((e)=>matchesSearch(e, search, searchFields, caseSensitive));
|
|
87
|
+
}
|
|
88
|
+
// Apply sorting
|
|
89
|
+
allEntities = sortEntities(allEntities, sort);
|
|
90
|
+
// Get total before pagination
|
|
91
|
+
const total = allEntities.length;
|
|
92
|
+
// Apply pagination
|
|
93
|
+
const paginated = allEntities.slice(offset, limit ? offset + limit : undefined);
|
|
94
|
+
return {
|
|
95
|
+
items: paginated,
|
|
96
|
+
total,
|
|
97
|
+
hasMore: limit ? offset + limit < total : false,
|
|
98
|
+
query: options
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
async quickSearch (query, options = {}) {
|
|
102
|
+
const result = await this.search({
|
|
103
|
+
...options,
|
|
104
|
+
search: query
|
|
105
|
+
});
|
|
106
|
+
return result.items;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { createSearchEngine };
|
|
112
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sources":["../../src/api/search.ts"],"sourcesContent":["import { BaseEntity } from '../schema/base';\nimport { SchemaRegistry } from '../schema/registry';\nimport { StorageProvider } from '../storage/interface';\nimport { QueryOptions, QueryResult, SortOption } from './query';\n\nexport interface SearchEngine {\n /**\n * Search entities with filtering, pagination, and sorting.\n */\n search<T extends BaseEntity>(options: QueryOptions): Promise<QueryResult<T>>;\n\n /**\n * Quick search by name across all types.\n */\n quickSearch<T extends BaseEntity>(\n query: string,\n options?: Pick<QueryOptions, 'type' | 'namespace' | 'limit'>\n ): Promise<T[]>;\n}\n\nexport interface SearchEngineOptions {\n provider: StorageProvider;\n registry: SchemaRegistry;\n defaultNamespace?: string;\n}\n\nexport const createSearchEngine = (options: SearchEngineOptions): SearchEngine => {\n const { provider, registry, defaultNamespace } = options;\n\n const textMatch = (\n text: string | undefined,\n query: string,\n caseSensitive: boolean\n ): boolean => {\n if (!text) return false;\n const a = caseSensitive ? text : text.toLowerCase();\n const b = caseSensitive ? query : query.toLowerCase();\n return a.includes(b);\n };\n\n const matchesSearch = (\n entity: BaseEntity,\n search: string,\n searchFields: string[],\n caseSensitive: boolean\n ): boolean => {\n // Always search name\n if (textMatch(entity.name, search, caseSensitive)) {\n return true;\n }\n\n // Search additional fields\n for (const field of searchFields) {\n const value = (entity as Record<string, unknown>)[field];\n if (typeof value === 'string' && textMatch(value, search, caseSensitive)) {\n return true;\n }\n // Handle arrays of strings (like sounds_like)\n if (Array.isArray(value)) {\n for (const item of value) {\n if (typeof item === 'string' && textMatch(item, search, caseSensitive)) {\n return true;\n }\n }\n }\n }\n\n return false;\n };\n\n const sortEntities = <T extends BaseEntity>(\n entities: T[],\n sort: SortOption[]\n ): T[] => {\n return [...entities].sort((a, b) => {\n for (const { field, direction } of sort) {\n const aVal = (a as Record<string, unknown>)[field];\n const bVal = (b as Record<string, unknown>)[field];\n\n if (aVal === bVal) continue;\n if (aVal === undefined || aVal === null) return direction === 'asc' ? 1 : -1;\n if (bVal === undefined || bVal === null) return direction === 'asc' ? -1 : 1;\n\n let cmp: number;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n cmp = aVal.localeCompare(bVal);\n } else if (aVal instanceof Date && bVal instanceof Date) {\n cmp = aVal.getTime() - bVal.getTime();\n } else {\n cmp = aVal < bVal ? -1 : 1;\n }\n\n return direction === 'asc' ? cmp : -cmp;\n }\n return 0;\n });\n };\n\n return {\n async search<T extends BaseEntity>(options: QueryOptions): Promise<QueryResult<T>> {\n const {\n type,\n namespace,\n ids,\n search,\n searchFields = [],\n caseSensitive = false,\n limit,\n offset = 0,\n sort = [{ field: 'name', direction: 'asc' }],\n } = options;\n\n // Determine which types to search\n const types = type\n ? (Array.isArray(type) ? type : [type])\n : registry.types();\n\n // Determine which namespaces to search\n const namespaces = namespace\n ? (Array.isArray(namespace) ? namespace : [namespace])\n : [defaultNamespace];\n\n // Collect entities\n let allEntities: T[] = [];\n\n for (const t of types) {\n for (const ns of namespaces) {\n const entities = await provider.getAll<T>(t, ns);\n allEntities = allEntities.concat(entities);\n }\n }\n\n // Apply ID filter\n if (ids?.length) {\n allEntities = allEntities.filter(e => ids.includes(e.id));\n }\n\n // Apply search filter\n if (search) {\n allEntities = allEntities.filter(e =>\n matchesSearch(e, search, searchFields, caseSensitive)\n );\n }\n\n // Apply sorting\n allEntities = sortEntities(allEntities, sort);\n\n // Get total before pagination\n const total = allEntities.length;\n\n // Apply pagination\n const paginated = allEntities.slice(offset, limit ? offset + limit : undefined);\n\n return {\n items: paginated,\n total,\n hasMore: limit ? offset + limit < total : false,\n query: options,\n };\n },\n\n async quickSearch<T extends BaseEntity>(\n query: string,\n options: Pick<QueryOptions, 'type' | 'namespace' | 'limit'> = {}\n ): Promise<T[]> {\n const result = await this.search<T>({\n ...options,\n search: query,\n });\n return result.items;\n },\n };\n};\n"],"names":["createSearchEngine","options","provider","registry","defaultNamespace","textMatch","text","query","caseSensitive","a","toLowerCase","b","includes","matchesSearch","entity","search","searchFields","name","field","value","Array","isArray","item","sortEntities","entities","sort","direction","aVal","bVal","undefined","cmp","localeCompare","Date","getTime","type","namespace","ids","limit","offset","types","namespaces","allEntities","t","ns","getAll","concat","length","filter","e","id","total","paginated","slice","items","hasMore","quickSearch","result"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a slug from a name.
|
|
3
|
+
* "John Doe" -> "john-doe"
|
|
4
|
+
* "API Reference" -> "api-reference"
|
|
5
|
+
*/
|
|
6
|
+
export declare const slugify: (name: string) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Generate a unique ID, appending a number if needed.
|
|
9
|
+
*/
|
|
10
|
+
export declare const generateUniqueId: (baseName: string, exists: (id: string) => Promise<boolean>) => Promise<string>;
|
package/dist/api/slug.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a slug from a name.
|
|
3
|
+
* "John Doe" -> "john-doe"
|
|
4
|
+
* "API Reference" -> "api-reference"
|
|
5
|
+
*/ const slugify = (name)=>{
|
|
6
|
+
const cleaned = name.toLowerCase().trim().replace(/[^\w\s-]/g, '') // Remove non-word chars
|
|
7
|
+
.replace(/[\s_]+/g, '-'); // Replace spaces/underscores
|
|
8
|
+
// Single-pass algorithm to collapse hyphens and trim (avoid ReDoS)
|
|
9
|
+
const result = [];
|
|
10
|
+
let prevWasHyphen = false;
|
|
11
|
+
let startIdx = 0;
|
|
12
|
+
let endIdx = cleaned.length;
|
|
13
|
+
// Skip leading hyphens
|
|
14
|
+
while(startIdx < cleaned.length && cleaned[startIdx] === '-'){
|
|
15
|
+
startIdx++;
|
|
16
|
+
}
|
|
17
|
+
// Skip trailing hyphens
|
|
18
|
+
while(endIdx > startIdx && cleaned[endIdx - 1] === '-'){
|
|
19
|
+
endIdx--;
|
|
20
|
+
}
|
|
21
|
+
// Build result, collapsing consecutive hyphens
|
|
22
|
+
for(let i = startIdx; i < endIdx; i++){
|
|
23
|
+
const char = cleaned[i];
|
|
24
|
+
if (char === '-') {
|
|
25
|
+
if (!prevWasHyphen) {
|
|
26
|
+
result.push('-');
|
|
27
|
+
prevWasHyphen = true;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
result.push(char);
|
|
31
|
+
prevWasHyphen = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result.join('');
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Generate a unique ID, appending a number if needed.
|
|
38
|
+
*/ const generateUniqueId = async (baseName, exists)=>{
|
|
39
|
+
const baseId = slugify(baseName);
|
|
40
|
+
if (!await exists(baseId)) {
|
|
41
|
+
return baseId;
|
|
42
|
+
}
|
|
43
|
+
// Try appending numbers
|
|
44
|
+
for(let i = 2; i <= 100; i++){
|
|
45
|
+
const candidateId = `${baseId}-${i}`;
|
|
46
|
+
if (!await exists(candidateId)) {
|
|
47
|
+
return candidateId;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Fallback to timestamp
|
|
51
|
+
return `${baseId}-${Date.now()}`;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { generateUniqueId, slugify };
|
|
55
|
+
//# sourceMappingURL=slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug.js","sources":["../../src/api/slug.ts"],"sourcesContent":["/**\n * Generate a slug from a name.\n * \"John Doe\" -> \"john-doe\"\n * \"API Reference\" -> \"api-reference\"\n */\nexport const slugify = (name: string): string => {\n const cleaned = name\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '') // Remove non-word chars\n .replace(/[\\s_]+/g, '-'); // Replace spaces/underscores\n \n // Single-pass algorithm to collapse hyphens and trim (avoid ReDoS)\n const result: string[] = [];\n let prevWasHyphen = false;\n let startIdx = 0;\n let endIdx = cleaned.length;\n \n // Skip leading hyphens\n while (startIdx < cleaned.length && cleaned[startIdx] === '-') {\n startIdx++;\n }\n \n // Skip trailing hyphens\n while (endIdx > startIdx && cleaned[endIdx - 1] === '-') {\n endIdx--;\n }\n \n // Build result, collapsing consecutive hyphens\n for (let i = startIdx; i < endIdx; i++) {\n const char = cleaned[i];\n if (char === '-') {\n if (!prevWasHyphen) {\n result.push('-');\n prevWasHyphen = true;\n }\n } else {\n result.push(char);\n prevWasHyphen = false;\n }\n }\n \n return result.join('');\n};\n\n/**\n * Generate a unique ID, appending a number if needed.\n */\nexport const generateUniqueId = async (\n baseName: string,\n exists: (id: string) => Promise<boolean>\n): Promise<string> => {\n const baseId = slugify(baseName);\n\n if (!await exists(baseId)) {\n return baseId;\n }\n\n // Try appending numbers\n for (let i = 2; i <= 100; i++) {\n const candidateId = `${baseId}-${i}`;\n if (!await exists(candidateId)) {\n return candidateId;\n }\n }\n\n // Fallback to timestamp\n return `${baseId}-${Date.now()}`;\n};\n"],"names":["slugify","name","cleaned","toLowerCase","trim","replace","result","prevWasHyphen","startIdx","endIdx","length","i","char","push","join","generateUniqueId","baseName","exists","baseId","candidateId","Date","now"],"mappings":"AAAA;;;;IAKO,MAAMA,OAAAA,GAAU,CAACC,IAAAA,GAAAA;IACpB,MAAMC,OAAAA,GAAUD,IAAAA,CACXE,WAAW,EAAA,CACXC,IAAI,GACJC,OAAO,CAAC,WAAA,EAAa,EAAA,CAAA;KACrBA,OAAO,CAAC,SAAA,EAAW,GAAA,CAAA,CAAA;;AAGxB,IAAA,MAAMC,SAAmB,EAAE;AAC3B,IAAA,IAAIC,aAAAA,GAAgB,KAAA;AACpB,IAAA,IAAIC,QAAAA,GAAW,CAAA;IACf,IAAIC,MAAAA,GAASP,QAAQQ,MAAM;;IAG3B,MAAOF,QAAAA,GAAWN,QAAQQ,MAAM,IAAIR,OAAO,CAACM,QAAAA,CAAS,KAAK,GAAA,CAAK;AAC3DA,QAAAA,QAAAA,EAAAA;AACJ,IAAA;;AAGA,IAAA,MAAOC,SAASD,QAAAA,IAAYN,OAAO,CAACO,MAAAA,GAAS,CAAA,CAAE,KAAK,GAAA,CAAK;AACrDA,QAAAA,MAAAA,EAAAA;AACJ,IAAA;;AAGA,IAAA,IAAK,IAAIE,CAAAA,GAAIH,QAAAA,EAAUG,CAAAA,GAAIF,QAAQE,CAAAA,EAAAA,CAAK;QACpC,MAAMC,IAAAA,GAAOV,OAAO,CAACS,CAAAA,CAAE;AACvB,QAAA,IAAIC,SAAS,GAAA,EAAK;AACd,YAAA,IAAI,CAACL,aAAAA,EAAe;AAChBD,gBAAAA,MAAAA,CAAOO,IAAI,CAAC,GAAA,CAAA;gBACZN,aAAAA,GAAgB,IAAA;AACpB,YAAA;QACJ,CAAA,MAAO;AACHD,YAAAA,MAAAA,CAAOO,IAAI,CAACD,IAAAA,CAAAA;YACZL,aAAAA,GAAgB,KAAA;AACpB,QAAA;AACJ,IAAA;IAEA,OAAOD,MAAAA,CAAOQ,IAAI,CAAC,EAAA,CAAA;AACvB;AAEA;;AAEC,IACM,MAAMC,gBAAAA,GAAmB,OAC5BC,QAAAA,EACAC,MAAAA,GAAAA;AAEA,IAAA,MAAMC,SAASlB,OAAAA,CAAQgB,QAAAA,CAAAA;IAEvB,IAAI,CAAC,MAAMC,MAAAA,CAAOC,MAAAA,CAAAA,EAAS;QACvB,OAAOA,MAAAA;AACX,IAAA;;AAGA,IAAA,IAAK,IAAIP,CAAAA,GAAI,CAAA,EAAGA,CAAAA,IAAK,KAAKA,CAAAA,EAAAA,CAAK;AAC3B,QAAA,MAAMQ,WAAAA,GAAc,CAAA,EAAGD,MAAAA,CAAO,CAAC,EAAEP,CAAAA,CAAAA,CAAG;QACpC,IAAI,CAAC,MAAMM,MAAAA,CAAOE,WAAAA,CAAAA,EAAc;YAC5B,OAAOA,WAAAA;AACX,QAAA;AACJ,IAAA;;AAGA,IAAA,OAAO,GAAGD,MAAAA,CAAO,CAAC,EAAEE,IAAAA,CAAKC,GAAG,EAAA,CAAA,CAAI;AACpC;;;;"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { SchemaMap } from '../schema/registry';
|
|
2
|
+
import { OvercontextAPI } from '../api/context';
|
|
3
|
+
import { OutputFormat } from './formatters';
|
|
4
|
+
export interface CLIBuilderOptions<TSchemas extends SchemaMap> {
|
|
5
|
+
api: OvercontextAPI<TSchemas>;
|
|
6
|
+
defaultFormat?: OutputFormat;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* CLI builder for consumers to create their own CLIs.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const cli = createCLIBuilder({ api });
|
|
13
|
+
*
|
|
14
|
+
* // In your CLI tool:
|
|
15
|
+
* const result = await cli.list({ type: 'person' });
|
|
16
|
+
* console.log(result);
|
|
17
|
+
*/
|
|
18
|
+
export declare const createCLIBuilder: <TSchemas extends SchemaMap>(options: CLIBuilderOptions<TSchemas>) => {
|
|
19
|
+
/**
|
|
20
|
+
* List entities.
|
|
21
|
+
*/
|
|
22
|
+
list: (opts: {
|
|
23
|
+
type: string;
|
|
24
|
+
search?: string;
|
|
25
|
+
limit?: number;
|
|
26
|
+
fields?: string[];
|
|
27
|
+
format?: OutputFormat;
|
|
28
|
+
namespace?: string;
|
|
29
|
+
}) => Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Get a single entity.
|
|
32
|
+
*/
|
|
33
|
+
get: (opts: {
|
|
34
|
+
type: string;
|
|
35
|
+
id: string;
|
|
36
|
+
format?: OutputFormat;
|
|
37
|
+
namespace?: string;
|
|
38
|
+
}) => Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Create an entity.
|
|
41
|
+
*/
|
|
42
|
+
create: (opts: {
|
|
43
|
+
type: string;
|
|
44
|
+
name: string;
|
|
45
|
+
data?: Record<string, unknown>;
|
|
46
|
+
id?: string;
|
|
47
|
+
format?: OutputFormat;
|
|
48
|
+
namespace?: string;
|
|
49
|
+
}) => Promise<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Update an entity.
|
|
52
|
+
*/
|
|
53
|
+
update: (opts: {
|
|
54
|
+
type: string;
|
|
55
|
+
id: string;
|
|
56
|
+
data: Record<string, unknown>;
|
|
57
|
+
format?: OutputFormat;
|
|
58
|
+
namespace?: string;
|
|
59
|
+
}) => Promise<string>;
|
|
60
|
+
/**
|
|
61
|
+
* Delete an entity.
|
|
62
|
+
*/
|
|
63
|
+
delete: (opts: {
|
|
64
|
+
type: string;
|
|
65
|
+
id: string;
|
|
66
|
+
format?: OutputFormat;
|
|
67
|
+
namespace?: string;
|
|
68
|
+
}) => Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* List available entity types.
|
|
71
|
+
*/
|
|
72
|
+
types: () => string[];
|
|
73
|
+
};
|
|
74
|
+
export type CLIBuilder<TSchemas extends SchemaMap> = ReturnType<typeof createCLIBuilder<TSchemas>>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { deleteCommand, updateCommand, createCommand, getCommand, listCommand } from './commands.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI builder for consumers to create their own CLIs.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const cli = createCLIBuilder({ api });
|
|
8
|
+
*
|
|
9
|
+
* // In your CLI tool:
|
|
10
|
+
* const result = await cli.list({ type: 'person' });
|
|
11
|
+
* console.log(result);
|
|
12
|
+
*/ const createCLIBuilder = (options)=>{
|
|
13
|
+
const { api, defaultFormat = 'table' } = options;
|
|
14
|
+
const createContext = (format, namespace)=>({
|
|
15
|
+
api,
|
|
16
|
+
outputFormat: format || defaultFormat,
|
|
17
|
+
namespace
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
/**
|
|
21
|
+
* List entities.
|
|
22
|
+
*/ list: (opts)=>listCommand(createContext(opts.format, opts.namespace), opts),
|
|
23
|
+
/**
|
|
24
|
+
* Get a single entity.
|
|
25
|
+
*/ get: (opts)=>getCommand(createContext(opts.format, opts.namespace), opts),
|
|
26
|
+
/**
|
|
27
|
+
* Create an entity.
|
|
28
|
+
*/ create: (opts)=>createCommand(createContext(opts.format, opts.namespace), opts),
|
|
29
|
+
/**
|
|
30
|
+
* Update an entity.
|
|
31
|
+
*/ update: (opts)=>updateCommand(createContext(opts.format, opts.namespace), opts),
|
|
32
|
+
/**
|
|
33
|
+
* Delete an entity.
|
|
34
|
+
*/ delete: (opts)=>deleteCommand(createContext(opts.format, opts.namespace), opts),
|
|
35
|
+
/**
|
|
36
|
+
* List available entity types.
|
|
37
|
+
*/ types: ()=>api.types()
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { createCLIBuilder };
|
|
42
|
+
//# sourceMappingURL=builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.js","sources":["../../src/cli/builder.ts"],"sourcesContent":["import { SchemaMap } from '../schema/registry';\nimport { OvercontextAPI } from '../api/context';\nimport {\n CommandContext,\n listCommand,\n getCommand,\n createCommand,\n updateCommand,\n deleteCommand,\n} from './commands';\nimport { OutputFormat } from './formatters';\n\nexport interface CLIBuilderOptions<TSchemas extends SchemaMap> {\n api: OvercontextAPI<TSchemas>;\n defaultFormat?: OutputFormat;\n}\n\n/**\n * CLI builder for consumers to create their own CLIs.\n * \n * @example\n * const cli = createCLIBuilder({ api });\n * \n * // In your CLI tool:\n * const result = await cli.list({ type: 'person' });\n * console.log(result);\n */\nexport const createCLIBuilder = <TSchemas extends SchemaMap>(\n options: CLIBuilderOptions<TSchemas>\n) => {\n const { api, defaultFormat = 'table' } = options;\n\n const createContext = (\n format?: OutputFormat,\n namespace?: string\n ): CommandContext<TSchemas> => ({\n api,\n outputFormat: format || defaultFormat,\n namespace,\n });\n\n return {\n /**\n * List entities.\n */\n list: (opts: {\n type: string;\n search?: string;\n limit?: number;\n fields?: string[];\n format?: OutputFormat;\n namespace?: string;\n }) => listCommand(createContext(opts.format, opts.namespace), opts),\n\n /**\n * Get a single entity.\n */\n get: (opts: {\n type: string;\n id: string;\n format?: OutputFormat;\n namespace?: string;\n }) => getCommand(createContext(opts.format, opts.namespace), opts),\n\n /**\n * Create an entity.\n */\n create: (opts: {\n type: string;\n name: string;\n data?: Record<string, unknown>;\n id?: string;\n format?: OutputFormat;\n namespace?: string;\n }) => createCommand(createContext(opts.format, opts.namespace), opts),\n\n /**\n * Update an entity.\n */\n update: (opts: {\n type: string;\n id: string;\n data: Record<string, unknown>;\n format?: OutputFormat;\n namespace?: string;\n }) => updateCommand(createContext(opts.format, opts.namespace), opts),\n\n /**\n * Delete an entity.\n */\n delete: (opts: {\n type: string;\n id: string;\n format?: OutputFormat;\n namespace?: string;\n }) => deleteCommand(createContext(opts.format, opts.namespace), opts),\n\n /**\n * List available entity types.\n */\n types: () => api.types(),\n };\n};\n\nexport type CLIBuilder<TSchemas extends SchemaMap> = ReturnType<\n typeof createCLIBuilder<TSchemas>\n>;\n"],"names":["createCLIBuilder","options","api","defaultFormat","createContext","format","namespace","outputFormat","list","opts","listCommand","get","getCommand","create","createCommand","update","updateCommand","delete","deleteCommand","types"],"mappings":";;AAiBA;;;;;;;;;IAUO,MAAMA,gBAAAA,GAAmB,CAC5BC,OAAAA,GAAAA;AAEA,IAAA,MAAM,EAAEC,GAAG,EAAEC,aAAAA,GAAgB,OAAO,EAAE,GAAGF,OAAAA;AAEzC,IAAA,MAAMG,aAAAA,GAAgB,CAClBC,MAAAA,EACAC,SAAAA,IAC4B;AAC5BJ,YAAAA,GAAAA;AACAK,YAAAA,YAAAA,EAAcF,MAAAA,IAAUF,aAAAA;AACxBG,YAAAA;SACJ,CAAA;IAEA,OAAO;AACH;;YAGAE,IAAAA,EAAM,CAACC,IAAAA,GAODC,WAAAA,CAAYN,aAAAA,CAAcK,KAAKJ,MAAM,EAAEI,IAAAA,CAAKH,SAAS,CAAA,EAAGG,IAAAA,CAAAA;AAE9D;;YAGAE,GAAAA,EAAK,CAACF,IAAAA,GAKAG,UAAAA,CAAWR,aAAAA,CAAcK,KAAKJ,MAAM,EAAEI,IAAAA,CAAKH,SAAS,CAAA,EAAGG,IAAAA,CAAAA;AAE7D;;YAGAI,MAAAA,EAAQ,CAACJ,IAAAA,GAOHK,aAAAA,CAAcV,aAAAA,CAAcK,KAAKJ,MAAM,EAAEI,IAAAA,CAAKH,SAAS,CAAA,EAAGG,IAAAA,CAAAA;AAEhE;;YAGAM,MAAAA,EAAQ,CAACN,IAAAA,GAMHO,aAAAA,CAAcZ,aAAAA,CAAcK,KAAKJ,MAAM,EAAEI,IAAAA,CAAKH,SAAS,CAAA,EAAGG,IAAAA,CAAAA;AAEhE;;YAGAQ,MAAAA,EAAQ,CAACR,IAAAA,GAKHS,aAAAA,CAAcd,aAAAA,CAAcK,KAAKJ,MAAM,EAAEI,IAAAA,CAAKH,SAAS,CAAA,EAAGG,IAAAA,CAAAA;AAEhE;;YAGAU,KAAAA,EAAO,IAAMjB,GAAAA,CAAIiB,KAAK;AAC1B,KAAA;AACJ;;;;"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { SchemaMap } from '../schema/registry';
|
|
2
|
+
import { OvercontextAPI } from '../api/context';
|
|
3
|
+
import { OutputFormat } from './formatters';
|
|
4
|
+
export interface CommandContext<TSchemas extends SchemaMap = SchemaMap> {
|
|
5
|
+
api: OvercontextAPI<TSchemas>;
|
|
6
|
+
outputFormat: OutputFormat;
|
|
7
|
+
namespace?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ListCommandOptions {
|
|
10
|
+
type: string;
|
|
11
|
+
search?: string;
|
|
12
|
+
limit?: number;
|
|
13
|
+
fields?: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a list command handler.
|
|
17
|
+
*/
|
|
18
|
+
export declare const listCommand: <TSchemas extends SchemaMap>(ctx: CommandContext<TSchemas>, options: ListCommandOptions) => Promise<string>;
|
|
19
|
+
export interface GetCommandOptions {
|
|
20
|
+
type: string;
|
|
21
|
+
id: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build a get command handler.
|
|
25
|
+
*/
|
|
26
|
+
export declare const getCommand: <TSchemas extends SchemaMap>(ctx: CommandContext<TSchemas>, options: GetCommandOptions) => Promise<string>;
|
|
27
|
+
export interface CreateCommandOptions {
|
|
28
|
+
type: string;
|
|
29
|
+
name: string;
|
|
30
|
+
data?: Record<string, unknown>;
|
|
31
|
+
id?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a create command handler.
|
|
35
|
+
*/
|
|
36
|
+
export declare const createCommand: <TSchemas extends SchemaMap>(ctx: CommandContext<TSchemas>, options: CreateCommandOptions) => Promise<string>;
|
|
37
|
+
export interface UpdateCommandOptions {
|
|
38
|
+
type: string;
|
|
39
|
+
id: string;
|
|
40
|
+
data: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build an update command handler.
|
|
44
|
+
*/
|
|
45
|
+
export declare const updateCommand: <TSchemas extends SchemaMap>(ctx: CommandContext<TSchemas>, options: UpdateCommandOptions) => Promise<string>;
|
|
46
|
+
export interface DeleteCommandOptions {
|
|
47
|
+
type: string;
|
|
48
|
+
id: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build a delete command handler.
|
|
52
|
+
*/
|
|
53
|
+
export declare const deleteCommand: <TSchemas extends SchemaMap>(ctx: CommandContext<TSchemas>, options: DeleteCommandOptions) => Promise<string>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { formatEntity, formatEntities } from './formatters.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build a list command handler.
|
|
5
|
+
*/ const listCommand = async (ctx, options)=>{
|
|
6
|
+
const result = await ctx.api.search({
|
|
7
|
+
type: options.type,
|
|
8
|
+
namespace: ctx.namespace,
|
|
9
|
+
search: options.search,
|
|
10
|
+
limit: options.limit || 20
|
|
11
|
+
});
|
|
12
|
+
return formatEntities(result.items, {
|
|
13
|
+
format: ctx.outputFormat,
|
|
14
|
+
fields: options.fields
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Build a get command handler.
|
|
19
|
+
*/ const getCommand = async (ctx, options)=>{
|
|
20
|
+
const entity = await ctx.api.get(options.type, options.id, ctx.namespace);
|
|
21
|
+
if (!entity) {
|
|
22
|
+
throw new Error(`Entity not found: ${options.type}/${options.id}`);
|
|
23
|
+
}
|
|
24
|
+
return formatEntity(entity, {
|
|
25
|
+
format: ctx.outputFormat
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Build a create command handler.
|
|
30
|
+
*/ const createCommand = async (ctx, options)=>{
|
|
31
|
+
const entity = await ctx.api.create(options.type, {
|
|
32
|
+
name: options.name,
|
|
33
|
+
...options.data
|
|
34
|
+
}, {
|
|
35
|
+
id: options.id,
|
|
36
|
+
namespace: ctx.namespace
|
|
37
|
+
});
|
|
38
|
+
return `Created ${options.type}: ${entity.id}`;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Build an update command handler.
|
|
42
|
+
*/ const updateCommand = async (ctx, options)=>{
|
|
43
|
+
await ctx.api.update(options.type, options.id, options.data, ctx.namespace);
|
|
44
|
+
return `Updated ${options.type}: ${options.id}`;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Build a delete command handler.
|
|
48
|
+
*/ const deleteCommand = async (ctx, options)=>{
|
|
49
|
+
const deleted = await ctx.api.delete(options.type, options.id, ctx.namespace);
|
|
50
|
+
if (!deleted) {
|
|
51
|
+
throw new Error(`Entity not found: ${options.type}/${options.id}`);
|
|
52
|
+
}
|
|
53
|
+
return `Deleted ${options.type}: ${options.id}`;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { createCommand, deleteCommand, getCommand, listCommand, updateCommand };
|
|
57
|
+
//# sourceMappingURL=commands.js.map
|