millas 0.2.12-beta → 0.2.12-beta-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 (116) hide show
  1. package/package.json +3 -16
  2. package/src/admin/ActivityLog.js +153 -52
  3. package/src/admin/Admin.js +400 -167
  4. package/src/admin/AdminAuth.js +213 -98
  5. package/src/admin/FormGenerator.js +372 -0
  6. package/src/admin/HookRegistry.js +256 -0
  7. package/src/admin/QueryEngine.js +263 -0
  8. package/src/admin/ViewContext.js +309 -0
  9. package/src/admin/WidgetRegistry.js +406 -0
  10. package/src/admin/index.js +17 -0
  11. package/src/admin/resources/AdminResource.js +383 -97
  12. package/src/admin/static/admin.css +1341 -0
  13. package/src/admin/static/date-picker.css +157 -0
  14. package/src/admin/static/date-picker.js +316 -0
  15. package/src/admin/static/json-editor.css +649 -0
  16. package/src/admin/static/json-editor.js +1429 -0
  17. package/src/admin/static/ui.js +1044 -0
  18. package/src/admin/views/layouts/base.njk +65 -1013
  19. package/src/admin/views/pages/detail.njk +40 -16
  20. package/src/admin/views/pages/form.njk +47 -599
  21. package/src/admin/views/pages/list.njk +145 -62
  22. package/src/admin/views/partials/form-field.njk +53 -0
  23. package/src/admin/views/partials/form-footer.njk +28 -0
  24. package/src/admin/views/partials/form-readonly.njk +114 -0
  25. package/src/admin/views/partials/form-scripts.njk +476 -0
  26. package/src/admin/views/partials/form-widget.njk +296 -0
  27. package/src/admin/views/partials/json-dialog.njk +80 -0
  28. package/src/admin/views/partials/json-editor.njk +37 -0
  29. package/src/admin.zip +0 -0
  30. package/src/auth/Auth.js +31 -10
  31. package/src/auth/AuthController.js +3 -1
  32. package/src/auth/AuthUser.js +119 -0
  33. package/src/cli.js +4 -2
  34. package/src/commands/createsuperuser.js +254 -0
  35. package/src/commands/lang.js +589 -0
  36. package/src/commands/migrate.js +154 -81
  37. package/src/commands/serve.js +82 -110
  38. package/src/container/AppInitializer.js +215 -0
  39. package/src/container/Application.js +278 -253
  40. package/src/container/HttpServer.js +156 -0
  41. package/src/container/MillasApp.js +29 -279
  42. package/src/container/MillasConfig.js +192 -0
  43. package/src/core/admin.js +5 -0
  44. package/src/core/auth.js +9 -0
  45. package/src/core/db.js +9 -0
  46. package/src/core/foundation.js +59 -0
  47. package/src/core/http.js +11 -0
  48. package/src/core/lang.js +1 -0
  49. package/src/core/mail.js +6 -0
  50. package/src/core/queue.js +7 -0
  51. package/src/core/validation.js +29 -0
  52. package/src/facades/Admin.js +1 -1
  53. package/src/facades/Auth.js +22 -39
  54. package/src/facades/Cache.js +21 -10
  55. package/src/facades/Database.js +1 -1
  56. package/src/facades/Events.js +18 -17
  57. package/src/facades/Facade.js +197 -0
  58. package/src/facades/Http.js +42 -45
  59. package/src/facades/Log.js +25 -49
  60. package/src/facades/Mail.js +27 -32
  61. package/src/facades/Queue.js +22 -15
  62. package/src/facades/Storage.js +18 -10
  63. package/src/facades/Url.js +53 -0
  64. package/src/http/HttpClient.js +673 -0
  65. package/src/http/ResponseDispatcher.js +18 -111
  66. package/src/http/UrlGenerator.js +375 -0
  67. package/src/http/WelcomePage.js +273 -0
  68. package/src/http/adapters/ExpressAdapter.js +315 -0
  69. package/src/http/adapters/HttpAdapter.js +168 -0
  70. package/src/http/adapters/index.js +9 -0
  71. package/src/i18n/I18nServiceProvider.js +91 -0
  72. package/src/i18n/Translator.js +635 -0
  73. package/src/i18n/defaults.js +122 -0
  74. package/src/i18n/index.js +164 -0
  75. package/src/i18n/locales/en.js +55 -0
  76. package/src/i18n/locales/sw.js +48 -0
  77. package/src/index.js +5 -144
  78. package/src/logger/formatters/PrettyFormatter.js +103 -57
  79. package/src/logger/internal.js +2 -2
  80. package/src/logger/patchConsole.js +91 -81
  81. package/src/middleware/MiddlewareRegistry.js +62 -82
  82. package/src/migrations/system/0001_users.js +21 -0
  83. package/src/migrations/system/0002_admin_log.js +25 -0
  84. package/src/migrations/system/0003_sessions.js +23 -0
  85. package/src/orm/fields/index.js +210 -188
  86. package/src/orm/migration/DefaultValueParser.js +325 -0
  87. package/src/orm/migration/InteractiveResolver.js +191 -0
  88. package/src/orm/migration/Makemigrations.js +312 -0
  89. package/src/orm/migration/MigrationGraph.js +227 -0
  90. package/src/orm/migration/MigrationRunner.js +202 -108
  91. package/src/orm/migration/MigrationWriter.js +463 -0
  92. package/src/orm/migration/ModelInspector.js +412 -344
  93. package/src/orm/migration/ModelScanner.js +225 -0
  94. package/src/orm/migration/ProjectState.js +213 -0
  95. package/src/orm/migration/RenameDetector.js +175 -0
  96. package/src/orm/migration/SchemaBuilder.js +8 -81
  97. package/src/orm/migration/operations/base.js +57 -0
  98. package/src/orm/migration/operations/column.js +191 -0
  99. package/src/orm/migration/operations/fields.js +252 -0
  100. package/src/orm/migration/operations/index.js +55 -0
  101. package/src/orm/migration/operations/models.js +152 -0
  102. package/src/orm/migration/operations/registry.js +131 -0
  103. package/src/orm/migration/operations/special.js +51 -0
  104. package/src/orm/migration/utils.js +208 -0
  105. package/src/orm/model/Model.js +81 -13
  106. package/src/providers/AdminServiceProvider.js +66 -9
  107. package/src/providers/AuthServiceProvider.js +46 -7
  108. package/src/providers/CacheStorageServiceProvider.js +5 -3
  109. package/src/providers/DatabaseServiceProvider.js +3 -2
  110. package/src/providers/EventServiceProvider.js +2 -1
  111. package/src/providers/LogServiceProvider.js +7 -3
  112. package/src/providers/MailServiceProvider.js +4 -3
  113. package/src/providers/QueueServiceProvider.js +4 -3
  114. package/src/router/Router.js +119 -152
  115. package/src/scaffold/templates.js +83 -26
  116. package/src/facades/Validation.js +0 -69
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { FieldDefinition } = require('../fields');
3
+ const { applyColumn } = require('./operations/column');
4
+ const { normaliseField } = require('./ProjectState');
4
5
 
