@apso/cli 0.30.0 → 0.32.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,115 @@
1
+ /**
2
+ * Database Import — intermediate model
3
+ *
4
+ * These types are the contract between the introspection layer (which reads a
5
+ * live Postgres database) and the conversion layer (which turns that into a
6
+ * `.apsorc`). Keeping the introspector behind the `Introspector` interface lets
7
+ * the conversion logic and the command be unit-tested with a fake source,
8
+ * without a real database connection.
9
+ */
10
+ /** ON DELETE referential action of a foreign key. */
11
+ export type OnDeleteAction = "CASCADE" | "RESTRICT" | "SET NULL" | "NO ACTION" | "SET DEFAULT";
12
+ /** A single introspected column. */
13
+ export interface IntrospectedColumn {
14
+ name: string;
15
+ /** pg_catalog udt_name, e.g. "int4", "varchar", "_text", "timestamptz". */
16
+ udtName: string;
17
+ /** information_schema.data_type, used to detect "USER-DEFINED" (enum) and "ARRAY". */
18
+ dataType: string;
19
+ nullable: boolean;
20
+ /** Raw default expression, e.g. "nextval('...')", "now()", "'active'::text". */
21
+ default: string | null;
22
+ charMaxLength: number | null;
23
+ numericPrecision: number | null;
24
+ numericScale: number | null;
25
+ ordinal: number;
26
+ /** True when the column's type is a user-defined enum. */
27
+ isEnum: boolean;
28
+ /** Links to IntrospectedEnum.name when isEnum is true. */
29
+ enumTypeName?: string;
30
+ }
31
+ /** A foreign key constraint (may be composite). */
32
+ export interface IntrospectedFk {
33
+ /** Local column(s) participating in the FK. */
34
+ columns: string[];
35
+ referencedTable: string;
36
+ referencedColumns: string[];
37
+ onDelete: OnDeleteAction;
38
+ }
39
+ /** A unique constraint (may be composite). */
40
+ export interface IntrospectedUnique {
41
+ name: string;
42
+ columns: string[];
43
+ }
44
+ /** A secondary index (may be composite). */
45
+ export interface IntrospectedIndex {
46
+ name: string;
47
+ columns: string[];
48
+ unique: boolean;
49
+ }
50
+ /** A user-defined enum type and its labels (in sort order). */
51
+ export interface IntrospectedEnum {
52
+ name: string;
53
+ labels: string[];
54
+ }
55
+ /** A single introspected base table. */
56
+ export interface IntrospectedTable {
57
+ name: string;
58
+ columns: IntrospectedColumn[];
59
+ /** Ordered primary-key column names ([] if the table has no primary key). */
60
+ primaryKey: string[];
61
+ foreignKeys: IntrospectedFk[];
62
+ uniqueConstraints: IntrospectedUnique[];
63
+ indexes: IntrospectedIndex[];
64
+ }
65
+ /** The full result of introspecting one Postgres schema. */
66
+ export interface IntrospectedSchema {
67
+ schema: string;
68
+ tables: IntrospectedTable[];
69
+ enums: IntrospectedEnum[];
70
+ /** Objects intentionally not imported, surfaced in the summary report. */
71
+ skipped: {
72
+ views: string[];
73
+ systemSchemas: string[];
74
+ };
75
+ }
76
+ /**
77
+ * The boundary the command depends on. The real implementation
78
+ * (PgIntrospector) connects to Postgres; tests provide a fake.
79
+ */
80
+ export interface Introspector {
81
+ introspect(schema: string): Promise<IntrospectedSchema>;
82
+ }
83
+ /**
84
+ * Everything the import couldn't represent losslessly, plus counts, used to
85
+ * print a summary the user can review before/after writing the `.apsorc`.
86
+ */
87
+ export interface ImportReport {
88
+ tablesImported: string[];
89
+ relationships: number;
90
+ viewsSkipped: string[];
91
+ systemSchemasSkipped: string[];
92
+ warnings: {
93
+ /** "table.column" whose Postgres array type was reduced to text. */
94
+ arraysLossy: string[];
95
+ /** Tables with composite primary keys (emitted as primary:true fields). */
96
+ compositePks: string[];
97
+ /** Tables with composite foreign keys (kept as scalar columns, no relationship). */
98
+ compositeFks: string[];
99
+ /** Tables whose single PK is not named "id" (emitted as a primary:true field). */
100
+ nonStandardPks: string[];
101
+ /** Tables with no primary key at all. */
102
+ noPrimaryKey: string[];
103
+ /** Columns whose type was unknown and defaulted to text. */
104
+ typesDefaulted: Array<{
105
+ column: string;
106
+ udt: string;
107
+ }>;
108
+ /** FK columns whose name could not be round-tripped to a relationship name. */
109
+ fkColumnNameUnmapped: string[];
110
+ /** "table.column" defaults that were dropped (unsupported expressions). */
111
+ defaultsDropped: string[];
112
+ /** Tables that look like pure join tables (candidates for manual ManyToMany). */
113
+ joinTablesDetected: string[];
114
+ };
115
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * Database Import — intermediate model
4
+ *
5
+ * These types are the contract between the introspection layer (which reads a
6
+ * live Postgres database) and the conversion layer (which turns that into a
7
+ * `.apsorc`). Keeping the introspector behind the `Introspector` interface lets
8
+ * the conversion logic and the command be unit-tested with a fake source,
9
+ * without a real database connection.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@apso/cli",
9
- "version": "0.30.0",
9
+ "version": "0.32.0",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@biomejs/biome": "^1.9.4",
@@ -22,6 +22,7 @@
22
22
  "eta": "^2.0.0",
23
23
  "inquirer": "^8.2.7",
24
24
  "ora": "^5.4.1",
25
+ "pg": "^8.21.0",
25
26
  "pluralize": "^8.0.0",
26
27
  "posthog-node": "^5.21.2",
27
28
  "rc": "^1.2.8",
@@ -41,6 +42,7 @@
41
42
  "@types/inquirer": "^8.2.12",
42
43
  "@types/mocha": "^10",
43
44
  "@types/node": "^20.19.43",
45
+ "@types/pg": "^8.20.0",
44
46
  "@types/pluralize": "^0.0.29",
45
47
  "@types/rc": "^1.2.1",
46
48
  "@types/shelljs": "^0.8.15",
@@ -2877,6 +2879,18 @@
2877
2879
  "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
2878
2880
  "dev": true
2879
2881
  },
