@nitronjs/framework 0.2.11 → 0.2.13

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.
@@ -239,7 +239,7 @@ class FileAnalyzer {
239
239
  if (declaration?.type === "VariableDeclaration") {
240
240
  for (const decl of declaration.declarations) {
241
241
  if (decl.id.type === "Identifier") {
242
- if (decl.id.name === "layout" &&
242
+ if (decl.id.name === "Layout" &&
243
243
  decl.init?.type === "BooleanLiteral" &&
244
244
  decl.init.value === false) {
245
245
  meta.layoutDisabled = true;
@@ -1,10 +1,31 @@
1
+ /**
2
+ * Custom JSX Runtime for NitronJS
3
+ *
4
+ * Handles React 19's strict key prop requirements and provides
5
+ * island architecture support for client components.
6
+ */
1
7
  const JSX_RUNTIME = `
2
8
  import * as React from 'react';
3
9
  import * as OriginalJsx from '__react_jsx_original__';
4
10
 
5
11
  const CTX = Symbol.for('__nitron_view_context__');
6
12
  const MARK = Symbol.for('__nitron_client_component__');
7
- const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
13
+ const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype', 'key', 'ref']);
14
+
15
+ // Filter React 19 key warnings from third-party libraries
16
+ if (typeof console !== 'undefined' && console.error) {
17
+ const originalError = console.error;
18
+ console.error = function(...args) {
19
+ const msg = args[0];
20
+ if (typeof msg === 'string' && (
21
+ msg.includes('A props object containing a "key" prop is being spread into JSX') ||
22
+ msg.includes('\`key\` is not a prop')
23
+ )) {
24
+ return;
25
+ }
26
+ return originalError.apply(console, args);
27
+ };
28
+ }
8
29
 
9
30
  function getContext() {
10
31
  return globalThis[CTX]?.getStore?.();
@@ -20,6 +41,7 @@ globalThis.request = () => {
20
41
  const DepthContext = React.createContext(false);
21
42
  const componentCache = new WeakMap();
22
43
 
44
+ // Sanitizes props for client-side hydration (removes functions, symbols, circular refs)
23
45
  function sanitizeProps(obj, seen = new WeakSet()) {
24
46
  if (obj == null) return obj;
25
47
 
@@ -62,6 +84,7 @@ function wrapWithDepth(children) {
62
84
  return OriginalJsx.jsx(DepthContext.Provider, { value: true, children });
63
85
  }
64
86
 
87
+ // Creates an island wrapper for client components (hydrated independently)
65
88
  function createIsland(Component, name) {
66
89
  function IslandBoundary(props) {
67
90
  if (React.useContext(DepthContext)) {
@@ -96,12 +119,23 @@ function getWrappedComponent(Component) {
96
119
  return componentCache.get(Component);
97
120
  }
98
121
 
122
+ // Extracts key from props without triggering React 19's getter warning
99
123
  function extractKey(props, key) {
100
- if (props == null || !('key' in props)) {
101
- return [props, key];
124
+ if (props == null) return [props, key];
125
+ if (!Object.prototype.hasOwnProperty.call(props, 'key')) return [props, key];
126
+
127
+ const cleanProps = {};
128
+ let extractedKey = key;
129
+
130
+ for (const k of Object.keys(props)) {
131
+ if (k === 'key') {
132
+ if (extractedKey == null) extractedKey = props[k];
133
+ } else {
134
+ cleanProps[k] = props[k];
135
+ }
102
136
  }
103
- const { key: propKey, ...rest } = props;
104
- return [rest, key ?? propKey];
137
+
138
+ return [cleanProps, extractedKey];
105
139
  }
106
140
 
107
141
  export function jsx(type, props, key) {
@@ -162,7 +162,8 @@ export function createServerModuleBlockerPlugin() {
162
162
 
163
163
  /**
164
164
  * Creates an esbuild plugin that transforms server functions for client use.
165
- * Replaces csrf() and route() calls with runtime lookups.
165
+ * Replaces csrf() calls with runtime lookups.
166
+ * Note: route() is now a global function defined in spa.js
166
167
  * @returns {import("esbuild").Plugin}
167
168
  */
168
169
  export function createServerFunctionsPlugin() {
@@ -176,7 +177,7 @@ export function createServerFunctionsPlugin() {
176
177
 
177
178
  let source = await fs.promises.readFile(args.path, "utf8");
178
179
 
179
- if (!source.includes("csrf(") && !source.includes("route(")) {
180
+ if (!source.includes("csrf(")) {
180
181
  return null;
181
182
  }
182
183
 
@@ -185,11 +186,6 @@ export function createServerFunctionsPlugin() {
185
186
  "window.__NITRON_RUNTIME__.csrf"
186
187
  );
187
188
 
188
- source = source.replace(
189
- /\broute\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
190
- (_, routeName) => `window.__NITRON_RUNTIME__.routes["${routeName}"]`
191
- );
192
-
193
189
  const ext = args.path.split(".").pop();
194
190
  const loader = ext === "tsx" ? "tsx" : ext === "ts" ? "ts" : ext === "jsx" ? "jsx" : "js";
195
191
 
@@ -1,6 +1,7 @@
1
1
  import dotenv from 'dotenv';
2
2
  import DB from '../../Database/DB.js';
3
3
  import Config from '../../Core/Config.js';
4
+ import Environment from '../../Core/Environment.js';
4
5
  import MigrationRunner from '../../Database/Migration/MigrationRunner.js';
5
6
  import seed from './SeedCommand.js';
6
7
 
@@ -8,6 +9,7 @@ export default async function migrate(options = {}) {
8
9
  const { seed: shouldSeed = false } = options;
9
10
 
10
11
  dotenv.config({ quiet: true });
12
+ Environment.setDev(true);
11
13
  await Config.initialize();
12
14
 
13
15
  try {
@@ -1,6 +1,7 @@
1
1
  import dotenv from 'dotenv';
2
2
  import DB from '../../Database/DB.js';
3
3
  import Config from '../../Core/Config.js';
4
+ import Environment from '../../Core/Environment.js';
4
5
  import MigrationRunner from '../../Database/Migration/MigrationRunner.js';
5
6
  import Output from '../../Console/Output.js';
6
7
  import seed from './SeedCommand.js';
@@ -9,6 +10,7 @@ export default async function migrateFresh(options = {}) {
9
10
  const { seed: shouldSeed = false } = options;
10
11
 
11
12
  dotenv.config({ quiet: true });
13
+ Environment.setDev(true);
12
14
  await Config.initialize();
13
15
 
14
16
  try {
@@ -1,6 +1,7 @@
1
1
  import dotenv from 'dotenv';
2
2
  import DB from '../../Database/DB.js';
3
3
  import Config from '../../Core/Config.js';
4
+ import Environment from '../../Core/Environment.js';
4
5
  import MigrationRunner from '../../Database/Migration/MigrationRunner.js';
5
6
  import Output from '../../Console/Output.js';
6
7
 
@@ -8,6 +9,7 @@ export default async function rollback(options = {}) {
8
9
  const { step = 1, all = false } = options;
9
10
 
10
11
  dotenv.config({ quiet: true });
12
+ Environment.setDev(true);
11
13
  await Config.initialize();
12
14
 
13
15
  try {
@@ -1,11 +1,13 @@
1
1
  import dotenv from 'dotenv';
2
2
  import DB from '../../Database/DB.js';
3
3
  import Config from '../../Core/Config.js';
4
+ import Environment from '../../Core/Environment.js';
4
5
  import MigrationRunner from '../../Database/Migration/MigrationRunner.js';
5
6
  import Output from '../../Console/Output.js';
6
7
 
7
8
  export default async function status() {
8
9
  dotenv.config({ quiet: true });
10
+ Environment.setDev(true);
9
11
  await Config.initialize();
10
12
 
11
13
  try {
@@ -1,10 +1,12 @@
1
1
  import dotenv from 'dotenv';
2
2
  import DB from '../../Database/DB.js';
3
3
  import Config from '../../Core/Config.js';
4
+ import Environment from '../../Core/Environment.js';
4
5
  import SeederRunner from '../../Database/Seeder/SeederRunner.js';
5
6
 
6
7
  export default async function seed(seederName = null) {
7
8
  dotenv.config({ quiet: true });
9
+ Environment.setDev(true);
8
10
  await Config.initialize();
9
11
 
10
12
  try {
@@ -4,12 +4,37 @@ import * as ReactDOMClient from 'react-dom/client';
4
4
  import * as OriginalJsx from 'react/jsx-runtime';
5
5
  import RefreshRuntime from 'react-refresh/runtime';
6
6
 
7
+ // Filter React 19 key warnings from third-party libraries
8
+ const originalError = console.error;
9
+ console.error = function(...args: any[]) {
10
+ const msg = args[0];
11
+ if (typeof msg === 'string' && (
12
+ msg.includes('A props object containing a "key" prop is being spread into JSX') ||
13
+ msg.includes('`key` is not a prop')
14
+ )) {
15
+ return;
16
+ }
17
+ return originalError.apply(console, args);
18
+ };
19
+
20
+ // Extracts key from props without triggering React 19's getter warning
7
21
  function extractKey(props: any, key: any): [any, any] {
8
- if (props == null || !('key' in props)) {
9
- return [props, key];
22
+ if (props == null) return [props, key];
23
+ if (!Object.prototype.hasOwnProperty.call(props, 'key')) return [props, key];
24
+
25
+ const cleanProps: any = {};
26
+ let extractedKey = key;
27
+
28
+ for (const k of Object.keys(props)) {
29
+ if (k === 'key') {
30
+ if (extractedKey == null) extractedKey = props[k];
31
+ }
32
+ else {
33
+ cleanProps[k] = props[k];
34
+ }
10
35
  }
11
- const { key: propKey, ...rest } = props;
12
- return [rest, key ?? propKey];
36
+
37
+ return [cleanProps, extractedKey];
13
38
  }
14
39
 
15
40
  function jsx(type: any, props: any, key?: any) {
@@ -3,12 +3,23 @@ import * as ReactDOM from 'react-dom';
3
3
  import * as ReactDOMClient from 'react-dom/client';
4
4
  import * as OriginalJsx from 'react/jsx-runtime';
5
5
 
6
+ // Extracts key from props without triggering React 19's getter warning
6
7
  function extractKey(props: any, key: any): [any, any] {
7
- if (props == null || !('key' in props)) {
8
- return [props, key];
8
+ if (props == null) return [props, key];
9
+ if (!Object.prototype.hasOwnProperty.call(props, 'key')) return [props, key];
10
+
11
+ const cleanProps: any = {};
12
+ let extractedKey = key;
13
+
14
+ for (const k of Object.keys(props)) {
15
+ if (k === 'key') {
16
+ if (extractedKey == null) extractedKey = props[k];
17
+ } else {
18
+ cleanProps[k] = props[k];
19
+ }
9
20
  }
10
- const { key: propKey, ...rest } = props;
11
- return [rest, key ?? propKey];
21
+
22
+ return [cleanProps, extractedKey];
12
23
  }
13
24
 
14
25
  function jsx(type: any, props: any, key?: any) {
@@ -154,13 +154,17 @@ class MySQLDriver {
154
154
  #handleError(error, sql, bindings = null) {
155
155
  if (Environment.isProd) {
156
156
  console.error("[MySQLDriver] Query failed", {
157
+ message: error.message,
157
158
  code: error.code,
158
159
  errno: error.errno,
159
- sqlState: error.sqlState
160
+ sqlState: error.sqlState,
161
+ sql: sql?.substring(0, 200)
160
162
  });
161
163
 
162
164
  const sanitized = new Error("Database query failed");
163
165
  sanitized.code = error.code;
166
+ sanitized.errno = error.errno;
167
+ sanitized.sqlState = error.sqlState;
164
168
  sanitized.driver = "mysql";
165
169
 
166
170
  return sanitized;
@@ -50,6 +50,8 @@ class MigrationRunner {
50
50
  Output.frameworkMigration('done', file);
51
51
  }
52
52
 
53
+ Output.newline();
54
+
53
55
  return { success: true, ran };
54
56
  }
55
57
 
@@ -247,4 +247,24 @@ function hydrate(modelClass, row) {
247
247
  return instance;
248
248
  }
249
249
 
250
+ // ─────────────────────────────────────────────────────────────────────────────
251
+ // QueryBuilder Method Forwarding
252
+ // Automatically forward all QueryBuilder methods to Model for fluent chaining
253
+ // ─────────────────────────────────────────────────────────────────────────────
254
+
255
+ const QUERY_METHODS = [
256
+ // Chain methods (return QueryBuilder)
257
+ 'distinct', 'orWhere', 'whereIn', 'whereNotIn',
258
+ 'whereBetween', 'whereNot', 'join', 'groupBy', 'offset',
259
+ // Terminal methods (return results)
260
+ 'count', 'max', 'min', 'sum', 'avg', 'delete'
261
+ ];
262
+
263
+ for (const method of QUERY_METHODS) {
264
+ Model[method] = function(...args) {
265
+ ensureTable(this);
266
+ return DB.table(this.table, null, this)[method](...args);
267
+ };
268
+ }
269
+
250
270
  export default Model;
@@ -135,8 +135,10 @@ class QueryBuilder {
135
135
  message: error.message,
136
136
  code: error.code,
137
137
  errno: error.errno,
138
- sql: error.sql?.substring(0, 100),
139
- sqlState: error.sqlState
138
+ sqlState: error.sqlState,
139
+ sql: error.sql?.substring(0, 200),
140
+ bindings: error.bindings,
141
+ driver: error.driver
140
142
  });
141
143
 
142
144
  const sanitized = new Error('Database query failed. Please contact support if the problem persists.');
@@ -297,24 +299,14 @@ class QueryBuilder {
297
299
  return this;
298
300
  }
299
301
 
300
- whereNull(column) {
301
- this.#wheres.push({
302
- type: 'null',
303
- column: this.#validateIdentifier(column),
304
- boolean: 'AND'
305
- });
306
-
307
- return this;
308
- }
309
-
310
- whereNotNull(column) {
311
- this.#wheres.push({
312
- type: 'notNull',
313
- column: this.#validateIdentifier(column),
314
- boolean: 'AND'
315
- });
316
-
317
- return this;
302
+ /**
303
+ * Add a WHERE NOT (!=) clause.
304
+ * @param {string} column - Column name
305
+ * @param {*} value - Value to compare
306
+ * @returns {QueryBuilder}
307
+ */
308
+ whereNot(column, value) {
309
+ return this.where(column, '!=', value);
318
310
  }
319
311
 
320
312
  whereBetween(column, values) {
@@ -334,22 +326,6 @@ class QueryBuilder {
334
326
  return this;
335
327
  }
336
328
 
337
- whereRaw(sql, bindings = []) {
338
- if (typeof sql !== 'string' || sql.length === 0) {
339
- throw new Error('whereRaw requires non-empty SQL string');
340
- }
341
-
342
- this.#wheres.push({
343
- type: 'raw',
344
- sql,
345
- boolean: 'AND'
346
- });
347
-
348
- this.#bindings.push(...bindings);
349
-
350
- return this;
351
- }
352
-
353
329
  join(table, first, operator, second, type = 'inner') {
354
330
  if (arguments.length === 3) {
355
331
  second = operator;
@@ -370,14 +346,6 @@ class QueryBuilder {
370
346
  return this;
371
347
  }
372
348
 
373
- leftJoin(table, first, operator, second) {
374
- return this.join(table, first, operator, second, 'left');
375
- }
376
-
377
- rightJoin(table, first, operator, second) {
378
- return this.join(table, first, operator, second, 'right');
379
- }
380
-
381
349
  orderBy(column, direction = 'ASC') {
382
350
  this.#orders.push({
383
351
  column: this.#validateIdentifier(column),
@@ -394,25 +362,6 @@ class QueryBuilder {
394
362
  return this;
395
363
  }
396
364
 
397
- having(column, operator, value) {
398
- if (arguments.length === 2) {
399
- value = operator;
400
- operator = '=';
401
- }
402
-
403
- operator = this.#validateWhereOperator(operator);
404
-
405
- this.#havings.push({
406
- column: this.#validateIdentifier(column),
407
- operator,
408
- value
409
- });
410
- this.#bindings.push(value);
411
-
412
-
413
- return this;
414
- }
415
-
416
365
  limit(value) {
417
366
  this.#limitValue = this.#validateInteger(value);
418
367
 
@@ -425,10 +374,6 @@ class QueryBuilder {
425
374
  return this;
426
375
  }
427
376
 
428
- forPage(page, perPage = 15) {
429
- return this.offset((page - 1) * perPage).limit(perPage);
430
- }
431
-
432
377
  async get() {
433
378
  const connection = await this.#getConnection();
434
379
 
@@ -740,13 +685,6 @@ class QueryBuilder {
740
685
  return sql;
741
686
  }
742
687
 
743
- toSql() {
744
- return this.#toSql();
745
- }
746
-
747
- getBindings() {
748
- return [...this.#bindings];
749
- }
750
688
  }
751
689
 
752
690
  export class RawExpression {
@@ -65,6 +65,25 @@ class Storage {
65
65
  await fs.promises.unlink(fullPath);
66
66
  }
67
67
 
68
+ /**
69
+ * Moves or renames a file in storage.
70
+ * @param {string} fromPath - Current relative path to the file
71
+ * @param {string} toPath - New relative path for the file
72
+ * @param {boolean} isPrivate - Whether to operate on private storage
73
+ * @returns {Promise<void>}
74
+ */
75
+ static async move(fromPath, toPath, isPrivate = false) {
76
+ const base = isPrivate ? this.#privateRoot : this.#publicRoot;
77
+ const fullFromPath = this.#validatePath(base, fromPath);
78
+ const fullToPath = this.#validatePath(base, toPath);
79
+
80
+ // Ensure target directory exists
81
+ const targetDir = path.dirname(fullToPath);
82
+ await fs.promises.mkdir(targetDir, { recursive: true });
83
+
84
+ await fs.promises.rename(fullFromPath, fullToPath);
85
+ }
86
+
68
87
  /**
69
88
  * Checks if a file exists in storage.
70
89
  * @param {string} filePath - Relative path to the file
@@ -171,6 +171,8 @@ class Server {
171
171
  for (const [key, field] of Object.entries(req.body)) {
172
172
  if (field?.toBuffer) {
173
173
  if (field.filename) {
174
+ const lastDot = field.filename.lastIndexOf(".");
175
+ field.extension = lastDot > 0 ? field.filename.slice(lastDot + 1).toLowerCase() : "";
174
176
  files[key] = field;
175
177
  }
176
178
  }
@@ -6,6 +6,28 @@
6
6
  var layouts = [];
7
7
  var navigating = false;
8
8
 
9
+ // Route helper function for client-side
10
+ globalThis.route = function(name, params) {
11
+ var runtime = window.__NITRON_RUNTIME__;
12
+ if (!runtime || !runtime.routes) {
13
+ console.error("Route runtime not initialized");
14
+ return "";
15
+ }
16
+
17
+ var pattern = runtime.routes[name];
18
+ if (!pattern) {
19
+ console.error('Route "' + name + '" not found');
20
+ return "";
21
+ }
22
+
23
+ if (!params) return pattern;
24
+
25
+ // Replace :param tokens with actual values
26
+ return pattern.replace(/:(\w+)/g, function(match, key) {
27
+ return params[key] !== undefined ? params[key] : match;
28
+ });
29
+ };
30
+
9
31
  function init() {
10
32
  var rt = window.__NITRON_RUNTIME__;
11
33
  if (rt && rt.layouts) layouts = rt.layouts;
package/lib/View/View.js CHANGED
@@ -371,7 +371,7 @@ class View {
371
371
  throw error;
372
372
  }
373
373
 
374
- const mergedMeta = this.#mergeMeta(layoutModules, mod.Meta);
374
+ const mergedMeta = this.#mergeMeta(layoutModules, mod.Meta, params);
375
375
 
376
376
  return {
377
377
  html,
@@ -381,30 +381,36 @@ class View {
381
381
  };
382
382
  }
383
383
 
384
- static #mergeMeta(layoutModules, viewMeta) {
384
+ static #mergeMeta(layoutModules, viewMeta, params = {}) {
385
385
  const result = {};
386
386
 
387
387
  for (const layoutMod of layoutModules) {
388
388
  if (layoutMod.Meta) {
389
- for (const key of Object.keys(layoutMod.Meta)) {
389
+ const resolvedLayoutMeta = typeof layoutMod.Meta === "function"
390
+ ? layoutMod.Meta(params)
391
+ : layoutMod.Meta;
392
+ for (const key of Object.keys(resolvedLayoutMeta)) {
390
393
  if (!(key in result)) {
391
- result[key] = layoutMod.Meta[key];
394
+ result[key] = resolvedLayoutMeta[key];
392
395
  }
393
396
  }
394
397
  }
395
398
  }
396
399
 
397
400
  if (viewMeta) {
398
- for (const key of Object.keys(viewMeta)) {
401
+ const resolvedViewMeta = typeof viewMeta === "function"
402
+ ? viewMeta(params)
403
+ : viewMeta;
404
+ for (const key of Object.keys(resolvedViewMeta)) {
399
405
  if (key === "title" && result.title?.template) {
400
- const viewTitle = typeof viewMeta.title === "object"
401
- ? viewMeta.title.default
402
- : viewMeta.title;
406
+ const viewTitle = typeof resolvedViewMeta.title === "object"
407
+ ? resolvedViewMeta.title.default
408
+ : resolvedViewMeta.title;
403
409
  if (viewTitle) {
404
410
  result.title = result.title.template.replace("%s", viewTitle);
405
411
  }
406
412
  } else {
407
- result[key] = viewMeta[key];
413
+ result[key] = resolvedViewMeta[key];
408
414
  }
409
415
  }
410
416
  }
@@ -648,8 +654,16 @@ class View {
648
654
  }
649
655
 
650
656
  const content = readFileSync(fullPath, "utf8");
651
- const matches = [...content.matchAll(/routes\["([^"]+)"\]/g)];
652
- const routes = matches.map(match => match[1]);
657
+
658
+ // Match both old format (routes["name"]) and new format (route("name"))
659
+ const oldMatches = [...content.matchAll(/routes\["([^"]+)"\]/g)];
660
+ const newMatches = [...content.matchAll(/route\s*\(\s*['"]([^'"]+)['"]/g)];
661
+
662
+ const routes = [
663
+ ...oldMatches.map(match => match[1]),
664
+ ...newMatches.map(match => match[1])
665
+ ];
666
+
653
667
  const relativePath = "/js/" + path.relative(jsDir, fullPath).replace(/\\/g, "/");
654
668
 
655
669
  this.#routesCache.set(relativePath, [...new Set(routes)]);
package/lib/index.d.ts CHANGED
@@ -1,15 +1,51 @@
1
1
  export class Storage {
2
- static url(path: string): string;
3
- static path(path: string): string;
4
- static disk(name: string): Storage;
5
- static get(path: string): Promise<Buffer>;
6
- static put(path: string, contents: string | Buffer): Promise<void>;
7
- static delete(path: string): Promise<boolean>;
8
- static exists(path: string): Promise<boolean>;
9
- static copy(from: string, to: string): Promise<void>;
10
- static move(from: string, to: string): Promise<void>;
11
- static files(directory: string): Promise<string[]>;
12
- static directories(directory: string): Promise<string[]>;
2
+ /**
3
+ * Reads a file from storage.
4
+ * @param filePath - Relative path to the file
5
+ * @param isPrivate - Whether to read from private storage (default: false)
6
+ * @returns File contents or null if not found
7
+ */
8
+ static get(filePath: string, isPrivate?: boolean): Promise<Buffer | null>;
9
+
10
+ /**
11
+ * Saves a file to storage.
12
+ * @param file - File object from multipart upload
13
+ * @param dir - Directory path within storage
14
+ * @param fileName - Name for the saved file
15
+ * @param isPrivate - Whether to save to private storage (default: false)
16
+ * @returns True if save successful
17
+ */
18
+ static put(file: { _buf: Buffer }, dir: string, fileName: string, isPrivate?: boolean): Promise<boolean>;
19
+
20
+ /**
21
+ * Deletes a file from storage.
22
+ * @param filePath - Relative path to the file
23
+ * @param isPrivate - Whether to delete from private storage (default: false)
24
+ */
25
+ static delete(filePath: string, isPrivate?: boolean): Promise<void>;
26
+
27
+ /**
28
+ * Moves or renames a file in storage.
29
+ * @param fromPath - Current relative path to the file
30
+ * @param toPath - New relative path for the file
31
+ * @param isPrivate - Whether to operate on private storage (default: false)
32
+ */
33
+ static move(fromPath: string, toPath: string, isPrivate?: boolean): Promise<void>;
34
+
35
+ /**
36
+ * Checks if a file exists in storage.
37
+ * @param filePath - Relative path to the file
38
+ * @param isPrivate - Whether to check private storage (default: false)
39
+ * @returns True if file exists
40
+ */
41
+ static exists(filePath: string, isPrivate?: boolean): boolean;
42
+
43
+ /**
44
+ * Gets the public URL for a file in public storage.
45
+ * @param filePath - Relative path to the file
46
+ * @returns Public URL path
47
+ */
48
+ static url(filePath: string): string;
13
49
  }
14
50
 
15
51
  export class Model {
@@ -20,9 +56,8 @@ export class Model {
20
56
  static hidden: string[];
21
57
  static casts: Record<string, string>;
22
58
 
23
- static all<T extends Model>(this: new () => T): Promise<T[]>;
59
+ static get<T extends Model>(this: new () => T): Promise<T[]>;
24
60
  static find<T extends Model>(this: new () => T, id: number | string): Promise<T | null>;
25
- static findOrFail<T extends Model>(this: new () => T, id: number | string): Promise<T>;
26
61
  static first<T extends Model>(this: new () => T): Promise<T | null>;
27
62
  static where<T extends Model>(this: new () => T, column: string, operator?: any, value?: any): QueryBuilder<T>;
28
63
  static select<T extends Model>(this: new () => T, ...columns: string[]): QueryBuilder<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitronjs/framework",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "NitronJS is a modern and extensible Node.js MVC framework built on Fastify. It focuses on clean architecture, modular structure, and developer productivity, offering built-in routing, middleware, configuration management, CLI tooling, and native React integration for scalable full-stack applications.",
5
5
  "bin": {
6
6
  "njs": "./cli/njs.js"