aiex-cli 0.1.1-beta.1 → 0.1.1-beta.2

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.
@@ -11,7 +11,7 @@ import { z } from "zod";
11
11
 
12
12
  //#region package.json
13
13
  var name = "aiex-cli";
14
- var version = "0.1.1-beta.1";
14
+ var version = "0.1.1-beta.2";
15
15
  var description = "JSON Schema → SQLite with AI-powered data extraction";
16
16
  var package_default = {
17
17
  name,
@@ -405,9 +405,60 @@ function getColumnChecks(prop, colName) {
405
405
  value: prop.maximum
406
406
  });
407
407
  }
408
+ if (prop.enum?.length) checks.push({
409
+ name: `${colName}_enum`,
410
+ column: colName,
411
+ kind: "enum_value",
412
+ value: prop.enum
413
+ });
408
414
  return checks;
409
415
  }
410
- function parseObjectToTable(schema, _warnings) {
416
+ function warnNonDrizzleBackedProperty(warnings, schemaPath, property) {
417
+ if (property.pattern) warnings.push(`${schemaPath}.pattern is kept for extraction guidance but is not emitted as a SQLite constraint because SQLite has no portable REGEXP support.`);
418
+ if (property.drizzle?.customType) warnings.push(`${schemaPath}.drizzle.customType is declared but not emitted. AIEX currently supports stable built-in Drizzle SQLite column types only.`);
419
+ }
420
+ function describeColumnType(columnType) {
421
+ switch (columnType.class) {
422
+ case "text": return {
423
+ drizzleType: columnType.mode === "json" ? `text({ mode: 'json' })` : "text()",
424
+ sqliteType: "text"
425
+ };
426
+ case "integer": return {
427
+ drizzleType: columnType.mode ? `integer({ mode: '${columnType.mode}' })` : "integer()",
428
+ sqliteType: "integer"
429
+ };
430
+ case "real": return {
431
+ drizzleType: "real()",
432
+ sqliteType: "real"
433
+ };
434
+ }
435
+ }
436
+ function columnNotes(property, column) {
437
+ const notes = [];
438
+ if (property.type === "object" || property.type === "array") notes.push("stored_as_json");
439
+ if (property.format === "date-time") notes.push("date_time_as_timestamp");
440
+ if (property.drizzle?.mode) notes.push(`drizzle_mode:${property.drizzle.mode}`);
441
+ if (property.foreignKey) notes.push(`foreign_key:${property.foreignKey.table}.${property.foreignKey.column}`);
442
+ if (column.default !== void 0) notes.push("default");
443
+ return notes;
444
+ }
445
+ function mapColumnToReport(schemaPath, table, property, column, relation) {
446
+ const columnType = describeColumnType(column.columnType);
447
+ return {
448
+ schemaPath,
449
+ table,
450
+ column: column.name,
451
+ drizzleType: columnType.drizzleType,
452
+ sqliteType: columnType.sqliteType,
453
+ nullable: column.isNullable,
454
+ primary: column.isPrimary,
455
+ unique: column.isUnique,
456
+ relation,
457
+ constraints: property.enum?.length ? { enumValues: property.enum } : void 0,
458
+ notes: columnNotes(property, column)
459
+ };
460
+ }
461
+ function parseObjectToTable(schema, warnings, mapping) {
411
462
  const tableName = schema.table.name;
412
463
  const columns = [];
413
464
  const checks = [];
@@ -426,6 +477,8 @@ function parseObjectToTable(schema, _warnings) {
426
477
  const column = mapPropertyToColumn(propName, prop, requiredFields.has(propName));
427
478
  columns.push(column);
428
479
  checks.push(...getColumnChecks(prop, column.name));
480
+ warnNonDrizzleBackedProperty(warnings, `$.properties.${propName}`, prop);
481
+ mapping.push(mapColumnToReport(`$.properties.${propName}`, tableName, prop, column, "root"));
429
482
  }
430
483
  if (schema.table.timestamps) {
431
484
  const tsCol = {
@@ -444,18 +497,36 @@ function parseObjectToTable(schema, _warnings) {
444
497
  ...tsCol,
445
498
  name: "updated_at"
446
499
  });
500
+ mapping.push(mapColumnToReport("$.table.timestamps.createdAt", tableName, {
501
+ type: "integer",
502
+ drizzle: { mode: "timestamp" }
503
+ }, tsCol, "root"));
504
+ mapping.push(mapColumnToReport("$.table.timestamps.updatedAt", tableName, {
505
+ type: "integer",
506
+ drizzle: { mode: "timestamp" }
507
+ }, {
508
+ ...tsCol,
509
+ name: "updated_at"
510
+ }, "root"));
511
+ }
512
+ if (schema.table.softDelete) {
513
+ const deletedAt = {
514
+ name: "deleted_at",
515
+ columnType: {
516
+ class: "integer",
517
+ mode: "timestamp"
518
+ },
519
+ isPrimary: false,
520
+ isAutoIncrement: false,
521
+ isNullable: true,
522
+ isUnique: false
523
+ };
524
+ columns.push(deletedAt);
525
+ mapping.push(mapColumnToReport("$.table.softDelete.deletedAt", tableName, {
526
+ type: "integer",
527
+ drizzle: { mode: "timestamp" }
528
+ }, deletedAt, "root"));
447
529
  }
448
- if (schema.table.softDelete) columns.push({
449
- name: "deleted_at",
450
- columnType: {
451
- class: "integer",
452
- mode: "timestamp"
453
- },
454
- isPrimary: false,
455
- isAutoIncrement: false,
456
- isNullable: true,
457
- isUnique: false
458
- });
459
530
  return checks.length > 0 ? {
460
531
  name: tableName,
461
532
  columns,
@@ -465,7 +536,7 @@ function parseObjectToTable(schema, _warnings) {
465
536
  columns
466
537
  };
467
538
  }
468
- function parseNestedObject(propName, property, parentTableName, warnings) {
539
+ function parseNestedObject(propName, property, parentTableName, warnings, mapping) {
469
540
  const nestedTableName = `${parentTableName}_${toSnakeCase(propName)}`;
470
541
  const columns = [];
471
542
  const checks = [];
@@ -478,6 +549,18 @@ function parseNestedObject(propName, property, parentTableName, warnings) {
478
549
  isNullable: false,
479
550
  isUnique: false
480
551
  });
552
+ mapping.push({
553
+ schemaPath: `$.properties.${propName}.id`,
554
+ table: nestedTableName,
555
+ column: "id",
556
+ drizzleType: "integer().primaryKey({ autoIncrement: true })",
557
+ sqliteType: "integer",
558
+ nullable: false,
559
+ primary: true,
560
+ unique: false,
561
+ relation: relationType,
562
+ notes: ["generated_nested_primary_key"]
563
+ });
481
564
  columns.push({
482
565
  name: `${parentTableName}_id`,
483
566
  columnType: { class: "integer" },
@@ -491,6 +574,18 @@ function parseNestedObject(propName, property, parentTableName, warnings) {
491
574
  column: "id"
492
575
  }
493
576
  });
577
+ mapping.push({
578
+ schemaPath: `$.properties.${propName}.${parentTableName}Id`,
579
+ table: nestedTableName,
580
+ column: `${parentTableName}_id`,
581
+ drizzleType: "integer().references(...)",
582
+ sqliteType: "integer",
583
+ nullable: false,
584
+ primary: false,
585
+ unique: false,
586
+ relation: relationType,
587
+ notes: [`generated_parent_foreign_key:${parentTableName}.id`]
588
+ });
494
589
  if (property.type === "object" && property.properties) for (const [childName, childProp] of Object.entries(property.properties)) {
495
590
  if (childProp.nested?.enabled) {
496
591
  warnings.push(`Nested property "${childName}" inside "${nestedTableName}" is skipped — only one level of nesting is supported. Remove nested.enabled or use drizzle.mode: 'json' instead.`);
@@ -499,6 +594,8 @@ function parseNestedObject(propName, property, parentTableName, warnings) {
499
594
  const column = mapPropertyToColumn(childName, childProp, false);
500
595
  columns.push(column);
501
596
  checks.push(...getColumnChecks(childProp, column.name));
597
+ warnNonDrizzleBackedProperty(warnings, `$.properties.${propName}.properties.${childName}`, childProp);
598
+ mapping.push(mapColumnToReport(`$.properties.${propName}.properties.${childName}`, nestedTableName, childProp, column, relationType));
502
599
  }
503
600
  const relation = {
504
601
  fromTable: nestedTableName,
@@ -531,15 +628,16 @@ function parseJsonSchema(schema) {
531
628
  const relations = [];
532
629
  const reverseRelations = [];
533
630
  const warnings = [];
534
- const mainTable = parseObjectToTable(schema, warnings);
631
+ const mapping = [];
632
+ const mainTable = parseObjectToTable(schema, warnings, mapping);
535
633
  tables.push(mainTable);
536
634
  for (const [propName, prop] of Object.entries(schema.properties)) if (prop.type === "object" && prop.nested?.enabled) {
537
- const nested = parseNestedObject(propName, prop, mainTable.name, warnings);
635
+ const nested = parseNestedObject(propName, prop, mainTable.name, warnings, mapping);
538
636
  tables.push(nested.table);
539
637
  relations.push(nested.relation);
540
638
  reverseRelations.push(nested.reverseRelation);
541
639
  } else if (prop.type === "array" && prop.items?.nested?.enabled && prop.items?.type === "object" && prop.items.properties) {
542
- const nested = parseNestedObject(propName, prop.items, mainTable.name, warnings);
640
+ const nested = parseNestedObject(propName, prop.items, mainTable.name, warnings, mapping);
543
641
  tables.push(nested.table);
544
642
  relations.push(nested.relation);
545
643
  reverseRelations.push(nested.reverseRelation);
@@ -548,7 +646,8 @@ function parseJsonSchema(schema) {
548
646
  tables,
549
647
  relations,
550
648
  reverseRelations,
551
- warnings
649
+ warnings,
650
+ mapping
552
651
  };
553
652
  }
554
653
 
@@ -810,7 +909,8 @@ const en = {
810
909
  description: "Sync JSON Schema to SQLite database",
811
910
  args: {
812
911
  generate: "Only generate Drizzle schema, skip migrate",
813
- name: "Name for the migration"
912
+ name: "Name for the migration",
913
+ force: "Allow high-risk schema migrations"
814
914
  },
815
915
  noSchemas: "No schema files found in {{path}}",
816
916
  runWebHint: "Run {{cmd}} to create and configure schemas in the Web UI",
@@ -825,7 +925,9 @@ const en = {
825
925
  databaseMigrated: "Database migrated",
826
926
  migrationsApplied: "Migrations applied",
827
927
  migrationFail: "Migration failed",
828
- runWithoutGenerate: "Done! Run without --generate to apply migrations"
928
+ runWithoutGenerate: "Done! Run without --generate to apply migrations",
929
+ riskSummary: "Migration risk: {{level}} ({{count}} item(s))",
930
+ highRiskBlocked: "High-risk schema migration blocked. Review .aiex/drizzle/schema-map.json and rerun with {{flag}} to continue."
829
931
  },
830
932
  extract: {
831
933
  description: "Extract structured data from text, images, or PDFs",
@@ -991,7 +1093,8 @@ const en = {
991
1093
  noFiles: "No schema files found",
992
1094
  migrationFailed: "Migration failed",
993
1095
  migrationHelperInvalidOutput: "Migration helper did not return valid output",
994
- migrationHelperFailed: "Migration helper failed"
1096
+ migrationHelperFailed: "Migration helper failed",
1097
+ highRiskMigrationBlocked: "High-risk schema migration blocked"
995
1098
  },
996
1099
  db: {
997
1100
  notFound: "Database not found at {{path}}. Run {{cmd}} first to create the database.",
@@ -1250,7 +1353,7 @@ async function initI18n(lng) {
1250
1353
  fallbackLng: "en",
1251
1354
  resources: {
1252
1355
  "en": { translation: en },
1253
- "zh-CN": { translation: await import("./zh-CN-Cs4MViVi.mjs").then((m) => m.zhCN) }
1356
+ "zh-CN": { translation: await import("./zh-CN-BAGJklRp.mjs").then((m) => m.zhCN) }
1254
1357
  },
1255
1358
  interpolation: { escapeValue: false },
1256
1359
  returnNull: false
@@ -1592,6 +1695,9 @@ function renderColumnType(ct) {
1592
1695
  function renderDefaultValue(value) {
1593
1696
  return JSON.stringify(value);
1594
1697
  }
1698
+ function renderSqlLiteral(value) {
1699
+ return typeof value === "number" ? String(value) : `'${value.replace(/'/g, "''")}'`;
1700
+ }
1595
1701
  function generateColumnDefinition(column) {
1596
1702
  if (column.isPrimary && column.isAutoIncrement) return ` ${column.name}: integer().primaryKey({ autoIncrement: true })`;
1597
1703
  let def = ` ${column.name}: ${renderColumnType(column.columnType)}`;
@@ -1618,6 +1724,9 @@ function renderCheckToDrizzle(check, tableVar) {
1618
1724
  case "max_value":
1619
1725
  expr = `${colRef} <= ${check.value}`;
1620
1726
  break;
1727
+ case "enum_value":
1728
+ expr = `${colRef} IN (${(Array.isArray(check.value) ? check.value : []).map(renderSqlLiteral).join(", ")})`;
1729
+ break;
1621
1730
  }
1622
1731
  return ` ${check.name}: check('${check.name}', sql\`${expr}\`)`;
1623
1732
  }
package/dist/index.d.mts CHANGED
@@ -305,8 +305,8 @@ type ColumnType = {
305
305
  interface CheckConstraint {
306
306
  name: string;
307
307
  column: string;
308
- kind: 'min_length' | 'max_length' | 'min_value' | 'max_value';
309
- value: number;
308
+ kind: 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'enum_value';
309
+ value: number | (string | number)[];
310
310
  }
311
311
  interface ParsedColumn {
312
312
  name: string;
@@ -322,6 +322,21 @@ interface ParsedColumn {
322
322
  column: string;
323
323
  };
324
324
  }
325
+ interface SchemaMappingEntry {
326
+ schemaPath: string;
327
+ table: string;
328
+ column: string;
329
+ drizzleType: string;
330
+ sqliteType: 'text' | 'integer' | 'real';
331
+ nullable: boolean;
332
+ primary: boolean;
333
+ unique: boolean;
334
+ relation?: 'root' | 'has-one' | 'has-many';
335
+ constraints?: {
336
+ enumValues?: (string | number)[];
337
+ };
338
+ notes: string[];
339
+ }
325
340
  interface ParsedTable {
326
341
  name: string;
327
342
  columns: ParsedColumn[];
@@ -345,6 +360,7 @@ interface ParseResult {
345
360
  relations: ParsedRelation[];
346
361
  reverseRelations: ParsedReverseRelation[];
347
362
  warnings: string[];
363
+ mapping?: SchemaMappingEntry[];
348
364
  }
349
365
  interface MigrationConfig {
350
366
  schemaPath: string;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as doctorDiagnosticsSeverityRows, g as buildDoctorDiagnostics, i as generateDrizzleConfig, m as parseJsonSchema, n as collectDoctorDiagnostics, p as JsonSchemaDefinitionSchema, r as createMigrationConfig, t as generateDrizzleSchema, v as doctorDiagnosticsTableRows, y as formatDoctorDiagnosticsJson } from "./generate-drizzle-schema-CaSMqQWx.mjs";
1
+ import { _ as doctorDiagnosticsSeverityRows, g as buildDoctorDiagnostics, i as generateDrizzleConfig, m as parseJsonSchema, n as collectDoctorDiagnostics, p as JsonSchemaDefinitionSchema, r as createMigrationConfig, t as generateDrizzleSchema, v as doctorDiagnosticsTableRows, y as formatDoctorDiagnosticsJson } from "./generate-drizzle-schema-sZ01QiYJ.mjs";
2
2
 
3
3
  export { JsonSchemaDefinitionSchema, buildDoctorDiagnostics, collectDoctorDiagnostics, createMigrationConfig, doctorDiagnosticsSeverityRows, doctorDiagnosticsTableRows, formatDoctorDiagnosticsJson, generateDrizzleConfig, generateDrizzleSchema, parseJsonSchema };
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://raw.githubusercontent.com/OSpoon/aiex-cli/main/app/cli/schemas/table-schema.json",
4
4
  "title": "aiex Table Schema",
5
- "description": "Defines a database table and its columns for aiex-cli schema-to-SQLite migration.",
5
+ "description": "Defines an AIEX Drizzle-backed schema dialect file for schema-to-SQLite migration.",
6
6
  "type": "object",
7
7
  "required": ["title", "type", "table", "properties"],
8
8
  "additionalProperties": false,
@@ -40,6 +40,12 @@
40
40
  "items": { "type": "string" },
41
41
  "description": "List of property names that are NOT NULL.",
42
42
  "default": []
43
+ },
44
+ "examples": {
45
+ "type": "array",
46
+ "description": "Few-shot examples used by AI extraction prompts.",
47
+ "items": { "$ref": "#/$defs/examplePair" },
48
+ "default": []
43
49
  }
44
50
  },
45
51
 
@@ -113,12 +119,17 @@
113
119
  },
114
120
  "pattern": {
115
121
  "type": "string",
116
- "description": "Regular expression constraint for string values."
122
+ "description": "Regular expression guidance for extraction and validation. Not emitted as a SQLite constraint because SQLite has no portable built-in REGEXP support."
117
123
  },
118
124
  "enum": {
119
125
  "type": "array",
120
- "items": { "type": "string" },
121
- "description": "Enumeration of allowed values for this field."
126
+ "items": {
127
+ "oneOf": [
128
+ { "type": "string" },
129
+ { "type": "number" }
130
+ ]
131
+ },
132
+ "description": "Enumeration of allowed values for this field. Emitted as a SQLite CHECK constraint."
122
133
  },
123
134
  "examples": {
124
135
  "type": "array",
@@ -132,20 +143,20 @@
132
143
  "minLength": {
133
144
  "type": "integer",
134
145
  "minimum": 0,
135
- "description": "Minimum string length (validation only, not a DB constraint)."
146
+ "description": "Minimum string length. Emitted as a SQLite CHECK constraint."
136
147
  },
137
148
  "maxLength": {
138
149
  "type": "integer",
139
150
  "minimum": 1,
140
- "description": "Maximum string length (validation only, not a DB constraint)."
151
+ "description": "Maximum string length. Emitted as a SQLite CHECK constraint."
141
152
  },
142
153
  "minimum": {
143
154
  "type": "number",
144
- "description": "Minimum numeric value (validation only, not a DB constraint)."
155
+ "description": "Minimum numeric value. Emitted as a SQLite CHECK constraint."
145
156
  },
146
157
  "maximum": {
147
158
  "type": "number",
148
- "description": "Maximum numeric value (validation only, not a DB constraint)."
159
+ "description": "Maximum numeric value. Emitted as a SQLite CHECK constraint."
149
160
  },
150
161
  "drizzle": {
151
162
  "$ref": "#/$defs/drizzleConfig",
@@ -198,7 +209,24 @@
198
209
  },
199
210
  "customType": {
200
211
  "type": "string",
201
- "description": "Custom Drizzle type name (reserved for future use)."
212
+ "description": "Custom Drizzle type name. Accepted by the schema shape but not emitted by AIEX; generation reports a warning."
213
+ }
214
+ }
215
+ },
216
+
217
+ "examplePair": {
218
+ "type": "object",
219
+ "required": ["text", "output"],
220
+ "additionalProperties": false,
221
+ "properties": {
222
+ "text": {
223
+ "type": "string",
224
+ "minLength": 1,
225
+ "description": "Source text example."
226
+ },
227
+ "output": {
228
+ "type": "object",
229
+ "description": "Expected extracted output for the source text example."
202
230
  }
203
231
  }
204
232
  },