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,27 @@
1
+
2
+ // core/manager.ts
3
+ import { QueryBuilder } from "./query-builder";
4
+
5
+ export class Manager<T> {
6
+ constructor(private model: new () => T) {}
7
+
8
+ create(data: Partial<T>) {
9
+ return QueryBuilder.insert(this.model, data);
10
+ }
11
+
12
+ update(data: Partial<T>, id: number) {
13
+ return QueryBuilder.update(this.model, data, id);
14
+ }
15
+
16
+ delete(id:number) {
17
+ return QueryBuilder.delete(this.model, id);
18
+ }
19
+
20
+ filter(filters: any) {
21
+ return QueryBuilder.filter(this.model, filters);
22
+ }
23
+
24
+ all() {
25
+ return QueryBuilder.all(this.model);
26
+ }
27
+ }
@@ -0,0 +1,74 @@
1
+ // core/query-builder.ts
2
+ import { db } from "../db/sqlite";
3
+ import { getModelFields } from "../metadata/model-registry";
4
+
5
+ export class QueryBuilder {
6
+
7
+ static delete(model: any, id: number){
8
+ model = model.name.toLowerCase();
9
+ return db
10
+ .prepare(`DELETE FROM "${model}" WHERE id = ?`)
11
+ .run(id);
12
+ }
13
+
14
+ static update(model: any, data: any, id: number){
15
+ const keys = Object.keys(data);
16
+ model = model.name.toLowerCase();
17
+ return db.prepare(`
18
+ UPDATE "${model}"
19
+ SET ${keys.map(k => `${k} = ?`).join(",")}
20
+ WHERE id = ?
21
+ `).run([...Object.values(data), id]);
22
+ }
23
+
24
+ static insert(model: any, data: any) {
25
+ model = model.name.toLowerCase();
26
+ const modelFields = getModelFields(model);
27
+
28
+ const finalData = { ...data };
29
+ for (const field of modelFields.fields) {
30
+ if (
31
+ field.type === "datetime" &&
32
+ field.options?.autoNowAdd &&
33
+ finalData[field.name] == null
34
+ ) {
35
+ finalData[field.name] = Date.now();
36
+ }
37
+ }
38
+ const keys = Object.keys(finalData);
39
+ const values = Object.values(finalData);
40
+ const sql = `
41
+ INSERT INTO ${model}
42
+ (${keys.join(",")})
43
+ VALUES (${keys.map(() => "?").join(",")})
44
+ `;
45
+
46
+ db.prepare(sql).run(values);
47
+ }
48
+
49
+ static all(model: any) {
50
+ model = model.name.toLowerCase();
51
+ const sql = `SELECT * FROM ${model};`;
52
+ return db.prepare(sql).all();
53
+ }
54
+
55
+ static async filter(model: any, filters: any) {
56
+
57
+ const clauses: string[] = [];
58
+ const values: any[] = [];
59
+
60
+ for (const key in filters) {
61
+ if (key.endsWith("__icontains")) {
62
+ const field = key.replace("__icontains", "");
63
+ clauses.push(`${field} LIKE ?`);
64
+ values.push(`%${filters[key]}%`);
65
+ } else {
66
+ clauses.push(`${key} = ?`);
67
+ values.push(filters[key]);
68
+ }
69
+ }
70
+
71
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
72
+ return db.prepare(`SELECT * FROM ${model} ${where}`).all(values);
73
+ }
74
+ }
File without changes
@@ -0,0 +1,72 @@
1
+ // orm/db/sql-types.ts
2
+ import { FieldMeta } from "../metadata/types";
3
+ import { SQLiteTypeMap } from "../metadata/types";
4
+ import { fieldModifiers } from "../metadata/modifiers";
5
+ import { IndexMeta } from "../metadata";
6
+
7
+ export function fieldToSQL(
8
+ field: FieldMeta,
9
+ ): string {
10
+ const typeMap = SQLiteTypeMap;
11
+ const typeFn = typeMap[field.type];
12
+
13
+ if (field.type === "foreignkey") {
14
+ const mods = [];
15
+
16
+ if (!field.options?.nullable) mods.push("NOT NULL");
17
+
18
+ return ["INTEGER", ...mods].join(" ");
19
+ }
20
+ if (!typeFn) {
21
+ throw new Error(`Unsupported field type: ${field.type}`);
22
+ }
23
+
24
+ const sqlType = typeFn(field);
25
+ const modifiers = fieldModifiers(field);
26
+
27
+ return [sqlType, modifiers].filter(Boolean).join(" ");
28
+ }
29
+
30
+
31
+ export function foreignKeyToSQL(field: FieldMeta): string | null {
32
+ if (field.type !== "foreignkey") return null;
33
+
34
+ const { to, toField, onDelete } = field.options ?? {};
35
+
36
+ if (!to || !toField) {
37
+ throw new Error(`ForeignKey field "${field.name}" missing 'to' or 'toField'`);
38
+ }
39
+
40
+ const clauses = [
41
+ `FOREIGN KEY (${field.name})`,
42
+ `REFERENCES ${to}(${toField})`,
43
+ ];
44
+
45
+ if (onDelete) {
46
+ clauses.push(`ON DELETE ${onDelete}`);
47
+ }
48
+
49
+ return clauses.join(" ");
50
+ }
51
+
52
+
53
+ // orm/db/sql/index.ts
54
+ export function createIndexSQL(
55
+ table: string,
56
+ index: IndexMeta
57
+ ) {
58
+ const name = index.name;
59
+ const fields = index.fields.map(f => `"${f}"`).join(", ");
60
+
61
+ return `
62
+ CREATE ${index.unique ? "UNIQUE" : ""} INDEX "${name}"
63
+ ON "${table}" (${fields});
64
+ `;
65
+ }
66
+
67
+ export function dropIndexSQL(
68
+ table: string,
69
+ index: IndexMeta
70
+ ) {
71
+ return `DROP INDEX "${index.name}";`;
72
+ }
@@ -0,0 +1,4 @@
1
+ // db/sqlite.ts
2
+ import Database from "better-sqlite3";
3
+
4
+ export const db = new Database("app.db");
@@ -0,0 +1,80 @@
1
+ // orm/decorators/fields.ts
2
+ import { MODEL_META } from "../metadata/keys";
3
+ import type { ModelMeta } from "../metadata/types";
4
+ import { registerField } from "../metadata/model-registry";
5
+
6
+ function getOrCreateMeta(metadata: any): ModelMeta {
7
+ if (!metadata[MODEL_META]) {
8
+ metadata[MODEL_META] = {
9
+ tableName: "", // set later by @Model
10
+ fields: [],
11
+ };
12
+ }
13
+ return metadata[MODEL_META];
14
+ }
15
+
16
+ export function PrimaryKey(className: string) {
17
+ return function (_: undefined, context: ClassFieldDecoratorContext) {
18
+ const meta = getOrCreateMeta(context.metadata);
19
+ meta.fields.push({
20
+ name: context.name as string,
21
+ type: "primary",
22
+ });
23
+ meta.primaryKey = context.name as string;
24
+ registerField(className.toLowerCase(), { name: context.name as string, type: "primary",})
25
+ };
26
+ }
27
+
28
+ export function CharField(className: string, options: { maxLength: number }) {
29
+ return function (_: undefined, context: ClassFieldDecoratorContext) {
30
+ const meta = getOrCreateMeta(context.metadata);
31
+
32
+ meta.fields.push({
33
+ name: context.name as string,
34
+ type: "string",
35
+ options,
36
+ });
37
+ registerField(className.toLowerCase(), { name: context.name as string, type: "string", options})
38
+ };
39
+ }
40
+
41
+ export function EmailField(className: string, options: {}) {
42
+ return function (_: undefined, context: ClassFieldDecoratorContext) {
43
+ const meta = getOrCreateMeta(context.metadata);
44
+
45
+ meta.fields.push({
46
+ name: context.name as string,
47
+ type: "email",
48
+ options
49
+ });
50
+ registerField(className.toLowerCase(), { name: context.name as string, type: "string", options})
51
+
52
+ };
53
+ }
54
+
55
+ export function DateTimeField(className: string, options?: { autoNowAdd?: boolean }) {
56
+ return function (_: undefined, context: ClassFieldDecoratorContext) {
57
+ const meta = getOrCreateMeta(context.metadata);
58
+
59
+ meta.fields.push({
60
+ name: context.name as string,
61
+ type: "datetime",
62
+ options,
63
+ });
64
+ registerField(className.toLowerCase(), { name: context.name as string, type: "datetime", options})
65
+ };
66
+ }
67
+
68
+
69
+ export function ForgeinKey(className: string, options?: {}){
70
+ return function (_: undefined, context: ClassFieldDecoratorContext) {
71
+ const meta = getOrCreateMeta(context.metadata);
72
+ meta.fields.push({
73
+ name: context.name as string,
74
+ type: "foreignkey",
75
+ options,
76
+ });
77
+ registerField(className.toLowerCase(), { name: context.name as string, type: "foreignkey", options})
78
+
79
+ }
80
+ }
@@ -0,0 +1,36 @@
1
+ // decorators/model.ts
2
+ import { MODEL_META } from "../metadata/keys";
3
+ import type { ModelMeta } from "../metadata/types";
4
+ import { Manager } from "../core/manager";
5
+ import { registerModel } from "../metadata/model-registry";
6
+
7
+ export function Model(options?: { tableName?: string }) {
8
+ return function <T extends { new (...args: any[]): {} }>(
9
+ constructor: T,
10
+ context: ClassDecoratorContext
11
+ ) {
12
+ context.addInitializer(() => {
13
+ let meta: ModelMeta = {
14
+ tableName: options?.tableName ?? constructor.name.toLowerCase(),
15
+ fields: [],
16
+ };
17
+
18
+ if (!meta) {
19
+ meta = {
20
+ tableName: constructor.name.toLowerCase(),
21
+ fields: [],
22
+ };
23
+ constructor[MODEL_META] = meta;
24
+ }
25
+
26
+ // attach metadata to constructor
27
+ (constructor as any)[MODEL_META] = meta;
28
+
29
+ meta.tableName = options?.tableName ?? constructor.name.toLowerCase();
30
+ // Django-style manager
31
+ (constructor as any).objects = new Manager(constructor);
32
+
33
+ registerModel(constructor); // 🔑 CRITICAL
34
+ });
35
+ };
36
+ }
File without changes
File without changes
@@ -0,0 +1,12 @@
1
+ // orm/metadata/field-types.ts
2
+ export type FieldType =
3
+ | "primary"
4
+ | "string"
5
+ | "text"
6
+ | "email"
7
+ | "number"
8
+ | "boolean"
9
+ | "datetime"
10
+ | "date"
11
+ | "json"
12
+ | "foreignkey";
@@ -0,0 +1,9 @@
1
+ // metadata/get-meta.ts
2
+ import { MODEL_META } from "./keys";
3
+ import type { ModelMeta } from "./types";
4
+
5
+ export function getModelMeta(model: any): ModelMeta {
6
+ const meta = model[MODEL_META];
7
+ if (!meta) throw new Error("Model metadata not found");
8
+ return meta;
9
+ }
@@ -0,0 +1,15 @@
1
+ // orm/metadata/index.ts
2
+ export interface IndexMeta {
3
+ name: string; // deterministic name
4
+ fields: string[]; // ordered
5
+ unique?: boolean;
6
+ }
7
+
8
+
9
+ export function indexName(
10
+ table: string,
11
+ fields: string[],
12
+ unique?: boolean
13
+ ) {
14
+ return `${table}_${fields.join("_")}${unique ? "_uniq" : "_idx"}`;
15
+ }
@@ -0,0 +1,2 @@
1
+ // orm/metadata/keys.ts
2
+ export const MODEL_META = Symbol.for("orm:model_meta");
@@ -0,0 +1,53 @@
1
+ // metadata/model-registry.ts
2
+ import { ModelMeta } from "./types";
3
+
4
+ class ModelRegistry {
5
+ models = new Map<Function, ModelMeta>();
6
+
7
+ registerModel(target: Function, meta: ModelMeta) {
8
+ this.models.set(target, meta);
9
+ }
10
+
11
+ getModel(target: Function) {
12
+ return this.models.get(target);
13
+ }
14
+ }
15
+
16
+ export const modelRegistry = new ModelRegistry();
17
+
18
+
19
+ const MODEL_REGISTRY = new Map<string, any>();
20
+
21
+
22
+ export function registerModel(model: any) {
23
+ MODEL_REGISTRY.set(model.name.toLowerCase(), model);
24
+ }
25
+
26
+ export function getAllModels() {
27
+ return Array.from(MODEL_REGISTRY.values());
28
+ }
29
+
30
+
31
+
32
+
33
+ const MODEL_META = new Map<string, any>();
34
+
35
+ export function registerField(modelName: string, field: any) {
36
+ let modelMeta = MODEL_META.get(modelName)
37
+
38
+ if(!modelMeta) {
39
+ modelMeta = {
40
+ fields: []
41
+ }
42
+ }
43
+ modelMeta.fields.push(field)
44
+ MODEL_META.set(modelName, modelMeta)
45
+ }
46
+
47
+ export function getModelByName(modelName) {
48
+ return MODEL_REGISTRY.get(modelName);
49
+ }
50
+
51
+ export function getModelFields(modelName: string) {
52
+ return MODEL_META.get(modelName)
53
+ }
@@ -0,0 +1,26 @@
1
+ // orm/db/sql/modifiers.ts
2
+ import { FieldMeta } from "./types";
3
+
4
+ export function fieldModifiers(
5
+ field: FieldMeta
6
+ ): string {
7
+ const mods: string[] = [];
8
+
9
+ if (!field.nullable) {
10
+ mods.push("NOT NULL");
11
+ }
12
+
13
+ if (field.default !== undefined) {
14
+ if (field.default === "now") {
15
+ "DEFAULT CURRENT_TIMESTAMP";
16
+ } else {
17
+ mods.push(`DEFAULT ${JSON.stringify(field.default)}`);
18
+ }
19
+ }
20
+
21
+ if (field.options?.unique) {
22
+ mods.push("UNIQUE");
23
+ }
24
+
25
+ return mods.join(" ");
26
+ }
@@ -0,0 +1,45 @@
1
+ export type ModelMeta = {
2
+ tableName: string;
3
+ fields: FieldMeta[];
4
+ primaryKey?: string;
5
+ indexes: {};
6
+ constraints: {};
7
+ };
8
+
9
+ // orm/metadata/types.ts
10
+ import { FieldType } from "./field-types";
11
+
12
+ export interface FieldMeta {
13
+ name: string;
14
+ type: FieldType;
15
+ nullable?: boolean;
16
+ default?: any;
17
+ options?: {
18
+ maxLength?: number;
19
+ autoNowAdd?: boolean;
20
+ unique?: boolean;
21
+ index?: boolean;
22
+ nullable?: boolean;
23
+ default?: any;
24
+ // FK
25
+ to?: string;
26
+ field?: string;
27
+ toField?: string
28
+ onDelete?: "CASCADE" | "SET NULL" | "RESTRICT";
29
+ };
30
+ }
31
+
32
+
33
+ // orm/db/sqlite/types.ts
34
+ export const SQLiteTypeMap: Record<string, (f: FieldMeta) => string> = {
35
+ primary: () => `INTEGER PRIMARY KEY AUTOINCREMENT`,
36
+ string: f => `VARCHAR(${f.options?.maxLength ?? 255})`,
37
+ email: () => `VARCHAR(255)`,
38
+ text: () => `TEXT`,
39
+ number: () => `INTEGER`,
40
+ boolean: () => `BOOLEAN`,
41
+ datetime: () => `DATETIME`,
42
+ date: () => `DATE`,
43
+ json: () => `TEXT`,
44
+ foreignkey: ()=> `FOREIGN KEY`,
45
+ };