@blimu/codegen 0.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,67 @@
1
+
2
+ import { CoreClient, FetchError, type ClientOption } from "./client";
3
+ {{~#each IR.services~}}
4
+ import { {{serviceName tag}} } from "./services/{{fileBase tag}}";
5
+ {{~/each~}}
6
+
7
+ {{~setVar "grouped" (groupByNamespace IR.services)~}}
8
+
9
+ {{~! Generate namespace classes for non-root services ~}}
10
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
11
+ {{~#if (ne namespace "")~}}
12
+ class {{pascal namespace}}Namespace {
13
+ {{~#each services~}}
14
+ readonly {{serviceProp (getServiceName tag)}}: {{serviceName tag}};
15
+ {{~/each~}}
16
+
17
+ constructor(core: CoreClient) {
18
+ {{~#each services~}}
19
+ this.{{serviceProp (getServiceName tag)}} = new {{serviceName tag}}(core);
20
+ {{~/each~}}
21
+ }
22
+ }
23
+
24
+ {{~/if~}}
25
+ {{~/each~}}
26
+
27
+ export class {{Client.name}} {
28
+ {{~! Root level services (no namespace) ~}}
29
+ {{~#each (getRootServices IR.services)~}}
30
+ readonly {{serviceProp tag}}: {{serviceName tag}};
31
+ {{~/each~}}
32
+ {{~! Namespace properties ~}}
33
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
34
+ {{~#if (ne namespace "")~}}
35
+ readonly {{serviceProp namespace}}: {{pascal namespace}}Namespace;
36
+ {{~/if~}}
37
+ {{~/each~}}
38
+
39
+ constructor(options?: ClientOption) {
40
+ const core = new CoreClient(options);
41
+
42
+ {{~! Initialize root services ~}}
43
+ {{~#each (getRootServices IR.services)~}}
44
+ this.{{serviceProp tag}} = new {{serviceName tag}}(core);
45
+ {{~/each~}}
46
+ {{~! Initialize namespaces ~}}
47
+ {{~#each (groupByNamespace IR.services) as |services namespace|~}}
48
+ {{~#if (ne namespace "")~}}
49
+ this.{{serviceProp namespace}} = new {{pascal namespace}}Namespace(core);
50
+ {{~/if~}}
51
+ {{~/each~}}
52
+ }
53
+ }
54
+
55
+ export type { ClientOption };
56
+
57
+ // Export FetchError for error handling
58
+ export { FetchError };
59
+ export const {{Client.name}}Error = FetchError;
60
+
61
+ // Re-exports for better ergonomics
62
+ export * from "./utils";
63
+ export * as Schema from "./schema";
64
+ export * as ZodSchema from "./schema.zod";
65
+ {{~#each IR.services~}}
66
+ export { {{serviceName tag}} } from "./services/{{fileBase tag}}";
67
+ {{~/each~}}
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "{{Client.packageName}}",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for {{Client.name}} API (auto-generated)",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": ["dist/**"],
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ },
14
+ "./services/*": {
15
+ "types": "./dist/services/*.d.ts",
16
+ "import": "./dist/services/*.mjs",
17
+ "require": "./dist/services/*.js"
18
+ },
19
+ "./schema": {
20
+ "types": "./dist/schema.d.ts",
21
+ "import": "./dist/schema.mjs",
22
+ "require": "./dist/schema.js"
23
+ },
24
+ "./client": {
25
+ "types": "./dist/client.d.ts",
26
+ "import": "./dist/client.mjs",
27
+ "require": "./dist/client.js"
28
+ },
29
+ "./utils": {
30
+ "types": "./dist/utils.d.ts",
31
+ "import": "./dist/utils.mjs",
32
+ "require": "./dist/utils.js"
33
+ }
34
+ },
35
+ "scripts": {
36
+ "build": "tsup src/index.ts src/services/*.ts src/schema.ts src/schema.zod.ts src/client.ts src/utils.ts --format cjs,esm --dts",
37
+ "typecheck": "tsc -p tsconfig.json --noEmit",
38
+ "lint": "eslint .",
39
+ "format": "eslint --fix . && prettier --write .",
40
+ "prepublishOnly": "npm run build && npm run typecheck || true"
41
+ },
42
+ "dependencies": {
43
+ "zod": "^4.3.5"
44
+ },
45
+ "devDependencies": {
46
+ "tsup": "^8.5.1"
47
+ }
48
+ }
@@ -0,0 +1,76 @@
1
+ // Generated types from OpenAPI components.schemas
2
+
3
+ export type Enum<T> = T[keyof T];
4
+
5
+ {{~! Enums: const objects + union types ~}}
6
+ {{~#if (gt (len IR.modelDefs) 0)~}}
7
+ {{~#each IR.modelDefs~}}
8
+ {{~#if (eq this.schema.kind "enum")~}}
9
+ export const {{this.name}} = {
10
+ {{~#each this.schema.enumValues as |v|~}}
11
+ "{{v}}": {{#if (or (eq v "true") (eq v "false"))}}{{v}}{{else if (reMatch "^-?[0-9]+(\\.[0-9]+)?$" v)}}{{v}}{{else}}"{{v}}"{{/if}},
12
+ {{~/each~}}
13
+ } as const;
14
+
15
+ export type {{this.name}} = Enum<typeof {{this.name}}>;
16
+
17
+ {{~/if~}}
18
+ {{~/each~}}
19
+
20
+ {{~! Objects and other named models: render interfaces/types from structured IR ~}}
21
+ {{~#each IR.modelDefs~}}
22
+ {{~#if this.annotations.description~}}
23
+ /**
24
+ * {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
25
+ */
26
+ {{~/if~}}
27
+ {{~#if (eq this.schema.kind "object")~}}
28
+ export interface {{this.name}} {
29
+ {{~#each this.schema.properties~}}
30
+ {{~#if this.annotations.description~}}
31
+ /** {{decodeHtml (replace this.annotations.description "*/" "*\\/")}} */
32
+ {{~/if~}}
33
+ {{quotePropName this.name}}{{#unless this.required}}?{{/unless}}: {{tsTypeStripNs this.type}};
34
+ {{~/each~}}
35
+ }
36
+
37
+ {{~else if (eq this.schema.kind "ref")~}}
38
+ export type {{this.name}} = {{tsTypeStripNs this.schema}};
39
+
40
+ {{~else if (or (eq this.schema.kind "oneOf") (eq this.schema.kind "anyOf") (eq this.schema.kind "allOf"))~}}
41
+ export type {{this.name}} = {{tsTypeStripNs this.schema}};
42
+
43
+ {{~else if (or (eq this.schema.kind "array") (eq this.schema.kind "string") (eq this.schema.kind "number") (eq this.schema.kind "integer") (eq this.schema.kind "boolean") (eq this.schema.kind "null") (eq this.schema.kind "enum"))~}}
44
+ export type {{this.name}} = {{tsTypeStripNs this.schema}};
45
+
46
+ {{~/if~}}
47
+ {{~/each~}}
48
+ {{~/if~}}
49
+
50
+ {{#if IR.services}}
51
+
52
+ // Operation query parameter interfaces
53
+
54
+ {{#each IR.services}}
55
+ {{#each this.operations}}
56
+ {{#if (gt (len this.queryParams) 0)}}
57
+ /**
58
+ * Query params for {{../tag}}.{{pascal (methodName this)}}
59
+ {{~#if this.description~}}
60
+ *
61
+ * {{decodeHtml (replace this.description "*/" "*\\/")}}
62
+ {{~/if~}}
63
+ */
64
+ export interface {{pascal ../tag}}{{pascal (methodName this)}}Query {
65
+ {{~#each this.queryParams~}}
66
+ {{~#if this.description~}}
67
+ /** {{decodeHtml (replace this.description "*/" "*\\/")}} */
68
+ {{~/if~}}
69
+ {{quotePropName this.name}}{{#unless this.required}}?{{/unless}}: {{tsTypeStripNs this.schema}};
70
+ {{~/each~}}
71
+ }
72
+
73
+ {{/if}}
74
+ {{/each}}
75
+ {{/each}}
76
+ {{/if}}
@@ -0,0 +1,126 @@
1
+ // Generated Zod schemas from OpenAPI components.schemas
2
+ // Use these schemas for runtime validation in forms, API requests, etc.
3
+
4
+ import { z } from "zod";
5
+ import * as Schema from "./schema";
6
+
7
+ {{#if (gt (len IR.modelDefs) 0)}}
8
+ {{~#each IR.modelDefs}}
9
+ {{~#if (eq this.schema.kind "enum")~}}
10
+ /**
11
+ * Zod schema for {{this.name}}
12
+ {{~#if this.annotations.description~}}
13
+ * {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
14
+ {{~/if~}}
15
+ */
16
+ export const {{this.name}}Schema = z.enum([
17
+ {{~#each this.schema.enumValues as |v|~}}
18
+ {{#if (or (eq v "true") (eq v "false"))}}{{v}}{{else if (reMatch "^-?[0-9]+(\\.[0-9]+)?$" v)}}{{v}}{{else}}{{json v}}{{/if}}{{#unless @last}},{{/unless}}
19
+ {{~/each~}}
20
+ ]);
21
+
22
+ {{~else if (eq this.schema.kind "object")~}}
23
+ /**
24
+ * Zod schema for {{this.name}}
25
+ {{~#if this.annotations.description~}}
26
+ * {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
27
+ {{~/if~}}
28
+ */
29
+ export const {{this.name}}Schema = z.object({
30
+ {{~#each this.schema.properties~}}
31
+ {{~#if this.annotations.description~}}
32
+ /** {{decodeHtml (replace this.annotations.description "*/" "*\\/")}} */
33
+ {{~/if~}}
34
+ {{quotePropName this.name}}: {{zodSchema this.type}}{{#unless this.required}}.optional(){{/unless}},
35
+ {{~/each~}}
36
+ });
37
+
38
+ {{~else if (eq this.schema.kind "ref")~}}
39
+ /**
40
+ * Zod schema for {{this.name}}
41
+ */
42
+ export const {{this.name}}Schema = Schema.{{this.schema.ref}}Schema;
43
+
44
+ {{~else if (eq this.schema.kind "oneOf")~}}
45
+ /**
46
+ * Zod schema for {{this.name}}
47
+ {{~#if this.annotations.description~}}
48
+ * {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
49
+ {{~/if~}}
50
+ */
51
+ export const {{this.name}}Schema = z.union([
52
+ {{~#each this.schema.oneOf as |opt|~}}
53
+ {{zodSchema opt}}{{#unless @last}},{{/unless}}
54
+ {{~/each~}}
55
+ ]);
56
+
57
+ {{~else if (eq this.schema.kind "anyOf")~}}
58
+ /**
59
+ * Zod schema for {{this.name}}
60
+ {{~#if this.annotations.description~}}
61
+ * {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
62
+ {{~/if~}}
63
+ */
64
+ export const {{this.name}}Schema = z.union([
65
+ {{~#each this.schema.anyOf as |opt|~}}
66
+ {{zodSchema opt}}{{#unless @last}},{{/unless}}
67
+ {{~/each~}}
68
+ ]);
69
+
70
+ {{~else if (eq this.schema.kind "allOf")~}}
71
+ /**
72
+ * Zod schema for {{this.name}}
73
+ {{~#if this.annotations.description~}}
74
+ * {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
75
+ {{~/if~}}
76
+ */
77
+ export const {{this.name}}Schema = {{#each this.schema.allOf}}{{#if @first}}{{zodSchema this}}{{else}}.and({{zodSchema this}}){{/if}}{{/each}};
78
+
79
+ {{~else if (eq this.schema.kind "array")~}}
80
+ /**
81
+ * Zod schema for {{this.name}}
82
+ {{~#if this.annotations.description~}}
83
+ * {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
84
+ {{~/if~}}
85
+ */
86
+ export const {{this.name}}Schema = z.array({{#if this.schema.items}}{{zodSchema this.schema.items}}{{else}}z.unknown(){{/if}});
87
+
88
+ {{~else~}}
89
+ /**
90
+ * Zod schema for {{this.name}}
91
+ {{~#if this.annotations.description~}}
92
+ * {{decodeHtml (replace this.annotations.description "*/" "*\\/")}}
93
+ {{~/if~}}
94
+ */
95
+ export const {{this.name}}Schema = {{zodSchema this.schema}};
96
+
97
+ {{~/if~}}
98
+ {{~/each~}}
99
+ {{~/if~}}
100
+
101
+ {{#if IR.services}}
102
+
103
+ // Operation query parameter Zod schemas
104
+
105
+ {{#each IR.services}}
106
+ {{#each this.operations}}
107
+ {{#if (gt (len this.queryParams) 0)}}
108
+ /**
109
+ * Zod schema for query params of {{../tag}}.{{pascal (methodName this)}}
110
+ {{~#if this.description~}}
111
+ * {{decodeHtml (replace this.description "*/" "*\\/")}}
112
+ {{~/if~}}
113
+ */
114
+ export const {{pascal ../tag}}{{pascal (methodName this)}}QuerySchema = z.object({
115
+ {{~#each this.queryParams~}}
116
+ {{~#if this.description~}}
117
+ /** {{decodeHtml (replace this.description "*/" "*\\/")}} */
118
+ {{~/if~}}
119
+ {{quotePropName this.name}}: {{zodSchema this.schema}}{{#unless this.required}}.optional(){{/unless}},
120
+ {{~/each~}}
121
+ });
122
+
123
+ {{/if}}
124
+ {{/each}}
125
+ {{/each}}
126
+ {{/if}}
@@ -0,0 +1,107 @@
1
+ import { CoreClient } from "../client";
2
+ import * as Schema from "../schema";
3
+
4
+ export class {{serviceName Service.tag}} {
5
+ constructor(private core: CoreClient) {}
6
+ {{#each Service.operations}}
7
+
8
+ /**
9
+ * {{this.method}} {{this.path}}
10
+ {{~#if summary~}}
11
+ *
12
+ * @summary {{decodeHtml (replace summary "*/" "*\\/")}}
13
+ {{~else if response.description~}}
14
+ *
15
+ * @returns {{decodeHtml (replace response.description "*/" "*\\/")}}
16
+ {{~/if~}}
17
+ {{~#if description~}}
18
+ *
19
+ * @description {{decodeHtml (replace description "*/" "*\\/")}}
20
+ {{~/if~}}
21
+ */
22
+ {{#if (isStreaming this)}}
23
+ async *{{methodName this}}(
24
+ {{#each (methodSignature this) as |param|}}
25
+ {{param}}{{#unless @last}},{{/unless}}
26
+ {{/each}}
27
+ ): AsyncGenerator<{{{streamingItemType this}}}, void, unknown> {
28
+ yield* this.core.requestStream({
29
+ method: "{{this.method}}",
30
+ path: {{pathTemplate this}},
31
+ {{#if (gt (len queryParams) 0)}}
32
+ query,
33
+ {{/if}}
34
+ {{#if requestBody}}
35
+ {{#if (eq requestBody.contentType "application/json")}}
36
+ headers: {
37
+ ...(init?.headers || {}),
38
+ "content-type": "application/json",
39
+ },
40
+ body: JSON.stringify(body),
41
+ {{else if (eq requestBody.contentType "multipart/form-data")}}
42
+ body: (body as any),
43
+ {{else if (eq requestBody.contentType "application/x-www-form-urlencoded")}}
44
+ headers: {
45
+ ...(init?.headers || {}),
46
+ "content-type": "application/x-www-form-urlencoded",
47
+ },
48
+ body: (body as any),
49
+ {{else}}
50
+ body: (body as any),
51
+ {{/if}}
52
+ {{/if}}
53
+ contentType: "{{response.contentType}}",
54
+ streamingFormat: "{{response.streamingFormat}}",
55
+ ...(init || {}),
56
+ });
57
+ }
58
+ {{else}}
59
+ {{methodName this}}(
60
+ {{#each (methodSignature this) as |param|}}
61
+ {{param}}{{#unless @last}},{{/unless}}
62
+ {{/each}}
63
+ ): Promise<{{{tsType response.schema}}}> {
64
+ return this.core.request({
65
+ method: "{{this.method}}",
66
+ path: {{pathTemplate this}},
67
+ {{#if (gt (len queryParams) 0)}}
68
+ query,
69
+ {{/if}}
70
+ {{#if requestBody}}
71
+ {{#if (eq requestBody.contentType "application/json")}}
72
+ headers: {
73
+ ...(init?.headers || {}),
74
+ "content-type": "application/json",
75
+ },
76
+ body: JSON.stringify(body),
77
+ {{else if (eq requestBody.contentType "multipart/form-data")}}
78
+ body: (body as any),
79
+ {{else if (eq requestBody.contentType "application/x-www-form-urlencoded")}}
80
+ headers: {
81
+ ...(init?.headers || {}),
82
+ "content-type": "application/x-www-form-urlencoded",
83
+ },
84
+ body: (body as any),
85
+ {{else}}
86
+ body: (body as any),
87
+ {{/if}}
88
+ {{/if}}
89
+ ...(init || {}),
90
+ });
91
+ }
92
+ {{/if}}
93
+ {{#if ../Client.includeQueryKeys}}
94
+ /**
95
+ * @summary Get query keys for {{methodName this}}
96
+ * @returns [{{queryKeyBase this}}{{#each (queryKeyArgs this)}}, {{this}}{{/each}}]
97
+ */
98
+ {{methodName this}}__queryKeys(
99
+ {{~#each (methodSignatureNoInit this) as |param|~}}
100
+ {{param}}{{#unless @last}},{{/unless}}
101
+ {{~/each~}}
102
+ ) {
103
+ return [{{queryKeyBase this}}{{#each (queryKeyArgs this)}}, {{this}}{{/each}}] as const;
104
+ }
105
+ {{/if}}
106
+ {{/each}}
107
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "rootDir": "./src",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "esModuleInterop": true,
7
+ "isolatedModules": true,
8
+ "declaration": true,
9
+ "removeComments": true,
10
+ "emitDecoratorMetadata": true,
11
+ "experimentalDecorators": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "target": "ES2023",
14
+ "sourceMap": true,
15
+ "outDir": "./dist",
16
+ "baseUrl": "./src",
17
+ "incremental": true,
18
+ "skipLibCheck": true,
19
+ "strictNullChecks": true,
20
+ "forceConsistentCasingInFileNames": true,
21
+ "noImplicitAny": false,
22
+ "strictBindCallApply": false,
23
+ "noFallthroughCasesInSwitch": false,
24
+ "useDefineForClassFields": false
25
+ },
26
+ "include": ["src/**/*"],
27
+ "exclude": ["node_modules", "dist"]
28
+ }
@@ -0,0 +1,168 @@
1
+ export type PaginableQuery = { limit?: number; offset?: number } & Record<string, unknown>;
2
+
3
+ export async function* paginate<T>(
4
+ fetchPage: (query?: any, init?: Omit<RequestInit, 'method' | 'body'>) => Promise<{ data?: T[]; hasMore?: boolean; limit?: number; offset?: number }>,
5
+ initialQuery: PaginableQuery = {},
6
+ pageSize = 100,
7
+ ): AsyncGenerator<T, void, unknown> {
8
+ let offset = Number(initialQuery.offset ?? 0);
9
+ const limit = Number(initialQuery.limit ?? pageSize);
10
+ // shallow copy to avoid mutating caller
11
+ const baseQuery: any = { ...initialQuery };
12
+ while (true) {
13
+ const page = await fetchPage({ ...baseQuery, limit, offset });
14
+ const items = page.data ?? [];
15
+ for (const item of items) {
16
+ yield item as T;
17
+ }
18
+ if (!page.hasMore || items.length < limit) break;
19
+ offset += limit;
20
+ }
21
+ }
22
+
23
+ export async function listAll<T>(
24
+ fetchPage: (query?: any, init?: Omit<RequestInit, 'method' | 'body'>) => Promise<{ data?: T[]; hasMore?: boolean; limit?: number; offset?: number }>,
25
+ query: PaginableQuery = {},
26
+ pageSize = 100,
27
+ ): Promise<T[]> {
28
+ const out: T[] = [];
29
+ for await (const item of paginate<T>(fetchPage, query, pageSize)) out.push(item);
30
+ return out;
31
+ }
32
+
33
+ /**
34
+ * Parse Server-Sent Events (SSE) stream
35
+ * Extracts data fields from text/event-stream format
36
+ */
37
+ export async function* parseSSEStream(
38
+ response: Response
39
+ ): AsyncGenerator<string, void, unknown> {
40
+ if (!response.body) {
41
+ return;
42
+ }
43
+
44
+ const reader = response.body.getReader();
45
+ const decoder = new TextDecoder();
46
+ let buffer = "";
47
+
48
+ try {
49
+ while (true) {
50
+ const { done, value } = await reader.read();
51
+ if (done) break;
52
+
53
+ buffer += decoder.decode(value, { stream: true });
54
+ const lines = buffer.split("\n");
55
+ buffer = lines.pop() || ""; // Keep incomplete line in buffer
56
+
57
+ let currentEvent: { type?: string; data?: string; id?: string } = {};
58
+
59
+ for (const line of lines) {
60
+ if (line.trim() === "") {
61
+ // Empty line indicates end of event
62
+ if (currentEvent.data !== undefined) {
63
+ yield currentEvent.data;
64
+ }
65
+ currentEvent = {};
66
+ continue;
67
+ }
68
+
69
+ const colonIndex = line.indexOf(":");
70
+ if (colonIndex === -1) continue;
71
+
72
+ const field = line.substring(0, colonIndex).trim();
73
+ const value = line.substring(colonIndex + 1).trim();
74
+
75
+ if (field === "data") {
76
+ currentEvent.data = currentEvent.data
77
+ ? currentEvent.data + "\n" + value
78
+ : value;
79
+ } else if (field === "event") {
80
+ currentEvent.type = value;
81
+ } else if (field === "id") {
82
+ currentEvent.id = value;
83
+ }
84
+ }
85
+ }
86
+
87
+ // Handle any remaining event in buffer
88
+ if (buffer.trim()) {
89
+ const lines = buffer.split("\n");
90
+ let currentEvent: { data?: string } = {};
91
+ for (const line of lines) {
92
+ if (line.trim() === "" && currentEvent.data !== undefined) {
93
+ yield currentEvent.data;
94
+ currentEvent = {};
95
+ continue;
96
+ }
97
+ const colonIndex = line.indexOf(":");
98
+ if (colonIndex !== -1) {
99
+ const field = line.substring(0, colonIndex).trim();
100
+ const value = line.substring(colonIndex + 1).trim();
101
+ if (field === "data") {
102
+ currentEvent.data = currentEvent.data
103
+ ? currentEvent.data + "\n" + value
104
+ : value;
105
+ }
106
+ }
107
+ }
108
+ if (currentEvent.data !== undefined) {
109
+ yield currentEvent.data;
110
+ }
111
+ }
112
+ } finally {
113
+ reader.releaseLock();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Parse NDJSON (Newline-Delimited JSON) stream
119
+ * Yields each JSON object as it arrives
120
+ */
121
+ export async function* parseNDJSONStream<T = any>(
122
+ response: Response
123
+ ): AsyncGenerator<T, void, unknown> {
124
+ if (!response.body) {
125
+ return;
126
+ }
127
+
128
+ const reader = response.body.getReader();
129
+ const decoder = new TextDecoder();
130
+ let buffer = "";
131
+
132
+ try {
133
+ while (true) {
134
+ const { done, value } = await reader.read();
135
+ if (done) break;
136
+
137
+ buffer += decoder.decode(value, { stream: true });
138
+ const lines = buffer.split("\n");
139
+ buffer = lines.pop() || ""; // Keep incomplete line in buffer
140
+
141
+ for (const line of lines) {
142
+ const trimmed = line.trim();
143
+ if (trimmed === "") continue;
144
+
145
+ try {
146
+ const parsed = JSON.parse(trimmed) as T;
147
+ yield parsed;
148
+ } catch (error) {
149
+ // Skip invalid JSON lines
150
+ console.warn("Skipping invalid JSON line:", trimmed);
151
+ }
152
+ }
153
+ }
154
+
155
+ // Handle any remaining line in buffer
156
+ if (buffer.trim()) {
157
+ try {
158
+ const parsed = JSON.parse(buffer.trim()) as T;
159
+ yield parsed;
160
+ } catch (error) {
161
+ // Skip invalid JSON
162
+ console.warn("Skipping invalid JSON in buffer:", buffer.trim());
163
+ }
164
+ }
165
+ } finally {
166
+ reader.releaseLock();
167
+ }
168
+ }