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

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.3";
15
15
  var description = "JSON Schema → SQLite with AI-powered data extraction";
16
16
  var package_default = {
17
17
  name,
@@ -405,9 +405,59 @@ 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
+ }
419
+ function describeColumnType(columnType) {
420
+ switch (columnType.class) {
421
+ case "text": return {
422
+ drizzleType: columnType.mode === "json" ? `text({ mode: 'json' })` : "text()",
423
+ sqliteType: "text"
424
+ };
425
+ case "integer": return {
426
+ drizzleType: columnType.mode ? `integer({ mode: '${columnType.mode}' })` : "integer()",
427
+ sqliteType: "integer"
428
+ };
429
+ case "real": return {
430
+ drizzleType: "real()",
431
+ sqliteType: "real"
432
+ };
433
+ }
434
+ }
435
+ function columnNotes(property, column) {
436
+ const notes = [];
437
+ if (property.type === "object" || property.type === "array") notes.push("stored_as_json");
438
+ if (property.format === "date-time") notes.push("date_time_as_timestamp");
439
+ if (property.drizzle?.mode) notes.push(`drizzle_mode:${property.drizzle.mode}`);
440
+ if (property.foreignKey) notes.push(`foreign_key:${property.foreignKey.table}.${property.foreignKey.column}`);
441
+ if (column.default !== void 0) notes.push("default");
442
+ return notes;
443
+ }
444
+ function mapColumnToReport(schemaPath, table, property, column, relation) {
445
+ const columnType = describeColumnType(column.columnType);
446
+ return {
447
+ schemaPath,
448
+ table,
449
+ column: column.name,
450
+ drizzleType: columnType.drizzleType,
451
+ sqliteType: columnType.sqliteType,
452
+ nullable: column.isNullable,
453
+ primary: column.isPrimary,
454
+ unique: column.isUnique,
455
+ relation,
456
+ constraints: property.enum?.length ? { enumValues: property.enum } : void 0,
457
+ notes: columnNotes(property, column)
458
+ };
459
+ }
460
+ function parseObjectToTable(schema, warnings, mapping) {
411
461
  const tableName = schema.table.name;
412
462
  const columns = [];
413
463
  const checks = [];
@@ -426,6 +476,8 @@ function parseObjectToTable(schema, _warnings) {
426
476
  const column = mapPropertyToColumn(propName, prop, requiredFields.has(propName));
427
477
  columns.push(column);
428
478
  checks.push(...getColumnChecks(prop, column.name));
479
+ warnNonDrizzleBackedProperty(warnings, `$.properties.${propName}`, prop);
480
+ mapping.push(mapColumnToReport(`$.properties.${propName}`, tableName, prop, column, "root"));
429
481
  }
430
482
  if (schema.table.timestamps) {
431
483
  const tsCol = {
@@ -444,18 +496,36 @@ function parseObjectToTable(schema, _warnings) {
444
496
  ...tsCol,
445
497
  name: "updated_at"
446
498
  });
499
+ mapping.push(mapColumnToReport("$.table.timestamps.createdAt", tableName, {
500
+ type: "integer",
501
+ drizzle: { mode: "timestamp" }
502
+ }, tsCol, "root"));
503
+ mapping.push(mapColumnToReport("$.table.timestamps.updatedAt", tableName, {
504
+ type: "integer",
505
+ drizzle: { mode: "timestamp" }
506
+ }, {
507
+ ...tsCol,
508
+ name: "updated_at"
509
+ }, "root"));
510
+ }
511
+ if (schema.table.softDelete) {
512
+ const deletedAt = {
513
+ name: "deleted_at",
514
+ columnType: {
515
+ class: "integer",
516
+ mode: "timestamp"
517
+ },
518
+ isPrimary: false,
519
+ isAutoIncrement: false,
520
+ isNullable: true,
521
+ isUnique: false
522
+ };
523
+ columns.push(deletedAt);
524
+ mapping.push(mapColumnToReport("$.table.softDelete.deletedAt", tableName, {
525
+ type: "integer",
526
+ drizzle: { mode: "timestamp" }
527
+ }, deletedAt, "root"));
447
528
  }
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
529
  return checks.length > 0 ? {
460
530
  name: tableName,
461
531
  columns,
@@ -465,7 +535,7 @@ function parseObjectToTable(schema, _warnings) {
465
535
  columns
466
536
  };
467
537
  }
468
- function parseNestedObject(propName, property, parentTableName, warnings) {
538
+ function parseNestedObject(propName, property, parentTableName, warnings, mapping) {
469
539
  const nestedTableName = `${parentTableName}_${toSnakeCase(propName)}`;
470
540
  const columns = [];
471
541
  const checks = [];
@@ -478,6 +548,18 @@ function parseNestedObject(propName, property, parentTableName, warnings) {
478
548
  isNullable: false,
479
549
  isUnique: false
480
550
  });
551
+ mapping.push({
552
+ schemaPath: `$.properties.${propName}.id`,
553
+ table: nestedTableName,
554
+ column: "id",
555
+ drizzleType: "integer().primaryKey({ autoIncrement: true })",
556
+ sqliteType: "integer",
557
+ nullable: false,
558
+ primary: true,
559
+ unique: false,
560
+ relation: relationType,
561
+ notes: ["generated_nested_primary_key"]
562
+ });
481
563
  columns.push({
482
564
  name: `${parentTableName}_id`,
483
565
  columnType: { class: "integer" },
@@ -491,6 +573,18 @@ function parseNestedObject(propName, property, parentTableName, warnings) {
491
573
  column: "id"
492
574
  }
493
575
  });
576
+ mapping.push({
577
+ schemaPath: `$.properties.${propName}.${parentTableName}Id`,
578
+ table: nestedTableName,
579
+ column: `${parentTableName}_id`,
580
+ drizzleType: "integer().references(...)",
581
+ sqliteType: "integer",
582
+ nullable: false,
583
+ primary: false,
584
+ unique: false,
585
+ relation: relationType,
586
+ notes: [`generated_parent_foreign_key:${parentTableName}.id`]
587
+ });
494
588
  if (property.type === "object" && property.properties) for (const [childName, childProp] of Object.entries(property.properties)) {
495
589
  if (childProp.nested?.enabled) {
496
590
  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 +593,8 @@ function parseNestedObject(propName, property, parentTableName, warnings) {
499
593
  const column = mapPropertyToColumn(childName, childProp, false);
500
594
  columns.push(column);
501
595
  checks.push(...getColumnChecks(childProp, column.name));
596
+ warnNonDrizzleBackedProperty(warnings, `$.properties.${propName}.properties.${childName}`, childProp);
597
+ mapping.push(mapColumnToReport(`$.properties.${propName}.properties.${childName}`, nestedTableName, childProp, column, relationType));
502
598
  }
503
599
  const relation = {
504
600
  fromTable: nestedTableName,
@@ -531,15 +627,16 @@ function parseJsonSchema(schema) {
531
627
  const relations = [];
532
628
  const reverseRelations = [];
533
629
  const warnings = [];
534
- const mainTable = parseObjectToTable(schema, warnings);
630
+ const mapping = [];
631
+ const mainTable = parseObjectToTable(schema, warnings, mapping);
535
632
  tables.push(mainTable);
536
633
  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);
634
+ const nested = parseNestedObject(propName, prop, mainTable.name, warnings, mapping);
538
635
  tables.push(nested.table);
539
636
  relations.push(nested.relation);
540
637
  reverseRelations.push(nested.reverseRelation);
541
638
  } 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);
639
+ const nested = parseNestedObject(propName, prop.items, mainTable.name, warnings, mapping);
543
640
  tables.push(nested.table);
544
641
  relations.push(nested.relation);
545
642
  reverseRelations.push(nested.reverseRelation);
@@ -548,7 +645,8 @@ function parseJsonSchema(schema) {
548
645
  tables,
549
646
  relations,
550
647
  reverseRelations,
551
- warnings
648
+ warnings,
649
+ mapping
552
650
  };
553
651
  }
554
652
 
@@ -561,10 +659,13 @@ const DrizzleModeSchema = z.enum([
561
659
  "boolean",
562
660
  "bigint"
563
661
  ]);
564
- const DrizzleExtensionSchema = z.object({
565
- mode: DrizzleModeSchema.optional(),
566
- customType: z.string().optional()
567
- }).optional();
662
+ const FormatSchema = z.enum([
663
+ "date-time",
664
+ "email",
665
+ "uri",
666
+ "json"
667
+ ]);
668
+ const DrizzleExtensionSchema = z.object({ mode: DrizzleModeSchema.optional() }).strict().optional();
568
669
  const NestedConfigSchema = z.object({
569
670
  enabled: z.literal(true),
570
671
  relation: z.enum(["has-one", "has-many"])
@@ -584,7 +685,7 @@ const JsonSchemaPropertySchema = z.lazy(() => z.object({
584
685
  "array",
585
686
  "null"
586
687
  ]),
587
- format: z.string().optional(),
688
+ format: FormatSchema.optional(),
588
689
  pattern: z.string().optional(),
589
690
  enum: z.array(z.union([z.string(), z.number()])).optional(),
590
691
  primary: z.boolean().optional(),
@@ -810,7 +911,8 @@ const en = {
810
911
  description: "Sync JSON Schema to SQLite database",
811
912
  args: {
812
913
  generate: "Only generate Drizzle schema, skip migrate",
813
- name: "Name for the migration"
914
+ name: "Name for the migration",
915
+ force: "Allow high-risk schema migrations"
814
916
  },
815
917
  noSchemas: "No schema files found in {{path}}",
816
918
  runWebHint: "Run {{cmd}} to create and configure schemas in the Web UI",
@@ -825,7 +927,9 @@ const en = {
825
927
  databaseMigrated: "Database migrated",
826
928
  migrationsApplied: "Migrations applied",
827
929
  migrationFail: "Migration failed",
828
- runWithoutGenerate: "Done! Run without --generate to apply migrations"
930
+ runWithoutGenerate: "Done! Run without --generate to apply migrations",
931
+ riskSummary: "Migration risk: {{level}} ({{count}} item(s))",
932
+ highRiskBlocked: "High-risk schema migration blocked. Review .aiex/drizzle/schema-map.json and rerun with {{flag}} to continue."
829
933
  },
830
934
  extract: {
831
935
  description: "Extract structured data from text, images, or PDFs",
@@ -991,7 +1095,8 @@ const en = {
991
1095
  noFiles: "No schema files found",
992
1096
  migrationFailed: "Migration failed",
993
1097
  migrationHelperInvalidOutput: "Migration helper did not return valid output",
994
- migrationHelperFailed: "Migration helper failed"
1098
+ migrationHelperFailed: "Migration helper failed",
1099
+ highRiskMigrationBlocked: "High-risk schema migration blocked"
995
1100
  },
996
1101
  db: {
997
1102
  notFound: "Database not found at {{path}}. Run {{cmd}} first to create the database.",
@@ -1250,7 +1355,7 @@ async function initI18n(lng) {
1250
1355
  fallbackLng: "en",
1251
1356
  resources: {
1252
1357
  "en": { translation: en },
1253
- "zh-CN": { translation: await import("./zh-CN-Cs4MViVi.mjs").then((m) => m.zhCN) }
1358
+ "zh-CN": { translation: await import("./zh-CN-BAGJklRp.mjs").then((m) => m.zhCN) }
1254
1359
  },
1255
1360
  interpolation: { escapeValue: false },
1256
1361
  returnNull: false
@@ -1592,6 +1697,9 @@ function renderColumnType(ct) {
1592
1697
  function renderDefaultValue(value) {
1593
1698
  return JSON.stringify(value);
1594
1699
  }
1700
+ function renderSqlLiteral(value) {
1701
+ return typeof value === "number" ? String(value) : `'${value.replace(/'/g, "''")}'`;
1702
+ }
1595
1703
  function generateColumnDefinition(column) {
1596
1704
  if (column.isPrimary && column.isAutoIncrement) return ` ${column.name}: integer().primaryKey({ autoIncrement: true })`;
1597
1705
  let def = ` ${column.name}: ${renderColumnType(column.columnType)}`;
@@ -1618,6 +1726,9 @@ function renderCheckToDrizzle(check, tableVar) {
1618
1726
  case "max_value":
1619
1727
  expr = `${colRef} <= ${check.value}`;
1620
1728
  break;
1729
+ case "enum_value":
1730
+ expr = `${colRef} IN (${(Array.isArray(check.value) ? check.value : []).map(renderSqlLiteral).join(", ")})`;
1731
+ break;
1621
1732
  }
1622
1733
  return ` ${check.name}: check('${check.name}', sql\`${expr}\`)`;
1623
1734
  }
package/dist/index.d.mts CHANGED
@@ -264,7 +264,7 @@ interface ForeignKeyRef {
264
264
  interface JsonSchemaProperty {
265
265
  description?: string;
266
266
  type: 'string' | 'integer' | 'number' | 'boolean' | 'object' | 'array' | 'null';
267
- format?: string;
267
+ format?: 'date-time' | 'email' | 'uri' | 'json';
268
268
  pattern?: string;
269
269
  enum?: (string | number)[];
270
270
  primary?: boolean;
@@ -279,7 +279,6 @@ interface JsonSchemaProperty {
279
279
  xPrompt?: string;
280
280
  drizzle?: {
281
281
  mode?: 'json' | 'timestamp' | 'timestamp_ms' | 'boolean' | 'bigint';
282
- customType?: string;
283
282
  };
284
283
  nested?: {
285
284
  enabled: true;
@@ -305,8 +304,8 @@ type ColumnType = {
305
304
  interface CheckConstraint {
306
305
  name: string;
307
306
  column: string;
308
- kind: 'min_length' | 'max_length' | 'min_value' | 'max_value';
309
- value: number;
307
+ kind: 'min_length' | 'max_length' | 'min_value' | 'max_value' | 'enum_value';
308
+ value: number | (string | number)[];
310
309
  }
311
310
  interface ParsedColumn {
312
311
  name: string;
@@ -322,6 +321,21 @@ interface ParsedColumn {
322
321
  column: string;
323
322
  };
324
323
  }
324
+ interface SchemaMappingEntry {
325
+ schemaPath: string;
326
+ table: string;
327
+ column: string;
328
+ drizzleType: string;
329
+ sqliteType: 'text' | 'integer' | 'real';
330
+ nullable: boolean;
331
+ primary: boolean;
332
+ unique: boolean;
333
+ relation?: 'root' | 'has-one' | 'has-many';
334
+ constraints?: {
335
+ enumValues?: (string | number)[];
336
+ };
337
+ notes: string[];
338
+ }
325
339
  interface ParsedTable {
326
340
  name: string;
327
341
  columns: ParsedColumn[];
@@ -345,6 +359,7 @@ interface ParseResult {
345
359
  relations: ParsedRelation[];
346
360
  reverseRelations: ParsedReverseRelation[];
347
361
  warnings: string[];
362
+ mapping?: SchemaMappingEntry[];
348
363
  }
349
364
  interface MigrationConfig {
350
365
  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-DbJcRtaQ.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",
@@ -195,10 +206,23 @@
195
206
  "type": "string",
196
207
  "enum": ["json", "timestamp", "timestamp_ms", "boolean", "bigint"],
197
208
  "description": "Override Drizzle column mode. 'json' stores as TEXT(json), 'timestamp'/'timestamp_ms' as INTEGER, 'boolean' as INTEGER(boolean), 'bigint' as INTEGER(bigint)."
198
- },
199
- "customType": {
209
+ }
210
+ }
211
+ },
212
+
213
+ "examplePair": {
214
+ "type": "object",
215
+ "required": ["text", "output"],
216
+ "additionalProperties": false,
217
+ "properties": {
218
+ "text": {
200
219
  "type": "string",
201
- "description": "Custom Drizzle type name (reserved for future use)."
220
+ "minLength": 1,
221
+ "description": "Source text example."
222
+ },
223
+ "output": {
224
+ "type": "object",
225
+ "description": "Expected extracted output for the source text example."
202
226
  }
203
227
  }
204
228
  },