tina4-nodejs 3.0.0-rc.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.
Files changed (119) hide show
  1. package/BENCHMARK_REPORT.md +96 -0
  2. package/CARBONAH.md +140 -0
  3. package/CLAUDE.md +599 -0
  4. package/COMPARISON.md +194 -0
  5. package/README.md +595 -0
  6. package/package.json +59 -0
  7. package/packages/cli/src/bin.ts +110 -0
  8. package/packages/cli/src/commands/init.ts +194 -0
  9. package/packages/cli/src/commands/migrate.ts +96 -0
  10. package/packages/cli/src/commands/migrateCreate.ts +59 -0
  11. package/packages/cli/src/commands/routes.ts +61 -0
  12. package/packages/cli/src/commands/serve.ts +58 -0
  13. package/packages/cli/src/commands/test.ts +83 -0
  14. package/packages/core/gallery/auth/meta.json +1 -0
  15. package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +22 -0
  16. package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +16 -0
  17. package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +97 -0
  18. package/packages/core/gallery/database/meta.json +1 -0
  19. package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +13 -0
  20. package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +17 -0
  21. package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +23 -0
  22. package/packages/core/gallery/error-overlay/meta.json +1 -0
  23. package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +17 -0
  24. package/packages/core/gallery/orm/meta.json +1 -0
  25. package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +12 -0
  26. package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +7 -0
  27. package/packages/core/gallery/queue/meta.json +1 -0
  28. package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +16 -0
  29. package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +10 -0
  30. package/packages/core/gallery/rest-api/meta.json +1 -0
  31. package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +6 -0
  32. package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +7 -0
  33. package/packages/core/gallery/templates/meta.json +1 -0
  34. package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +15 -0
  35. package/packages/core/gallery/templates/src/templates/gallery_page.twig +257 -0
  36. package/packages/core/public/css/tina4.css +2463 -0
  37. package/packages/core/public/css/tina4.min.css +1 -0
  38. package/packages/core/public/favicon.ico +0 -0
  39. package/packages/core/public/images/logo.svg +5 -0
  40. package/packages/core/public/images/tina4-logo-icon.webp +0 -0
  41. package/packages/core/public/js/frond.min.js +420 -0
  42. package/packages/core/public/js/tina4-dev-admin.min.js +327 -0
  43. package/packages/core/public/js/tina4.min.js +93 -0
  44. package/packages/core/public/swagger/index.html +90 -0
  45. package/packages/core/public/swagger/oauth2-redirect.html +63 -0
  46. package/packages/core/src/ai.ts +359 -0
  47. package/packages/core/src/api.ts +248 -0
  48. package/packages/core/src/auth.ts +287 -0
  49. package/packages/core/src/cache.ts +121 -0
  50. package/packages/core/src/constants.ts +48 -0
  51. package/packages/core/src/container.ts +90 -0
  52. package/packages/core/src/devAdmin.ts +2024 -0
  53. package/packages/core/src/devMailbox.ts +316 -0
  54. package/packages/core/src/dotenv.ts +172 -0
  55. package/packages/core/src/errorOverlay.test.ts +122 -0
  56. package/packages/core/src/errorOverlay.ts +278 -0
  57. package/packages/core/src/events.ts +112 -0
  58. package/packages/core/src/fakeData.ts +309 -0
  59. package/packages/core/src/graphql.ts +812 -0
  60. package/packages/core/src/health.ts +31 -0
  61. package/packages/core/src/htmlElement.ts +172 -0
  62. package/packages/core/src/i18n.ts +136 -0
  63. package/packages/core/src/index.ts +88 -0
  64. package/packages/core/src/logger.ts +226 -0
  65. package/packages/core/src/messenger.ts +822 -0
  66. package/packages/core/src/middleware.ts +138 -0
  67. package/packages/core/src/queue.ts +481 -0
  68. package/packages/core/src/queueBackends/kafkaBackend.ts +348 -0
  69. package/packages/core/src/queueBackends/rabbitmqBackend.ts +479 -0
  70. package/packages/core/src/rateLimiter.ts +107 -0
  71. package/packages/core/src/request.ts +189 -0
  72. package/packages/core/src/response.ts +146 -0
  73. package/packages/core/src/routeDiscovery.ts +87 -0
  74. package/packages/core/src/router.ts +398 -0
  75. package/packages/core/src/scss.ts +366 -0
  76. package/packages/core/src/server.ts +610 -0
  77. package/packages/core/src/service.ts +380 -0
  78. package/packages/core/src/session.ts +480 -0
  79. package/packages/core/src/sessionHandlers/mongoHandler.ts +286 -0
  80. package/packages/core/src/sessionHandlers/valkeyHandler.ts +184 -0
  81. package/packages/core/src/static.ts +58 -0
  82. package/packages/core/src/testing.ts +233 -0
  83. package/packages/core/src/types.ts +98 -0
  84. package/packages/core/src/watcher.ts +37 -0
  85. package/packages/core/src/websocket.ts +408 -0
  86. package/packages/core/src/wsdl.ts +546 -0
  87. package/packages/core/templates/errors/302.twig +14 -0
  88. package/packages/core/templates/errors/401.twig +9 -0
  89. package/packages/core/templates/errors/403.twig +29 -0
  90. package/packages/core/templates/errors/404.twig +29 -0
  91. package/packages/core/templates/errors/500.twig +38 -0
  92. package/packages/core/templates/errors/502.twig +9 -0
  93. package/packages/core/templates/errors/503.twig +12 -0
  94. package/packages/core/templates/errors/base.twig +37 -0
  95. package/packages/frond/src/engine.ts +1475 -0
  96. package/packages/frond/src/index.ts +2 -0
  97. package/packages/orm/src/adapters/firebird.ts +455 -0
  98. package/packages/orm/src/adapters/mssql.ts +440 -0
  99. package/packages/orm/src/adapters/mysql.ts +355 -0
  100. package/packages/orm/src/adapters/postgres.ts +362 -0
  101. package/packages/orm/src/adapters/sqlite.ts +270 -0
  102. package/packages/orm/src/autoCrud.ts +231 -0
  103. package/packages/orm/src/baseModel.ts +536 -0
  104. package/packages/orm/src/database.ts +321 -0
  105. package/packages/orm/src/fakeData.ts +118 -0
  106. package/packages/orm/src/index.ts +49 -0
  107. package/packages/orm/src/migration.ts +392 -0
  108. package/packages/orm/src/model.ts +56 -0
  109. package/packages/orm/src/query.ts +113 -0
  110. package/packages/orm/src/seeder.ts +120 -0
  111. package/packages/orm/src/sqlTranslation.ts +272 -0
  112. package/packages/orm/src/types.ts +110 -0
  113. package/packages/orm/src/validation.ts +93 -0
  114. package/packages/swagger/src/generator.ts +189 -0
  115. package/packages/swagger/src/index.ts +2 -0
  116. package/packages/swagger/src/ui.ts +48 -0
  117. package/skills/tina4-developer.skill +0 -0
  118. package/skills/tina4-js.skill +0 -0
  119. package/skills/tina4-maintainer.skill +0 -0
