webspresso 0.0.90 → 0.0.93

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 (125) hide show
  1. package/README.md +98 -2509
  2. package/bin/commands/admin-password.js +8 -2
  3. package/bin/commands/api.js +7 -1
  4. package/bin/commands/favicon-generate.js +6 -4
  5. package/bin/commands/mcp.js +162 -0
  6. package/bin/commands/new.js +7 -1
  7. package/bin/commands/page.js +7 -1
  8. package/bin/commands/seed.js +7 -1
  9. package/bin/commands/service.js +199 -0
  10. package/bin/commands/skill.js +7 -1
  11. package/bin/webspresso.js +4 -0
  12. package/core/auth/middleware.js +9 -0
  13. package/core/compileSchema.js +1 -5
  14. package/core/compression/compressible.js +63 -0
  15. package/core/compression/compression-stream.js +59 -0
  16. package/core/compression/index.js +25 -0
  17. package/core/compression/middleware.js +310 -0
  18. package/core/compression/negotiate.js +165 -0
  19. package/core/errors/base.js +31 -0
  20. package/core/errors/domain.js +193 -0
  21. package/core/errors/http.js +133 -0
  22. package/core/errors/index.js +77 -0
  23. package/core/errors/middleware.js +162 -0
  24. package/core/errors/normalize.js +171 -0
  25. package/core/kernel/app.js +2 -1
  26. package/core/kernel/index.js +5 -1
  27. package/core/orm/cache/fingerprint.js +23 -5
  28. package/core/orm/complexity.js +120 -0
  29. package/core/orm/eager-loader.js +54 -9
  30. package/core/orm/events.js +64 -14
  31. package/core/orm/index.js +83 -12
  32. package/core/orm/model.js +13 -2
  33. package/core/orm/query-builder.js +30 -4
  34. package/core/orm/repository.js +57 -25
  35. package/core/orm/transaction.js +39 -5
  36. package/core/orm/utils.js +6 -3
  37. package/core/queue/adapters/database.js +317 -0
  38. package/core/queue/adapters/memory.js +176 -0
  39. package/core/queue/adapters/redis.js +357 -0
  40. package/core/queue/index.js +21 -0
  41. package/core/queue/job.js +103 -0
  42. package/core/queue/manager.js +413 -0
  43. package/core/realtime/adapters/redis.js +431 -0
  44. package/core/realtime/adapters/socket-io.js +203 -0
  45. package/core/realtime/adapters/sse.js +210 -0
  46. package/core/realtime/adapters/websocket.js +337 -0
  47. package/core/realtime/auth-manager.js +140 -0
  48. package/core/realtime/core.js +359 -0
  49. package/core/realtime/emitter.js +98 -0
  50. package/core/realtime/errors.js +71 -0
  51. package/core/realtime/index.js +79 -0
  52. package/core/realtime/reconnect-manager.js +118 -0
  53. package/core/realtime/registry.js +243 -0
  54. package/core/shutdown/http-adapter.js +134 -0
  55. package/core/shutdown/index.js +12 -0
  56. package/core/shutdown/shutdown-manager.js +312 -0
  57. package/core/ssr/index.js +13 -0
  58. package/core/ssr/stream.js +174 -0
  59. package/core/validation/index.js +70 -0
  60. package/index.d.ts +924 -53
  61. package/index.js +110 -1
  62. package/package.json +30 -2
  63. package/plugins/admin-panel/api.js +37 -4
  64. package/plugins/admin-panel/app.js +57 -17
  65. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +10 -2
  66. package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
  67. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +51 -27
  68. package/plugins/admin-panel/client/parts/07-model-list.js +9 -4
  69. package/plugins/admin-panel/client/parts/08-record-list.js +37 -26
  70. package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
  71. package/plugins/admin-panel/core/admin-module.js +2 -2
  72. package/plugins/admin-panel/core/api-extensions.js +149 -0
  73. package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
  74. package/plugins/admin-panel/index.js +155 -23
  75. package/plugins/admin-panel/modules/bulk-actions.js +9 -2
  76. package/plugins/admin-panel/modules/custom-pages.js +144 -45
  77. package/plugins/admin-panel/modules/dashboard.js +8 -1
  78. package/plugins/admin-panel/modules/menu.js +85 -0
  79. package/plugins/admin-panel/modules/user-management.js +5 -2
  80. package/plugins/admin-panel/vendor/mithril.min.js +1 -0
  81. package/plugins/admin-panel/vendor/quill.min.js +7 -0
  82. package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
  83. package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
  84. package/plugins/basic-auth/index.js +293 -0
  85. package/plugins/data-exchange/export-xlsx.js +28 -2
  86. package/plugins/data-exchange/import.js +55 -16
  87. package/plugins/data-exchange/index.js +19 -0
  88. package/plugins/data-exchange/parse-table.js +14 -5
  89. package/plugins/data-exchange/record-selection.js +2 -0
  90. package/plugins/email/index.js +14 -1
  91. package/plugins/index.js +33 -0
  92. package/plugins/mcp/auth.js +104 -0
  93. package/plugins/mcp/discovery.js +88 -0
  94. package/plugins/mcp/index.js +208 -0
  95. package/plugins/mcp/prompts.js +149 -0
  96. package/plugins/mcp/providers/orm.js +325 -0
  97. package/plugins/mcp/providers/services.js +190 -0
  98. package/plugins/mcp/providers/system.js +87 -0
  99. package/plugins/mcp/server.js +472 -0
  100. package/plugins/mcp/transports/sse.js +139 -0
  101. package/plugins/mcp/transports/stdio.js +108 -0
  102. package/plugins/queue/index.js +108 -0
  103. package/plugins/realtime/index.js +150 -0
  104. package/plugins/recaptcha/index.js +11 -0
  105. package/plugins/redirect/index.js +9 -1
  106. package/plugins/rest-resources/index.js +280 -11
  107. package/plugins/upload/index.js +16 -0
  108. package/src/app-context.js +46 -6
  109. package/src/file-router.js +366 -130
  110. package/src/helpers.js +20 -2
  111. package/src/njk-frontmatter.js +24 -14
  112. package/src/plugin-manager.js +124 -4
  113. package/src/server.js +332 -54
  114. package/src/services/builtins/auth.js +273 -0
  115. package/src/services/builtins/exchange.js +229 -0
  116. package/src/services/builtins/mail.js +134 -0
  117. package/src/services/builtins/media.js +196 -0
  118. package/src/services/builtins/system.js +221 -0
  119. package/src/services/discovery.js +141 -0
  120. package/src/services/executor.js +292 -0
  121. package/src/services/index.js +70 -0
  122. package/src/services/memoize.js +256 -0
  123. package/src/services/registry.js +290 -0
  124. package/src/services/validator.js +230 -0
  125. package/templates/skills/webspresso-usage/REFERENCE-framework.md +6 -4
