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
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const { CreateModel } = require('../../orm/migration/operations');
4
+
5
+ /**
6
+ * System migration: millas_sessions
7
+ * Equivalent to Django's django_session.
8
+ */
9
+ module.exports = {
10
+ dependencies: [['system', '0001_users']],
11
+
12
+ operations: [
13
+ new CreateModel('millas_sessions', {
14
+ session_key: { type: 'string', max: 64, nullable: false, unique: false, default: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
15
+ user_id: { type: 'integer', unsigned: true, nullable: false, unique: false, default: null, max: null, enumValues: null, references: { table: 'users', column: 'id', onDelete: 'CASCADE' }, precision: null, scale: null },
16
+ payload: { type: 'text', nullable: true, unique: false, default: null, max: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
17
+ ip_address: { type: 'string', max: 45, nullable: true, unique: false, default: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
18
+ user_agent: { type: 'string', max: 512, nullable: true, unique: false, default: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
19
+ expires_at: { type: 'timestamp', nullable: false, unique: false, default: null, max: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
20
+ created_at: { type: 'timestamp', nullable: true, unique: false, default: null, max: null, unsigned: false, enumValues: null, references: null, precision: null, scale: null },
21
+ }),
22
+ ],
23
+ };
@@ -1,200 +1,222 @@
1
1
  'use strict';
2
2
 
3
3
  class FieldDefinition {
4
- constructor(type, options = {}) {
5
- this.type = type;
6
- this.options = options;
7
- this.nullable = options.nullable ?? false;
8
- this.unique = options.unique ?? false;
9
- this.default = options.default !== undefined ? options.default : undefined;
10
- this.primary = options.primary ?? false;
11
- this.unsigned = options.unsigned ?? false;
12
- this.max = options.max ?? null;
13
- this.enumValues = options.enumValues ?? null;
14
- this.references = options.references ?? null;
15
- this._isForeignKey = options._isForeignKey ?? false;
16
- this._isOneToOne = options._isOneToOne ?? false;
17
- this._isManyToMany = options._isManyToMany ?? false;
18
- this._fkModel = options._fkModel ?? null;
19
- this._fkModelRef = options._fkModelRef ?? null;
20
- this._fkToField = options._fkToField ?? 'id';
21
- this._fkOnDelete = options._fkOnDelete ?? 'CASCADE';
22
- this._fkRelatedName = options._fkRelatedName ?? null;
23
- this._m2mThrough = options._m2mThrough ?? null;
24
- }
25
-
26
- nullable_(val = true) { this.nullable = val; return this; }
27
- unique_(val = true) { this.unique = val; return this; }
28
- default_(val) { this.default = val; return this; }
29
- unsigned_(val = true) { this.unsigned = val; return this; }
30
- references_(table, col) { this.references = { table, column: col }; return this; }
4
+ constructor(type, options = {}) {
5
+ this.type = type;
6
+ this.options = options;
7
+ this.nullable = options.nullable ?? false;
8
+ this.unique = options.unique ?? false;
9
+ this.default = options.default !== undefined ? options.default : undefined;
10
+ this.primary = options.primary ?? false;
11
+ this.unsigned = options.unsigned ?? false;
12
+ this.max = options.max ?? null;
13
+ this.enumValues = options.enumValues ?? null;
14
+ this.references = options.references ?? null;
15
+ this._isForeignKey = options._isForeignKey ?? false;
16
+ this._isOneToOne = options._isOneToOne ?? false;
17
+ this._isManyToMany = options._isManyToMany ?? false;
18
+ this._fkModel = options._fkModel ?? null;
19
+ this._fkModelRef = options._fkModelRef ?? null;
20
+ this._fkToField = options._fkToField ?? 'id';
21
+ this._fkOnDelete = options._fkOnDelete ?? 'CASCADE';
22
+ this._fkRelatedName = options._fkRelatedName ?? null;
23
+ this._m2mThrough = options._m2mThrough ?? null;
24
+ }
25
+
26
+ nullable_(val = true) {
27
+ this.nullable = val;
28
+ return this;
29
+ }
30
+
31
+ unique_(val = true) {
32
+ this.unique = val;
33
+ return this;
34
+ }
35
+
36
+ default_(val) {
37
+ this.default = val;
38
+ return this;
39
+ }
40
+
41
+ unsigned_(val = true) {
42
+ this.unsigned = val;
43
+ return this;
44
+ }
45
+
46
+ references_(table, col) {
47
+ this.references = {table, column: col};
48
+ return this;
49
+ }
31
50
  }
32
51
 
33
52
  function _makeModelRef(model) {
34
- if (typeof model === 'function') return model;
35
- if (model === 'self') return null;
36
- return () => {
37
- const path = require('path');
38
- const modelsDir = path.join(process.cwd(), 'app', 'models');
39
- try {
40
- return require(path.join(modelsDir, model));
41
- } catch {
42
- try {
43
- const fs = require('fs');
44
- const files = fs.readdirSync(modelsDir);
45
- const match = files.find(f =>
46
- f.replace(/\.js$/, '').toLowerCase() === model.toLowerCase()
47
- );
48
- if (match) return require(path.join(modelsDir, match));
49
- } catch {}
50
- return null;
51
- }
52
- };
53
+ if (typeof model === 'function') return model;
54
+ if (model === 'self') return null;
55
+ return () => {
56
+ const path = require('path');
57
+ const modelsDir = path.join(process.cwd(), 'app', 'models');
58
+ try {
59
+ return require(path.join(modelsDir, model));
60
+ } catch {
61
+ try {
62
+ const fs = require('fs');
63
+ const files = fs.readdirSync(modelsDir);
64
+ const match = files.find(f =>
65
+ f.replace(/\.js$/, '').toLowerCase() === model.toLowerCase()
66
+ );
67
+ if (match) return require(path.join(modelsDir, match));
68
+ } catch {
69
+ }
70
+ return null;
71
+ }
72
+ };
53
73
  }
54
74
 
55
75
  const fields = {
56
76
 
57
- id(options = {}) {
58
- return new FieldDefinition('id', { primary: true, unsigned: true, ...options });
59
- },
60
-
61
- string(options = {}) {
62
- return new FieldDefinition('string', { max: 255, ...options });
63
- },
64
-
65
- text(options = {}) {
66
- return new FieldDefinition('text', options);
67
- },
68
-
69
- integer(options = {}) {
70
- return new FieldDefinition('integer', options);
71
- },
72
-
73
- bigInteger(options = {}) {
74
- return new FieldDefinition('bigInteger', options);
75
- },
76
-
77
- float(options = {}) {
78
- return new FieldDefinition('float', options);
79
- },
80
-
81
- decimal(precision = 8, scale = 2, options = {}) {
82
- return new FieldDefinition('decimal', { precision, scale, ...options });
83
- },
84
-
85
- boolean(options = {}) {
86
- return new FieldDefinition('boolean', options);
87
- },
88
-
89
- json(options = {}) {
90
- return new FieldDefinition('json', options);
91
- },
92
-
93
- date(options = {}) {
94
- return new FieldDefinition('date', options);
95
- },
96
-
97
- timestamp(options = {}) {
98
- return new FieldDefinition('timestamp', { nullable: true, ...options });
99
- },
100
-
101
- enum(values, options = {}) {
102
- return new FieldDefinition('enum', { enumValues: values, ...options });
103
- },
104
-
105
- uuid(options = {}) {
106
- return new FieldDefinition('uuid', options);
107
- },
108
-
109
- /**
110
- * ForeignKey — Django-style.
111
- *
112
- * Declares the integer column AND wires the BelongsTo relation automatically.
113
- * No `static relations` block needed.
114
- *
115
- * Field name convention:
116
- * author → accessor: book.author() column: author_id
117
- * author_id → accessor: book.author() column: author_id
118
- *
119
- * @param {string|Function} model 'Author' | () => Author | 'self'
120
- * @param {object} [opts]
121
- * @param {boolean} [opts.nullable] allow NULL (default: false)
122
- * @param {string} [opts.onDelete] CASCADE|SET NULL|RESTRICT|PROTECT|DO_NOTHING (default: CASCADE)
123
- * @param {string} [opts.relatedName] reverse accessor on target, e.g. 'books' → author.books()
124
- * pass '+' to suppress the reverse relation
125
- * @param {string} [opts.toField] target column (default: 'id')
126
- *
127
- * @example
128
- * author: fields.ForeignKey('Author', { onDelete: 'CASCADE', relatedName: 'books' })
129
- * editor: fields.ForeignKey('User', { nullable: true, onDelete: 'SET NULL' })
130
- * parent: fields.ForeignKey('self', { nullable: true, relatedName: 'children' })
131
- */
132
- ForeignKey(model, opts = {}) {
133
- return new FieldDefinition('integer', {
134
- unsigned: true,
135
- nullable: opts.nullable ?? false,
136
- _isForeignKey: true,
137
- _fkModel: model,
138
- _fkModelRef: _makeModelRef(model),
139
- _fkToField: opts.toField ?? 'id',
140
- _fkOnDelete: opts.onDelete ?? 'CASCADE',
141
- _fkRelatedName: opts.relatedName ?? null,
142
- });
143
- },
144
-
145
- /**
146
- * OneToOne — unique ForeignKey. Both directions wired automatically.
147
- *
148
- * @example
149
- * user: fields.OneToOne('User', { relatedName: 'profile' })
150
- * // profile.user() and user.profile() both work
151
- */
152
- OneToOne(model, opts = {}) {
153
- return new FieldDefinition('integer', {
154
- unsigned: true,
155
- unique: true,
156
- nullable: opts.nullable ?? false,
157
- _isForeignKey: true,
158
- _isOneToOne: true,
159
- _fkModel: model,
160
- _fkModelRef: _makeModelRef(model),
161
- _fkToField: opts.toField ?? 'id',
162
- _fkOnDelete: opts.onDelete ?? 'CASCADE',
163
- _fkRelatedName: opts.relatedName ?? null,
164
- });
165
- },
166
-
167
- /**
168
- * ManyToMany — no DB column. Generates pivot table migration.
169
- * Pivot table auto-named: sorted model names joined with underscore.
170
- *
171
- * @example
172
- * tags: fields.ManyToMany('Tag', { relatedName: 'courses' })
173
- * tags: fields.ManyToMany('Tag', { through: 'course_tags', relatedName: 'courses' })
174
- */
175
- ManyToMany(model, opts = {}) {
176
- return new FieldDefinition('m2m', {
177
- nullable: true,
178
- _isManyToMany: true,
179
- _fkModel: model,
180
- _fkModelRef: _makeModelRef(model),
181
- _fkRelatedName: opts.relatedName ?? null,
182
- _m2mThrough: opts.through ?? null,
183
- });
184
- },
185
-
186
- /** Legacy — kept for backward compatibility. Prefer ForeignKey(). */
187
- foreignId(column, options = {}) {
188
- const [table, col] = column.endsWith('_id')
189
- ? [column.slice(0, -3) + 's', 'id']
190
- : [null, null];
191
- return new FieldDefinition('integer', {
192
- unsigned: true,
193
- nullable: options.nullable ?? false,
194
- references: table ? { table, column: col } : null,
195
- ...options,
196
- });
197
- },
77
+ id(options = {}) {
78
+ return new FieldDefinition('id', {primary: true, unsigned: true, ...options});
79
+ },
80
+
81
+ string(options = {}) {
82
+ return new FieldDefinition('string', {max: 255, ...options});
83
+ },
84
+
85
+ text(options = {}) {
86
+ return new FieldDefinition('text', options);
87
+ },
88
+
89
+ integer(options = {}) {
90
+ return new FieldDefinition('integer', options);
91
+ },
92
+
93
+ bigInteger(options = {}) {
94
+ return new FieldDefinition('bigInteger', options);
95
+ },
96
+
97
+ float(options = {}) {
98
+ return new FieldDefinition('float', options);
99
+ },
100
+
101
+ decimal(precision = 8, scale = 2, options = {}) {
102
+ return new FieldDefinition('decimal', {precision, scale, ...options});
103
+ },
104
+
105
+ boolean(options = {}) {
106
+ return new FieldDefinition('boolean', options);
107
+ },
108
+
109
+ json(options = {}) {
110
+ return new FieldDefinition('json', options);
111
+ },
112
+
113
+ date(options = {}) {
114
+ return new FieldDefinition('date', options);
115
+ },
116
+
117
+ timestamp(options = {}) {
118
+ return new FieldDefinition('timestamp', {nullable: true, ...options});
119
+ },
120
+
121
+ enum(values, options = {}) {
122
+ return new FieldDefinition('enum', {enumValues: values, ...options});
123
+ },
124
+
125
+ uuid(options = {}) {
126
+ return new FieldDefinition('uuid', options);
127
+ },
128
+
129
+ /**
130
+ * ForeignKey — Django-style.
131
+ *
132
+ * Declares the integer column AND wires the BelongsTo relation automatically.
133
+ * No `static relations` block needed.
134
+ *
135
+ * Field name convention:
136
+ * author → accessor: book.author() column: author_id
137
+ * author_id → accessor: book.author() column: author_id
138
+ *
139
+ * @param {string|Function} model 'Author' | () => Author | 'self'
140
+ * @param {object} [opts]
141
+ * @param {boolean} [opts.nullable] allow NULL (default: false)
142
+ * @param {string} [opts.onDelete] CASCADE|SET NULL|RESTRICT|PROTECT|DO_NOTHING (default: CASCADE)
143
+ * @param {string} [opts.relatedName] reverse accessor on target, e.g. 'books' → author.books()
144
+ * pass '+' to suppress the reverse relation
145
+ * @param {string} [opts.toField] target column (default: 'id')
146
+ *
147
+ * @example
148
+ * author: fields.ForeignKey('Author', { onDelete: 'CASCADE', relatedName: 'books' })
149
+ * editor: fields.ForeignKey('User', { nullable: true, onDelete: 'SET NULL' })
150
+ * parent: fields.ForeignKey('self', { nullable: true, relatedName: 'children' })
151
+ */
152
+ ForeignKey(model, opts = {}) {
153
+ return new FieldDefinition('integer', {
154
+ unsigned: true,
155
+ nullable: opts.nullable ?? false,
156
+ _isForeignKey: true,
157
+ _fkModel: model,
158
+ _fkModelRef: _makeModelRef(model),
159
+ _fkToField: opts.toField ?? 'id',
160
+ _fkOnDelete: opts.onDelete ?? 'CASCADE',
161
+ _fkRelatedName: opts.relatedName ?? null,
162
+ });
163
+ },
164
+
165
+ /**
166
+ * OneToOne — unique ForeignKey. Both directions wired automatically.
167
+ *
168
+ * @example
169
+ * user: fields.OneToOne('User', { relatedName: 'profile' })
170
+ * // profile.user() and user.profile() both work
171
+ */
172
+ OneToOne(model, opts = {}) {
173
+ return new FieldDefinition('integer', {
174
+ unsigned: true,
175
+ unique: true,
176
+ nullable: opts.nullable ?? false,
177
+ _isForeignKey: true,
178
+ _isOneToOne: true,
179
+ _fkModel: model,
180
+ _fkModelRef: _makeModelRef(model),
181
+ _fkToField: opts.toField ?? 'id',
182
+ _fkOnDelete: opts.onDelete ?? 'CASCADE',
183
+ _fkRelatedName: opts.relatedName ?? null,
184
+ });
185
+ },
186
+
187
+ /**
188
+ * ManyToMany — no DB column. Generates pivot table migration.
189
+ * Pivot table auto-named: sorted model names joined with underscore.
190
+ *
191
+ * @example
192
+ * tags: fields.ManyToMany('Tag', { relatedName: 'courses' })
193
+ * tags: fields.ManyToMany('Tag', { through: 'course_tags', relatedName: 'courses' })
194
+ */
195
+ ManyToMany(model, opts = {}) {
196
+ return new FieldDefinition('m2m', {
197
+ nullable: true,
198
+ _isManyToMany: true,
199
+ _fkModel: model,
200
+ _fkModelRef: _makeModelRef(model),
201
+ _fkRelatedName: opts.relatedName ?? null,
202
+ _m2mThrough: opts.through ?? null,
203
+ });
204
+ },
205
+
206
+ /** Legacy — kept for backward compatibility. Prefer ForeignKey(). */
207
+ foreignId(column, options = {}) {
208
+ const [table, col] = column.endsWith('_id')
209
+ ? [column.slice(0, -3) + 's', 'id']
210
+ : [null, null];
211
+ return new FieldDefinition('integer', {
212
+ unsigned: true,
213
+ nullable: options.nullable ?? false,
214
+ references: table ? {table, column: col} : null,
215
+ ...options,
216
+ });
217
+ },
218
+ CASCADE: "CASCADE",
219
+ SET_NULL: "SET_NULL"
198
220
  };
199
221
 
200
- module.exports = { fields, FieldDefinition };
222
+ module.exports = {fields, FieldDefinition};