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,366 @@
1
+ // Tina4 SCSS — Zero-dependency SCSS-to-CSS compiler (subset).
2
+ // Supports variables, nesting, & parent selector, @import, @mixin/@include, comments, basic math.
3
+
4
+ import { readFileSync, existsSync } from "node:fs";
5
+ import { join, resolve, dirname } from "node:path";
6
+
7
+ // ── Types ────────────────────────────────────────────────────────
8
+
9
+ export interface ScssConfig {
10
+ importPaths?: string[];
11
+ variables?: Record<string, string>;
12
+ }
13
+
14
+ // ── ScssCompiler ─────────────────────────────────────────────────
15
+
16
+ export class ScssCompiler {
17
+ private _importPaths: string[];
18
+ private _variables: Record<string, string>;
19
+
20
+ constructor(config?: ScssConfig) {
21
+ this._importPaths = config?.importPaths ? [...config.importPaths] : [];
22
+ this._variables = config?.variables ? { ...config.variables } : {};
23
+ }
24
+
25
+ /** Compile an SCSS string to CSS. */
26
+ compile(source: string): string {
27
+ return compileString(source, this._importPaths, { ...this._variables });
28
+ }
29
+
30
+ /** Compile an SCSS file to CSS. */
31
+ compileFile(filePath: string): string {
32
+ const absPath = resolve(filePath);
33
+ const content = readFileSync(absPath, "utf-8");
34
+ const paths = [dirname(absPath), ...this._importPaths];
35
+ return compileString(content, paths, { ...this._variables });
36
+ }
37
+
38
+ /** Add a directory to the import resolution path. */
39
+ addImportPath(path: string): void {
40
+ this._importPaths.push(resolve(path));
41
+ }
42
+
43
+ /** Set or override an SCSS variable. */
44
+ setVariable(name: string, value: string): void {
45
+ // Strip leading $ if provided
46
+ const key = name.startsWith("$") ? name.slice(1) : name;
47
+ this._variables[key] = value;
48
+ }
49
+ }
50
+
51
+ // ── Internal Compilation Pipeline ────────────────────────────────
52
+
53
+ function compileString(
54
+ scss: string,
55
+ importPaths: string[],
56
+ variables: Record<string, string>
57
+ ): string {
58
+ // 1. Resolve @import statements
59
+ const imported = new Set<string>();
60
+ scss = resolveImports(scss, importPaths, imported);
61
+
62
+ // 2. Strip single-line comments (preserve /* */ block comments)
63
+ scss = scss.replace(/(?<![:"'])\/\/[^\n]*/g, "");
64
+
65
+ // 3. Extract and store variables
66
+ scss = extractVariables(scss, variables);
67
+
68
+ // 4. Extract mixins
69
+ const mixins: Record<string, { params: string[]; body: string }> = {};
70
+ scss = extractMixins(scss, mixins);
71
+
72
+ // 5. Resolve @include
73
+ scss = resolveIncludes(scss, mixins);
74
+
75
+ // 6. Substitute variables
76
+ scss = substituteVariables(scss, variables);
77
+
78
+ // 7. Evaluate basic math in property values
79
+ scss = evalMath(scss);
80
+
81
+ // 8. Flatten nested rules
82
+ const css = flattenNesting(scss);
83
+
84
+ // 9. Cleanup
85
+ return cleanup(css);
86
+ }
87
+
88
+ // ── Import Resolution ────────────────────────────────────────────
89
+
90
+ function resolveImports(content: string, paths: string[], imported: Set<string>): string {
91
+ return content.replace(/@import\s+["']?([^"';\n]+)["']?\s*;/g, (_match, name: string) => {
92
+ name = name.trim();
93
+ const candidates: string[] = [];
94
+ for (const base of paths) {
95
+ candidates.push(
96
+ join(base, `${name}.scss`),
97
+ join(base, `_${name}.scss`),
98
+ join(base, name),
99
+ );
100
+ }
101
+ for (const candidate of candidates) {
102
+ if (existsSync(candidate) && !imported.has(candidate)) {
103
+ imported.add(candidate);
104
+ const fileContent = readFileSync(candidate, "utf-8");
105
+ return resolveImports(fileContent, [dirname(candidate), ...paths], imported);
106
+ }
107
+ }
108
+ return `/* IMPORT NOT FOUND: ${name} */`;
109
+ });
110
+ }
111
+
112
+ // ── Variables ────────────────────────────────────────────────────
113
+
114
+ function extractVariables(scss: string, variables: Record<string, string>): string {
115
+ return scss.replace(/\$([a-zA-Z_][\w-]*)\s*:\s*([^;]+);/g, (_m, name: string, value: string) => {
116
+ let resolved = value.trim();
117
+ // Resolve variable references within the value
118
+ for (const [vName, vVal] of Object.entries(variables)) {
119
+ resolved = resolved.replaceAll(`$${vName}`, vVal);
120
+ }
121
+ variables[name] = resolved;
122
+ return "";
123
+ });
124
+ }
125
+
126
+ function substituteVariables(scss: string, variables: Record<string, string>): string {
127
+ // Sort by longest name first to avoid partial matches
128
+ const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
129
+ for (const name of sorted) {
130
+ scss = scss.replaceAll(`$${name}`, variables[name]);
131
+ }
132
+ return scss;
133
+ }
134
+
135
+ // ── Mixins ───────────────────────────────────────────────────────
136
+
137
+ function extractMixins(
138
+ scss: string,
139
+ mixins: Record<string, { params: string[]; body: string }>
140
+ ): string {
141
+ const pattern = /@mixin\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*\{/g;
142
+ let match: RegExpExecArray | null;
143
+ const locations: { start: number; end: number; name: string }[] = [];
144
+
145
+ while ((match = pattern.exec(scss)) !== null) {
146
+ const name = match[1];
147
+ const paramsStr = match[2] ?? "";
148
+ const params = paramsStr
149
+ .split(",")
150
+ .map((p) => p.trim().replace(/^\$/, ""))
151
+ .filter(Boolean);
152
+
153
+ const bodyStart = match.index + match[0].length;
154
+ const body = findBlock(scss, bodyStart);
155
+ if (body !== null) {
156
+ mixins[name] = { params, body };
157
+ locations.push({
158
+ start: match.index,
159
+ end: bodyStart + body.length + 1,
160
+ name,
161
+ });
162
+ }
163
+ }
164
+
165
+ // Remove mixin definitions from source (reverse order to preserve indices)
166
+ let result = scss;
167
+ for (const loc of locations.reverse()) {
168
+ result = result.slice(0, loc.start) + result.slice(loc.end);
169
+ }
170
+ return result;
171
+ }
172
+
173
+ function resolveIncludes(
174
+ scss: string,
175
+ mixins: Record<string, { params: string[]; body: string }>
176
+ ): string {
177
+ return scss.replace(
178
+ /@include\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*;/g,
179
+ (_m, name: string, argsStr: string | undefined) => {
180
+ if (!(name in mixins)) {
181
+ return `/* MIXIN NOT FOUND: ${name} */`;
182
+ }
183
+ const mixin = mixins[name];
184
+ const args = argsStr
185
+ ? argsStr.split(",").map((a) => a.trim()).filter(Boolean)
186
+ : [];
187
+ let body = mixin.body;
188
+ for (let i = 0; i < mixin.params.length; i++) {
189
+ const paramName = mixin.params[i].split(":")[0].trim();
190
+ const defaultVal = mixin.params[i].includes(":")
191
+ ? mixin.params[i].split(":").slice(1).join(":").trim()
192
+ : "";
193
+ const value = i < args.length ? args[i] : defaultVal;
194
+ body = body.replaceAll(`$${paramName}`, value);
195
+ }
196
+ return body;
197
+ }
198
+ );
199
+ }
200
+
201
+ // ── Math Evaluation ──────────────────────────────────────────────
202
+
203
+ function evalMath(scss: string): string {
204
+ return scss.replace(
205
+ /([\d.]+)([a-z%]*)\s*([+\-*/])\s*([\d.]+)([a-z%]*)/g,
206
+ (_m, n1: string, u1: string, op: string, n2: string, _u2: string) => {
207
+ try {
208
+ const num1 = parseFloat(n1);
209
+ const num2 = parseFloat(n2);
210
+ let result: number;
211
+ switch (op) {
212
+ case "+": result = num1 + num2; break;
213
+ case "-": result = num1 - num2; break;
214
+ case "*": result = num1 * num2; break;
215
+ case "/": result = num2 !== 0 ? num1 / num2 : 0; break;
216
+ default: return _m;
217
+ }
218
+ const unit = u1 || "";
219
+ if (result === Math.floor(result)) {
220
+ return `${Math.floor(result)}${unit}`;
221
+ }
222
+ return `${result.toFixed(2)}${unit}`;
223
+ } catch {
224
+ return _m;
225
+ }
226
+ }
227
+ );
228
+ }
229
+
230
+ // ── Nesting Flattener ────────────────────────────────────────────
231
+
232
+ function flattenNesting(scss: string): string {
233
+ const output: string[] = [];
234
+ flattenBlock(scss, [], output);
235
+ return output.join("\n");
236
+ }
237
+
238
+ function flattenBlock(content: string, parentSelectors: string[], output: string[]): void {
239
+ let pos = 0;
240
+ const properties: string[] = [];
241
+
242
+ while (pos < content.length) {
243
+ // Skip whitespace
244
+ while (pos < content.length && /[\s]/.test(content[pos])) {
245
+ pos++;
246
+ }
247
+ if (pos >= content.length) break;
248
+
249
+ // Block comment — preserve
250
+ if (content[pos] === "/" && content[pos + 1] === "*") {
251
+ const end = content.indexOf("*/", pos + 2);
252
+ if (end === -1) break;
253
+ output.push(content.slice(pos, end + 2));
254
+ pos = end + 2;
255
+ continue;
256
+ }
257
+
258
+ // @media query — special handling
259
+ if (content.slice(pos, pos + 6) === "@media") {
260
+ const brace = content.indexOf("{", pos);
261
+ if (brace === -1) break;
262
+ const mediaQuery = content.slice(pos, brace).trim();
263
+ const body = findBlock(content, brace + 1);
264
+ if (body === null) break;
265
+ pos = brace + 1 + body.length + 1;
266
+
267
+ const innerOutput: string[] = [];
268
+ flattenBlock(body, parentSelectors, innerOutput);
269
+ if (innerOutput.length > 0) {
270
+ output.push(`${mediaQuery} {`);
271
+ for (const line of innerOutput) {
272
+ output.push(` ${line}`);
273
+ }
274
+ output.push("}");
275
+ }
276
+ continue;
277
+ }
278
+
279
+ // Find next { or ;
280
+ const bracePos = content.indexOf("{", pos);
281
+ const semiPos = content.indexOf(";", pos);
282
+
283
+ // Property (has ; before { or no { at all)
284
+ if (semiPos !== -1 && (bracePos === -1 || semiPos < bracePos)) {
285
+ const prop = content.slice(pos, semiPos).trim();
286
+ if (prop && !prop.startsWith("@")) {
287
+ properties.push(prop);
288
+ }
289
+ pos = semiPos + 1;
290
+ continue;
291
+ }
292
+
293
+ // Nested block
294
+ if (bracePos !== -1) {
295
+ const selectorText = content.slice(pos, bracePos).trim();
296
+ const body = findBlock(content, bracePos + 1);
297
+ if (body === null) break;
298
+ pos = bracePos + 1 + body.length + 1;
299
+
300
+ if (!selectorText) continue;
301
+
302
+ // Expand selectors with parent reference (&)
303
+ const selectors = selectorText.split(",").map((s) => s.trim());
304
+ const newSelectors: string[] = [];
305
+ for (const sel of selectors) {
306
+ if (parentSelectors.length > 0) {
307
+ for (const parent of parentSelectors) {
308
+ if (sel.includes("&")) {
309
+ newSelectors.push(sel.replace(/&/g, parent));
310
+ } else {
311
+ newSelectors.push(`${parent} ${sel}`);
312
+ }
313
+ }
314
+ } else {
315
+ newSelectors.push(sel);
316
+ }
317
+ }
318
+
319
+ flattenBlock(body, newSelectors, output);
320
+ continue;
321
+ }
322
+
323
+ // Remaining text — treat as property
324
+ const remaining = content.slice(pos).trim();
325
+ if (remaining) {
326
+ properties.push(remaining);
327
+ }
328
+ break;
329
+ }
330
+
331
+ // Emit properties for current selector
332
+ if (properties.length > 0 && parentSelectors.length > 0) {
333
+ const selectorStr = parentSelectors.join(", ");
334
+ output.push(`${selectorStr} {`);
335
+ for (const prop of properties) {
336
+ output.push(` ${prop};`);
337
+ }
338
+ output.push("}");
339
+ }
340
+ }
341
+
342
+ // ── Utilities ────────────────────────────────────────────────────
343
+
344
+ function findBlock(content: string, start: number): string | null {
345
+ let depth = 1;
346
+ let pos = start;
347
+ while (pos < content.length && depth > 0) {
348
+ if (content[pos] === "{") depth++;
349
+ else if (content[pos] === "}") depth--;
350
+ if (depth > 0) pos++;
351
+ }
352
+ return depth === 0 ? content.slice(start, pos) : null;
353
+ }
354
+
355
+ function cleanup(css: string): string {
356
+ // Remove empty rulesets
357
+ css = css.replace(/[^{}]+\{\s*\}/g, "");
358
+ // Remove multiple blank lines
359
+ css = css.replace(/\n{3,}/g, "\n\n");
360
+ // Remove trailing whitespace per line
361
+ css = css
362
+ .split("\n")
363
+ .map((line) => line.trimEnd())
364
+ .join("\n");
365
+ return css.trim() + "\n";
366
+ }