@semilayer/codegen 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.
- package/dist/index.d.ts +34 -0
- package/dist/index.js +265 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SemiLayerConfig, LensConfig, FieldConfig } from '@semilayer/core';
|
|
2
|
+
|
|
3
|
+
interface GenerateOptions {
|
|
4
|
+
/** Pre-parsed config object or path to config file */
|
|
5
|
+
config: SemiLayerConfig | string;
|
|
6
|
+
/** Output directory (default: './generated/semilayer') */
|
|
7
|
+
outDir?: string;
|
|
8
|
+
/** Write files to disk (default: true). Set false to get output only. */
|
|
9
|
+
write?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface GenerateResult {
|
|
12
|
+
files: Array<{
|
|
13
|
+
path: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}>;
|
|
16
|
+
lenses: string[];
|
|
17
|
+
durationMs: number;
|
|
18
|
+
}
|
|
19
|
+
declare function generate(opts: GenerateOptions): Promise<GenerateResult>;
|
|
20
|
+
|
|
21
|
+
declare function emitMetadata(lenses: Record<string, LensConfig>): string;
|
|
22
|
+
|
|
23
|
+
declare function emitLensClients(lenses: Record<string, LensConfig>): string;
|
|
24
|
+
|
|
25
|
+
declare function emitRootClient(lensNames: string[]): string;
|
|
26
|
+
|
|
27
|
+
declare function emitIndexFile(lensNames: string[]): string;
|
|
28
|
+
|
|
29
|
+
/** 'articles' → 'Articles', 'user_profiles' → 'UserProfiles' */
|
|
30
|
+
declare function pascalCase(name: string): string;
|
|
31
|
+
/** Map FieldConfig.type to TypeScript type string */
|
|
32
|
+
declare function fieldToTsType(field: FieldConfig): string;
|
|
33
|
+
|
|
34
|
+
export { type GenerateOptions, type GenerateResult, emitIndexFile, emitLensClients, emitMetadata, emitRootClient, fieldToTsType, generate, pascalCase };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// src/generator.ts
|
|
2
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { validateConfig } from "@semilayer/core";
|
|
5
|
+
|
|
6
|
+
// src/utils.ts
|
|
7
|
+
function pascalCase(name) {
|
|
8
|
+
return name.replace(/[-_]+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
9
|
+
}
|
|
10
|
+
function fieldToTsType(field) {
|
|
11
|
+
switch (field.type) {
|
|
12
|
+
case "text":
|
|
13
|
+
return "string";
|
|
14
|
+
case "number":
|
|
15
|
+
return "number";
|
|
16
|
+
case "boolean":
|
|
17
|
+
return "boolean";
|
|
18
|
+
case "date":
|
|
19
|
+
return "string";
|
|
20
|
+
// ISO 8601 from JSON
|
|
21
|
+
case "json":
|
|
22
|
+
return "unknown";
|
|
23
|
+
case "enum":
|
|
24
|
+
if (field.values && field.values.length > 0) {
|
|
25
|
+
return field.values.map((v) => `'${v}'`).join(" | ");
|
|
26
|
+
}
|
|
27
|
+
return "string";
|
|
28
|
+
case "relation":
|
|
29
|
+
return "number | string";
|
|
30
|
+
// FK — could be either
|
|
31
|
+
default:
|
|
32
|
+
return "unknown";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function fileHeader() {
|
|
36
|
+
return "// Auto-generated by @semilayer/codegen \u2014 do not edit\n";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/emitters/metadata.ts
|
|
40
|
+
function emitMetadata(lenses) {
|
|
41
|
+
const lines = [fileHeader()];
|
|
42
|
+
for (const [name, lens] of Object.entries(lenses)) {
|
|
43
|
+
const typeName = `${pascalCase(name)}Metadata`;
|
|
44
|
+
lines.push(`export interface ${typeName} {`);
|
|
45
|
+
for (const [fieldName, field] of Object.entries(lens.fields)) {
|
|
46
|
+
lines.push(` ${fieldName}: ${fieldToTsType(field)}`);
|
|
47
|
+
}
|
|
48
|
+
lines.push("}");
|
|
49
|
+
lines.push("");
|
|
50
|
+
}
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/emitters/lens-client.ts
|
|
55
|
+
var FACET_IMPORTS = {
|
|
56
|
+
search: ["SearchParams", "SearchResponse"],
|
|
57
|
+
similar: ["SimilarParams", "SimilarResponse"]
|
|
58
|
+
};
|
|
59
|
+
var QUERY_IMPORTS = ["QueryParams", "QueryResponse"];
|
|
60
|
+
var STREAM_IMPORTS = ["StreamSearchParams", "SearchResult"];
|
|
61
|
+
var STREAM_QUERY_IMPORTS = ["StreamQueryParams"];
|
|
62
|
+
var STREAM_SUBSCRIBE_IMPORTS = ["SubscribeParams", "StreamEvent"];
|
|
63
|
+
function hasQueryEnabled(lens) {
|
|
64
|
+
return !!lens.rules && "query" in lens.rules;
|
|
65
|
+
}
|
|
66
|
+
function streamModesFor(lens) {
|
|
67
|
+
const rules = lens.rules?.stream;
|
|
68
|
+
if (rules?.enabled === false) return { chunked: false, live: false };
|
|
69
|
+
const modes = rules?.modes;
|
|
70
|
+
if (!modes) return { chunked: true, live: true };
|
|
71
|
+
return {
|
|
72
|
+
chunked: modes.includes("chunked"),
|
|
73
|
+
live: modes.includes("live")
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function emitLensClients(lenses) {
|
|
77
|
+
const clientImports = /* @__PURE__ */ new Set(["BeamClient"]);
|
|
78
|
+
const metadataImports = [];
|
|
79
|
+
for (const [name, lens] of Object.entries(lenses)) {
|
|
80
|
+
metadataImports.push(`${pascalCase(name)}Metadata`);
|
|
81
|
+
const hasQuery = hasQueryEnabled(lens);
|
|
82
|
+
if (hasQuery) {
|
|
83
|
+
for (const i of QUERY_IMPORTS) clientImports.add(i);
|
|
84
|
+
}
|
|
85
|
+
for (const facetName of Object.keys(lens.facets)) {
|
|
86
|
+
const imports = FACET_IMPORTS[facetName];
|
|
87
|
+
if (imports) {
|
|
88
|
+
for (const i of imports) clientImports.add(i);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const modes = streamModesFor(lens);
|
|
92
|
+
const hasSearchFacet = "search" in lens.facets;
|
|
93
|
+
const emitsChunkedSearch = modes.chunked && hasSearchFacet;
|
|
94
|
+
const emitsChunkedQuery = modes.chunked && hasQuery;
|
|
95
|
+
const emitsLive = modes.live;
|
|
96
|
+
if (emitsChunkedSearch) {
|
|
97
|
+
for (const i of STREAM_IMPORTS) clientImports.add(i);
|
|
98
|
+
}
|
|
99
|
+
if (emitsChunkedQuery) {
|
|
100
|
+
for (const i of STREAM_QUERY_IMPORTS) clientImports.add(i);
|
|
101
|
+
}
|
|
102
|
+
if (emitsLive) {
|
|
103
|
+
for (const i of STREAM_SUBSCRIBE_IMPORTS) clientImports.add(i);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const lines = [fileHeader()];
|
|
107
|
+
const typeImports = [...clientImports].filter((i) => i !== "BeamClient");
|
|
108
|
+
if (typeImports.length > 0) {
|
|
109
|
+
lines.push(`import type { ${typeImports.join(", ")} } from '@semilayer/client'`);
|
|
110
|
+
}
|
|
111
|
+
lines.push(`import { BeamClient } from '@semilayer/client'`);
|
|
112
|
+
lines.push(`import type { ${metadataImports.join(", ")} } from './metadata.js'`);
|
|
113
|
+
lines.push("");
|
|
114
|
+
for (const [name, lens] of Object.entries(lenses)) {
|
|
115
|
+
const className = `${pascalCase(name)}Lens`;
|
|
116
|
+
const metaType = `${pascalCase(name)}Metadata`;
|
|
117
|
+
lines.push(`export class ${className} {`);
|
|
118
|
+
lines.push(` constructor(private readonly client: BeamClient) {}`);
|
|
119
|
+
if (hasQueryEnabled(lens)) {
|
|
120
|
+
lines.push("");
|
|
121
|
+
lines.push(` async query(params?: QueryParams): Promise<QueryResponse<${metaType}>> {`);
|
|
122
|
+
lines.push(` return this.client.query<${metaType}>('${name}', params)`);
|
|
123
|
+
lines.push(` }`);
|
|
124
|
+
}
|
|
125
|
+
for (const facetName of Object.keys(lens.facets)) {
|
|
126
|
+
lines.push("");
|
|
127
|
+
switch (facetName) {
|
|
128
|
+
case "search":
|
|
129
|
+
lines.push(` async search(params: SearchParams): Promise<SearchResponse<${metaType}>> {`);
|
|
130
|
+
lines.push(` return this.client.search<${metaType}>('${name}', params)`);
|
|
131
|
+
lines.push(` }`);
|
|
132
|
+
break;
|
|
133
|
+
case "similar":
|
|
134
|
+
lines.push(` async similar(params: SimilarParams): Promise<SimilarResponse<${metaType}>> {`);
|
|
135
|
+
lines.push(` return this.client.similar<${metaType}>('${name}', params)`);
|
|
136
|
+
lines.push(` }`);
|
|
137
|
+
break;
|
|
138
|
+
default:
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const modes = streamModesFor(lens);
|
|
143
|
+
const hasSearchFacet = "search" in lens.facets;
|
|
144
|
+
const hasQuery = hasQueryEnabled(lens);
|
|
145
|
+
const emitsChunkedSearch = modes.chunked && hasSearchFacet;
|
|
146
|
+
const emitsChunkedQuery = modes.chunked && hasQuery;
|
|
147
|
+
const emitsLiveSub = modes.live;
|
|
148
|
+
const emitsStreamNamespace = emitsChunkedSearch || emitsChunkedQuery || emitsLiveSub;
|
|
149
|
+
if (emitsStreamNamespace) {
|
|
150
|
+
lines.push("");
|
|
151
|
+
lines.push(` /** Streaming surface \u2014 chunked results, live tail. */`);
|
|
152
|
+
lines.push(` readonly stream = {`);
|
|
153
|
+
if (emitsChunkedSearch) {
|
|
154
|
+
lines.push(
|
|
155
|
+
` search: (params: StreamSearchParams): AsyncIterable<SearchResult<${metaType}>> =>`
|
|
156
|
+
);
|
|
157
|
+
lines.push(` this.client.stream.search<${metaType}>('${name}', params),`);
|
|
158
|
+
}
|
|
159
|
+
if (emitsChunkedQuery) {
|
|
160
|
+
lines.push(
|
|
161
|
+
` query: (params: StreamQueryParams): AsyncIterable<${metaType}> =>`
|
|
162
|
+
);
|
|
163
|
+
lines.push(` this.client.stream.query<${metaType}>('${name}', params),`);
|
|
164
|
+
}
|
|
165
|
+
if (emitsLiveSub) {
|
|
166
|
+
lines.push(
|
|
167
|
+
` subscribe: (params?: SubscribeParams): AsyncIterable<StreamEvent<${metaType}>> =>`
|
|
168
|
+
);
|
|
169
|
+
lines.push(` this.client.stream.subscribe<${metaType}>('${name}', params),`);
|
|
170
|
+
}
|
|
171
|
+
lines.push(` }`);
|
|
172
|
+
}
|
|
173
|
+
if (emitsLiveSub) {
|
|
174
|
+
lines.push("");
|
|
175
|
+
lines.push(
|
|
176
|
+
` /** Observe one record \u2014 first yield is the current state, subsequent yields are per-subscription deltas. */`
|
|
177
|
+
);
|
|
178
|
+
lines.push(` observe(recordId: string): AsyncIterable<${metaType}> {`);
|
|
179
|
+
lines.push(` return this.client.observe<${metaType}>('${name}', recordId)`);
|
|
180
|
+
lines.push(` }`);
|
|
181
|
+
}
|
|
182
|
+
lines.push("}");
|
|
183
|
+
lines.push("");
|
|
184
|
+
}
|
|
185
|
+
return lines.join("\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/emitters/root-client.ts
|
|
189
|
+
function emitRootClient(lensNames) {
|
|
190
|
+
const lines = [fileHeader()];
|
|
191
|
+
lines.push(`import { BeamClient, type BeamConfig } from '@semilayer/client'`);
|
|
192
|
+
const lensImports = lensNames.map((n) => `${pascalCase(n)}Lens`).join(", ");
|
|
193
|
+
lines.push(`import { ${lensImports} } from './lenses.js'`);
|
|
194
|
+
lines.push("");
|
|
195
|
+
lines.push("export class Beam {");
|
|
196
|
+
for (const name of lensNames) {
|
|
197
|
+
lines.push(` readonly ${name}: ${pascalCase(name)}Lens`);
|
|
198
|
+
}
|
|
199
|
+
lines.push("");
|
|
200
|
+
lines.push(" constructor(config: BeamConfig) {");
|
|
201
|
+
lines.push(" const client = new BeamClient(config)");
|
|
202
|
+
for (const name of lensNames) {
|
|
203
|
+
lines.push(` this.${name} = new ${pascalCase(name)}Lens(client)`);
|
|
204
|
+
}
|
|
205
|
+
lines.push(" }");
|
|
206
|
+
lines.push("}");
|
|
207
|
+
lines.push("");
|
|
208
|
+
lines.push("export function createBeam(config: BeamConfig): Beam {");
|
|
209
|
+
lines.push(" return new Beam(config)");
|
|
210
|
+
lines.push("}");
|
|
211
|
+
lines.push("");
|
|
212
|
+
return lines.join("\n");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/emitters/index-file.ts
|
|
216
|
+
function emitIndexFile(lensNames) {
|
|
217
|
+
const lines = [fileHeader()];
|
|
218
|
+
lines.push(`export { Beam, createBeam } from './client.js'`);
|
|
219
|
+
const lensExports = lensNames.map((n) => `${pascalCase(n)}Lens`).join(", ");
|
|
220
|
+
lines.push(`export { ${lensExports} } from './lenses.js'`);
|
|
221
|
+
const metaExports = lensNames.map((n) => `${pascalCase(n)}Metadata`).join(", ");
|
|
222
|
+
lines.push(`export type { ${metaExports} } from './metadata.js'`);
|
|
223
|
+
lines.push("");
|
|
224
|
+
return lines.join("\n");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/generator.ts
|
|
228
|
+
async function generate(opts) {
|
|
229
|
+
const start = Date.now();
|
|
230
|
+
let config;
|
|
231
|
+
if (typeof opts.config === "string") {
|
|
232
|
+
const mod = await import(opts.config);
|
|
233
|
+
config = mod.default ?? mod;
|
|
234
|
+
} else {
|
|
235
|
+
config = opts.config;
|
|
236
|
+
}
|
|
237
|
+
validateConfig(config);
|
|
238
|
+
const lensNames = Object.keys(config.lenses);
|
|
239
|
+
const outDir = opts.outDir ?? "./generated/semilayer";
|
|
240
|
+
const files = [
|
|
241
|
+
{ path: join(outDir, "metadata.ts"), content: emitMetadata(config.lenses) },
|
|
242
|
+
{ path: join(outDir, "lenses.ts"), content: emitLensClients(config.lenses) },
|
|
243
|
+
{ path: join(outDir, "client.ts"), content: emitRootClient(lensNames) },
|
|
244
|
+
{ path: join(outDir, "index.ts"), content: emitIndexFile(lensNames) }
|
|
245
|
+
];
|
|
246
|
+
if (opts.write !== false) {
|
|
247
|
+
await mkdir(outDir, { recursive: true });
|
|
248
|
+
await Promise.all(files.map((f) => writeFile(f.path, f.content, "utf-8")));
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
files,
|
|
252
|
+
lenses: lensNames,
|
|
253
|
+
durationMs: Date.now() - start
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
export {
|
|
257
|
+
emitIndexFile,
|
|
258
|
+
emitLensClients,
|
|
259
|
+
emitMetadata,
|
|
260
|
+
emitRootClient,
|
|
261
|
+
fieldToTsType,
|
|
262
|
+
generate,
|
|
263
|
+
pascalCase
|
|
264
|
+
};
|
|
265
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generator.ts","../src/utils.ts","../src/emitters/metadata.ts","../src/emitters/lens-client.ts","../src/emitters/root-client.ts","../src/emitters/index-file.ts"],"sourcesContent":["import { writeFile, mkdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { SemiLayerConfig } from '@semilayer/core'\nimport { validateConfig } from '@semilayer/core'\nimport { emitMetadata } from './emitters/metadata.js'\nimport { emitLensClients } from './emitters/lens-client.js'\nimport { emitRootClient } from './emitters/root-client.js'\nimport { emitIndexFile } from './emitters/index-file.js'\n\nexport interface GenerateOptions {\n /** Pre-parsed config object or path to config file */\n config: SemiLayerConfig | string\n /** Output directory (default: './generated/semilayer') */\n outDir?: string\n /** Write files to disk (default: true). Set false to get output only. */\n write?: boolean\n}\n\nexport interface GenerateResult {\n files: Array<{ path: string; content: string }>\n lenses: string[]\n durationMs: number\n}\n\nexport async function generate(opts: GenerateOptions): Promise<GenerateResult> {\n const start = Date.now()\n\n // Load config\n let config: SemiLayerConfig\n if (typeof opts.config === 'string') {\n const mod = await import(opts.config)\n config = mod.default ?? mod\n } else {\n config = opts.config\n }\n\n // Validate (throws on invalid config)\n validateConfig(config)\n\n const lensNames = Object.keys(config.lenses)\n const outDir = opts.outDir ?? './generated/semilayer'\n\n // Generate file contents\n const files = [\n { path: join(outDir, 'metadata.ts'), content: emitMetadata(config.lenses) },\n { path: join(outDir, 'lenses.ts'), content: emitLensClients(config.lenses) },\n { path: join(outDir, 'client.ts'), content: emitRootClient(lensNames) },\n { path: join(outDir, 'index.ts'), content: emitIndexFile(lensNames) },\n ]\n\n // Write to disk\n if (opts.write !== false) {\n await mkdir(outDir, { recursive: true })\n await Promise.all(files.map((f) => writeFile(f.path, f.content, 'utf-8')))\n }\n\n return {\n files,\n lenses: lensNames,\n durationMs: Date.now() - start,\n }\n}\n","import type { FieldConfig } from '@semilayer/core'\n\n/** 'articles' → 'Articles', 'user_profiles' → 'UserProfiles' */\nexport function pascalCase(name: string): string {\n return name\n .replace(/[-_]+(.)/g, (_, c: string) => c.toUpperCase())\n .replace(/^(.)/, (_, c: string) => c.toUpperCase())\n}\n\n/** Map FieldConfig.type to TypeScript type string */\nexport function fieldToTsType(field: FieldConfig): string {\n switch (field.type) {\n case 'text':\n return 'string'\n case 'number':\n return 'number'\n case 'boolean':\n return 'boolean'\n case 'date':\n return 'string' // ISO 8601 from JSON\n case 'json':\n return 'unknown'\n case 'enum':\n if (field.values && field.values.length > 0) {\n return field.values.map((v) => `'${v}'`).join(' | ')\n }\n return 'string'\n case 'relation':\n return 'number | string' // FK — could be either\n default:\n return 'unknown'\n }\n}\n\nexport function fileHeader(): string {\n return '// Auto-generated by @semilayer/codegen — do not edit\\n'\n}\n","import type { LensConfig } from '@semilayer/core'\nimport { pascalCase, fieldToTsType, fileHeader } from '../utils.js'\n\nexport function emitMetadata(lenses: Record<string, LensConfig>): string {\n const lines: string[] = [fileHeader()]\n\n for (const [name, lens] of Object.entries(lenses)) {\n const typeName = `${pascalCase(name)}Metadata`\n lines.push(`export interface ${typeName} {`)\n\n // Fields keys/types ARE the output schema — no resolution needed\n for (const [fieldName, field] of Object.entries(lens.fields)) {\n lines.push(` ${fieldName}: ${fieldToTsType(field)}`)\n }\n\n lines.push('}')\n lines.push('')\n }\n\n return lines.join('\\n')\n}\n","import type { LensConfig } from '@semilayer/core'\nimport { pascalCase, fileHeader } from '../utils.js'\n\nconst FACET_IMPORTS: Record<string, string[]> = {\n search: ['SearchParams', 'SearchResponse'],\n similar: ['SimilarParams', 'SimilarResponse'],\n}\n\nconst QUERY_IMPORTS = ['QueryParams', 'QueryResponse']\n\n/** Imports needed for the streaming surface when a lens emits any `stream.*` method. */\nconst STREAM_IMPORTS = ['StreamSearchParams', 'SearchResult']\nconst STREAM_QUERY_IMPORTS = ['StreamQueryParams']\nconst STREAM_SUBSCRIBE_IMPORTS = ['SubscribeParams', 'StreamEvent']\n\nfunction hasQueryEnabled(lens: LensConfig): boolean {\n return !!lens.rules && 'query' in lens.rules\n}\n\n/**\n * Which streaming modes should be emitted for this lens?\n *\n * - `rules.stream.enabled === false` → neither mode\n * - `rules.stream.modes` explicitly set → only those modes\n * - otherwise (default) → both chunked and live\n */\nfunction streamModesFor(lens: LensConfig): { chunked: boolean; live: boolean } {\n const rules = lens.rules?.stream\n if (rules?.enabled === false) return { chunked: false, live: false }\n const modes = rules?.modes\n if (!modes) return { chunked: true, live: true }\n return {\n chunked: modes.includes('chunked'),\n live: modes.includes('live'),\n }\n}\n\nexport function emitLensClients(lenses: Record<string, LensConfig>): string {\n // Collect all needed imports\n const clientImports = new Set<string>(['BeamClient'])\n const metadataImports: string[] = []\n\n for (const [name, lens] of Object.entries(lenses)) {\n metadataImports.push(`${pascalCase(name)}Metadata`)\n const hasQuery = hasQueryEnabled(lens)\n if (hasQuery) {\n for (const i of QUERY_IMPORTS) clientImports.add(i)\n }\n for (const facetName of Object.keys(lens.facets)) {\n const imports = FACET_IMPORTS[facetName]\n if (imports) {\n for (const i of imports) clientImports.add(i)\n }\n }\n\n // Streaming imports — only if the lens emits at least one stream member\n const modes = streamModesFor(lens)\n const hasSearchFacet = 'search' in lens.facets\n const emitsChunkedSearch = modes.chunked && hasSearchFacet\n const emitsChunkedQuery = modes.chunked && hasQuery\n const emitsLive = modes.live\n\n if (emitsChunkedSearch) {\n for (const i of STREAM_IMPORTS) clientImports.add(i)\n }\n if (emitsChunkedQuery) {\n for (const i of STREAM_QUERY_IMPORTS) clientImports.add(i)\n }\n if (emitsLive) {\n for (const i of STREAM_SUBSCRIBE_IMPORTS) clientImports.add(i)\n }\n }\n\n const lines: string[] = [fileHeader()]\n\n // Import types from @semilayer/client\n const typeImports = [...clientImports].filter((i) => i !== 'BeamClient')\n if (typeImports.length > 0) {\n lines.push(`import type { ${typeImports.join(', ')} } from '@semilayer/client'`)\n }\n lines.push(`import { BeamClient } from '@semilayer/client'`)\n lines.push(`import type { ${metadataImports.join(', ')} } from './metadata.js'`)\n lines.push('')\n\n // Emit per-lens class\n for (const [name, lens] of Object.entries(lenses)) {\n const className = `${pascalCase(name)}Lens`\n const metaType = `${pascalCase(name)}Metadata`\n\n lines.push(`export class ${className} {`)\n lines.push(` constructor(private readonly client: BeamClient) {}`)\n\n // ── HTTP ops ──────────────────────────────────────────\n\n // query() only when rules.query is defined\n if (hasQueryEnabled(lens)) {\n lines.push('')\n lines.push(` async query(params?: QueryParams): Promise<QueryResponse<${metaType}>> {`)\n lines.push(` return this.client.query<${metaType}>('${name}', params)`)\n lines.push(` }`)\n }\n\n for (const facetName of Object.keys(lens.facets)) {\n lines.push('')\n switch (facetName) {\n case 'search':\n lines.push(` async search(params: SearchParams): Promise<SearchResponse<${metaType}>> {`)\n lines.push(` return this.client.search<${metaType}>('${name}', params)`)\n lines.push(` }`)\n break\n case 'similar':\n lines.push(` async similar(params: SimilarParams): Promise<SimilarResponse<${metaType}>> {`)\n lines.push(` return this.client.similar<${metaType}>('${name}', params)`)\n lines.push(` }`)\n break\n default:\n // dedup, classify — planned for v0.3, skip codegen for now\n break\n }\n }\n\n // ── Streaming ops ────────────────────────────────────\n\n const modes = streamModesFor(lens)\n const hasSearchFacet = 'search' in lens.facets\n const hasQuery = hasQueryEnabled(lens)\n const emitsChunkedSearch = modes.chunked && hasSearchFacet\n const emitsChunkedQuery = modes.chunked && hasQuery\n const emitsLiveSub = modes.live\n const emitsStreamNamespace = emitsChunkedSearch || emitsChunkedQuery || emitsLiveSub\n\n if (emitsStreamNamespace) {\n lines.push('')\n lines.push(` /** Streaming surface — chunked results, live tail. */`)\n lines.push(` readonly stream = {`)\n if (emitsChunkedSearch) {\n lines.push(\n ` search: (params: StreamSearchParams): AsyncIterable<SearchResult<${metaType}>> =>`,\n )\n lines.push(` this.client.stream.search<${metaType}>('${name}', params),`)\n }\n if (emitsChunkedQuery) {\n lines.push(\n ` query: (params: StreamQueryParams): AsyncIterable<${metaType}> =>`,\n )\n lines.push(` this.client.stream.query<${metaType}>('${name}', params),`)\n }\n if (emitsLiveSub) {\n lines.push(\n ` subscribe: (params?: SubscribeParams): AsyncIterable<StreamEvent<${metaType}>> =>`,\n )\n lines.push(` this.client.stream.subscribe<${metaType}>('${name}', params),`)\n }\n lines.push(` }`)\n }\n\n // observe — single-record top-level verb, emitted alongside live mode\n if (emitsLiveSub) {\n lines.push('')\n lines.push(\n ` /** Observe one record — first yield is the current state, subsequent yields are per-subscription deltas. */`,\n )\n lines.push(` observe(recordId: string): AsyncIterable<${metaType}> {`)\n lines.push(` return this.client.observe<${metaType}>('${name}', recordId)`)\n lines.push(` }`)\n }\n\n lines.push('}')\n lines.push('')\n }\n\n return lines.join('\\n')\n}\n","import { pascalCase, fileHeader } from '../utils.js'\n\nexport function emitRootClient(lensNames: string[]): string {\n const lines: string[] = [fileHeader()]\n\n // Imports\n lines.push(`import { BeamClient, type BeamConfig } from '@semilayer/client'`)\n const lensImports = lensNames.map((n) => `${pascalCase(n)}Lens`).join(', ')\n lines.push(`import { ${lensImports} } from './lenses.js'`)\n lines.push('')\n\n // Class\n lines.push('export class Beam {')\n for (const name of lensNames) {\n lines.push(` readonly ${name}: ${pascalCase(name)}Lens`)\n }\n lines.push('')\n lines.push(' constructor(config: BeamConfig) {')\n lines.push(' const client = new BeamClient(config)')\n for (const name of lensNames) {\n lines.push(` this.${name} = new ${pascalCase(name)}Lens(client)`)\n }\n lines.push(' }')\n lines.push('}')\n lines.push('')\n\n // Factory\n lines.push('export function createBeam(config: BeamConfig): Beam {')\n lines.push(' return new Beam(config)')\n lines.push('}')\n lines.push('')\n\n return lines.join('\\n')\n}\n","import { pascalCase, fileHeader } from '../utils.js'\n\nexport function emitIndexFile(lensNames: string[]): string {\n const lines: string[] = [fileHeader()]\n\n lines.push(`export { Beam, createBeam } from './client.js'`)\n\n const lensExports = lensNames.map((n) => `${pascalCase(n)}Lens`).join(', ')\n lines.push(`export { ${lensExports} } from './lenses.js'`)\n\n const metaExports = lensNames.map((n) => `${pascalCase(n)}Metadata`).join(', ')\n lines.push(`export type { ${metaExports} } from './metadata.js'`)\n lines.push('')\n\n return lines.join('\\n')\n}\n"],"mappings":";AAAA,SAAS,WAAW,aAAa;AACjC,SAAS,YAAY;AAErB,SAAS,sBAAsB;;;ACAxB,SAAS,WAAW,MAAsB;AAC/C,SAAO,KACJ,QAAQ,aAAa,CAAC,GAAG,MAAc,EAAE,YAAY,CAAC,EACtD,QAAQ,QAAQ,CAAC,GAAG,MAAc,EAAE,YAAY,CAAC;AACtD;AAGO,SAAS,cAAc,OAA4B;AACxD,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,UAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;AAC3C,eAAO,MAAM,OAAO,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,KAAK;AAAA,MACrD;AACA,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEO,SAAS,aAAqB;AACnC,SAAO;AACT;;;ACjCO,SAAS,aAAa,QAA4C;AACvE,QAAM,QAAkB,CAAC,WAAW,CAAC;AAErC,aAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAM,WAAW,GAAG,WAAW,IAAI,CAAC;AACpC,UAAM,KAAK,oBAAoB,QAAQ,IAAI;AAG3C,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,KAAK,MAAM,GAAG;AAC5D,YAAM,KAAK,KAAK,SAAS,KAAK,cAAc,KAAK,CAAC,EAAE;AAAA,IACtD;AAEA,UAAM,KAAK,GAAG;AACd,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACjBA,IAAM,gBAA0C;AAAA,EAC9C,QAAQ,CAAC,gBAAgB,gBAAgB;AAAA,EACzC,SAAS,CAAC,iBAAiB,iBAAiB;AAC9C;AAEA,IAAM,gBAAgB,CAAC,eAAe,eAAe;AAGrD,IAAM,iBAAiB,CAAC,sBAAsB,cAAc;AAC5D,IAAM,uBAAuB,CAAC,mBAAmB;AACjD,IAAM,2BAA2B,CAAC,mBAAmB,aAAa;AAElE,SAAS,gBAAgB,MAA2B;AAClD,SAAO,CAAC,CAAC,KAAK,SAAS,WAAW,KAAK;AACzC;AASA,SAAS,eAAe,MAAuD;AAC7E,QAAM,QAAQ,KAAK,OAAO;AAC1B,MAAI,OAAO,YAAY,MAAO,QAAO,EAAE,SAAS,OAAO,MAAM,MAAM;AACnE,QAAM,QAAQ,OAAO;AACrB,MAAI,CAAC,MAAO,QAAO,EAAE,SAAS,MAAM,MAAM,KAAK;AAC/C,SAAO;AAAA,IACL,SAAS,MAAM,SAAS,SAAS;AAAA,IACjC,MAAM,MAAM,SAAS,MAAM;AAAA,EAC7B;AACF;AAEO,SAAS,gBAAgB,QAA4C;AAE1E,QAAM,gBAAgB,oBAAI,IAAY,CAAC,YAAY,CAAC;AACpD,QAAM,kBAA4B,CAAC;AAEnC,aAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,oBAAgB,KAAK,GAAG,WAAW,IAAI,CAAC,UAAU;AAClD,UAAM,WAAW,gBAAgB,IAAI;AACrC,QAAI,UAAU;AACZ,iBAAW,KAAK,cAAe,eAAc,IAAI,CAAC;AAAA,IACpD;AACA,eAAW,aAAa,OAAO,KAAK,KAAK,MAAM,GAAG;AAChD,YAAM,UAAU,cAAc,SAAS;AACvC,UAAI,SAAS;AACX,mBAAW,KAAK,QAAS,eAAc,IAAI,CAAC;AAAA,MAC9C;AAAA,IACF;AAGA,UAAM,QAAQ,eAAe,IAAI;AACjC,UAAM,iBAAiB,YAAY,KAAK;AACxC,UAAM,qBAAqB,MAAM,WAAW;AAC5C,UAAM,oBAAoB,MAAM,WAAW;AAC3C,UAAM,YAAY,MAAM;AAExB,QAAI,oBAAoB;AACtB,iBAAW,KAAK,eAAgB,eAAc,IAAI,CAAC;AAAA,IACrD;AACA,QAAI,mBAAmB;AACrB,iBAAW,KAAK,qBAAsB,eAAc,IAAI,CAAC;AAAA,IAC3D;AACA,QAAI,WAAW;AACb,iBAAW,KAAK,yBAA0B,eAAc,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF;AAEA,QAAM,QAAkB,CAAC,WAAW,CAAC;AAGrC,QAAM,cAAc,CAAC,GAAG,aAAa,EAAE,OAAO,CAAC,MAAM,MAAM,YAAY;AACvE,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,KAAK,iBAAiB,YAAY,KAAK,IAAI,CAAC,6BAA6B;AAAA,EACjF;AACA,QAAM,KAAK,gDAAgD;AAC3D,QAAM,KAAK,iBAAiB,gBAAgB,KAAK,IAAI,CAAC,yBAAyB;AAC/E,QAAM,KAAK,EAAE;AAGb,aAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAM,YAAY,GAAG,WAAW,IAAI,CAAC;AACrC,UAAM,WAAW,GAAG,WAAW,IAAI,CAAC;AAEpC,UAAM,KAAK,gBAAgB,SAAS,IAAI;AACxC,UAAM,KAAK,uDAAuD;AAKlE,QAAI,gBAAgB,IAAI,GAAG;AACzB,YAAM,KAAK,EAAE;AACb,YAAM,KAAK,8DAA8D,QAAQ,MAAM;AACvF,YAAM,KAAK,gCAAgC,QAAQ,MAAM,IAAI,YAAY;AACzE,YAAM,KAAK,KAAK;AAAA,IAClB;AAEA,eAAW,aAAa,OAAO,KAAK,KAAK,MAAM,GAAG;AAChD,YAAM,KAAK,EAAE;AACb,cAAQ,WAAW;AAAA,QACjB,KAAK;AACH,gBAAM,KAAK,gEAAgE,QAAQ,MAAM;AACzF,gBAAM,KAAK,iCAAiC,QAAQ,MAAM,IAAI,YAAY;AAC1E,gBAAM,KAAK,KAAK;AAChB;AAAA,QACF,KAAK;AACH,gBAAM,KAAK,mEAAmE,QAAQ,MAAM;AAC5F,gBAAM,KAAK,kCAAkC,QAAQ,MAAM,IAAI,YAAY;AAC3E,gBAAM,KAAK,KAAK;AAChB;AAAA,QACF;AAEE;AAAA,MACJ;AAAA,IACF;AAIA,UAAM,QAAQ,eAAe,IAAI;AACjC,UAAM,iBAAiB,YAAY,KAAK;AACxC,UAAM,WAAW,gBAAgB,IAAI;AACrC,UAAM,qBAAqB,MAAM,WAAW;AAC5C,UAAM,oBAAoB,MAAM,WAAW;AAC3C,UAAM,eAAe,MAAM;AAC3B,UAAM,uBAAuB,sBAAsB,qBAAqB;AAExE,QAAI,sBAAsB;AACxB,YAAM,KAAK,EAAE;AACb,YAAM,KAAK,+DAA0D;AACrE,YAAM,KAAK,uBAAuB;AAClC,UAAI,oBAAoB;AACtB,cAAM;AAAA,UACJ,wEAAwE,QAAQ;AAAA,QAClF;AACA,cAAM,KAAK,mCAAmC,QAAQ,MAAM,IAAI,aAAa;AAAA,MAC/E;AACA,UAAI,mBAAmB;AACrB,cAAM;AAAA,UACJ,yDAAyD,QAAQ;AAAA,QACnE;AACA,cAAM,KAAK,kCAAkC,QAAQ,MAAM,IAAI,aAAa;AAAA,MAC9E;AACA,UAAI,cAAc;AAChB,cAAM;AAAA,UACJ,wEAAwE,QAAQ;AAAA,QAClF;AACA,cAAM,KAAK,sCAAsC,QAAQ,MAAM,IAAI,aAAa;AAAA,MAClF;AACA,YAAM,KAAK,KAAK;AAAA,IAClB;AAGA,QAAI,cAAc;AAChB,YAAM,KAAK,EAAE;AACb,YAAM;AAAA,QACJ;AAAA,MACF;AACA,YAAM,KAAK,8CAA8C,QAAQ,KAAK;AACtE,YAAM,KAAK,kCAAkC,QAAQ,MAAM,IAAI,cAAc;AAC7E,YAAM,KAAK,KAAK;AAAA,IAClB;AAEA,UAAM,KAAK,GAAG;AACd,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;AC1KO,SAAS,eAAe,WAA6B;AAC1D,QAAM,QAAkB,CAAC,WAAW,CAAC;AAGrC,QAAM,KAAK,iEAAiE;AAC5E,QAAM,cAAc,UAAU,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI;AAC1E,QAAM,KAAK,YAAY,WAAW,uBAAuB;AACzD,QAAM,KAAK,EAAE;AAGb,QAAM,KAAK,qBAAqB;AAChC,aAAW,QAAQ,WAAW;AAC5B,UAAM,KAAK,cAAc,IAAI,KAAK,WAAW,IAAI,CAAC,MAAM;AAAA,EAC1D;AACA,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,qCAAqC;AAChD,QAAM,KAAK,2CAA2C;AACtD,aAAW,QAAQ,WAAW;AAC5B,UAAM,KAAK,YAAY,IAAI,UAAU,WAAW,IAAI,CAAC,cAAc;AAAA,EACrE;AACA,QAAM,KAAK,KAAK;AAChB,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,EAAE;AAGb,QAAM,KAAK,wDAAwD;AACnE,QAAM,KAAK,2BAA2B;AACtC,QAAM,KAAK,GAAG;AACd,QAAM,KAAK,EAAE;AAEb,SAAO,MAAM,KAAK,IAAI;AACxB;;;AC/BO,SAAS,cAAc,WAA6B;AACzD,QAAM,QAAkB,CAAC,WAAW,CAAC;AAErC,QAAM,KAAK,gDAAgD;AAE3D,QAAM,cAAc,UAAU,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI;AAC1E,QAAM,KAAK,YAAY,WAAW,uBAAuB;AAEzD,QAAM,cAAc,UAAU,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,UAAU,EAAE,KAAK,IAAI;AAC9E,QAAM,KAAK,iBAAiB,WAAW,yBAAyB;AAChE,QAAM,KAAK,EAAE;AAEb,SAAO,MAAM,KAAK,IAAI;AACxB;;;ALSA,eAAsB,SAAS,MAAgD;AAC7E,QAAM,QAAQ,KAAK,IAAI;AAGvB,MAAI;AACJ,MAAI,OAAO,KAAK,WAAW,UAAU;AACnC,UAAM,MAAM,MAAM,OAAO,KAAK;AAC9B,aAAS,IAAI,WAAW;AAAA,EAC1B,OAAO;AACL,aAAS,KAAK;AAAA,EAChB;AAGA,iBAAe,MAAM;AAErB,QAAM,YAAY,OAAO,KAAK,OAAO,MAAM;AAC3C,QAAM,SAAS,KAAK,UAAU;AAG9B,QAAM,QAAQ;AAAA,IACZ,EAAE,MAAM,KAAK,QAAQ,aAAa,GAAG,SAAS,aAAa,OAAO,MAAM,EAAE;AAAA,IAC1E,EAAE,MAAM,KAAK,QAAQ,WAAW,GAAG,SAAS,gBAAgB,OAAO,MAAM,EAAE;AAAA,IAC3E,EAAE,MAAM,KAAK,QAAQ,WAAW,GAAG,SAAS,eAAe,SAAS,EAAE;AAAA,IACtE,EAAE,MAAM,KAAK,QAAQ,UAAU,GAAG,SAAS,cAAc,SAAS,EAAE;AAAA,EACtE;AAGA,MAAI,KAAK,UAAU,OAAO;AACxB,UAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AACvC,UAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,MAAM,UAAU,EAAE,MAAM,EAAE,SAAS,OAAO,CAAC,CAAC;AAAA,EAC3E;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR,YAAY,KAAK,IAAI,IAAI;AAAA,EAC3B;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@semilayer/codegen",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "SemiLayer codegen — Beam client code generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"dev": "tsup --watch",
|
|
20
|
+
"lint": "eslint src/",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@semilayer/core": "workspace:*"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.7.0",
|
|
31
|
+
"vitest": "^3.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|