@toiroakr/lines-db 0.6.0 → 0.7.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/src/database.ts CHANGED
@@ -4,6 +4,8 @@ import { JsonlWriter } from './jsonl-writer.js';
4
4
  import { SchemaLoader } from './schema-loader.js';
5
5
  import { DirectoryScanner } from './directory-scanner.js';
6
6
  import { hasBackward } from './schema.js';
7
+ import { findSchemaFile } from './schema-extensions.js';
8
+ import { dirname, basename } from 'node:path';
7
9
  import type {
8
10
  DatabaseConfig,
9
11
  TableSchema,
@@ -148,13 +150,18 @@ export class LinesDB<Tables extends TableDefs> {
148
150
 
149
151
  try {
150
152
  const { pathToFileURL } = await import('node:url');
151
- const schemaPath = tableConfig.jsonlPath.replace('.jsonl', '.schema.ts');
152
- const schemaUrl = pathToFileURL(schemaPath).href;
153
- const schemaModule = await import(`${schemaUrl}?t=${Date.now()}`);
153
+ const schemaPath = await findSchemaFile(
154
+ dirname(tableConfig.jsonlPath),
155
+ basename(tableConfig.jsonlPath, '.jsonl'),
156
+ );
157
+ if (schemaPath) {
158
+ const schemaUrl = pathToFileURL(schemaPath).href;
159
+ const schemaModule = await import(`${schemaUrl}?t=${Date.now()}`);
154
160
 
155
- // Try to get foreign keys from exported 'schema' or directly from module
156
- const schemaExport = schemaModule.schema || schemaModule.default;
157
- foreignKeys = schemaExport?.foreignKeys || schemaModule.foreignKeys;
161
+ // Try to get foreign keys from exported 'schema' or directly from module
162
+ const schemaExport = schemaModule.schema || schemaModule.default;
163
+ foreignKeys = schemaExport?.foreignKeys || schemaModule.foreignKeys;
164
+ }
158
165
  } catch {
159
166
  // Schema file not found - will continue without validation
160
167
  }
@@ -256,7 +263,11 @@ export class LinesDB<Tables extends TableDefs> {
256
263
  // Only load if not already provided via config
257
264
  try {
258
265
  const { pathToFileURL } = await import('node:url');
259
- const schemaPath = config.jsonlPath.replace('.jsonl', '.schema.ts');
266
+ const schemaPath = await findSchemaFile(
267
+ dirname(config.jsonlPath),
268
+ basename(config.jsonlPath, '.jsonl'),
269
+ );
270
+ if (!schemaPath) throw new Error('Schema file not found');
260
271
  const schemaUrl = pathToFileURL(schemaPath).href;
261
272
  const schemaModule = await import(`${schemaUrl}?t=${Date.now()}`);
262
273
 
@@ -280,8 +291,23 @@ export class LinesDB<Tables extends TableDefs> {
280
291
  } else if (schemaModule.indexes) {
281
292
  schemaMetadata.indexes = schemaModule.indexes;
282
293
  }
294
+
295
+ // Debug: log loaded metadata
296
+ if (process.env.DEBUG_LINES_DB) {
297
+ console.log(`[lines-db] Schema metadata for ${tableName}:`);
298
+ console.log(` primaryKey: ${schemaMetadata.primaryKey}`);
299
+ console.log(` foreignKeys: ${JSON.stringify(schemaMetadata.foreignKeys)}`);
300
+ console.log(` indexes: ${JSON.stringify(schemaMetadata.indexes)}`);
301
+ }
283
302
  } catch (_error) {
284
303
  // Schema file not found - this is OK
304
+ // Debug: log error for investigation
305
+ if (process.env.DEBUG_LINES_DB) {
306
+ console.warn(
307
+ `[lines-db] Failed to load schema metadata for ${tableName}:`,
308
+ _error instanceof Error ? _error.message : String(_error),
309
+ );
310
+ }
285
311
  }
286
312
  }
287
313
 
@@ -384,6 +410,18 @@ export class LinesDB<Tables extends TableDefs> {
384
410
  }
385
411
  if (indexes) {
386
412
  schema.indexes = indexes;
413
+
414
+ // Apply unique constraint from single-column unique indexes to column definitions
415
+ // This is required for foreign key references, as SQLite requires the referenced column
416
+ // to have a UNIQUE constraint in the table definition (not just an index)
417
+ for (const index of indexes) {
418
+ if (index.unique && index.columns.length === 1) {
419
+ const col = schema.columns.find((c) => c.name === index.columns[0]);
420
+ if (col && !col.unique && !col.primaryKey) {
421
+ col.unique = true;
422
+ }
423
+ }
424
+ }
387
425
  }
388
426
 
389
427
  this.schemas.set(tableName, schema);
@@ -419,13 +457,31 @@ export class LinesDB<Tables extends TableDefs> {
419
457
  // Quote table name to handle special characters
420
458
  const quotedTableName = this.quoteTableName(schema.name);
421
459
 
460
+ // Build a set of columns that should have UNIQUE constraint
461
+ // This includes columns marked as unique in schema AND single-column unique indexes
462
+ // The latter is required for foreign key references, as SQLite requires the referenced column
463
+ // to have a UNIQUE constraint in the table definition (not just a separately created index)
464
+ const uniqueColumns = new Set<string>();
465
+ for (const col of schema.columns) {
466
+ if (col.unique) {
467
+ uniqueColumns.add(col.name);
468
+ }
469
+ }
470
+ if (schema.indexes) {
471
+ for (const index of schema.indexes) {
472
+ if (index.unique && index.columns.length === 1) {
473
+ uniqueColumns.add(index.columns[0]);
474
+ }
475
+ }
476
+ }
477
+
422
478
  const columnDefs = schema.columns.map((col) => {
423
479
  // JSON type is stored as TEXT in SQLite
424
480
  const sqlType = col.type === 'JSON' ? 'TEXT' : col.type;
425
481
  const parts = [this.quoteIdentifier(col.name), sqlType];
426
482
  if (col.primaryKey) parts.push('PRIMARY KEY');
427
483
  if (col.notNull) parts.push('NOT NULL');
428
- if (col.unique) parts.push('UNIQUE');
484
+ if (uniqueColumns.has(col.name) && !col.primaryKey) parts.push('UNIQUE');
429
485
  return parts.join(' ');
430
486
  });
431
487
 
package/src/index.ts CHANGED
@@ -7,6 +7,14 @@ export { defineSchema, hasBackward } from './schema.js';
7
7
  export { TypeGenerator } from './type-generator.js';
8
8
  export { ensureTableRowsValid } from './jsonl-migration.js';
9
9
  export type { TableValidationOptions } from './jsonl-migration.js';
10
+ export {
11
+ SCHEMA_EXTENSIONS,
12
+ findSchemaFile,
13
+ isSchemaFile,
14
+ extractTableNameFromSchemaFile,
15
+ rewriteExtensionForImport,
16
+ } from './schema-extensions.js';
17
+ export type { SchemaExtension } from './schema-extensions.js';
10
18
  export { detectRuntime, RUNTIME } from './runtime.js';
11
19
  export type { RuntimeEnvironment } from './runtime.js';
12
20
  export type { SQLiteDatabase, SQLiteStatement } from './sqlite-adapter.js';
@@ -0,0 +1,155 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import {
3
+ isSchemaFile,
4
+ extractTableNameFromSchemaFile,
5
+ rewriteExtensionForImport,
6
+ findSchemaFile,
7
+ findSchemaFileInEntries,
8
+ SCHEMA_EXTENSIONS,
9
+ } from './schema-extensions.js';
10
+ import { writeFile, mkdir, rm } from 'node:fs/promises';
11
+ import { join } from 'node:path';
12
+ import { tmpdir } from 'node:os';
13
+
14
+ describe('schema-extensions', () => {
15
+ describe('isSchemaFile', () => {
16
+ it('should recognize .schema.ts', () => {
17
+ expect(isSchemaFile('users.schema.ts')).toBe(true);
18
+ });
19
+
20
+ it('should recognize .schema.mts', () => {
21
+ expect(isSchemaFile('users.schema.mts')).toBe(true);
22
+ });
23
+
24
+ it('should recognize .schema.cts', () => {
25
+ expect(isSchemaFile('users.schema.cts')).toBe(true);
26
+ });
27
+
28
+ it('should reject non-schema files', () => {
29
+ expect(isSchemaFile('users.ts')).toBe(false);
30
+ expect(isSchemaFile('db.ts')).toBe(false);
31
+ expect(isSchemaFile('schema.ts')).toBe(false);
32
+ });
33
+ });
34
+
35
+ describe('extractTableNameFromSchemaFile', () => {
36
+ it('should extract from .schema.ts', () => {
37
+ expect(extractTableNameFromSchemaFile('users.schema.ts')).toBe('users');
38
+ });
39
+
40
+ it('should extract from .schema.mts', () => {
41
+ expect(extractTableNameFromSchemaFile('users.schema.mts')).toBe('users');
42
+ });
43
+
44
+ it('should extract from .schema.cts', () => {
45
+ expect(extractTableNameFromSchemaFile('users.schema.cts')).toBe('users');
46
+ });
47
+
48
+ it('should return null for non-schema files', () => {
49
+ expect(extractTableNameFromSchemaFile('users.ts')).toBeNull();
50
+ expect(extractTableNameFromSchemaFile('db.ts')).toBeNull();
51
+ });
52
+ });
53
+
54
+ describe('rewriteExtensionForImport', () => {
55
+ it('.schema.ts -> .schema.js', () => {
56
+ expect(rewriteExtensionForImport('./users.schema.ts')).toBe('./users.schema.js');
57
+ });
58
+
59
+ it('.schema.mts -> .schema.mjs', () => {
60
+ expect(rewriteExtensionForImport('./users.schema.mts')).toBe('./users.schema.mjs');
61
+ });
62
+
63
+ it('.schema.cts -> .schema.cjs', () => {
64
+ expect(rewriteExtensionForImport('./users.schema.cts')).toBe('./users.schema.cjs');
65
+ });
66
+
67
+ it('should not modify paths without matching extensions', () => {
68
+ expect(rewriteExtensionForImport('./users.js')).toBe('./users.js');
69
+ });
70
+ });
71
+
72
+ describe('findSchemaFile', () => {
73
+ let testDir: string;
74
+
75
+ beforeEach(async () => {
76
+ testDir = join(tmpdir(), `schema-ext-test-${Date.now()}`);
77
+ await mkdir(testDir, { recursive: true });
78
+ });
79
+
80
+ afterEach(async () => {
81
+ await rm(testDir, { recursive: true, force: true });
82
+ });
83
+
84
+ it('should find .schema.ts file', async () => {
85
+ await writeFile(join(testDir, 'users.schema.ts'), 'export const schema = {};');
86
+ const result = await findSchemaFile(testDir, 'users');
87
+ expect(result).toBe(join(testDir, 'users.schema.ts'));
88
+ });
89
+
90
+ it('should find .schema.mts file', async () => {
91
+ await writeFile(join(testDir, 'users.schema.mts'), 'export const schema = {};');
92
+ const result = await findSchemaFile(testDir, 'users');
93
+ expect(result).toBe(join(testDir, 'users.schema.mts'));
94
+ });
95
+
96
+ it('should find .schema.cts file', async () => {
97
+ await writeFile(join(testDir, 'users.schema.cts'), 'export const schema = {};');
98
+ const result = await findSchemaFile(testDir, 'users');
99
+ expect(result).toBe(join(testDir, 'users.schema.cts'));
100
+ });
101
+
102
+ it('should prefer .schema.ts over .schema.mts', async () => {
103
+ await writeFile(join(testDir, 'users.schema.ts'), 'export const schema = {};');
104
+ await writeFile(join(testDir, 'users.schema.mts'), 'export const schema = {};');
105
+ const result = await findSchemaFile(testDir, 'users');
106
+ expect(result).toBe(join(testDir, 'users.schema.ts'));
107
+ });
108
+
109
+ it('should return undefined when no schema file exists', async () => {
110
+ const result = await findSchemaFile(testDir, 'users');
111
+ expect(result).toBeUndefined();
112
+ });
113
+ });
114
+
115
+ describe('findSchemaFileInEntries', () => {
116
+ it('should find .schema.ts in entries', () => {
117
+ const entries = [
118
+ { isFile: () => true, name: 'users.jsonl' },
119
+ { isFile: () => true, name: 'users.schema.ts' },
120
+ ];
121
+ const result = findSchemaFileInEntries('/data', 'users', entries);
122
+ expect(result).toBe(join('/data', 'users.schema.ts'));
123
+ });
124
+
125
+ it('should find .schema.mts in entries', () => {
126
+ const entries = [
127
+ { isFile: () => true, name: 'users.jsonl' },
128
+ { isFile: () => true, name: 'users.schema.mts' },
129
+ ];
130
+ const result = findSchemaFileInEntries('/data', 'users', entries);
131
+ expect(result).toBe(join('/data', 'users.schema.mts'));
132
+ });
133
+
134
+ it('should prefer .schema.ts over .schema.mts in entries', () => {
135
+ const entries = [
136
+ { isFile: () => true, name: 'users.schema.ts' },
137
+ { isFile: () => true, name: 'users.schema.mts' },
138
+ ];
139
+ const result = findSchemaFileInEntries('/data', 'users', entries);
140
+ expect(result).toBe(join('/data', 'users.schema.ts'));
141
+ });
142
+
143
+ it('should return undefined when no schema file in entries', () => {
144
+ const entries = [{ isFile: () => true, name: 'users.jsonl' }];
145
+ const result = findSchemaFileInEntries('/data', 'users', entries);
146
+ expect(result).toBeUndefined();
147
+ });
148
+ });
149
+
150
+ describe('SCHEMA_EXTENSIONS', () => {
151
+ it('should have correct priority order', () => {
152
+ expect(SCHEMA_EXTENSIONS).toEqual(['.schema.ts', '.schema.mts', '.schema.cts']);
153
+ });
154
+ });
155
+ });
@@ -0,0 +1,89 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+
4
+ /**
5
+ * Supported schema file extensions, in priority order.
6
+ * The first match wins when discovering schema files.
7
+ */
8
+ export const SCHEMA_EXTENSIONS = ['.schema.ts', '.schema.mts', '.schema.cts'] as const;
9
+ export type SchemaExtension = (typeof SCHEMA_EXTENSIONS)[number];
10
+
11
+ /**
12
+ * Map from schema extensions to their JavaScript import counterparts.
13
+ */
14
+ const SCHEMA_TO_JS_IMPORT_MAP: Record<string, string> = {
15
+ '.schema.ts': '.schema.js',
16
+ '.schema.mts': '.schema.mjs',
17
+ '.schema.cts': '.schema.cjs',
18
+ };
19
+
20
+ /**
21
+ * Try each supported schema extension and return the full path of the first
22
+ * one that exists on disk. Returns undefined if none is found.
23
+ */
24
+ export async function findSchemaFile(
25
+ dir: string,
26
+ tableName: string,
27
+ ): Promise<string | undefined> {
28
+ for (const ext of SCHEMA_EXTENSIONS) {
29
+ const candidate = join(dir, `${tableName}${ext}`);
30
+ try {
31
+ await access(candidate);
32
+ return candidate;
33
+ } catch {
34
+ // Continue to next extension
35
+ }
36
+ }
37
+ return undefined;
38
+ }
39
+
40
+ /**
41
+ * Synchronously find a schema file among directory entries.
42
+ * Returns the full path of the first match, or undefined.
43
+ */
44
+ export function findSchemaFileInEntries(
45
+ dataDirPath: string,
46
+ tableName: string,
47
+ entries: { isFile(): boolean; name: string }[],
48
+ ): string | undefined {
49
+ for (const ext of SCHEMA_EXTENSIONS) {
50
+ const candidateName = `${tableName}${ext}`;
51
+ if (entries.some((e) => e.isFile() && e.name === candidateName)) {
52
+ return join(dataDirPath, candidateName);
53
+ }
54
+ }
55
+ return undefined;
56
+ }
57
+
58
+ /**
59
+ * Check if a filename matches any supported schema file pattern.
60
+ */
61
+ export function isSchemaFile(fileName: string): boolean {
62
+ return SCHEMA_EXTENSIONS.some((ext) => fileName.endsWith(ext));
63
+ }
64
+
65
+ /**
66
+ * Extract table name from a schema filename.
67
+ * e.g., "users.schema.ts" -> "users", "users.schema.mts" -> "users"
68
+ */
69
+ export function extractTableNameFromSchemaFile(fileName: string): string | null {
70
+ for (const ext of SCHEMA_EXTENSIONS) {
71
+ if (fileName.endsWith(ext)) {
72
+ return fileName.slice(0, -ext.length);
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+
78
+ /**
79
+ * Rewrite a TypeScript path to its JavaScript counterpart for ESM imports.
80
+ * ".schema.ts" -> ".schema.js", ".schema.mts" -> ".schema.mjs", ".schema.cts" -> ".schema.cjs"
81
+ */
82
+ export function rewriteExtensionForImport(filePath: string): string {
83
+ for (const [tsExt, jsExt] of Object.entries(SCHEMA_TO_JS_IMPORT_MAP)) {
84
+ if (filePath.endsWith(tsExt)) {
85
+ return filePath.slice(0, -tsExt.length) + jsExt;
86
+ }
87
+ }
88
+ return filePath;
89
+ }
@@ -65,6 +65,87 @@ describe('SchemaLoader', () => {
65
65
  expect(schema['~standard'].vendor).toBe('default-export');
66
66
  });
67
67
 
68
+ it('should load schema from .mts file', async () => {
69
+ const jsonlPath = join(testDir, 'users.jsonl');
70
+ const schemaPath = join(testDir, 'users.schema.mts');
71
+
72
+ await writeFile(jsonlPath, '{"id":1}\n');
73
+ await writeFile(
74
+ schemaPath,
75
+ `
76
+ export const schema = {
77
+ '~standard': {
78
+ version: 1,
79
+ vendor: 'mts-test',
80
+ validate: () => ({ value: {} })
81
+ }
82
+ };
83
+ `,
84
+ );
85
+
86
+ const schema = await SchemaLoader.loadSchema(jsonlPath);
87
+
88
+ expect(schema).toBeDefined();
89
+ expect(schema['~standard'].vendor).toBe('mts-test');
90
+ });
91
+
92
+ it('should load schema from .cts file', async () => {
93
+ const jsonlPath = join(testDir, 'users.jsonl');
94
+ const schemaPath = join(testDir, 'users.schema.cts');
95
+
96
+ await writeFile(jsonlPath, '{"id":1}\n');
97
+ await writeFile(
98
+ schemaPath,
99
+ `
100
+ export const schema = {
101
+ '~standard': {
102
+ version: 1,
103
+ vendor: 'cts-test',
104
+ validate: () => ({ value: {} })
105
+ }
106
+ };
107
+ `,
108
+ );
109
+
110
+ const schema = await SchemaLoader.loadSchema(jsonlPath);
111
+
112
+ expect(schema).toBeDefined();
113
+ expect(schema['~standard'].vendor).toBe('cts-test');
114
+ });
115
+
116
+ it('should prefer .schema.ts over .schema.mts when both exist', async () => {
117
+ const jsonlPath = join(testDir, 'users.jsonl');
118
+
119
+ await writeFile(jsonlPath, '{"id":1}\n');
120
+ await writeFile(
121
+ join(testDir, 'users.schema.ts'),
122
+ `
123
+ export const schema = {
124
+ '~standard': {
125
+ version: 1,
126
+ vendor: 'ts-priority',
127
+ validate: () => ({ value: {} })
128
+ }
129
+ };
130
+ `,
131
+ );
132
+ await writeFile(
133
+ join(testDir, 'users.schema.mts'),
134
+ `
135
+ export const schema = {
136
+ '~standard': {
137
+ version: 1,
138
+ vendor: 'mts-secondary',
139
+ validate: () => ({ value: {} })
140
+ }
141
+ };
142
+ `,
143
+ );
144
+
145
+ const schema = await SchemaLoader.loadSchema(jsonlPath);
146
+ expect(schema['~standard'].vendor).toBe('ts-priority');
147
+ });
148
+
68
149
  it('should throw when no schema file exists', async () => {
69
150
  const jsonlPath = join(testDir, 'users.jsonl');
70
151
  await writeFile(jsonlPath, '{"id":1}\n');
@@ -72,6 +153,15 @@ describe('SchemaLoader', () => {
72
153
  await expect(SchemaLoader.loadSchema(jsonlPath)).rejects.toThrow(/Schema file not found/);
73
154
  });
74
155
 
156
+ it('should list all supported extensions in error message', async () => {
157
+ const jsonlPath = join(testDir, 'users.jsonl');
158
+ await writeFile(jsonlPath, '{"id":1}\n');
159
+
160
+ await expect(SchemaLoader.loadSchema(jsonlPath)).rejects.toThrow(
161
+ /\.schema\.ts.*\.schema\.mts.*\.schema\.cts/,
162
+ );
163
+ });
164
+
75
165
  it('should throw for invalid schema export', async () => {
76
166
  const jsonlPath = join(testDir, 'users.jsonl');
77
167
  const schemaPath = join(testDir, 'users.schema.ts');
@@ -1,7 +1,7 @@
1
1
  import { pathToFileURL } from 'node:url';
2
- import { access } from 'node:fs/promises';
3
- import { dirname, join, basename } from 'node:path';
2
+ import { dirname, basename } from 'node:path';
4
3
  import type { StandardSchema } from './types.js';
4
+ import { findSchemaFile, SCHEMA_EXTENSIONS } from './schema-extensions.js';
5
5
 
6
6
  export class SchemaLoader {
7
7
  /**
@@ -10,31 +10,23 @@ export class SchemaLoader {
10
10
  static async hasSchema(jsonlPath: string): Promise<boolean> {
11
11
  const dir = dirname(jsonlPath);
12
12
  const tableName = basename(jsonlPath, '.jsonl');
13
- const schemaPath = join(dir, `${tableName}.schema.ts`);
14
-
15
- try {
16
- await access(schemaPath);
17
- return true;
18
- } catch {
19
- return false;
20
- }
13
+ const schemaPath = await findSchemaFile(dir, tableName);
14
+ return schemaPath !== undefined;
21
15
  }
22
16
 
23
17
  /**
24
18
  * Load a validation schema file for a table
25
- * Requires ${tableName}.schema.ts to exist alongside the JSONL file
19
+ * Requires ${tableName}.schema.{ts,mts,cts} to exist alongside the JSONL file
26
20
  */
27
21
  static async loadSchema(jsonlPath: string): Promise<StandardSchema> {
28
22
  const dir = dirname(jsonlPath);
29
23
  const tableName = basename(jsonlPath, '.jsonl');
30
- const schemaPath = join(dir, `${tableName}.schema.ts`);
24
+ const schemaPath = await findSchemaFile(dir, tableName);
31
25
 
32
- try {
33
- await access(schemaPath);
34
- } catch (error) {
35
- throw new Error(`Schema file not found for table '${tableName}'. Expected: ${schemaPath}`, {
36
- cause: error instanceof Error ? error : undefined,
37
- });
26
+ if (!schemaPath) {
27
+ throw new Error(
28
+ `Schema file not found for table '${tableName}'. Expected one of: ${SCHEMA_EXTENSIONS.map((ext) => `${tableName}${ext}`).join(', ')}`,
29
+ );
38
30
  }
39
31
 
40
32
  try {
@@ -1,10 +1,12 @@
1
1
  import { readdir } from 'node:fs/promises';
2
2
  import { join, relative, basename, dirname, isAbsolute } from 'node:path';
3
3
  import { writeFile, mkdir } from 'node:fs/promises';
4
+ import { findSchemaFileInEntries, rewriteExtensionForImport } from './schema-extensions.js';
4
5
 
5
6
  export interface TypeGeneratorOptions {
6
7
  dataDir: string;
7
8
  projectRoot?: string; // Default: current working directory
9
+ output?: string; // Output file path (default: db.ts in dataDir)
8
10
  }
9
11
 
10
12
  interface TableInfo {
@@ -27,7 +29,9 @@ export class TypeGenerator {
27
29
  this.dataDirPath = isAbsolute(this.dataDir)
28
30
  ? this.dataDir
29
31
  : join(this.projectRoot, this.dataDir);
30
- this.outputFile = join(this.dataDirPath, 'db.ts');
32
+ this.outputFile = options.output
33
+ ? (isAbsolute(options.output) ? options.output : join(this.projectRoot, options.output))
34
+ : join(this.dataDirPath, 'db.ts');
31
35
  }
32
36
 
33
37
  /**
@@ -67,15 +71,11 @@ export class TypeGenerator {
67
71
  for (const entry of entries) {
68
72
  if (entry.isFile() && entry.name.endsWith('.jsonl')) {
69
73
  const tableName = basename(entry.name, '.jsonl');
70
- const schemaFileName = `${tableName}.schema.ts`;
71
- const schemaFilePath = join(this.dataDirPath, schemaFileName);
72
-
73
- // Check if schema file exists
74
- const hasSchema = entries.some((e) => e.isFile() && e.name === schemaFileName);
74
+ const schemaFilePath = findSchemaFileInEntries(this.dataDirPath, tableName, entries);
75
75
 
76
76
  tables.push({
77
77
  tableName,
78
- schemaFile: hasSchema ? schemaFilePath : undefined,
78
+ schemaFile: schemaFilePath,
79
79
  });
80
80
  }
81
81
  }
@@ -108,9 +108,10 @@ export class TypeGenerator {
108
108
  usedAliases.add(schemaIdentifier);
109
109
 
110
110
  // Calculate relative path from output file to schema file
111
- let relativePath = relative(join(this.outputFile, '..'), table.schemaFile)
112
- .replace(/\\/g, '/') // Convert Windows paths to Unix-style
113
- .replace('.ts', '.js'); // Import from .js (TypeScript module resolution)
111
+ let relativePath = rewriteExtensionForImport(
112
+ relative(join(this.outputFile, '..'), table.schemaFile)
113
+ .replace(/\\/g, '/'), // Convert Windows paths to Unix-style
114
+ );
114
115
 
115
116
  // Ensure relative path starts with './' or '../'
116
117
  if (!relativePath.startsWith('.')) {