@@ -0,0 +1,231 @@
1
+ import type { RouteDefinition, Tina4Request, Tina4Response } from "@tina4/core";
2
+ import type { DiscoveredModel } from "./model.js";
3
+ import { getAdapter } from "./database.js";
4
+ import { buildQuery, parseQueryString } from "./query.js";
5
+ import { validate } from "./validation.js";
6
+
7
+ export function generateCrudRoutes(models: DiscoveredModel[]): RouteDefinition[] {
8
+ const routes: RouteDefinition[] = [];
9
+
10
+ for (const { definition } of models) {
11
+ const { tableName, fields, softDelete, tableFilter } = definition;
12
+ const basePath = `/api/${tableName}`;
13
+
14
+ // Find primary key field
15
+ const pkField = Object.entries(fields).find(([, def]) => def.primaryKey)?.[0] ?? "id";
16
+
17
+ // Build extra WHERE conditions for soft delete and table filter
18
+ const extraConditions: string[] = [];
19
+ if (softDelete) {
20
+ extraConditions.push(`"is_deleted" = 0`);
21
+ }
22
+ if (tableFilter) {
23
+ extraConditions.push(tableFilter);
24
+ }
25
+
26
+ // GET /api/{table} -- List with filtering and pagination
27
+ routes.push({
28
+ method: "GET",
29
+ pattern: basePath,
30
+ meta: {
31
+ summary: `List ${tableName}`,
32
+ tags: [tableName],
33
+ },
34
+ handler: async (req: Tina4Request, res: Tina4Response) => {
35
+ const adapter = getAdapter();
36
+ const options = parseQueryString(req.query);
37
+ const { sql, countSql, params } = buildQuery(tableName, options, extraConditions);
38
+
39
+ const countParams = params.slice(0, -2); // Remove LIMIT and OFFSET
40
+ const items = adapter.query(sql, params);
41
+ const [{ total }] = adapter.query<{ total: number }>(countSql, countParams);
42
+
43
+ const limit = options.limit ?? 20;
44
+ const page = options.page ?? 1;
45
+
46
+ res.json({
47
+ data: items,
48
+ meta: {
49
+ total,
50
+ page,
51
+ limit,
52
+ totalPages: Math.ceil(total / limit),
53
+ },
54
+ });
55
+ },
56
+ });
57
+
58
+ // GET /api/{table}/:id -- Get by ID
59
+ routes.push({
60
+ method: "GET",
61
+ pattern: `${basePath}/{id}`,
62
+ meta: {
63
+ summary: `Get ${tableName} by ID`,
64
+ tags: [tableName],
65
+ },
66
+ handler: async (req: Tina4Request, res: Tina4Response) => {
67
+ const adapter = getAdapter();
68
+ const conditions = [`"${pkField}" = ?`, ...extraConditions];
69
+ const items = adapter.query(
70
+ `SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
71
+ [req.params.id],
72
+ );
73
+ if (items.length === 0) {
74
+ res.status(404).json({ error: "Not Found", statusCode: 404 });
75
+ return;
76
+ }
77
+ res.json({ data: items[0] });
78
+ },
79
+ });
80
+
81
+ // POST /api/{table} -- Create
82
+ routes.push({
83
+ method: "POST",
84
+ pattern: basePath,
85
+ meta: {
86
+ summary: `Create ${tableName}`,
87
+ tags: [tableName],
88
+ },
89
+ handler: async (req: Tina4Request, res: Tina4Response) => {
90
+ const body = req.body as Record<string, unknown> | undefined;
91
+ if (!body || typeof body !== "object") {
92
+ res.status(400).json({ error: "Request body is required", statusCode: 400 });
93
+ return;
94
+ }
95
+
96
+ const errors = validate(body, fields, false);
97
+ if (errors.length > 0) {
98
+ res.status(422).json({ error: "Validation failed", statusCode: 422, errors });
99
+ return;
100
+ }
101
+
102
+ const adapter = getAdapter();
103
+
104
+ // Filter to known fields, exclude auto-increment PKs
105
+ const insertFields = Object.entries(body).filter(([key]) => {
106
+ const def = fields[key];
107
+ return def && !(def.primaryKey && def.autoIncrement);
108
+ });
109
+
110
+ // Add is_deleted = 0 for soft delete models
111
+ if (softDelete) {
112
+ insertFields.push(["is_deleted", 0]);
113
+ }
114
+
115
+ const columns = insertFields.map(([k]) => `"${k}"`).join(", ");
116
+ const placeholders = insertFields.map(() => "?").join(", ");
117
+ const values = insertFields.map(([, v]) => v);
118
+
119
+ const result = adapter.execute(
120
+ `INSERT INTO "${tableName}" (${columns}) VALUES (${placeholders})`,
121
+ values,
122
+ ) as { lastInsertRowid?: number };
123
+
124
+ const id = result.lastInsertRowid;
125
+ const created = adapter.query(
126
+ `SELECT * FROM "${tableName}" WHERE "${pkField}" = ?`,
127
+ [id],
128
+ );
129
+
130
+ res.status(201).json({ data: created[0] });
131
+ },
132
+ });
133
+
134
+ // PUT /api/{table}/:id -- Update
135
+ routes.push({
136
+ method: "PUT",
137
+ pattern: `${basePath}/{id}`,
138
+ meta: {
139
+ summary: `Update ${tableName}`,
140
+ tags: [tableName],
141
+ },
142
+ handler: async (req: Tina4Request, res: Tina4Response) => {
143
+ const body = req.body as Record<string, unknown> | undefined;
144
+ if (!body || typeof body !== "object") {
145
+ res.status(400).json({ error: "Request body is required", statusCode: 400 });
146
+ return;
147
+ }
148
+
149
+ const errors = validate(body, fields, true);
150
+ if (errors.length > 0) {
151
+ res.status(422).json({ error: "Validation failed", statusCode: 422, errors });
152
+ return;
153
+ }
154
+
155
+ const adapter = getAdapter();
156
+
157
+ // Check exists (respect soft delete)
158
+ const conditions = [`"${pkField}" = ?`, ...extraConditions];
159
+ const existing = adapter.query(
160
+ `SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
161
+ [req.params.id],
162
+ );
163
+ if (existing.length === 0) {
164
+ res.status(404).json({ error: "Not Found", statusCode: 404 });
165
+ return;
166
+ }
167
+
168
+ // Filter to known fields
169
+ const updateFields = Object.entries(body).filter(([key]) => fields[key] && !fields[key].primaryKey);
170
+ if (updateFields.length === 0) {
171
+ res.json({ data: existing[0] });
172
+ return;
173
+ }
174
+
175
+ const setClause = updateFields.map(([k]) => `"${k}" = ?`).join(", ");
176
+ const values = [...updateFields.map(([, v]) => v), req.params.id];
177
+
178
+ adapter.execute(
179
+ `UPDATE "${tableName}" SET ${setClause} WHERE "${pkField}" = ?`,
180
+ values,
181
+ );
182
+
183
+ const updated = adapter.query(
184
+ `SELECT * FROM "${tableName}" WHERE "${pkField}" = ?`,
185
+ [req.params.id],
186
+ );
187
+
188
+ res.json({ data: updated[0] });
189
+ },
190
+ });
191
+
192
+ // DELETE /api/{table}/:id -- Delete (soft or hard)
193
+ routes.push({
194
+ method: "DELETE",
195
+ pattern: `${basePath}/{id}`,
196
+ meta: {
197
+ summary: `Delete ${tableName}`,
198
+ tags: [tableName],
199
+ },
200
+ handler: async (req: Tina4Request, res: Tina4Response) => {
201
+ const adapter = getAdapter();
202
+
203
+ const conditions = [`"${pkField}" = ?`, ...extraConditions];
204
+ const existing = adapter.query(
205
+ `SELECT * FROM "${tableName}" WHERE ${conditions.join(" AND ")}`,
206
+ [req.params.id],
207
+ );
208
+ if (existing.length === 0) {
209
+ res.status(404).json({ error: "Not Found", statusCode: 404 });
210
+ return;
211
+ }
212
+
213
+ if (softDelete) {
214
+ adapter.execute(
215
+ `UPDATE "${tableName}" SET is_deleted = 1 WHERE "${pkField}" = ?`,
216
+ [req.params.id],
217
+ );
218
+ res.json({ message: "Deleted (soft)", data: existing[0] });
219
+ } else {
220
+ adapter.execute(
221
+ `DELETE FROM "${tableName}" WHERE "${pkField}" = ?`,
222
+ [req.params.id],
223
+ );
224
+ res.json({ message: "Deleted", data: existing[0] });
225
+ }
226
+ },
227
+ });
228
+ }
229
+
230
+ return routes;
231
+ }