@zenera/rag 1.1.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.
@@ -0,0 +1,246 @@
1
+ import { isObject, refName } from "./schema.js";
2
+ const KEYWORDS = new Set(['string', 'number', 'boolean', 'object', 'any', 'unknown', 'null']);
3
+ export class Printer {
4
+ #types;
5
+ #tags = new Map();
6
+ #names = new Map();
7
+ constructor(types) {
8
+ this.#types = types;
9
+ this.#assignNames();
10
+ this.#findTags();
11
+ }
12
+ /** The TypeScript identifier a type id is printed as. */
13
+ identifier(id) {
14
+ return this.#names.get(id) ?? sanitize(id);
15
+ }
16
+ /** An inline type expression — what goes to the right of a colon. */
17
+ signature(schema, depth = 0) {
18
+ if (schema === true || schema === undefined) {
19
+ return 'unknown';
20
+ }
21
+ if (schema === false) {
22
+ return 'never';
23
+ }
24
+ if (!isObject(schema)) {
25
+ return 'unknown';
26
+ }
27
+ const ref = refName(schema);
28
+ if (ref !== undefined) {
29
+ return this.#types[ref] ? this.identifier(ref) : 'unknown';
30
+ }
31
+ if ('const' in schema) {
32
+ return literal(schema.const);
33
+ }
34
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
35
+ return schema.enum.map(literal).join(' | ');
36
+ }
37
+ for (const [key, join] of [
38
+ ['oneOf', ' | '],
39
+ ['anyOf', ' | '],
40
+ ['allOf', ' & '],
41
+ ]) {
42
+ const list = schema[key];
43
+ if (Array.isArray(list) && list.length > 0) {
44
+ const parts = list.map((s) => this.#wrap(s, depth));
45
+ return dedupe(parts).join(join);
46
+ }
47
+ }
48
+ const types = typesOf(schema);
49
+ if (types.length > 1) {
50
+ return dedupe(types.map((t) => this.#scalar(t, schema, depth))).join(' | ');
51
+ }
52
+ return this.#scalar(types[0], schema, depth);
53
+ }
54
+ /** A whole `export interface` / `export type` declaration for one id. */
55
+ declaration(id, options = {}) {
56
+ const schema = this.#types[id];
57
+ if (!schema) {
58
+ return '';
59
+ }
60
+ const name = this.identifier(id);
61
+ const comment = this.#comment(doc(schema), options, '');
62
+ const head = comment ? `${comment}\n` : '';
63
+ const properties = isObject(schema.properties) ? schema.properties : undefined;
64
+ if (!properties) {
65
+ return `${head}export type ${name} = ${this.signature(schema)};\n`;
66
+ }
67
+ return `${head}export interface ${name} ${this.#body(id, schema, options)}\n`;
68
+ }
69
+ // -----------------------------------------------------------------------
70
+ #body(id, schema, options) {
71
+ const properties = isObject(schema.properties) ? schema.properties : {};
72
+ const required = new Set(Array.isArray(schema.required) ? schema.required.filter(isString) : []);
73
+ const tag = this.#tags.get(id);
74
+ const lines = ['{'];
75
+ for (const [key, value] of Object.entries(properties)) {
76
+ if (options.only && !options.only.has(key)) {
77
+ continue;
78
+ }
79
+ const type = tag && tag.property === key ? literal(tag.value) : this.signature(value, 1);
80
+ lines.push(this.#comment(doc(value), options, ' '));
81
+ lines.push(` ${property(key)}${required.has(key) ? '' : '?'}: ${type};`);
82
+ }
83
+ const extra = schema.additionalProperties;
84
+ if (extra !== undefined && extra !== false) {
85
+ lines.push(` [key: string]: ${this.signature(extra, 1)};`);
86
+ }
87
+ lines.push('}');
88
+ // A `/** … */` line is emitted as '' when docs are off; dropping the
89
+ // blanks here keeps the two modes from differing by whitespace.
90
+ return lines.filter((line) => line !== '').join('\n');
91
+ }
92
+ /**
93
+ * The discriminated child rendered inline: `Cat` alone is the interface,
94
+ * but inside `type Pet = …` it is the branch, and the branch is what makes
95
+ * `if (pet.petType === 'cat')` narrow.
96
+ */
97
+ #wrap(schema, depth) {
98
+ return this.signature(schema, depth + 1);
99
+ }
100
+ #scalar(type, schema, depth) {
101
+ switch (type) {
102
+ case 'null':
103
+ return 'null';
104
+ case 'boolean':
105
+ return 'boolean';
106
+ case 'integer':
107
+ case 'number':
108
+ return 'number';
109
+ case 'string':
110
+ return 'string';
111
+ case 'array': {
112
+ const items = schema.prefixItems;
113
+ if (Array.isArray(items)) {
114
+ return `[${items.map((s) => this.signature(s, depth + 1)).join(', ')}]`;
115
+ }
116
+ const inner = this.signature(schema.items, depth + 1);
117
+ return /[ |&]/.test(inner) ? `(${inner})[]` : `${inner}[]`;
118
+ }
119
+ case 'object':
120
+ return this.#inline(schema, depth);
121
+ default:
122
+ return schema.properties || schema.additionalProperties
123
+ ? this.#inline(schema, depth)
124
+ : 'unknown';
125
+ }
126
+ }
127
+ /** An object with no name of its own, printed where it stands. */
128
+ #inline(schema, depth) {
129
+ const properties = isObject(schema.properties) ? schema.properties : undefined;
130
+ if (!properties) {
131
+ const extra = schema.additionalProperties;
132
+ return extra === undefined || extra === true || extra === false
133
+ ? 'Record<string, unknown>'
134
+ : `Record<string, ${this.signature(extra, depth + 1)}>`;
135
+ }
136
+ // Past a few levels an inline object stops being readable and the
137
+ // graph has a named node for it anyway.
138
+ if (depth >= 3) {
139
+ return 'Record<string, unknown>';
140
+ }
141
+ const required = new Set(Array.isArray(schema.required) ? schema.required.filter(isString) : []);
142
+ const parts = Object.entries(properties).map(([key, value]) => `${property(key)}${required.has(key) ? '' : '?'}: ${this.signature(value, depth + 1)}`);
143
+ return parts.length > 0 ? `{ ${parts.join('; ')} }` : 'Record<string, unknown>';
144
+ }
145
+ #comment(text, options, indent) {
146
+ if (!options.docs || !text) {
147
+ return '';
148
+ }
149
+ const max = options.maxDoc ?? 200;
150
+ const one = text.replace(/\s+/g, ' ').trim();
151
+ const cut = one.length > max ? `${one.slice(0, max - 1)}…` : one;
152
+ return `${indent}/** ${cut.replace(/\*\//g, '*\u200b/')} */`;
153
+ }
154
+ /** Ids differ, identifiers may not; the second claimant is suffixed. */
155
+ #assignNames() {
156
+ const taken = new Set();
157
+ for (const id of Object.keys(this.#types)) {
158
+ let name = sanitize(id);
159
+ for (let n = 2; taken.has(name); n++) {
160
+ name = `${sanitize(id)}${n}`;
161
+ }
162
+ taken.add(name);
163
+ this.#names.set(id, name);
164
+ }
165
+ }
166
+ /**
167
+ * Both spellings of a discriminated union: the tag beside a `oneOf`, and
168
+ * the tag on a base type the children `allOf` into.
169
+ */
170
+ #findTags() {
171
+ for (const [id, schema] of Object.entries(this.#types)) {
172
+ const discriminator = schema.discriminator;
173
+ if (!isObject(discriminator) || typeof discriminator.propertyName !== 'string') {
174
+ continue;
175
+ }
176
+ const property = discriminator.propertyName;
177
+ const mapping = isObject(discriminator.mapping) ? discriminator.mapping : {};
178
+ for (const [value, target] of Object.entries(mapping)) {
179
+ const child = typeof target === 'string' ? tail(target) : undefined;
180
+ if (child && this.#types[child]) {
181
+ this.#tags.set(child, { property, value });
182
+ }
183
+ }
184
+ for (const child of this.#children(id, schema)) {
185
+ if (!this.#tags.has(child)) {
186
+ this.#tags.set(child, { property, value: child });
187
+ }
188
+ }
189
+ }
190
+ }
191
+ #children(id, schema) {
192
+ const branches = [schema.oneOf, schema.anyOf].filter(Array.isArray).flat();
193
+ const named = branches.map(refName).filter(isString);
194
+ if (named.length > 0) {
195
+ return named;
196
+ }
197
+ // The `allOf` idiom: the base declares the tag, the children point back.
198
+ return Object.entries(this.#types)
199
+ .filter(([, other]) => (Array.isArray(other.allOf) ? other.allOf : []).some((s) => refName(s) === id))
200
+ .map(([other]) => other);
201
+ }
202
+ }
203
+ // ---------------------------------------------------------------------------
204
+ const isString = (v) => typeof v === 'string';
205
+ function typesOf(schema) {
206
+ const type = schema.type;
207
+ if (Array.isArray(type)) {
208
+ return type.filter(isString);
209
+ }
210
+ return [isString(type) ? type : undefined];
211
+ }
212
+ function dedupe(parts) {
213
+ return [...new Set(parts)];
214
+ }
215
+ function literal(value) {
216
+ if (typeof value === 'string') {
217
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
218
+ }
219
+ if (value === null) {
220
+ return 'null';
221
+ }
222
+ if (typeof value === 'number' || typeof value === 'boolean') {
223
+ return String(value);
224
+ }
225
+ return 'unknown';
226
+ }
227
+ /** A key that is not a plain identifier has to be quoted. */
228
+ export function property(key) {
229
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : `'${key.replace(/'/g, "\\'")}'`;
230
+ }
231
+ function doc(schema) {
232
+ if (!isObject(schema)) {
233
+ return '';
234
+ }
235
+ const description = schema.description ?? schema.title;
236
+ return isString(description) ? description : '';
237
+ }
238
+ function tail(pointer) {
239
+ return decodeURIComponent((pointer.split('/').pop() ?? '').replace(/~1/g, '/').replace(/~0/g, '~'));
240
+ }
241
+ function sanitize(id) {
242
+ const cleaned = id.replace(/[^A-Za-z0-9_$]+/g, '_').replace(/^_+/, '');
243
+ const safe = /^[A-Za-z_$]/.test(cleaned) ? cleaned : `T${cleaned}`;
244
+ return KEYWORDS.has(safe) ? `${safe}_` : safe || 'Anonymous';
245
+ }
246
+ //# sourceMappingURL=typescript.js.map
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@zenera/rag",
3
+ "version": "1.1.0",
4
+ "description": "Retrieval over API descriptions: openapi/swagger documents as a searchable graph.",
5
+ "keywords": [
6
+ "agents",
7
+ "ai-agents",
8
+ "llm",
9
+ "openapi",
10
+ "rag",
11
+ "vector-search"
12
+ ],
13
+ "license": "MIT",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "author": "Andrey Ryabov",
18
+ "homepage": "https://github.com/andreyryabov/ZeneraNeo#readme",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/andreyryabov/ZeneraNeo.git",
22
+ "directory": "packages/rag"
23
+ },
24
+ "bugs": "https://github.com/andreyryabov/ZeneraNeo/issues",
25
+ "type": "module",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ },
31
+ "./command": {
32
+ "types": "./dist/command.d.ts",
33
+ "default": "./dist/command.js"
34
+ },
35
+ "./tools": {
36
+ "types": "./dist/schema/tools.d.ts",
37
+ "default": "./dist/schema/tools.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "!dist/**/*.map"
43
+ ],
44
+ "engines": {
45
+ "node": ">=24"
46
+ },
47
+ "scripts": {
48
+ "build": "tsc -b",
49
+ "clean": "tsc -b --clean && rm -rf dist",
50
+ "prepack": "tsc -b"
51
+ },
52
+ "dependencies": {
53
+ "@apidevtools/swagger-parser": "^12.0.0",
54
+ "@lancedb/lancedb": "^0.38.0",
55
+ "graphology": "^0.26.0",
56
+ "@zenera/cli": "^1.1.0",
57
+ "@zenera/neo": "^1.1.0"
58
+ }
59
+ }