@nitronjs/framework 0.2.3 → 0.2.5

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 (68) hide show
  1. package/README.md +3 -1
  2. package/cli/create.js +88 -72
  3. package/cli/njs.js +14 -7
  4. package/lib/Auth/Auth.js +167 -0
  5. package/lib/Build/CssBuilder.js +9 -0
  6. package/lib/Build/FileAnalyzer.js +16 -0
  7. package/lib/Build/HydrationBuilder.js +17 -0
  8. package/lib/Build/Manager.js +15 -0
  9. package/lib/Build/colors.js +4 -0
  10. package/lib/Build/plugins.js +84 -20
  11. package/lib/Console/Commands/DevCommand.js +13 -9
  12. package/lib/Console/Commands/MakeCommand.js +24 -10
  13. package/lib/Console/Commands/MigrateCommand.js +0 -1
  14. package/lib/Console/Commands/MigrateFreshCommand.js +17 -25
  15. package/lib/Console/Commands/MigrateRollbackCommand.js +6 -3
  16. package/lib/Console/Commands/MigrateStatusCommand.js +6 -3
  17. package/lib/Console/Commands/SeedCommand.js +4 -2
  18. package/lib/Console/Commands/StorageLinkCommand.js +20 -5
  19. package/lib/Console/Output.js +142 -0
  20. package/lib/Core/Config.js +2 -1
  21. package/lib/Core/Paths.js +8 -0
  22. package/lib/Database/DB.js +141 -51
  23. package/lib/Database/Drivers/MySQLDriver.js +102 -157
  24. package/lib/Database/Migration/Checksum.js +3 -8
  25. package/lib/Database/Migration/MigrationRepository.js +25 -35
  26. package/lib/Database/Migration/MigrationRunner.js +56 -61
  27. package/lib/Database/Model.js +157 -83
  28. package/lib/Database/QueryBuilder.js +31 -0
  29. package/lib/Database/QueryValidation.js +36 -44
  30. package/lib/Database/Schema/Blueprint.js +25 -36
  31. package/lib/Database/Schema/Manager.js +31 -68
  32. package/lib/Database/Seeder/SeederRunner.js +12 -31
  33. package/lib/Date/DateTime.js +9 -0
  34. package/lib/Encryption/Encryption.js +52 -0
  35. package/lib/Faker/Faker.js +11 -0
  36. package/lib/Filesystem/Storage.js +120 -0
  37. package/lib/HMR/Server.js +81 -10
  38. package/lib/Hashing/Hash.js +41 -0
  39. package/lib/Http/Server.js +177 -152
  40. package/lib/Logging/{Manager.js → Log.js} +68 -80
  41. package/lib/Mail/Mail.js +187 -0
  42. package/lib/Route/Router.js +416 -0
  43. package/lib/Session/File.js +135 -233
  44. package/lib/Session/Manager.js +117 -171
  45. package/lib/Session/Memory.js +28 -38
  46. package/lib/Session/Session.js +71 -107
  47. package/lib/Support/Str.js +103 -0
  48. package/lib/Translation/Lang.js +54 -0
  49. package/lib/View/Client/hmr-client.js +94 -51
  50. package/lib/View/Client/nitronjs-icon.png +0 -0
  51. package/lib/View/{Manager.js → View.js} +44 -29
  52. package/lib/index.d.ts +42 -8
  53. package/lib/index.js +19 -12
  54. package/package.json +1 -1
  55. package/skeleton/app/Controllers/HomeController.js +7 -1
  56. package/skeleton/resources/css/global.css +1 -0
  57. package/skeleton/resources/views/Site/Home.tsx +456 -79
  58. package/skeleton/tsconfig.json +6 -1
  59. package/lib/Auth/Manager.js +0 -111
  60. package/lib/Database/Connection.js +0 -61
  61. package/lib/Database/Manager.js +0 -162
  62. package/lib/Encryption/Manager.js +0 -47
  63. package/lib/Filesystem/Manager.js +0 -74
  64. package/lib/Hashing/Manager.js +0 -25
  65. package/lib/Mail/Manager.js +0 -120
  66. package/lib/Route/Loader.js +0 -80
  67. package/lib/Route/Manager.js +0 -286
  68. package/lib/Translation/Manager.js +0 -49
@@ -1,29 +1,60 @@
1
1
  import DB from './DB.js';