package/index.js CHANGED
@@ -10,9 +10,21 @@ const {
10
10
  getAppContext,
11
11
  getDb,
12
12
  hasDb,
13
+ getShutdownManager,
14
+ hasShutdownManager,
15
+ getServiceRegistry,
16
+ hasServiceRegistry,
13
17
  resetAppContext,
14
18
  setAppContext,
15
19
  } = require('./src/app-context');
20
+ const {
21
+ createServiceRegistry,
22
+ defineService,
23
+ ServiceRegistry,
24
+ } = require('./src/services');
25
+ const { ShutdownManager, NodeHttpAdapter } = require('./core/shutdown');
26
+ const compression = require('./core/compression');
27
+ const errors = require('./core/errors');
16
28
  const {
17
29
  mountPages,
18
30
  filePathToRoute,
@@ -43,7 +55,7 @@ const {
43
55
  // ORM exports (lazy loaded)
44
56
  const orm = require('./core/orm');
45
57
 
46
- /** Application kernel (event bus, plugin shell, view resolver, flows). Use `kernel.createApp`; not the framework SSR `createApp`. */
58
+ /** Standalone experimental application kernel (event bus, plugin shell, view resolver, flows). Distinct from framework SSR `createApp`. */
47
59
  const kernel = require('./core/kernel');
48
60
 
49
61
  // Built-in plugins
@@ -64,6 +76,20 @@ const {
64
76
  rateLimitPlugin,
65
77
  contentPlugin,
66
78
  csrfPlugin,
79
+ corsPlugin,
80
+ emailPlugin,
81
+ basicAuthPlugin,
82
+ realtimePlugin,
83
+ realtime,
84
+ createRealtime,
85
+ websocket,
86
+ sse,
87
+ socketIo,
88
+ redis,
89
+ createRedisAdapter,
90
+ mcpPlugin,
91
+ createMcpServer,
92
+ McpServer,
67
93
  } = require('./plugins');
68
94
 
69
95
  const content = require('./core/content');
@@ -78,8 +104,67 @@ module.exports = {
78
104
  getAppContext,
79
105
  getDb,
80
106
  hasDb,
107
+ getShutdownManager,
108
+ hasShutdownManager,
109
+ getServiceRegistry,
110
+ hasServiceRegistry,
81
111
  resetAppContext,
82
112
  setAppContext,
113
+
114
+ // Services Layer
115
+ createServiceRegistry,
116
+ defineService,
117
+ service: defineService,
118
+ ServiceRegistry,
119
+ memoize: require('./src/services').memoize,
120
+ parseTtlMs: require('./src/services').parseTtlMs,
121
+ stableCacheKey: require('./src/services').stableCacheKey,
122
+ sanitizeInput: require('./src/services').sanitizeInput,
123
+ maskSensitiveData: require('./src/services').maskSensitiveData,
124
+ discoverServices: require('./src/services').discoverServices,
125
+ filePathToServiceName: require('./src/services').filePathToServiceName,
126
+ toCamelCase: require('./src/services').toCamelCase,
127
+ createAuthServices: require('./src/services').createAuthServices,
128
+ createMailServices: require('./src/services').createMailServices,
129
+ createMediaServices: require('./src/services').createMediaServices,
130
+ createSystemServices: require('./src/services').createSystemServices,
131
+ createExchangeServices: require('./src/services').createExchangeServices,
132
+ validateServiceInput: require('./src/services').validateServiceInput,
133
+ executeService: require('./src/services').executeService,
134
+
135
+ // Shutdown & Lifecycle
136
+ ShutdownManager,
137
+ NodeHttpAdapter,
138
+
139
+ // HTTP Response Compression
140
+ compression,
141
+ createCompressionMiddleware: compression.createCompressionMiddleware,
142
+
143
+ // Exceptions & Error Handling
144
+ errors,
145
+ WebspressoError: errors.WebspressoError,
146
+ HttpError: errors.HttpError,
147
+ BadRequestError: errors.BadRequestError,
148
+ UnauthorizedError: errors.UnauthorizedError,
149
+ ForbiddenError: errors.ForbiddenError,
150
+ NotFoundError: errors.NotFoundError,
151
+ MethodNotAllowedError: errors.MethodNotAllowedError,
152
+ ConflictError: errors.ConflictError,
153
+ PayloadTooLargeError: errors.PayloadTooLargeError,
154
+ UnsupportedMediaTypeError: errors.UnsupportedMediaTypeError,
155
+ UnprocessableEntityError: errors.UnprocessableEntityError,
156
+ TooManyRequestsError: errors.TooManyRequestsError,
157
+ ValidationError: errors.ValidationError,
158
+ ConfigurationError: errors.ConfigurationError,
159
+ PluginError: errors.PluginError,
160
+ SecurityError: errors.SecurityError,
161
+ RequestError: errors.RequestError,
162
+ RequestAbortedError: errors.RequestAbortedError,
163
+ RouterError: errors.RouterError,
164
+ RouteNotFoundError: errors.RouteNotFoundError,
165
+ RouteGenerationError: errors.RouteGenerationError,
166
+ normalizeError: errors.normalizeError,
167
+ toErrorResponseObject: errors.toErrorResponseObject,
83
168
 
84
169
  // Router utilities (for advanced use)
85
170
  mountPages,
@@ -97,6 +182,11 @@ module.exports = {
97
182
  // Template helpers
98
183
  createHelpers,
99
184
  utils,
185
+
186
+ // SSR Streaming
187
+ ssr: require('./core/ssr'),
188
+ renderStream: require('./core/ssr').renderStream,
189
+ createHtmlStream: require('./core/ssr').createHtmlStream,
100
190
 
101
191
  // Asset management
102
192
  AssetManager,
@@ -135,7 +225,26 @@ module.exports = {
135
225
  rateLimitPlugin,
136
226
  contentPlugin,
137
227
  csrfPlugin,
228
+ corsPlugin,
229
+ emailPlugin,
230
+ basicAuthPlugin,
231
+ realtimePlugin,
232
+ realtime,
233
+ createRealtime,
234
+ websocket,
235
+ sse,
236
+ socketIo,
237
+ redis,
238
+ createRedisAdapter,
239
+ queuePlugin: require('./plugins/queue'),
240
+ queue: require('./core/queue'),
241
+ createQueueManager: require('./core/queue').createQueueManager,
242
+ QueueManager: require('./core/queue').QueueManager,
243
+ mcpPlugin,
244
+ createMcpServer,
245
+ McpServer,
138
246
 
139
247
  // Schema-driven CMS core (framework-agnostic)
140
248
  content,
141
249
  };
250
+
package/package.json CHANGED
@@ -1,14 +1,34 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.90",
3
+ "version": "0.0.93",
4
4
  "description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "default": "./index.js"
11
+ },
12
+ "./errors": {
13
+ "types": "./index.d.ts",
14
+ "default": "./core/errors/index.js"
15
+ },
16
+ "./plugins": "./plugins/index.js",
17
+ "./plugins/*": "./plugins/*",
18
+ "./realtime": "./core/realtime/index.js",
19
+ "./core/*": "./core/*",
20
+ "./src/*": "./src/*",
21
+ "./views/*": "./views/*",
22
+ "./templates/*": "./templates/*",
23
+ "./package.json": "./package.json"
24
+ },
7
25
  "bin": {
8
26
  "webspresso": "./bin/webspresso.js"
9
27
  },
10
28
  "scripts": {
11
29
  "test": "vitest run",
30
+ "test:security": "vitest run tests/security/",
31
+ "security": "npm run test:security && npm audit",
12
32
  "test:watch": "vitest",
13
33
  "test:coverage": "vitest run --coverage",
14
34
  "test:e2e": "playwright test",
@@ -22,6 +42,10 @@
22
42
  "bench:ci:local": "npm run bench:baseline && npm run bench:ci",
23
43
  "check:types": "tsc --project tests/ts-smoke/tsconfig.json",
24
44
  "rebuild:native": "npm rebuild better-sqlite3 bcrypt sharp",
45
+ "docs:dev": "docboot dev ./docs",
46
+ "docs:build": "docboot build ./docs",
47
+ "docs:serve": "docboot serve dist",
48
+ "docs:doctor": "docboot doctor ./docs",
25
49
  "release": "release-it"
26
50
  },
27
51
  "keywords": [
@@ -62,7 +86,7 @@
62
86
  "csv-parse": "^5.6.0",
63
87
  "dayjs": "^1.11.19",
64
88
  "exceljs": "^4.4.0",
65
- "express": "^4.18.2",
89
+ "express": "^5.2.1",
66
90
  "express-session": "^1.18.0",
67
91
  "helmet": "^7.2.0",
68
92
  "inquirer": "^8.2.6",
@@ -114,6 +138,7 @@
114
138
  "@vitest/coverage-v8": "^3.0.0",
115
139
  "better-sqlite3": "^11.10.0",
116
140
  "chokidar": "^3.5.3",
141
+ "docboot": "^0.3.0",
117
142
  "dotenv": "^16.3.1",
118
143
  "express-rate-limit": "^8.4.1",
119
144
  "release-it": "^20.2.1",
@@ -129,5 +154,8 @@
129
154
  "tar": "^7.5.11",
130
155
  "undici": "^6.24.0",
131
156
  "uuid": "^11.1.1"
157
+ },
158
+ "allowScripts": {
159
+ "better-sqlite3@11.10.0": true
132
160
  }
133
161
  }
@@ -384,6 +384,9 @@ function isColumnSortable(name, meta, model) {
384
384
 
385
385
  // Apply filters
386
386
  for (const [colName, filter] of Object.entries(filterParams)) {
387
+ if (!model.columns.has(colName) || !filter || typeof filter !== 'object') {
388
+ continue;
389
+ }
387
390
  const colMeta = model.columns.get(colName);
388
391
  const colType = colMeta?.type || 'string';
389
392
  const op = filter.op || (colType === 'boolean' ? 'eq' : 'contains');
@@ -568,6 +571,32 @@ function isColumnSortable(name, meta, model) {
568
571
  }
569
572
  }
570
573
 
574
+ /** Extract structured field errors from Zod, ORM or database validation exceptions */
575
+ function extractValidationErrors(error) {
576
+ if (!error) return null;
577
+ const fields = {};
578
+ if (error.errors && typeof error.errors === 'object') {
579
+ if (Array.isArray(error.errors)) {
580
+ for (const item of error.errors) {
581
+ if (item.path && item.message) {
582
+ const key = Array.isArray(item.path) ? item.path.join('.') : String(item.path);
583
+ fields[key] = item.message;
584
+ }
585
+ }
586
+ } else {
587
+ Object.assign(fields, error.errors);
588
+ }
589
+ } else if (error.issues && Array.isArray(error.issues)) {
590
+ for (const issue of error.issues) {
591
+ const key = issue.path && issue.path.length > 0 ? issue.path.join('.') : 'root';
592
+ fields[key] = issue.message;
593
+ }
594
+ } else if (error.fields && typeof error.fields === 'object') {
595
+ Object.assign(fields, error.fields);
596
+ }
597
+ return Object.keys(fields).length > 0 ? fields : null;
598
+ }
599
+
571
600
  /**
572
601
  * Create record
573
602
  */
@@ -588,7 +617,8 @@ function isColumnSortable(name, meta, model) {
588
617
  const value = req.body[colName];
589
618
  if (isRichTextEmpty(value)) {
590
619
  return res.status(400).json({
591
- error: `Field "${colName}" is required`
620
+ error: `Field "${colName}" is required`,
621
+ fields: { [colName]: `Field "${colName}" is required` },
592
622
  });
593
623
  }
594
624
  }
@@ -599,7 +629,8 @@ function isColumnSortable(name, meta, model) {
599
629
 
600
630
  res.status(201).json({ data: sanitizeForOutput(record, model) });
601
631
  } catch (error) {
602
- res.status(400).json({ error: error.message });
632
+ const fields = extractValidationErrors(error);
633
+ res.status(400).json({ error: error.message, ...(fields ? { fields } : {}) });
603
634
  }
604
635
  }
605
636
 
@@ -623,7 +654,8 @@ function isColumnSortable(name, meta, model) {
623
654
  const value = req.body[colName];
624
655
  if (isRichTextEmpty(value)) {
625
656
  return res.status(400).json({
626
- error: `Field "${colName}" is required`
657
+ error: `Field "${colName}" is required`,
658
+ fields: { [colName]: `Field "${colName}" is required` },
627
659
  });
628
660
  }
629
661
  }
@@ -638,7 +670,8 @@ function isColumnSortable(name, meta, model) {
638
670
 
639
671
  res.json({ data: sanitizeForOutput(record, model) });
640
672
  } catch (error) {
641
- res.status(400).json({ error: error.message });
673
+ const fields = extractValidationErrors(error);
674
+ res.status(400).json({ error: error.message, ...(fields ? { fields } : {}) });
642
675
  }
643
676
  }
