@toiroakr/lines-db 0.3.0 → 0.4.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/schema.ts CHANGED
@@ -5,9 +5,9 @@ import type { StandardSchema, Table, ForeignKeyDefinition, IndexDefinition } fro
5
5
  */
6
6
  export interface SchemaOptions {
7
7
  /**
8
- * Primary key columns
8
+ * Primary key column
9
9
  */
10
- primaryKey?: string[];
10
+ primaryKey?: string;
11
11
 
12
12
  /**
13
13
  * Foreign key constraints
@@ -39,9 +39,9 @@ export interface BiDirectionalSchema<Input extends Table = Table, Output extends
39
39
  backward?: (output: Output) => Input;
40
40
 
41
41
  /**
42
- * Primary key columns
42
+ * Primary key column
43
43
  */
44
- primaryKey?: string[];
44
+ primaryKey?: string;
45
45
 
46
46
  /**
47
47
  * Foreign key constraints
@@ -79,9 +79,9 @@ export interface BiDirectionalSchema<Input extends Table = Table, Output extends
79
79
  * const schema = defineSchema(
80
80
  * v.object({ id: v.number(), customerId: v.number() }),
81
81
  * {
82
- * primaryKey: ['id'],
82
+ * primaryKey: 'id',
83
83
  * foreignKeys: [
84
- * { columns: ['customerId'], references: { table: 'users', columns: ['id'] } }
84
+ * { column: 'customerId', references: { table: 'users', column: 'id' } }
85
85
  * ]
86
86
  * }
87
87
  * );
package/src/types.ts CHANGED
@@ -18,10 +18,10 @@ export type InferInput<T> = T extends StandardSchemaV1<infer I, unknown> ? I : n
18
18
  export type InferOutput<T> = T extends StandardSchemaV1<unknown, infer O> ? O : never;
19
19
 
20
20
  export interface ForeignKeyDefinition {
21
- columns: string[];
21
+ column: string;
22
22
  references: {
23
23
  table: string;
24
- columns: string[];
24
+ column: string;
25
25
  };
26
26
  onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';
27
27
  onUpdate?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';
package/src/validator.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { readdir, stat } from 'node:fs/promises';
2
- import { join, basename } from 'node:path';
2
+ import { join, basename, dirname } from 'node:path';
3
3
  import { JsonlReader } from './jsonl-reader.js';
4
4
  import { SchemaLoader } from './schema-loader.js';
5
- import type { StandardSchemaIssue, JsonObject } from './types.js';
6
- import type { BiDirectionalSchema } from './schema.js';
5
+ import { LinesDB } from './database.js';
6
+ import type { StandardSchemaIssue } from './types.js';
7
7
 
8
8
  export interface ValidationResult {
9
9
  valid: boolean;
@@ -91,10 +91,10 @@ export class Validator {
91
91
  allWarnings.push(...result.warnings);
92
92
  }
93
93
 
94
- // Then, validate foreign keys across all tables (only for files with schema)
95
- if (filesWithSchema.length > 0) {
96
- const fkErrors = await this.validateForeignKeys(dirPath, filesWithSchema);
97
- allErrors.push(...fkErrors);
94
+ // Then, validate by actually loading into database
95
+ if (filesWithSchema.length > 0 && allErrors.length === 0) {
96
+ const dbErrors = await this.validateWithDatabase(dirPath, filesWithSchema);
97
+ allErrors.push(...dbErrors);
98
98
  }
99
99
 
100
100
  return {
@@ -105,80 +105,71 @@ export class Validator {
105
105
  }
106
106
 
107
107
  /**
108
- * Validate foreign key constraints across all tables
108
+ * Validate by loading data into an actual database
109
+ * This catches constraint violations (unique, primary key, foreign key, etc.)
109
110
  */
110
- private async validateForeignKeys(
111
+ private async validateWithDatabase(
111
112
  dirPath: string,
112
113
  jsonlFiles: string[],
113
114
  ): Promise<ValidationErrorDetail[]> {
114
115
  const errors: ValidationErrorDetail[] = [];
115
116
 
116
- // Load all table data
117
- const tableData = new Map<string, JsonObject[]>();
118
- const tableSchemas = new Map<string, BiDirectionalSchema>();
119
-
120
- for (const file of jsonlFiles) {
121
- const tableName = basename(file, '.jsonl');
122
- const data = await JsonlReader.read(file);
123
- const schema = await SchemaLoader.loadSchema(file);
124
-
125
- tableData.set(tableName, data);
126
- tableSchemas.set(tableName, schema as BiDirectionalSchema);
127
- }
128
-
129
- // Check foreign keys for each table
130
- for (const file of jsonlFiles) {
131
- const tableName = basename(file, '.jsonl');
132
- const schema = tableSchemas.get(tableName);
133
- const data = tableData.get(tableName);
134
-
135
- if (!schema || !data || !schema.foreignKeys) {
136
- continue;
137
- }
138
-
139
- // Check each foreign key constraint
140
- for (const fk of schema.foreignKeys) {
141
- const referencedTable = fk.references.table;
142
- const referencedData = tableData.get(referencedTable);
143
-
144
- if (!referencedData) {
145
- // Referenced table not found - skip validation
146
- continue;
147
- }
148
-
149
- // Build index of referenced values for fast lookup
150
- const referencedValues = new Set<string>();
151
- for (const refRow of referencedData) {
152
- // Build composite key from referenced columns
153
- const keyValues = fk.references.columns.map((col) => refRow[col]);
154
- const compositeKey = JSON.stringify(keyValues);
155
- referencedValues.add(compositeKey);
156
- }
117
+ // Capture console.warn messages
118
+ const warnMessages: string[] = [];
119
+ const originalWarn = console.warn;
120
+ console.warn = (...args: unknown[]) => {
121
+ const message = args.map((arg) => String(arg)).join(' ');
122
+ warnMessages.push(message);
123
+ // Still output to console for debugging
124
+ originalWarn(...args);
125
+ };
157
126
 
158
- // Check each row in current table
159
- for (let i = 0; i < data.length; i++) {
160
- const row = data[i];
161
- const foreignKeyValues = fk.columns.map((col) => row[col]);
162
- const compositeKey = JSON.stringify(foreignKeyValues);
163
-
164
- // Check if foreign key value exists in referenced table
165
- if (!referencedValues.has(compositeKey)) {
166
- errors.push({
167
- file,
168
- tableName,
169
- rowIndex: i, // 0-indexed, will be converted to 1-indexed in formatter
170
- issues: [],
171
- type: 'foreignKey',
172
- foreignKeyError: {
173
- column: fk.columns.join(', '),
174
- value: foreignKeyValues.length === 1 ? foreignKeyValues[0] : foreignKeyValues,
175
- referencedTable: referencedTable,
176
- referencedColumn: fk.references.columns.join(', '),
127
+ try {
128
+ // Try to initialize database with the data directory
129
+ const db = LinesDB.create({ dataDir: dirPath });
130
+ await db.initialize();
131
+ await db.close();
132
+
133
+ // Check if there were any loading errors
134
+ for (const message of warnMessages) {
135
+ if (message.includes('Failed to load table')) {
136
+ // Extract table name from message
137
+ const tableNameMatch = message.match(/Failed to load table '([^']+)'/);
138
+ const tableName = tableNameMatch ? tableNameMatch[1] : 'unknown';
139
+
140
+ const file = jsonlFiles.find((f) => basename(f, '.jsonl') === tableName);
141
+
142
+ errors.push({
143
+ file: file || `${dirPath}/${tableName}.jsonl`,
144
+ tableName,
145
+ rowIndex: 0,
146
+ issues: [
147
+ {
148
+ message: message.replace(/^Warning:\s*/, ''),
149
+ path: [],
177
150
  },
178
- });
179
- }
151
+ ],
152
+ type: 'schema',
153
+ });
180
154
  }
181
155
  }
156
+ } catch (error) {
157
+ // If initialization itself fails, report it
158
+ errors.push({
159
+ file: dirPath,
160
+ tableName: 'database',
161
+ rowIndex: 0,
162
+ issues: [
163
+ {
164
+ message: `Database initialization failed: ${error instanceof Error ? error.message : String(error)}`,
165
+ path: [],
166
+ },
167
+ ],
168
+ type: 'schema',
169
+ });
170
+ } finally {
171
+ // Restore console.warn
172
+ console.warn = originalWarn;
182
173
  }
183
174
 
184
175
  return errors;
@@ -196,7 +187,7 @@ export class Validator {
196
187
 
197
188
  const errors: ValidationErrorDetail[] = [];
198
189
 
199
- // Validate each row
190
+ // Validate each row with schema
200
191
  for (let i = 0; i < data.length; i++) {
201
192
  const row = data[i];
202
193
  const result = schema['~standard'].validate(row);
@@ -217,6 +208,13 @@ export class Validator {
217
208
  }
218
209
  }
219
210
 
211
+ // If schema validation passed, validate with database
212
+ if (errors.length === 0) {
213
+ const dirPath = dirname(filePath);
214
+ const dbErrors = await this.validateWithDatabase(dirPath, [filePath]);
215
+ errors.push(...dbErrors);
216
+ }
217
+
220
218
  return {
221
219
  valid: errors.length === 0,
222
220
  errors,