2882
+ "node_modules/@types/pg": {
2883
+ "version": "8.20.0",
2884
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
2885
+ "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==",
2886
+ "dev": true,
2887
+ "license": "MIT",
2888
+ "dependencies": {
2889
+ "@types/node": "*",
2890
+ "pg-protocol": "*",
2891
+ "pg-types": "^2.2.0"
2892
+ }
2893
+ },
2880
2894
  "node_modules/@types/pluralize": {
2881
2895
  "version": "0.0.29",
2882
2896
  "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz",
@@ -13612,6 +13626,95 @@
13612
13626
  "node": "*"
13613
13627
  }
13614
13628
  },
13629
+ "node_modules/pg": {
13630
+ "version": "8.22.0",
13631
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz",
13632
+ "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==",
13633
+ "license": "MIT",
13634
+ "dependencies": {
13635
+ "pg-connection-string": "^2.14.0",
13636
+ "pg-pool": "^3.14.0",
13637
+ "pg-protocol": "^1.15.0",
13638
+ "pg-types": "2.2.0",
13639
+ "pgpass": "1.0.5"
13640
+ },
13641
+ "engines": {
13642
+ "node": ">= 16.0.0"
13643
+ },
13644
+ "optionalDependencies": {
13645
+ "pg-cloudflare": "^1.4.0"
13646
+ },
13647
+ "peerDependencies": {
13648
+ "pg-native": ">=3.0.1"
13649
+ },
13650
+ "peerDependenciesMeta": {
13651
+ "pg-native": {
13652
+ "optional": true
13653
+ }
13654
+ }
13655
+ },
13656
+ "node_modules/pg-cloudflare": {
13657
+ "version": "1.4.0",
13658
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz",
13659
+ "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==",
13660
+ "license": "MIT",
13661
+ "optional": true
13662
+ },
13663
+ "node_modules/pg-connection-string": {
13664
+ "version": "2.14.0",
13665
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz",
13666
+ "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==",
13667
+ "license": "MIT"
13668
+ },
13669
+ "node_modules/pg-int8": {
13670
+ "version": "1.0.1",
13671
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
13672
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
13673
+ "license": "ISC",
13674
+ "engines": {
13675
+ "node": ">=4.0.0"
13676
+ }
13677
+ },
13678
+ "node_modules/pg-pool": {
13679
+ "version": "3.14.0",
13680
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz",
13681
+ "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
13682
+ "license": "MIT",
13683
+ "peerDependencies": {
13684
+ "pg": ">=8.0"
13685
+ }
13686
+ },
13687
+ "node_modules/pg-protocol": {
13688
+ "version": "1.15.0",
13689
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz",
13690
+ "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==",
13691
+ "license": "MIT"
13692
+ },
13693
+ "node_modules/pg-types": {
13694
+ "version": "2.2.0",
13695
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
13696
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
13697
+ "license": "MIT",
13698
+ "dependencies": {
13699
+ "pg-int8": "1.0.1",
13700
+ "postgres-array": "~2.0.0",
13701
+ "postgres-bytea": "~1.0.0",
13702
+ "postgres-date": "~1.0.4",
13703
+ "postgres-interval": "^1.1.0"
13704
+ },
13705
+ "engines": {
13706
+ "node": ">=4"
13707
+ }
13708
+ },
13709
+ "node_modules/pgpass": {
13710
+ "version": "1.0.5",
13711
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
13712
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
13713
+ "license": "MIT",
13714
+ "dependencies": {
13715
+ "split2": "^4.1.0"
13716
+ }
13717
+ },
13615
13718
  "node_modules/picocolors": {
13616
13719
  "version": "1.0.0",
13617
13720
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -13736,6 +13839,45 @@
13736
13839
  "node": ">= 0.4"
13737
13840
  }