5
6
  /**
6
7
  * SchemaBuilder
@@ -27,7 +28,7 @@ class SchemaBuilder {
27
28
  const fields = ModelClass.fields;
28
29
 
29
30
  await this._db.schema.createTable(table, (t) => {
30
- this._applyFields(t, fields, ModelClass);
31
+ this._applyFields(t, fields);
31
32
  });
32
33
  }
33
34
 
@@ -87,87 +88,13 @@ class SchemaBuilder {
87
88
 
88
89
  // ─── Internal ─────────────────────────────────────────────────────────────
89
90
 
90
- _applyFields(tableBuilder, fields, ModelClass) {
91
+ // Delegates to operations/column.js applyColumn — single source of truth
92
+ // for the type → knex column builder mapping.
93
+ _applyFields(tableBuilder, fields) {
91
94
  for (const [name, field] of Object.entries(fields)) {
92
- this._applyField(tableBuilder, name, field, ModelClass);
93
- }
94
- }
95
-
96
- _applyField(t, name, field) {
97
- let col;
98
-
99
- switch (field.type) {
100
- case 'id':
101
- t.increments(name);
102
- return;
103
-
104
- case 'string':
105
- col = t.string(name, field.max || 255);
106
- break;
107
-
108
- case 'text':
109
- col = t.text(name);
110
- break;
111
-
112
- case 'integer':
113
- col = field.unsigned ? t.integer(name).unsigned() : t.integer(name);
114
- break;
115
-
116
- case 'bigInteger':
117
- col = field.unsigned ? t.bigInteger(name).unsigned() : t.bigInteger(name);
118
- break;
119
-
120
- case 'float':
121
- col = t.float(name);
122
- break;
123
-
124
- case 'decimal':
125
- col = t.decimal(name, field.precision || 8, field.scale || 2);
126
- break;
127
-
128
- case 'boolean':
129
- col = t.boolean(name);
130
- break;
131
-
132
- case 'json':
133
- col = t.json(name);
134
- break;
135
-
136
- case 'date':
137
- col = t.date(name);
138
- break;
139
-
140
- case 'timestamp':
141
- col = t.timestamp(name, { useTz: false });
142
- break;
143
-
144
- case 'enum':
145
- col = t.enum(name, field.enumValues || []);
146
- break;
147
-
148
- case 'uuid':
149
- col = t.uuid(name);
150
- break;
151
-
152
- default:
153
- col = t.string(name);
154
- }
155
-
156
- if (!col) return;
157
-
158
- if (field.nullable) col = col.nullable();
159
- else if (field.type !== 'id') col = col.notNullable();
160
-
161
- if (field.unique) col = col.unique();
162
-
163
- if (field.default !== undefined) col = col.defaultTo(field.default);
164
-
165
- if (field.references) {
166
- col = col.references(field.references.column)
167
- .inTable(field.references.table)
168
- .onDelete('CASCADE');
95
+ applyColumn(tableBuilder, name, normaliseField(field));
169
96
  }
170
97
  }
171
98
  }
172
99
 
173
- module.exports = SchemaBuilder;
100
+ module.exports = SchemaBuilder;
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * BaseOperation
5
+ *
6
+ * Abstract base class for all migration operations.
7
+ *
8
+ * Every operation must implement:
9
+ * applyState(projectState) — mutate the in-memory ProjectState (no DB touch)
10
+ * up(db) — apply the change to the live database
11
+ * down(db) — revert the change from the live database
12
+ * toJSON() — return a plain serialisable descriptor
13
+ *
14
+ * The `type` property is set by each subclass and must match the key used
15
+ * in the deserialise() registry in registry.js.
16
+ */
17
+ class BaseOperation {
18
+ /**
19
+ * Mutate the in-memory ProjectState.
20
+ * Called during migration graph replay (makemigrations) — never touches DB.
21
+ * @param {import('../ProjectState').ProjectState} _state
22
+ */
23
+ // eslint-disable-next-line no-unused-vars
24
+ applyState(_state) {
25
+ throw new Error(`${this.constructor.name}.applyState() not implemented`);
26
+ }
27
+
28
+ /**
29
+ * Apply this operation to the live database (forward migration).
30
+ * @param {import('knex').Knex} _db
31
+ */
32
+ // eslint-disable-next-line no-unused-vars
33
+ async up(_db) {
34
+ throw new Error(`${this.constructor.name}.up() not implemented`);
35
+ }
36
+
37
+ /**
38
+ * Revert this operation from the live database (rollback).
39
+ * @param {import('knex').Knex} _db
40
+ */
41
+ // eslint-disable-next-line no-unused-vars
42
+ async down(_db) {
43
+ throw new Error(`${this.constructor.name}.down() not implemented`);
44
+ }
45
+
46
+ /**
47
+ * Return a plain, JSON-serialisable descriptor for this operation.
48
+ * Used by MigrationWriter to write migration files and by MigrationGraph
49
+ * to reload them via deserialise().
50
+ * @returns {object}
51
+ */
52
+ toJSON() {
53
+ throw new Error(`${this.constructor.name}.toJSON() not implemented`);
54
+ }
55
+ }
56
+
57
+ module.exports = { BaseOperation };
@@ -0,0 +1,191 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * column.js
5
+ *
6
+ * Knex column builder helpers shared across all field-level operations.
7
+ *
8
+ * Having these in one place means:
9
+ * - The type → knex method mapping is never duplicated
10
+ * - AlterField reuses the same logic as AddField, with `.alter()` appended
11
+ * - FK constraint attachment is explicit and separated from column creation
12
+ *
13
+ * Exports:
14
+ * applyColumn(t, name, def) — add a new column to a table builder
15
+ * alterColumn(t, name, def) — modify an existing column (.alter())
16
+ * attachFKConstraints(db, table, fields) — attach FK constraints via ALTER TABLE
17
+ * after all tables in a migration exist
18
+ */
19
+
20
+ // ─── Core column builder ──────────────────────────────────────────────────────
21
+
22
+ /**
23
+ * Add a single column to a knex table builder.
24
+ *
25
+ * Handles all supported field types, nullability, uniqueness, defaults,
26
+ * and inline FK constraints (references).
27
+ *
28
+ * Pass `{ ...def, references: null }` to suppress FK constraint creation
29
+ * when deferring constraints to a later ALTER TABLE pass.
30
+ *
31
+ * @param {object} t — knex table builder (from createTable / table callback)
32
+ * @param {string} name — column name
33
+ * @param {object} def — normalised field definition from ProjectState.normaliseField()
34
+ */
35
+ function applyColumn(t, name, def) {
36
+ const col = _buildColumn(t, name, def);
37
+ if (!col) return; // 'id' handled internally by _buildColumn
38
+
39
+ _applyModifiers(col, def);
40
+ }
41
+
42
+ /**
43
+ * Modify an existing column in a knex alterTable builder.
44
+ * Identical to applyColumn but appends `.alter()` — required by knex to
45
+ * signal that this is a column modification, not a new column addition.
46
+ *
47
+ * Note: FK constraints are NOT altered here — use attachFKConstraints()
48
+ * to manage them separately. Most DBs require DROP CONSTRAINT + re-add
49
+ * for FK changes, which is safer to do explicitly.
50
+ *
51
+ * @param {object} t — knex table builder (from alterTable callback)
52
+ * @param {string} name — column name
53
+ * @param {object} def — normalised field definition
54
+ */
55
+ function alterColumn(t, name, def) {
56
+ const col = _buildColumn(t, name, def, { forAlter: true });
57
+ if (!col) return;
58
+
59
+ _applyModifiers(col, def, { skipFK: true }); // FKs not altered inline
60
+ col.alter();
61
+ }
62
+
63
+ /**
64
+ * Attach FK constraints for a set of fields on a table.
65
+ *
66
+ * Called by MigrationRunner AFTER all tables in a migration have been
67
+ * created — this guarantees all referenced tables exist.
68
+ *
69
+ * All FK columns for a given table are batched into a single ALTER TABLE
70
+ * statement, not one per column.
71
+ *
72
+ * @param {import('knex').Knex} db
73
+ * @param {string} table — table name
74
+ * @param {object} fields — { columnName: normalisedDef, ... }
75
+ */
76
+ async function attachFKConstraints(db, table, fields) {
77
+ const fkEntries = Object.entries(fields).filter(([, def]) => def.references);
78
+
79
+ if (fkEntries.length === 0) return;
80
+
81
+ await db.schema.alterTable(table, (t) => {
82
+ for (const [name, def] of fkEntries) {
83
+ const ref = def.references;
84
+ t.foreign(name)
85
+ .references(ref.column)
86
+ .inTable(ref.table)
87
+ .onDelete(ref.onDelete || 'CASCADE');
88
+ }
89
+ });
90
+ }
91
+
92
+ // ─── Internal helpers ─────────────────────────────────────────────────────────
93
+
94
+ /**
95
+ * Build a knex column builder for a given field type.
96
+ * Returns null for 'id' fields (handled by t.increments which returns void).
97
+ *
98
+ * @param {object} t
99
+ * @param {string} name
100
+ * @param {object} def
101
+ * @param {object} [opts]
102
+ * @param {boolean} [opts.forAlter] — if true, skip t.increments (can't alter PK)
103
+ * @returns {object|null} knex column builder
104
+ */
105
+ function _buildColumn(t, name, def, opts = {}) {
106
+ switch (def.type) {
107
+ case 'id':
108
+ if (!opts.forAlter) t.increments(name).primary();
109
+ return null; // increments() doesn't return a chainable column builder
110
+
111
+ case 'string':
112
+ return t.string(name, def.max || 255);
113
+
114
+ case 'text':
115
+ return t.text(name);
116
+
117
+ case 'integer':
118
+ return def.unsigned
119
+ ? t.integer(name).unsigned()
120
+ : t.integer(name);
121
+
122
+ case 'bigInteger':
123
+ return def.unsigned
124
+ ? t.bigInteger(name).unsigned()
125
+ : t.bigInteger(name);
126
+
127
+ case 'float':
128
+ return t.float(name);
129
+
130
+ case 'decimal':
131
+ return t.decimal(name, def.precision || 8, def.scale || 2);
132
+
133
+ case 'boolean':
134
+ return t.boolean(name);
135
+
136
+ case 'json':
137
+ return t.json(name);
138
+
139
+ case 'date':
140
+ return t.date(name);
141
+
142
+ case 'timestamp':
143
+ return t.timestamp(name, { useTz: false });
144
+
145
+ case 'enum':
146
+ return t.enu(name, def.enumValues || []);
147
+
148
+ case 'uuid':
149
+ return t.uuid(name);
150
+
151
+ default:
152
+ return t.string(name); // safe fallback
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Apply nullability, uniqueness, default, and FK constraint modifiers
158
+ * to an already-built knex column builder.
159
+ *
160
+ * @param {object} col — knex column builder
161
+ * @param {object} def — normalised field def
162
+ * @param {object} [opts]
163
+ * @param {boolean} [opts.skipFK] — skip FK constraint (used by alterColumn)
164
+ */
165
+ function _applyModifiers(col, def, opts = {}) {
166
+ // Nullability
167
+ if (def.nullable) {
168
+ col.nullable();
169
+ } else if (def.type !== 'id') {
170
+ col.notNullable();
171
+ }
172
+
173
+ // Uniqueness
174
+ if (def.unique) col.unique();
175
+
176
+ // Default value
177
+ if (def.default !== null && def.default !== undefined) {
178
+ col.defaultTo(def.default);
179
+ }
180
+
181
+ // Inline FK constraint — skipped when deferring to attachFKConstraints()
182
+ if (!opts.skipFK && def.references) {
183
+ const ref = def.references;
184
+ col
185
+ .references(ref.column)
186
+ .inTable(ref.table)
187
+ .onDelete(ref.onDelete || 'CASCADE');
188
+ }
189
+ }
190
+
191
+ module.exports = { applyColumn, alterColumn, attachFKConstraints };
@@ -0,0 +1,252 @@
1
+ 'use strict';
2
+
3
+ const { BaseOperation } = require('./base');
4
+ const { applyColumn, alterColumn } = require('./column');
5
+ const { normaliseField } = require('../ProjectState');
6
+ const { resolveDefault } = require('../DefaultValueParser');
7
+
8
+ /**
9
+ * fields.js
10
+ *
11
+ * Column-level migration operations:
12
+ * AddField — add a new column (with optional safe backfill for NOT NULL)
13
+ * RemoveField — drop a column
14
+ * AlterField — modify a column definition
15
+ * RenameField — rename a column
16
+ *
17
+ * Key improvement over the old Operations.js:
18
+ * AlterField no longer duplicates the type→knex switch. It delegates to
19
+ * alterColumn() from column.js — one place for all column-building logic.
20
+ */
21
+
22
+ // ─── AddField ─────────────────────────────────────────────────────────────────
23
+
24
+ class AddField extends BaseOperation {
25
+ /**
26
+ * @param {string} table
27
+ * @param {string} column
28
+ * @param {object} field — FieldDefinition or normalised plain object
29
+ * @param {object} [oneOffDefault] — backfill descriptor for existing rows.
30
+ * NOT part of the model schema — only lives in this migration file.
31
+ * Shape: { kind: 'literal', value } | { kind: 'callable', expression }
32
+ * Legacy: plain primitive (backward compat)
33
+ */
34
+ constructor(table, column, field, oneOffDefault = undefined) {
35
+ super();
36
+ this.type = 'AddField';
37
+ this.table = table;
38
+ this.column = column;
39
+ this.field = field;
40
+ this.oneOffDefault = oneOffDefault;
41
+ }
42
+
43
+ applyState(state) {
44
+ state.addField(this.table, this.column, this.field);
45
+ }
46
+
47
+ async up(db) {
48
+ const def = normaliseField(this.field);
49
+ const hasBackfill = this.oneOffDefault !== undefined && this.oneOffDefault !== null;
50
+ const needsSafe = hasBackfill && !def.nullable && def.default === null;
51
+
52
+ if (needsSafe) {
53
+ await this._safeBackfill(db, def);
54
+ } else {
55
+ await db.schema.table(this.table, (t) => {
56
+ applyColumn(t, this.column, def);
57
+ });
58
+ }
59
+ }
60
+
61
+ async down(db) {
62
+ await db.schema.table(this.table, (t) => {
63
+ t.dropColumn(this.column);
64
+ });
65
+ }
66
+
67
+ toJSON() {
68
+ const j = {
69
+ type: 'AddField',
70
+ table: this.table,
71
+ column: this.column,
72
+ field: this.field,
73
+ };
74
+ if (this.oneOffDefault !== undefined) j.oneOffDefault = this.oneOffDefault;
75
+ return j;
76
+ }
77
+
78
+ // ── Private ────────────────────────────────────────────────────────────────
79
+
80
+ /**
81
+ * Three-step safe backfill for adding a NOT NULL column to a non-empty table.
82
+ *
83
+ * Step 1 — Add column as nullable so existing rows don't immediately
84
+ * violate the NOT NULL constraint.
85
+ * Step 2 — Backfill existing rows with the one-off default value.
86
+ * Callable defaults (uuid, timestamp) are invoked per row so
87
+ * each row gets its own unique value.
88
+ * Literal defaults are applied in a single bulk UPDATE.
89
+ * Step 3 — Tighten the column to NOT NULL now that all rows have a value.
90
+ */
91
+ async _safeBackfill(db, def) {
92
+ const resolved = resolveDefault(this.oneOffDefault);
93
+ const isCallable = typeof resolved === 'function';
94
+
95
+ // Step 1: add as nullable
96
+ await db.schema.table(this.table, (t) => {
97
+ applyColumn(t, this.column, { ...def, nullable: true, default: null });
98
+ });
99
+
100
+ // Step 2: backfill
101
+ if (isCallable) {
102
+ // Callable — fetch PKs and update each row individually
103
+ const rows = await db(this.table).whereNull(this.column).select('id');
104
+ for (const row of rows) {
105
+ await db(this.table)
106
+ .where('id', row.id)
107
+ .update({ [this.column]: resolved() });
108
+ }
109
+ } else {
110
+ // Literal — single bulk UPDATE
111
+ await db(this.table)
112
+ .whereNull(this.column)
113
+ .update({ [this.column]: resolved });
114
+ }
115
+
116
+ // Step 3: tighten to NOT NULL
117
+ await db.schema.alterTable(this.table, (t) => {
118
+ alterColumn(t, this.column, { ...def, nullable: false });
119
+ });
120
+ }
121
+ }
122
+
123
+ // ─── RemoveField ──────────────────────────────────────────────────────────────
124
+
125
+ class RemoveField extends BaseOperation {
126
+ /**
127
+ * @param {string} table
128
+ * @param {string} column
129
+ * @param {object} field — kept for down() reconstruction
130
+ */
131
+ constructor(table, column, field) {
132
+ super();
133
+ this.type = 'RemoveField';
134
+ this.table = table;
135
+ this.column = column;
136
+ this.field = field;
137
+ }
138
+
139
+ applyState(state) {
140
+ state.removeField(this.table, this.column);
141
+ }
142
+
143
+ async up(db) {
144
+ await db.schema.table(this.table, (t) => {
145
+ t.dropColumn(this.column);
146
+ });
147
+ }
148
+
149
+ async down(db) {
150
+ await db.schema.table(this.table, (t) => {
151
+ applyColumn(t, this.column, normaliseField(this.field));
152
+ });
153
+ }
154
+
155
+ toJSON() {
156
+ return {
157
+ type: 'RemoveField',
158
+ table: this.table,
159
+ column: this.column,
160
+ field: this.field,
161
+ };
162
+ }
163
+ }
164
+
165
+ // ─── AlterField ───────────────────────────────────────────────────────────────
166
+
167
+ class AlterField extends BaseOperation {
168
+ /**
169
+ * @param {string} table
170
+ * @param {string} column
171
+ * @param {object} field — new field definition
172
+ * @param {object} previousField — old field definition (for down())
173
+ */
174
+ constructor(table, column, field, previousField) {
175
+ super();
176
+ this.type = 'AlterField';
177
+ this.table = table;
178
+ this.column = column;
179
+ this.field = field;
180
+ this.previousField = previousField;
181
+ }
182
+
183
+ applyState(state) {
184
+ state.alterField(this.table, this.column, this.field);
185
+ }
186
+
187
+ async up(db) {
188
+ await db.schema.alterTable(this.table, (t) => {
189
+ alterColumn(t, this.column, normaliseField(this.field));
190
+ });
191
+ }
192
+
193
+ async down(db) {
194
+ await db.schema.alterTable(this.table, (t) => {
195
+ alterColumn(t, this.column, normaliseField(this.previousField));
196
+ });
197
+ }
198
+
199
+ toJSON() {
200
+ return {
201
+ type: 'AlterField',
202
+ table: this.table,
203
+ column: this.column,
204
+ field: this.field,
205
+ previousField: this.previousField,
206
+ };
207
+ }
208
+ }
209
+
210
+ // ─── RenameField ──────────────────────────────────────────────────────────────
211
+
212
+ class RenameField extends BaseOperation {
213
+ /**
214
+ * @param {string} table
215
+ * @param {string} oldColumn
216
+ * @param {string} newColumn
217
+ */
218
+ constructor(table, oldColumn, newColumn) {
219
+ super();
220
+ this.type = 'RenameField';
221
+ this.table = table;
222
+ this.oldColumn = oldColumn;
223
+ this.newColumn = newColumn;
224
+ }
225
+
226
+ applyState(state) {
227
+ state.renameField(this.table, this.oldColumn, this.newColumn);
228
+ }
229
+
230
+ async up(db) {
231
+ await db.schema.table(this.table, (t) => {
232
+ t.renameColumn(this.oldColumn, this.newColumn);
233
+ });
234
+ }
235
+
236
+ async down(db) {
237
+ await db.schema.table(this.table, (t) => {
238
+ t.renameColumn(this.newColumn, this.oldColumn);
239
+ });
240
+ }
241
+
242
+ toJSON() {
243
+ return {
244
+ type: 'RenameField',
245
+ table: this.table,
246
+ oldColumn: this.oldColumn,
247
+ newColumn: this.newColumn,
248
+ };
249
+ }
250
+ }
251
+
252
+ module.exports = { AddField, RemoveField, AlterField, RenameField };
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * operations/index.js
5
+ *
6
+ * Public surface of the operations/ folder.
7
+ *
8
+ * Import from here for the full set:
9
+ * require('./operations')
10
+ *
11
+ * Or import directly from a sub-module when you only need one concern:
12
+ * require('./operations/models') — CreateModel, DeleteModel, RenameModel
13
+ * require('./operations/fields') — AddField, RemoveField, AlterField, RenameField
14
+ * require('./operations/column') — applyColumn, alterColumn, attachFKConstraints
15
+ * require('./operations/registry') — deserialise, migrations proxy
16
+ * require('./operations/special') — RunSQL
17
+ */
18
+
19
+ const { BaseOperation } = require('./base');
20
+ const { applyColumn, alterColumn,
21
+ attachFKConstraints } = require('./column');
22
+ const { CreateModel, DeleteModel, RenameModel } = require('./models');
23
+ const { AddField, RemoveField,
24
+ AlterField, RenameField } = require('./fields');
25
+ const { RunSQL } = require('./special');
26
+ const { deserialise, migrations, _tableFromName } = require('./registry');
27
+
28
+ module.exports = {
29
+ // Base
30
+ BaseOperation,
31
+
32
+ // Column helpers
33
+ applyColumn,
34
+ alterColumn,
35
+ attachFKConstraints,
36
+
37
+ // Table-level ops
38
+ CreateModel,
39
+ DeleteModel,
40
+ RenameModel,
41
+
42
+ // Field-level ops
43
+ AddField,
44
+ RemoveField,
45
+ AlterField,
46
+ RenameField,
47
+
48
+ // Escape hatch
49
+ RunSQL,
50
+
51
+ // Registry
52
+ deserialise,
53
+ migrations,
54
+ _tableFromName,
55
+ };