forge-admin 0.0.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.
Files changed (131) hide show
  1. package/README.md +73 -0
  2. package/app.db +0 -0
  3. package/components.json +20 -0
  4. package/dist/assets/index-BPVmexx_.css +1 -0
  5. package/dist/assets/index-BtNewH3n.js +258 -0
  6. package/dist/favicon.ico +0 -0
  7. package/dist/index.html +27 -0
  8. package/dist/placeholder.svg +1 -0
  9. package/dist/robots.txt +14 -0
  10. package/eslint.config.js +26 -0
  11. package/index.html +26 -0
  12. package/package.json +107 -0
  13. package/postcss.config.js +6 -0
  14. package/public/favicon.ico +0 -0
  15. package/public/placeholder.svg +1 -0
  16. package/public/robots.txt +14 -0
  17. package/src/App.css +42 -0
  18. package/src/App.tsx +32 -0
  19. package/src/admin/convertSchema.ts +83 -0
  20. package/src/admin/factory.ts +12 -0
  21. package/src/admin/introspecter.ts +6 -0
  22. package/src/admin/router.ts +38 -0
  23. package/src/admin/schema.ts +17 -0
  24. package/src/admin/sqlite.ts +73 -0
  25. package/src/admin/types.ts +35 -0
  26. package/src/components/AdminLayout.tsx +19 -0
  27. package/src/components/AdminSidebar.tsx +102 -0
  28. package/src/components/DataTable.tsx +166 -0
  29. package/src/components/ModelForm.tsx +221 -0
  30. package/src/components/NavLink.tsx +28 -0
  31. package/src/components/StatCard.tsx +32 -0
  32. package/src/components/ui/accordion.tsx +52 -0
  33. package/src/components/ui/alert-dialog.tsx +104 -0
  34. package/src/components/ui/alert.tsx +43 -0
  35. package/src/components/ui/aspect-ratio.tsx +5 -0
  36. package/src/components/ui/avatar.tsx +38 -0
  37. package/src/components/ui/badge.tsx +29 -0
  38. package/src/components/ui/breadcrumb.tsx +90 -0
  39. package/src/components/ui/button.tsx +47 -0
  40. package/src/components/ui/calendar.tsx +54 -0
  41. package/src/components/ui/card.tsx +43 -0
  42. package/src/components/ui/carousel.tsx +224 -0
  43. package/src/components/ui/chart.tsx +303 -0
  44. package/src/components/ui/checkbox.tsx +26 -0
  45. package/src/components/ui/collapsible.tsx +9 -0
  46. package/src/components/ui/command.tsx +132 -0
  47. package/src/components/ui/context-menu.tsx +178 -0
  48. package/src/components/ui/dialog.tsx +95 -0
  49. package/src/components/ui/drawer.tsx +87 -0
  50. package/src/components/ui/dropdown-menu.tsx +179 -0
  51. package/src/components/ui/form.tsx +129 -0
  52. package/src/components/ui/hover-card.tsx +27 -0
  53. package/src/components/ui/input-otp.tsx +61 -0
  54. package/src/components/ui/input.tsx +22 -0
  55. package/src/components/ui/label.tsx +17 -0
  56. package/src/components/ui/menubar.tsx +207 -0
  57. package/src/components/ui/navigation-menu.tsx +120 -0
  58. package/src/components/ui/pagination.tsx +81 -0
  59. package/src/components/ui/popover.tsx +29 -0
  60. package/src/components/ui/progress.tsx +23 -0
  61. package/src/components/ui/radio-group.tsx +36 -0
  62. package/src/components/ui/resizable.tsx +37 -0
  63. package/src/components/ui/scroll-area.tsx +38 -0
  64. package/src/components/ui/select.tsx +143 -0
  65. package/src/components/ui/separator.tsx +20 -0
  66. package/src/components/ui/sheet.tsx +107 -0
  67. package/src/components/ui/sidebar.tsx +637 -0
  68. package/src/components/ui/skeleton.tsx +7 -0
  69. package/src/components/ui/slider.tsx +23 -0
  70. package/src/components/ui/sonner.tsx +27 -0
  71. package/src/components/ui/switch.tsx +27 -0
  72. package/src/components/ui/table.tsx +72 -0
  73. package/src/components/ui/tabs.tsx +53 -0
  74. package/src/components/ui/textarea.tsx +21 -0
  75. package/src/components/ui/toast.tsx +111 -0
  76. package/src/components/ui/toaster.tsx +24 -0
  77. package/src/components/ui/toggle-group.tsx +49 -0
  78. package/src/components/ui/toggle.tsx +37 -0
  79. package/src/components/ui/tooltip.tsx +28 -0
  80. package/src/components/ui/use-toast.ts +3 -0
  81. package/src/config/define.ts +6 -0
  82. package/src/config/index.ts +0 -0
  83. package/src/config/load.ts +45 -0
  84. package/src/config/types.ts +5 -0
  85. package/src/hooks/use-mobile.tsx +19 -0
  86. package/src/hooks/use-toast.ts +186 -0
  87. package/src/index.css +142 -0
  88. package/src/lib/models.ts +138 -0
  89. package/src/lib/utils.ts +6 -0
  90. package/src/main.tsx +5 -0
  91. package/src/orm/cli/makemigrations.ts +63 -0
  92. package/src/orm/cli/migrate.ts +127 -0
  93. package/src/orm/cli.ts +30 -0
  94. package/src/orm/core/base-model.ts +6 -0
  95. package/src/orm/core/manager.ts +27 -0
  96. package/src/orm/core/query-builder.ts +74 -0
  97. package/src/orm/db/connection.ts +0 -0
  98. package/src/orm/db/sql-types.ts +72 -0
  99. package/src/orm/db/sqlite.ts +4 -0
  100. package/src/orm/decorators/field.ts +80 -0
  101. package/src/orm/decorators/model.ts +36 -0
  102. package/src/orm/decorators/relations.ts +0 -0
  103. package/src/orm/metadata/field-metadata.ts +0 -0
  104. package/src/orm/metadata/field-types.ts +12 -0
  105. package/src/orm/metadata/get-meta.ts +9 -0
  106. package/src/orm/metadata/index.ts +15 -0
  107. package/src/orm/metadata/keys.ts +2 -0
  108. package/src/orm/metadata/model-registry.ts +53 -0
  109. package/src/orm/metadata/modifiers.ts +26 -0
  110. package/src/orm/metadata/types.ts +45 -0
  111. package/src/orm/migration-engine/diff.ts +243 -0
  112. package/src/orm/migration-engine/operations.ts +186 -0
  113. package/src/orm/schema/build.ts +138 -0
  114. package/src/orm/schema/state.ts +23 -0
  115. package/src/orm/schema/writeMigrations.ts +21 -0
  116. package/src/orm/syncdb.ts +25 -0
  117. package/src/pages/Dashboard.tsx +127 -0
  118. package/src/pages/Index.tsx +18 -0
  119. package/src/pages/ModelPage.tsx +177 -0
  120. package/src/pages/NotFound.tsx +24 -0
  121. package/src/pages/SchemaEditor.tsx +170 -0
  122. package/src/pages/Settings.tsx +166 -0
  123. package/src/server.ts +69 -0
  124. package/src/vite-env.d.ts +1 -0
  125. package/tailwind.config.js +112 -0
  126. package/tailwind.config.ts +114 -0
  127. package/tsconfig.app.json +30 -0
  128. package/tsconfig.json +16 -0
  129. package/tsconfig.node.json +22 -0
  130. package/vite.config.js +23 -0
  131. package/vite.config.ts +18 -0