13738
13841
  },
13842
+ "node_modules/postgres-array": {
13843
+ "version": "2.0.0",
13844
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
13845
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
13846
+ "license": "MIT",
13847
+ "engines": {
13848
+ "node": ">=4"
13849
+ }
13850
+ },
13851
+ "node_modules/postgres-bytea": {
13852
+ "version": "1.0.1",
13853
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
13854
+ "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
13855
+ "license": "MIT",
13856
+ "engines": {
13857
+ "node": ">=0.10.0"
13858
+ }
13859
+ },
13860
+ "node_modules/postgres-date": {
13861
+ "version": "1.0.7",
13862
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
13863
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
13864
+ "license": "MIT",
13865
+ "engines": {
13866
+ "node": ">=0.10.0"
13867
+ }
13868
+ },
13869
+ "node_modules/postgres-interval": {
13870
+ "version": "1.2.0",
13871
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
13872
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
13873
+ "license": "MIT",
13874
+ "dependencies": {
13875
+ "xtend": "^4.0.0"
13876
+ },
13877
+ "engines": {
13878
+ "node": ">=0.10.0"
13879
+ }
13880
+ },
13739
13881
  "node_modules/posthog-node": {
13740
13882
  "version": "5.21.2",
13741
13883
  "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.21.2.tgz",
@@ -15482,6 +15624,15 @@
15482
15624
  "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==",
15483
15625
  "dev": true
15484
15626
  },