2
2
 
3
+ /**
4
+ * Base model class for database entities with Active Record pattern.
5
+ * Provides CRUD operations and query builder integration.
6
+ *
7
+ * @example
8
+ * class User extends Model {
9
+ * static table = "users";
10
+ * }
11
+ *
12
+ * const user = await User.find(1);
13
+ * user.name = "John";
14
+ * await user.save();
15
+ */
3
16
  class Model {
17
+ /** @type {string|null} Database table name - must be defined in subclass */
4
18
  static table = null;
5
19
 
20
+ /**
21
+ * Creates a new model instance with attribute proxy support.
22
+ * @param {Object} attrs - Initial attributes
23
+ */
6
24
  constructor(attrs = {}) {
7
25
  Object.defineProperty(this, '_attributes', { value: {}, writable: true });
8
26
  Object.defineProperty(this, '_original', { value: {}, writable: true });
9
27
  Object.defineProperty(this, '_exists', { value: false, writable: true });
10
-
28
+
11
29
  Object.assign(this._attributes, attrs);
12
30
  this._original = { ...this._attributes };
13
-
31
+
14
32
  return new Proxy(this, {
15
- get(target, prop) {
16
- if (prop.startsWith('_') || typeof target[prop] === 'function' || prop === 'constructor') {
33
+ get: (target, prop) => {
34
+ if (typeof prop === 'symbol' || prop === 'constructor') {
35
+ return target[prop];
36
+ }
37
+
38
+ if (prop.startsWith('_')) {
17
39
  return target[prop];
18
40
  }
41
+
42
+ if (prop in target && typeof target[prop] === 'function') {
43
+ return target[prop].bind(target);
44
+ }
45
+
19
46
  return target._attributes[prop];
20
47
  },
21
- set(target, prop, value) {
22
- if (prop.startsWith('_')) {
48
+ set: (target, prop, value) => {
49
+ if (typeof prop === 'symbol' || prop.startsWith('_')) {
23
50
  target[prop] = value;
51
+
24
52
  return true;
25
53
  }
26
- if (prop in target) return false;
54
+
55
+ if (prop in target) {
56
+ return false;
57
+ }
27
58
 
28
59
  target._attributes[prop] = value;
29
60
 
@@ -32,145 +63,188 @@ class Model {
32
63
  });
33
64
  }
34
65
 
66
+ // ─────────────────────────────────────────────────────────────────────────────
67
+ // Static Query Methods
68
+ // ─────────────────────────────────────────────────────────────────────────────
69
+
70
+ /**
71
+ * Get all records from the table.
72
+ * @returns {Promise<Model[]>}
73
+ */
35
74
  static async get() {
36
- if (!this.table) {
37
- throw new Error(`Model ${this.name} must define a static 'table' property`);
38
- }
75
+ ensureTable(this);
39
76
 
40
77
  const rows = await DB.table(this.table).get();
41
78
 
42
- return rows.map(row => {
43
- const instance = new this();
44
-
45
- for (const [key, value] of Object.entries(row)) {
46
- if (value instanceof Date) {
47
- instance._attributes[key] = value.toISOString().slice(0, 19).replace('T', ' ');
48
- } else {
49
- instance._attributes[key] = value;
50
- }
51
- }
52
-
53
- instance._exists = true;
54
- instance._original = { ...instance._attributes };
55
-
56
- return instance;
57
- });
79
+ return rows.map(row => hydrate(this, row));
58
80
  }
59
81
 
82
+ /**
83
+ * Find a record by ID.
84
+ * @param {number|string} id - Record ID
85
+ * @returns {Promise<Model|null>}
86
+ */
60
87
  static async find(id) {
61
- if (!this.table) {
62
- throw new Error(`Model ${this.name} must define a static 'table' property`);
63
- }
88
+ ensureTable(this);
64
89
 
65
- const row = await DB.table(this.table).where("id", id).first();
90
+ const row = await DB.table(this.table).where('id', id).first();
66
91
 
67
- if (!row) return null;
92
+ return row ? hydrate(this, row) : null;
93
+ }
68
94
 
69
- const instance = new this();
70
-
71
- for (const [key, value] of Object.entries(row)) {
72
- if (value instanceof Date) {
73
- instance._attributes[key] = value.toISOString().slice(0, 19).replace('T', ' ');
74
- } else {
75
- instance._attributes[key] = value;
76
- }
77
- }
78
-
79
- instance._exists = true;
80
- instance._original = { ...instance._attributes };
81
-
82
- return instance;
95
+ /**
96
+ * Get the first record from the table.
97
+ * @returns {Promise<Model|null>}
98
+ */
99
+ static async first() {
100
+ ensureTable(this);
101
+
102
+ const row = await DB.table(this.table).first();
103
+
104
+ return row ? hydrate(this, row) : null;
83
105
  }
84
106
 
107
+ /**
108
+ * Add a WHERE clause to the query.
109
+ * @param {string|Object} column - Column name or conditions object
110
+ * @param {string} [operator] - Comparison operator or value (if 2 args)
111
+ * @param {*} [value] - Value to compare
112
+ * @returns {import('./QueryBuilder.js').default}
113
+ */
85
114
  static where(column, operator, value) {
86
- if (!this.table) {
87
- throw new Error(`Model ${this.name} must define a static 'table' property`);
115
+ ensureTable(this);
116
+
117
+ if (arguments.length === 2) {
118
+ return DB.table(this.table, null, this).where(column, operator);
88
119
  }
89
120
 
90
121
  return DB.table(this.table, null, this).where(column, operator, value);
91
122
  }
92
123
 
124
+ /**
125
+ * Select specific columns.
126
+ * @param {...string} columns - Column names
127
+ * @returns {import('./QueryBuilder.js').default}
128
+ */
93
129
  static select(...columns) {
94
- if (!this.table) {
95
- throw new Error(`Model ${this.name} must define a static 'table' property`);
96
- }
130
+ ensureTable(this);
97
131
 
98
132
  return DB.table(this.table, null, this).select(...columns);
99
133
  }
100
134
 
135
+ /**
136
+ * Order results by column.
137
+ * @param {string} column - Column name
138
+ * @param {'ASC'|'DESC'} [direction='ASC'] - Sort direction
139
+ * @returns {import('./QueryBuilder.js').default}
140
+ */
101
141
  static orderBy(column, direction = 'ASC') {
102
- if (!this.table) {
103
- throw new Error(`Model ${this.name} must define a static 'table' property`);
104
- }
142
+ ensureTable(this);
105
143
 
106
144
  return DB.table(this.table, null, this).orderBy(column, direction);
107
145
  }
108
146
 
147
+ /**
148
+ * Limit the number of results.
149
+ * @param {number} value - Maximum records to return
150
+ * @returns {import('./QueryBuilder.js').default}
151
+ */
109
152
  static limit(value) {
110
- if (!this.table) {
111
- throw new Error(`Model ${this.name} must define a static 'table' property`);
112
- }
153
+ ensureTable(this);
113
154
 
114
155
  return DB.table(this.table, null, this).limit(value);
115
156
  }
116
157
 
117
- static async first() {
118
- if (!this.table) {
119
- throw new Error(`Model ${this.name} must define a static 'table' property`);
120
- }
121
-
122
- return await DB.table(this.table, null, this).limit(1).first();
123
- }
158
+ // ─────────────────────────────────────────────────────────────────────────────
159
+ // Instance Methods
160
+ // ─────────────────────────────────────────────────────────────────────────────
124
161
 
162
+ /**
163
+ * Save the model (insert or update).
164
+ * @returns {Promise<Model>}
165
+ */
125
166
  async save() {
126
- const constructor = this.constructor;
167
+ const table = this.constructor.table;
127
168
  const data = {};
128
169
 
129
170
  for (const [key, value] of Object.entries(this._attributes)) {
130
- if (value !== undefined && (key !== "id" || !this._exists)) {
171
+ if (value !== undefined && (key !== 'id' || !this._exists)) {
131
172
  data[key] = value;
132
173
  }
133
174
  }
134
175
 
135
176
  if (this._exists) {
136
- const primaryKeyValue = this._attributes["id"];
137
- await DB.table(constructor.table)
138
- .where("id", primaryKeyValue)
139
- .update(data);
140
-
141
- Object.assign(this._attributes, data);
142
- this._original = { ...this._attributes };
177
+ await DB.table(table).where('id', this._attributes.id).update(data);
143
178
  }
144
179
  else {
145
- const id = await DB.table(constructor.table).insert(data);
146
- this._attributes["id"] = id;
147
-
148
- this._original = { ...this._attributes };
180
+ const id = await DB.table(table).insert(data);
181
+ this._attributes.id = id;
149
182
  this._exists = true;
150
183
  }
151
184
 
185
+ this._original = { ...this._attributes };
186
+
152
187
  return this;
153
188
  }
154
189
 
190
+ /**
191
+ * Delete the model from database.
192
+ * @returns {Promise<boolean>}
193
+ * @throws {Error} If model doesn't exist in database
194
+ */
155
195
  async delete() {
156
- const constructor = this.constructor;
157
- const primaryKeyValue = this._attributes["id"];
158
-
159
196
  if (!this._exists) {
160
197
  throw new Error('Cannot delete a model that does not exist');
161
198
  }
162
199
 
163
- await DB.table(constructor.table)
164
- .where("id", primaryKeyValue)
165
- .delete();
166
-
200
+ await DB.table(this.constructor.table).where('id', this._attributes.id).delete();
167
201
  this._exists = false;
202
+
168
203
  return true;
169
204
  }
170
205
 
206
+ /**
207
+ * Convert model attributes to plain object.
208
+ * @returns {Object}
209
+ */
171
210
  toObject() {
172
211
  return { ...this._attributes };
173
212
  }
174
213
  }
175
214
 
215
+ // ─────────────────────────────────────────────────────────────────────────────
216
+ // Private Helper Functions (module-scoped to avoid ES6 static inheritance issues)
217
+ // ─────────────────────────────────────────────────────────────────────────────
218
+
219
+ /**
220
+ * Validates that table property is defined on the model class.
221
+ * @param {typeof Model} modelClass
222
+ */
223
+ function ensureTable(modelClass) {
224
+ if (!modelClass.table) {
225
+ throw new Error(`Model ${modelClass.name} must define a static 'table' property`);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Creates a model instance from a database row.
231
+ * @param {typeof Model} modelClass
232
+ * @param {Object} row
233
+ * @returns {Model}
234
+ */
235
+ function hydrate(modelClass, row) {
236
+ const instance = new modelClass();
237
+
238
+ for (const [key, value] of Object.entries(row)) {
239
+ instance._attributes[key] = value instanceof Date
240
+ ? value.toISOString().slice(0, 19).replace('T', ' ')
241
+ : value;
242
+ }
243
+
244
+ instance._exists = true;
245
+ instance._original = { ...instance._attributes };
246
+
247
+ return instance;
248
+ }
249
+
176
250
  export default Model;
@@ -1,6 +1,17 @@
1
1
  import { validateDirection, validateIdentifier, validateWhereOperator } from "./QueryValidation.js";
2
2
  import Environment from "../Core/Environment.js";
3
3
 
4
+ /**
5
+ * SQL query builder with fluent interface.
6
+ * Provides chainable methods for building safe parameterized queries.
7
+ *
8
+ * @example
9
+ * const users = await DB.table("users")
10
+ * .where("status", "active")
11
+ * .orderBy("created_at", "DESC")
12
+ * .limit(10)
13
+ * .get();
14
+ */
4
15
  class QueryBuilder {
5
16
  #table = null;
6
17
  #connection = null;
@@ -483,6 +494,26 @@ class QueryBuilder {
483
494
  return parseInt(result) || 0;
484
495
  }
485
496
 
497
+ async max(column) {
498
+ return await this.#aggregate('MAX', column);
499
+ }
500
+
501
+ async min(column) {
502
+ return await this.#aggregate('MIN', column);
503
+ }
504
+
505
+ async sum(column) {
506
+ const result = await this.#aggregate('SUM', column);
507
+
508
+ return parseFloat(result) || 0;
509
+ }
510
+
511
+ async avg(column) {
512
+ const result = await this.#aggregate('AVG', column);
513
+
514
+ return parseFloat(result) || 0;
515
+ }
516
+
486
517
  async #aggregate(func, column) {
487
518
  const alias = func.toLowerCase();
488
519
  const originalSelect = this.#selectColumns;
@@ -1,7 +1,7 @@
1
1
  const RESERVED_KEYWORDS = new Set([
2
- 'order', 'key', 'group', 'index', 'table', 'column', 'select', 'insert',
3
- 'update', 'delete', 'from', 'where', 'join', 'left', 'right', 'inner',
4
- 'outer', 'on', 'and', 'or', 'not', 'null', 'true', 'false', 'like',
2
+ 'order', 'key', 'group', 'index', 'table', 'column', 'select', 'insert',
3
+ 'update', 'delete', 'from', 'where', 'join', 'left', 'right', 'inner',
4
+ 'outer', 'on', 'and', 'or', 'not', 'null', 'true', 'false', 'like',
5
5
  'in', 'between', 'is', 'as', 'by', 'asc', 'desc', 'limit', 'offset',
6
6
  'having', 'distinct', 'all', 'any', 'exists', 'case', 'when', 'then',
7
7
  'else', 'end', 'if', 'into', 'values', 'set', 'create', 'drop', 'alter',
@@ -12,72 +12,57 @@ const RESERVED_KEYWORDS = new Set([
12
12
  'range', 'rows', 'rank', 'row', 'status', 'type', 'level', 'value', 'name'
13
13
  ]);
14
14
 
15
- function escapeIdentifier(identifier) {
16
- if (RESERVED_KEYWORDS.has(identifier.toLowerCase())) {
17
- return `\`${identifier}\``;
18
- }
19
- return identifier;
20
- }
15
+ const ALLOWED_WHERE_OPERATORS = new Set([
16
+ '=', '!=', '<>', '<', '>', '<=', '>=',
17
+ 'LIKE', 'NOT LIKE', 'ILIKE', 'NOT ILIKE',
18
+ 'IN', 'NOT IN', 'IS', 'IS NOT',
19
+ 'BETWEEN', 'NOT BETWEEN',
20
+ 'REGEXP', 'NOT REGEXP', 'RLIKE', 'NOT RLIKE'
21
+ ]);
22
+
23
+ const IDENTIFIER_PATTERNS = [
24
+ /^[a-zA-Z_][a-zA-Z0-9_]*$/,
25
+ /^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*$/,
26
+ /^[a-zA-Z_][a-zA-Z0-9_]*\.\*$/
27
+ ];
28
+
29
+ // ─────────────────────────────────────────────────────────────────────────────
30
+ // Public Functions
31
+ // ─────────────────────────────────────────────────────────────────────────────
21
32
 
22
33
  export function validateIdentifier(identifier) {
23
34
  if (typeof identifier !== 'string' || identifier.length === 0) {
24
35
  throw new Error('Identifier must be a non-empty string');
25
36
  }
26
-
27
37
  if (identifier.length > 64) {
28
38
  throw new Error(`Invalid identifier: "${identifier}". Maximum length is 64 characters.`);
29
39
  }
30
-
31
40
  if (identifier === '*' || identifier === '*.*') {
32
41
  return identifier;
33
42
  }
34
-
35
43
  if (/--|#|\/\*|\*\//.test(identifier)) {
36
44
  throw new Error(`Invalid identifier: "${identifier}". SQL comments are not allowed.`);
37
45
  }
38
46
 
39
- const patterns = [
40
- /^[a-zA-Z_][a-zA-Z0-9_]*$/,
41
- /^[a-zA-Z_][a-zA-Z0-9_]*\.[a-zA-Z_][a-zA-Z0-9_]*$/,
42
- /^[a-zA-Z_][a-zA-Z0-9_]*\.\*$/
43
- ];
44
-
45
- const isValid = patterns.some(pattern => pattern.test(identifier));
46
-
47
- if (!isValid) {
48
- throw new Error(`Invalid identifier: "${identifier}". Must be alphanumeric with underscores, optionally qualified with table name.`);
47
+ if (!IDENTIFIER_PATTERNS.some(p => p.test(identifier))) {
48
+ throw new Error(`Invalid identifier: "${identifier}". Must be alphanumeric with underscores.`);
49
49
  }
50
50
 
51
51
  if (identifier.includes('.')) {
52
52
  const [table, column] = identifier.split('.');
53
- if (column === '*') {
54
- return `${escapeIdentifier(table)}.*`;
55
- }
56
- return `${escapeIdentifier(table)}.${escapeIdentifier(column)}`;
53
+ return column === '*'
54
+ ? `${escapeKeyword(table)}.*`
55
+ : `${escapeKeyword(table)}.${escapeKeyword(column)}`;
57
56
  }
58
57
 
59
- return escapeIdentifier(identifier);
58
+ return escapeKeyword(identifier);
60
59
  }
61
60
 
62
61
  export function validateWhereOperator(operator) {
63
62
  const normalized = String(operator).trim().toUpperCase();
64
- const allowedOperators = new Set([
65
- '=', '!=', '<>', '<', '>', '<=', '>=',
66
- 'LIKE', 'NOT LIKE', 'ILIKE', 'NOT ILIKE',
67
- 'IN', 'NOT IN',
68
- 'IS', 'IS NOT',
69
- 'BETWEEN', 'NOT BETWEEN',
70
- 'REGEXP', 'NOT REGEXP',
71
- 'RLIKE', 'NOT RLIKE'
72
- ]);
73
-
74
- if (!allowedOperators.has(normalized)) {
75
- throw new Error(
76
- `Invalid WHERE operator: '${operator}'. ` +
77
- `Allowed operators: ${[...allowedOperators].join(', ')}`
78
- );
63
+ if (!ALLOWED_WHERE_OPERATORS.has(normalized)) {
64
+ throw new Error(`Invalid WHERE operator: '${operator}'.`);
79
65
  }
80
-
81
66
  return normalized;
82
67
  }
83
68
 
@@ -86,6 +71,13 @@ export function validateDirection(direction) {
86
71
  if (upper !== 'ASC' && upper !== 'DESC') {
87
72
  throw new Error(`Invalid direction: ${direction}. Use 'ASC' or 'DESC'.`);
88
73
  }
89
-
90
74
  return upper;
91
75
  }
76
+
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+ // Private Functions
79
+ // ─────────────────────────────────────────────────────────────────────────────
80
+
81
+ function escapeKeyword(identifier) {
82
+ return RESERVED_KEYWORDS.has(identifier.toLowerCase()) ? `\`${identifier}\`` : identifier;
83
+ }
@@ -6,6 +6,10 @@ class Blueprint {
6
6
  this.#tableName = tableName;
7
7
  }
8
8
 
9
+ // ─────────────────────────────────────────────────────────────────────────
10
+ // Public Methods
11
+ // ─────────────────────────────────────────────────────────────────────────
12
+
9
13
  getTableName() {
10
14
  return this.#tableName;
11
15
  }
@@ -14,43 +18,8 @@ class Blueprint {
14
18
  return this.#columns;
15
19
  }
16
20
 
17
- #addColumn(type, name, options = {}) {
18
- const column = {
19
- name,
20
- type,
21
- ...options,
22
- modifiers: {
23
- nullable: false,
24
- default: null,
25
- unique: false
26
- }
27
- };
28
-
29
- this.#columns.push(column);
30
-
31
- return {
32
- nullable: () => {
33
- column.modifiers.nullable = true;
34
- return this;
35
- },
36
- default: (value) => {
37
- column.modifiers.default = value;
38
- return this;
39
- },
40
- unique: () => {
41
- column.modifiers.unique = true;
42
- return this;
43
- }
44
- };
45
- }
46
-
47
21
  id() {
48
- const column = {
49
- name: 'id',
50
- type: 'id',
51
- modifiers: {}
52
- };
53
- this.#columns.push(column);
22
+ this.#columns.push({ name: 'id', type: 'id', modifiers: {} });
54
23
  return this;
55
24
  }
56
25
 
@@ -81,6 +50,26 @@ class Blueprint {
81
50
  json(name) {
82
51
  return this.#addColumn('json', name);
83
52
  }
53
+
54
+ // ─────────────────────────────────────────────────────────────────────────
55
+ // Private Methods
56
+ // ─────────────────────────────────────────────────────────────────────────
57
+
58
+ #addColumn(type, name, options = {}) {
59
+ const column = {
60
+ name,
61
+ type,
62
+ ...options,
63
+ modifiers: { nullable: false, default: null, unique: false }
64
+ };
65
+ this.#columns.push(column);
66
+
67
+ return {
68
+ nullable: () => { column.modifiers.nullable = true; return this; },
69
+ default: (value) => { column.modifiers.default = value; return this; },
70
+ unique: () => { column.modifiers.unique = true; return this; }
71
+ };
72
+ }
84
73
  }
85
74
 
86
75
  export default Blueprint;