@@ -0,0 +1,243 @@
1
+
2
+ import { db } from "../db/sqlite";
3
+
4
+ function sameIndex(a: any, b: any) {
5
+ return (
6
+ a.unique === b.unique &&
7
+ a.fields.length === b.fields.length &&
8
+ a.fields.every((f: string, i: number) => f === b.fields[i])
9
+ );
10
+ }
11
+
12
+
13
+ function getRowCount(table: string): number {
14
+ const row = db
15
+ .prepare(`SELECT COUNT(*) as count FROM "${table}"`)
16
+ .get();
17
+
18
+ return row?.count ?? 0;
19
+ }
20
+
21
+ function validateAddColumn(
22
+ op: any,
23
+ existingRowCount: number
24
+ ) {
25
+ const f = op.fieldMeta;
26
+
27
+ if (!f.options?.nullable && f.options?.default == null && existingRowCount > 0) {
28
+ throw new Error(
29
+ `Cannot add non-nullable field '${f.name}' without a default. ` +
30
+ `Forge requires either a default or nullable=true.`
31
+ );
32
+ }
33
+ }
34
+
35
+
36
+
37
+ function sameFields(a: any, b: any) {
38
+ const aKeys = Object.keys(a);
39
+ const bKeys = Object.keys(b);
40
+
41
+ if (aKeys.length !== bKeys.length) return false;
42
+
43
+ return aKeys.every(key =>
44
+ b[key] &&
45
+ a[key].type === b[key].type &&
46
+ JSON.stringify(a[key].options ?? {}) ===
47
+ JSON.stringify(b[key].options ?? {})
48
+ );
49
+ }
50
+
51
+ export function diffStates(oldState: any, newState: any) {
52
+ const ops: any[] = [];
53
+
54
+ const oldModels = oldState.models;
55
+ const newModels = newState.models;
56
+
57
+ const oldTables = Object.keys(oldModels);
58
+ const newTables = Object.keys(newModels);
59
+
60
+ const removedTables = oldTables.filter(t => !newTables.includes(t));
61
+ const addedTables = newTables.filter(t => !oldTables.includes(t));
62
+
63
+ const consumedOldTables = new Set<string>();
64
+ const consumedNewTables = new Set<string>();
65
+
66
+ // 1️⃣ TABLE RENAME DETECTION
67
+ for (const oldTable of removedTables) {
68
+ for (const newTable of addedTables) {
69
+ if (consumedOldTables.has(oldTable)) continue;
70
+ if (consumedNewTables.has(newTable)) continue;
71
+
72
+ const a = oldModels[oldTable];
73
+ const b = newModels[newTable];
74
+
75
+ if (sameFields(a.fields, b.fields)) {
76
+ ops.push({
77
+ type: "RenameTable",
78
+ from: oldTable,
79
+ to: newTable,
80
+ });
81
+
82
+ consumedOldTables.add(oldTable);
83
+ consumedNewTables.add(newTable);
84
+ }
85
+ }
86
+ }
87
+
88
+ // 2️⃣ CREATE TABLE
89
+ for (const table of newTables) {
90
+ if (oldModels[table]) continue;
91
+ if (consumedNewTables.has(table)) continue;
92
+
93
+ ops.push({
94
+ type: "CreateTable",
95
+ meta: newModels[table],
96
+ });
97
+ }
98
+
99
+ // 3️⃣ DROP TABLE
100
+ for (const table of oldTables) {
101
+ if (newModels[table]) continue;
102
+ if (consumedOldTables.has(table)) continue;
103
+
104
+ ops.push({
105
+ type: "DropTable",
106
+ model: table,
107
+ });
108
+ }
109
+
110
+ // 4️⃣ COLUMN-LEVEL DIFFS
111
+ for (const table of newTables) {
112
+ if (!oldModels[table]) continue;
113
+ if (consumedNewTables.has(table)) continue;
114
+
115
+ const oldFields = oldModels[table].fields;
116
+ const newFields = newModels[table].fields;
117
+
118
+ const removedFields = Object.keys(oldFields).filter(
119
+ f => !newFields[f]
120
+ );
121
+
122
+ const addedFields = Object.keys(newFields).filter(
123
+ f => !oldFields[f]
124
+ );
125
+
126
+ const consumedOldFields = new Set<string>();
127
+ const consumedNewFields = new Set<string>();
128
+
129
+ // 4️⃣a COLUMN RENAME DETECTION
130
+ for (const oldField of removedFields) {
131
+ for (const newField of addedFields) {
132
+ if (consumedOldFields.has(oldField)) continue;
133
+ if (consumedNewFields.has(newField)) continue;
134
+
135
+ const a = oldFields[oldField];
136
+ const b = newFields[newField];
137
+
138
+ if (
139
+ a.type === b.type &&
140
+ JSON.stringify(a.options ?? {}) ===
141
+ JSON.stringify(b.options ?? {})
142
+ ) {
143
+ ops.push({
144
+ type: "RenameColumn",
145
+ model: table,
146
+ from: oldField,
147
+ to: newField,
148
+ });
149
+
150
+ consumedOldFields.add(oldField);
151
+ consumedNewFields.add(newField);
152
+ }
153
+ }
154
+ }
155
+
156
+ // 4️⃣b ADD COLUMN
157
+ for (const field of addedFields) {
158
+ if (consumedNewFields.has(field)) continue;
159
+
160
+ validateAddColumn(
161
+ {
162
+ type: "AddColumn",
163
+ model: table,
164
+ field: field,
165
+ fieldMeta: newFields[field],
166
+ },
167
+ getRowCount(table)
168
+ );
169
+
170
+ ops.push({
171
+ type: "AddColumn",
172
+ model: table,
173
+ field,
174
+ fieldMeta: newFields[field],
175
+ });
176
+ }
177
+
178
+ // 4️⃣c REMOVE COLUMN
179
+ for (const field of removedFields) {
180
+ if (consumedOldFields.has(field)) continue;
181
+
182
+ ops.push({
183
+ type: "RemoveColumn",
184
+ model: table,
185
+ field,
186
+ });
187
+ }
188
+ }
189
+
190
+ // 5️⃣ INDEX DIFFS
191
+ const seenCreate = new Set<string>();
192
+ const seenDelete = new Set<string>();
193
+ for (const table of newTables) {
194
+ if (!oldModels[table]) continue;
195
+
196
+ const oldIndexes = oldModels[table].indexes ?? [];
197
+ const newIndexes = newModels[table].indexes ?? [];
198
+
199
+ const consumedOld = new Set<number>();
200
+ const consumedNew = new Set<number>();
201
+
202
+ // Detect unchanged / renamed indexes (future-proof)
203
+ for (let i = 0; i < oldIndexes.length; i++) {
204
+ for (let j = 0; j < newIndexes.length; j++) {
205
+ if (consumedOld.has(i) || consumedNew.has(j)) continue;
206
+
207
+ if (sameIndex(oldIndexes[i], newIndexes[j])) {
208
+ consumedOld.add(i);
209
+ consumedNew.add(j);
210
+ }
211
+ }
212
+ }
213
+
214
+ // Added indexes
215
+ newIndexes.forEach((idx: any, i: number) => {
216
+ if (consumedNew.has(i)) return;
217
+ if(!seenCreate.has(idx['name'])){
218
+ ops.push({
219
+ type: "CreateIndex",
220
+ table,
221
+ index: idx,
222
+ });
223
+ seenCreate.add(idx['name']);
224
+ }
225
+ });
226
+
227
+ // Removed indexes
228
+ oldIndexes.forEach((idx: any, i: number) => {
229
+ if (consumedOld.has(i)) return;
230
+ if(!seenDelete.has(idx['name'])){
231
+ ops.push({
232
+ type: "DropIndex",
233
+ table,
234
+ index: idx,
235
+ });
236
+ seenDelete.add(idx['name']);
237
+
238
+ }
239
+ });
240
+ }
241
+
242
+ return ops;
243
+ }
@@ -0,0 +1,186 @@
1
+ import { fieldToSQL } from "../db/sql-types";
2
+ import { ModelMeta } from "../metadata/types";
3
+ import { createIndexSQL, dropIndexSQL } from "../db/sql-types";
4
+ import { db } from "../db/sqlite";
5
+
6
+ function createTableSQL(tableName: string, meta: ModelMeta): string {
7
+ const columns: string[] = [];
8
+ const foreignKeys: string[] = [];
9
+
10
+ for (const field of Object.values(meta.fields)) {
11
+ if (field.type === "foreignkey") {
12
+ columns.push(
13
+ `"${field.name}" INTEGER ${field.options?.default ? `DEFAULT ${field.options.default}` : "NOT NULL"}`
14
+ );
15
+
16
+ foreignKeys.push(
17
+ `FOREIGN KEY ("${field.name}") REFERENCES "${field.options.to}"("${field.options.toField}")` +
18
+ (field.options.onDelete
19
+ ? ` ON DELETE ${field.options.onDelete}`
20
+ : "")
21
+ );
22
+ } else {
23
+ columns.push(`"${field.name}" ${fieldToSQL(field)}`);
24
+ }
25
+ }
26
+
27
+ return `
28
+ CREATE TABLE "${tableName}" (
29
+ ${[...columns, ...foreignKeys].join(",\n")}
30
+ );
31
+ `;
32
+ }
33
+
34
+
35
+ export function rebuildTable(
36
+ table: string,
37
+ oldState: ModelMeta,
38
+ newState: ModelMeta
39
+ ): string[] {
40
+ const tempTable = `${table}__new`;
41
+
42
+ const columns = Object.keys(oldState.fields).filter(
43
+ c => newState.fields[c]
44
+ );
45
+
46
+ const columnList = columns.map(c => `"${c}"`).join(", ");
47
+
48
+ return [
49
+ createTableSQL(tempTable, newState),
50
+ `
51
+ INSERT INTO "${tempTable}" (${columnList})
52
+ SELECT ${columnList} FROM "${table}";
53
+ `,
54
+ `DROP TABLE "${table}";`,
55
+ `ALTER TABLE "${tempTable}" RENAME TO "${table}";`,
56
+ ];
57
+ }
58
+
59
+
60
+
61
+ export function opToSQL(op: any): string | null {
62
+ switch (op.type) {
63
+ case "CreateTable":
64
+ return createTableSQL(op.meta.tableName, op.meta);
65
+
66
+ case "RenameColumn":
67
+ return `
68
+ ALTER TABLE ${op.model}
69
+ RENAME COLUMN ${op.from} TO ${op.to};
70
+ `;
71
+
72
+ case "RenameTable":
73
+ return `
74
+ ALTER TABLE "${op.from}"
75
+ RENAME TO "${op.to}";
76
+ `;
77
+
78
+
79
+ case "AddColumn":
80
+ return `ALTER TABLE ${op.table}
81
+ ADD COLUMN ${op.field} ${fieldToSQL(op.fieldMeta)};`;
82
+
83
+ case "RemoveColumn":
84
+ return `ALTER TABLE ${op.model}
85
+ DROP COLUMN ${op.field};`
86
+
87
+ case "DropTable":
88
+ return `DROP TABLE ${op.model};`;
89
+
90
+ case "CreateIndex":
91
+ return createIndexSQL(op.table, op.index);
92
+
93
+ case "DropIndex":
94
+ return dropIndexSQL(op.table, op.index);
95
+
96
+ default:
97
+ return null;
98
+ }
99
+ }
100
+
101
+
102
+ export function requiresRebuild(op: any): boolean {
103
+ return [
104
+ "AddColumn",
105
+ "RemoveColumn",
106
+ "RenameColumn",
107
+ "AddConstraint",
108
+ "RemoveConstraint",
109
+ "AlterField",
110
+ ].includes(op.type);
111
+ }
112
+
113
+
114
+
115
+
116
+ export function introspectTable(tableName: string): ModelMeta {
117
+ const columns = db
118
+ .prepare(`PRAGMA table_info("${tableName}")`)
119
+ .all();
120
+
121
+ const fks = db
122
+ .prepare(`PRAGMA foreign_key_list("${tableName}")`)
123
+ .all();
124
+
125
+ const fields: any = {};
126
+
127
+ for (const col of columns) {
128
+ fields[col.name] = {
129
+ name: col.name,
130
+ type: col.pk === 1 ? "primary" : "string",
131
+ default: col.default,
132
+ options: col.options,
133
+ };
134
+ }
135
+
136
+ for (const fk of fks) {
137
+ fields[fk.from].type = "foreignkey";
138
+ fields[fk.from].options = {
139
+ to: fk.table,
140
+ toField: fk.to,
141
+ onDelete: fk.on_delete,
142
+ };
143
+ }
144
+
145
+ return {
146
+ tableName,
147
+ fields,
148
+ indexes: {},
149
+ constraints: {},
150
+ };
151
+ }
152
+
153
+ export function applyOpToSchema(
154
+ state: ModelMeta,
155
+ op: any
156
+ ): ModelMeta {
157
+ const next = structuredClone(state);
158
+
159
+ switch (op.type) {
160
+ case "AddColumn":
161
+ next.fields[op.field] = op.fieldMeta;
162
+ break;
163
+
164
+ case "RemoveColumn":
165
+ delete next.fields[op.field];
166
+ break;
167
+
168
+ case "RenameColumn":
169
+ next.fields[op.to] = {
170
+ ...next.fields[op.from],
171
+ name: op.to,
172
+ };
173
+ delete next.fields[op.from];
174
+ break;
175
+
176
+ case "AddConstraint":
177
+ next.constraints[op.constraint.name] = op.constraint;
178
+ break;
179
+
180
+ case "RemoveConstraint":
181
+ delete next.constraints[op.name];
182
+ break;
183
+ }
184
+
185
+ return next;
186
+ }
@@ -0,0 +1,138 @@
1
+ import { FieldMeta } from "../metadata/types";
2
+ import { getAllModels, getModelFields } from "../metadata/model-registry";
3
+ import { indexName } from "../metadata";
4
+
5
+ export function buildSchemaState() {
6
+ const models: any = {};
7
+ const modelIndexes: any = {};
8
+ for (const model of getAllModels()) {
9
+ const meta = model[Symbol.for("orm:model_meta")];
10
+ const modelFields = getModelFields(meta.tableName);
11
+ const fields = modelFields.fields;
12
+
13
+ const indexes = [...(meta.indexes ?? [])];
14
+ for (const field of fields) {
15
+ // primary key
16
+ if (field.type === "primary") {
17
+ indexes.push({
18
+ name: indexName(meta.tableName, [field.name], true),
19
+ fields: [field.name],
20
+ unique: true,
21
+ });
22
+ }
23
+
24
+ // unique field
25
+ if (field.options?.unique) {
26
+ indexes.push({
27
+ name: indexName(meta.tableName, [field.name], true),
28
+ fields: [field.name],
29
+ unique: true,
30
+ });
31
+ }
32
+
33
+ // foreign key
34
+ // if (field.type === "foreignkey") {
35
+ // indexes.push({
36
+ // name: indexName(meta.tableName, [field.name], false),
37
+ // fields: [field.name],
38
+ // unique: false,
39
+ // });
40
+ // }
41
+ }
42
+
43
+
44
+
45
+ models[meta.tableName] = {
46
+ tableName: meta.tableName,
47
+ fields: Object.fromEntries(
48
+ modelFields.fields.map((f: FieldMeta) => [
49
+ f.name,
50
+ {
51
+ name: f.name,
52
+ type: f.type,
53
+ default: f.default ?? null,
54
+ options: f.options ?? {},
55
+ },
56
+ ])
57
+ ),
58
+ indexes: indexes,
59
+ constraints: [],
60
+ };
61
+ }
62
+
63
+ return {
64
+ version: 1,
65
+ dialect: "generic",
66
+ models,
67
+ };
68
+ }
69
+
70
+ export function reverseOp(op: any): any {
71
+ switch (op.type) {
72
+ case "AddColumn":
73
+ return {
74
+ type: "RemoveColumn",
75
+ model: op.model,
76
+ field: op.field,
77
+ };
78
+
79
+ case "RemoveColumn":
80
+ return {
81
+ type: "AddColumn",
82
+ model: op.model,
83
+ field: op.field,
84
+ fieldMeta: op.fieldMeta,
85
+ };
86
+
87
+ case "RenameColumn":
88
+ return {
89
+ type: "RenameColumn",
90
+ model: op.model,
91
+ from: op.to,
92
+ to: op.from,
93
+ };
94
+
95
+ case "RenameTable":
96
+ return {
97
+ type: "RenameTable",
98
+ from: op.to,
99
+ to: op.from,
100
+ };
101
+
102
+ case "CreateTable":
103
+ return {
104
+ type: "DropTable",
105
+ model: op.meta.tableName, // ✅ FIX
106
+ };
107
+
108
+ case "DropTable":
109
+ return {
110
+ type: "CreateTable",
111
+ meta: op.meta, // model name is inside meta
112
+ };
113
+
114
+ case "CreateIndex":
115
+ return {
116
+ type: "DropIndex",
117
+ index: op.index
118
+ };
119
+
120
+ case "DropIndex":
121
+ return {
122
+ type: "CreateIndex",
123
+ index: op.index
124
+ };
125
+
126
+
127
+ case "AddConstraint":
128
+ return {
129
+ type: "RemoveConstraint",
130
+ index: op.index
131
+ }
132
+
133
+
134
+ default:
135
+ throw new Error(`No reverse for ${op.type}`);
136
+ }
137
+ }
138
+
@@ -0,0 +1,23 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const STATE_PATH = path.resolve("src/orm/migrations/_state.json");
5
+
6
+ export function loadState() {
7
+ if (!fs.existsSync(STATE_PATH)) {
8
+ return {
9
+ version: 1,
10
+ dialect: "generic",
11
+ models: {},
12
+ };
13
+ }
14
+
15
+ return JSON.parse(fs.readFileSync(STATE_PATH, "utf-8"));
16
+ }
17
+
18
+ export function saveState(state: any) {
19
+ fs.writeFileSync(
20
+ STATE_PATH,
21
+ JSON.stringify(state, null, 2)
22
+ );
23
+ }
@@ -0,0 +1,21 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ export function writeMigration(ops: any[]) {
5
+ if (!ops.length) return null;
6
+
7
+ const ts = Date.now();
8
+ const name = `${ts}_auto.ts`;
9
+ const file = path.resolve("src/orm/migrations", name);
10
+
11
+ fs.writeFileSync(
12
+ file,
13
+ `export default {
14
+ up: ${JSON.stringify(ops, null, 2)},
15
+ down: [],
16
+ };
17
+ `
18
+ );
19
+
20
+ return name;
21
+ }
@@ -0,0 +1,25 @@
1
+ // orm/syncdb.ts
2
+ import { getAllModels, getModelFields } from "./metadata/model-registry";
3
+ import { getModelMeta } from "./metadata/get-meta";
4
+ import { fieldToSQL } from "./db/sql-types";
5
+ import { db } from "./db/sqlite";
6
+
7
+ export async function syncdb() {
8
+ for (const model of getAllModels()) {
9
+ const meta = getModelMeta(model);
10
+ const modelFields = getModelFields(meta.tableName)
11
+ const columns: string[] = [];
12
+ for (const field of modelFields.fields) {
13
+ const sqlType = fieldToSQL(field);
14
+ columns.push(`${field.name} ${sqlType}`);
15
+ }
16
+
17
+ const sql = `
18
+ CREATE TABLE IF NOT EXISTS ${meta.tableName} (
19
+ ${columns.join(",\n")}
20
+ );
21
+ `;
22
+
23
+ db.exec(sql);
24
+ }
25
+ }