644
677
 
@@ -247,36 +247,76 @@ function wrapCustomPageWithLayout(page, rawComp) {
247
247
 
248
248
  return {
249
249
  oninit: function(vnode) {
250
- vnode.state.instance = typeof rawComp === 'function' ? rawComp.call(this, vnode) : rawComp;
251
- if (vnode.state.instance && typeof vnode.state.instance.oninit === 'function') {
252
- return vnode.state.instance.oninit.call(this, vnode);
250
+ try {
251
+ vnode.state.instance = typeof rawComp === 'function' ? rawComp.call(this, vnode) : rawComp;
252
+ if (vnode.state.instance && typeof vnode.state.instance.oninit === 'function') {
253
+ return vnode.state.instance.oninit.call(this, vnode);
254
+ }
255
+ } catch (err) {
256
+ console.error('[Admin Panel] Error in oninit for custom page "' + page.id + '":', err);
257
+ vnode.state.renderError = err;
253
258
  }
254
259
  },
255
260
  oncreate: function(vnode) {
256
- const comp = vnode.state.instance || rawComp;
257
- if (comp && typeof comp.oncreate === 'function') return comp.oncreate.call(this, vnode);
261
+ try {
262
+ const comp = vnode.state.instance || rawComp;
263
+ if (comp && typeof comp.oncreate === 'function') return comp.oncreate.call(this, vnode);
264
+ } catch (err) {
265
+ console.error('[Admin Panel] Error in oncreate for custom page "' + page.id + '":', err);
266
+ }
258
267
  },
259
268
  onupdate: function(vnode) {
260
- const comp = vnode.state.instance || rawComp;
261
- if (comp && typeof comp.onupdate === 'function') return comp.onupdate.call(this, vnode);
269
+ try {
270
+ const comp = vnode.state.instance || rawComp;
271
+ if (comp && typeof comp.onupdate === 'function') return comp.onupdate.call(this, vnode);
272
+ } catch (err) {
273
+ console.error('[Admin Panel] Error in onupdate for custom page "' + page.id + '":', err);
274
+ }
262
275
  },
263
276
  onbeforeremove: function(vnode) {
264
- const comp = vnode.state.instance || rawComp;
265
- if (comp && typeof comp.onbeforeremove === 'function') return comp.onbeforeremove.call(this, vnode);
277
+ try {
278
+ const comp = vnode.state.instance || rawComp;
279
+ if (comp && typeof comp.onbeforeremove === 'function') return comp.onbeforeremove.call(this, vnode);
280
+ } catch (err) {
281
+ console.error('[Admin Panel] Error in onbeforeremove for custom page "' + page.id + '":', err);
282
+ }
266
283
  },
267
284
  onremove: function(vnode) {
268
- const comp = vnode.state.instance || rawComp;
269
- if (comp && typeof comp.onremove === 'function') return comp.onremove.call(this, vnode);
285
+ try {
286
+ const comp = vnode.state.instance || rawComp;
287
+ if (comp && typeof comp.onremove === 'function') return comp.onremove.call(this, vnode);
288
+ } catch (err) {
289
+ console.error('[Admin Panel] Error in onremove for custom page "' + page.id + '":', err);
290
+ }
270
291
  },
271
292
  view: function(vnode) {
293
+ if (vnode.state.renderError) {
294
+ var err = vnode.state.renderError;
295
+ var breadcrumbs = [{ label: page.title, href: page.path }];
296
+ return m(Layout, [
297
+ m(Breadcrumb, { items: breadcrumbs }),
298
+ m('.p-6.bg-red-50.border.border-red-200.rounded-xl', [
299
+ m('h3.text-lg.font-semibold.text-red-800', 'Sayfa Yüklenirken Hata Oluştu'),
300
+ m('p.text-sm.text-red-600.mt-1', err.message || String(err)),
301
+ ])
302
+ ]);
303
+ }
272
304
  const comp = vnode.state.instance || rawComp;
273
305
  var content;
274
- if (comp && typeof comp.view === 'function') {
275
- content = comp.view.call(this, vnode);
276
- } else if (typeof comp === 'function') {
277
- content = comp.call(this, vnode);
278
- } else {
279
- content = comp;
306
+ try {
307
+ if (comp && typeof comp.view === 'function') {
308
+ content = comp.view.call(this, vnode);
309
+ } else if (typeof comp === 'function') {
310
+ content = comp.call(this, vnode);
311
+ } else {
312
+ content = comp;
313
+ }
314
+ } catch (err) {
315
+ console.error('[Admin Panel] Error rendering custom page "' + page.id + '":', err);
316
+ content = m('.p-6.bg-red-50.border.border-red-200.rounded-xl', [
317
+ m('h3.text-lg.font-semibold.text-red-800', 'Sayfa Çizilirken Hata Oluştu'),
318
+ m('p.text-sm.text-red-600.mt-1', err.message || String(err)),
319
+ ]);
280
320
  }
281
321
  if (content && (content.tag === Layout || (content.tag && content.tag.name === 'Layout'))) {
282
322
  return content;
@@ -18,7 +18,10 @@ const api = {
18
18
  if (response.status === 401 && path.indexOf('/auth/') !== 0) {
19
19
  redirectToLogin();
20
20
  }
21
- throw new Error(error.error || 'Request failed');
21
+ const err = new Error(error.error || 'Request failed');
22
+ if (error.fields) err.fields = error.fields;
23
+ err.response = error;
24
+ throw err;
22
25
  }
23
26
 
24
27
  return response.json();
@@ -56,10 +59,15 @@ async function downloadDataExchangeXlsx(modelName, payload) {
56
59
  const blob = await res.blob();
57
60
  var url = URL.createObjectURL(blob);
58
61
  var a = document.createElement('a');
62
+ a.style.display = 'none';
59
63
  a.href = url;
60
64
  a.download = modelName + '-export.xlsx';
65
+ document.body.appendChild(a);
61
66
  a.click();
62
- URL.revokeObjectURL(url);
67
+ setTimeout(function () {
68
+ if (a.parentNode) a.parentNode.removeChild(a);
69
+ URL.revokeObjectURL(url);
70
+ }, 1000);
63
71
  }
64
72
 
65
73
  // Helper: Capitalize first letter of each word