15627
+ "node_modules/split2": {
15628
+ "version": "4.2.0",
15629
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
15630
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
15631
+ "license": "ISC",
15632
+ "engines": {
15633
+ "node": ">= 10.x"
15634
+ }
15635
+ },
15485
15636
  "node_modules/sprintf-js": {
15486
15637
  "version": "1.0.3",
15487
15638
  "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -17275,7 +17426,6 @@
17275
17426
  "version": "4.0.2",
17276
17427
  "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
17277
17428
  "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
17278
- "dev": true,
17279
17429
  "engines": {
17280
17430
  "node": ">=0.4"
17281
17431
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.30.0",
2
+ "version": "0.32.0",
3
3
  "commands": {
4
4
  "config": {
5
5
  "id": "config",
@@ -627,6 +627,123 @@
627
627
  },
628
628
  "args": {}
629
629
  },
630
+ "import:data": {
631
+ "id": "import:data",
632
+ "description": "Copy table data from a source Supabase (Postgres) database into the target database rebuilt from the imported schema. Reads the source read-only; inserts into empty target tables.",
633
+ "strict": true,
634
+ "pluginName": "@apso/cli",
635
+ "pluginAlias": "@apso/cli",
636
+ "pluginType": "core",
637
+ "aliases": [],
638
+ "hiddenAliases": [],
639
+ "examples": [
640
+ "$ apso import data -s \"postgresql://...source...\" -t \"postgresql://...target...\"",
641
+ "$ apso import data --dry-run",
642
+ "$ apso import data --tables users,orders"
643
+ ],
644
+ "flags": {
645
+ "source": {
646
+ "name": "source",
647
+ "type": "option",
648
+ "char": "s",
649
+ "description": "Source connection string. Falls back to SUPABASE_DB_URL or DATABASE_URL, then prompts.",
650
+ "multiple": false
651
+ },
652
+ "target": {
653
+ "name": "target",
654
+ "type": "option",
655
+ "char": "t",
656
+ "description": "Target connection string. Falls back to TARGET_DATABASE_URL or APSO_DATABASE_URL, then prompts.",
657
+ "multiple": false
658
+ },
659
+ "schema": {
660
+ "name": "schema",
661
+ "type": "option",
662
+ "description": "Database schema",
663
+ "multiple": false,
664
+ "default": "public"
665
+ },
666
+ "tables": {
667
+ "name": "tables",
668
+ "type": "option",
669
+ "description": "Comma-separated list of source tables to copy (default: all)",
670
+ "multiple": false
671
+ },
672
+ "batch-size": {
673
+ "name": "batch-size",
674
+ "type": "option",
675
+ "description": "Rows per insert batch",
676
+ "multiple": false,
677
+ "default": 500
678
+ },
679
+ "dry-run": {
680
+ "name": "dry-run",
681
+ "type": "boolean",
682
+ "description": "Plan and report row counts without writing",
683
+ "allowNo": false
684
+ },
685
+ "yes": {
686
+ "name": "yes",
687
+ "type": "boolean",
688
+ "char": "y",
689
+ "description": "Skip the confirmation prompt",
690
+ "allowNo": false
691
+ }
692
+ },
693
+ "args": {}
694
+ },
695
+ "import:supabase": {
696
+ "id": "import:supabase",
697
+ "description": "Introspect an existing Supabase (Postgres) database and generate a local .apsorc schema. Read-only: it never writes to the source database.",
698
+ "strict": true,
699
+ "pluginName": "@apso/cli",
700
+ "pluginAlias": "@apso/cli",
701
+ "pluginType": "core",
702
+ "aliases": [],
703
+ "hiddenAliases": [],
704
+ "examples": [
705
+ "$ apso import supabase",
706
+ "$ apso import supabase -c \"postgresql://postgres:[pw]@db.[ref].supabase.co:5432/postgres\"",
707
+ "$ SUPABASE_DB_URL=\"postgresql://...\" apso import supabase --dry-run"
708
+ ],
709
+ "flags": {
710
+ "connection-string": {
711
+ "name": "connection-string",
712
+ "type": "option",
713
+ "char": "c",
714
+ "description": "Postgres connection string. Falls back to SUPABASE_DB_URL or DATABASE_URL, then prompts.",
715
+ "multiple": false
716
+ },
717
+ "schema": {
718
+ "name": "schema",
719
+ "type": "option",
720
+ "description": "Database schema to import",
721
+ "multiple": false,
722
+ "default": "public"
723
+ },
724
+ "out": {
725
+ "name": "out",
726
+ "type": "option",
727
+ "char": "o",
728
+ "description": "Path to write the .apsorc (default: existing .apsorc or ./.apsorc)",
729
+ "multiple": false
730
+ },
731
+ "dry-run": {
732
+ "name": "dry-run",
733
+ "type": "boolean",
734
+ "description": "Introspect and print a summary without writing any file",
735
+ "allowNo": false
736
+ },
737
+ "yes": {
738
+ "name": "yes",
739
+ "type": "boolean",
740
+ "char": "y",
741
+ "description": "Skip the overwrite confirmation prompt",
742
+ "allowNo": false
743
+ }
744
+ },
745
+ "args": {}
746
+ },
630
747
  "mcp:serve": {
631
748
  "id": "mcp:serve",
632
749
  "description": "Start an MCP server exposing Apso tools over stdio. Used by AI coding agents (Claude Code, Cursor, etc.) to design schemas, generate APIs, and deploy backends.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apso/cli",
3
- "version": "0.30.0",
3
+ "version": "0.32.0",
4
4
  "mcpName": "io.github.apsoai/apso",
5
5
  "description": "Apso CLI",
6
6
  "author": "Apso by Mavric - @mavric",
@@ -37,6 +37,7 @@
37
37
  "eta": "^2.0.0",
38
38
  "inquirer": "^8.2.7",
39
39
  "ora": "^5.4.1",
40
+ "pg": "^8.21.0",
40
41
  "pluralize": "^8.0.0",
41
42
  "posthog-node": "^5.21.2",
42
43
  "rc": "^1.2.8",
@@ -53,6 +54,7 @@
53
54
  "@types/inquirer": "^8.2.12",
54
55
  "@types/mocha": "^10",
55
56
  "@types/node": "^20.19.43",
57
+ "@types/pg": "^8.20.0",
56
58
  "@types/pluralize": "^0.0.29",
57
59
  "@types/rc": "^1.2.1",
58
60
  "@types/shelljs": "^0.8.15",
@@ -98,6 +100,9 @@
98
100
  },
99
101
  "mcp": {
100
102
  "description": "MCP server for AI coding agents"
103
+ },
104
+ "import": {
105
+ "description": "Import an existing database into a local .apsorc schema"
101
106
  }
102
107
  },
103
108
  "update": {