odac 1.1.0 → 1.2.0

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 (113) hide show
  1. package/.agent/rules/coding.md +27 -0
  2. package/.agent/rules/memory.md +33 -0
  3. package/.agent/rules/project.md +30 -0
  4. package/.agent/rules/workflow.md +16 -0
  5. package/.github/workflows/release.yml +42 -1
  6. package/.github/workflows/test-coverage.yml +6 -5
  7. package/.github/workflows/test-publish.yml +36 -0
  8. package/.husky/pre-commit +10 -0
  9. package/.husky/pre-push +13 -0
  10. package/.releaserc.js +3 -3
  11. package/CHANGELOG.md +67 -0
  12. package/README.md +16 -0
  13. package/bin/odac.js +182 -40
  14. package/client/odac.js +10 -4
  15. package/docs/backend/01-overview/03-development-server.md +38 -45
  16. package/docs/backend/02-structure/01-typical-project-layout.md +59 -26
  17. package/docs/backend/03-config/00-configuration-overview.md +6 -6
  18. package/docs/backend/03-config/01-database-connection.md +2 -2
  19. package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
  20. package/docs/backend/03-config/03-request-timeout.md +1 -1
  21. package/docs/backend/03-config/04-environment-variables.md +4 -4
  22. package/docs/backend/03-config/05-early-hints.md +2 -2
  23. package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
  24. package/docs/backend/04-routing/07-cron-jobs.md +17 -1
  25. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
  26. package/docs/backend/05-controllers/03-controller-classes.md +40 -20
  27. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
  28. package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
  29. package/docs/backend/08-database/01-getting-started.md +2 -2
  30. package/docs/backend/10-authentication/03-register.md +1 -1
  31. package/docs/backend/10-authentication/04-odac-register-forms.md +2 -2
  32. package/docs/backend/10-authentication/05-session-management.md +15 -1
  33. package/docs/backend/10-authentication/06-odac-login-forms.md +2 -2
  34. package/docs/backend/10-authentication/07-magic-links.md +1 -1
  35. package/docs/index.json +5 -1
  36. package/jest.config.js +1 -1
  37. package/package.json +9 -5
  38. package/src/Auth.js +58 -23
  39. package/src/Config.js +7 -7
  40. package/src/Env.js +3 -1
  41. package/src/Ipc.js +7 -0
  42. package/src/Lang.js +9 -2
  43. package/src/Odac.js +44 -35
  44. package/src/Request.js +1 -1
  45. package/src/Route/Cron.js +58 -17
  46. package/src/Route/Internal.js +1 -1
  47. package/src/Route.js +282 -99
  48. package/src/Server.js +40 -3
  49. package/src/Storage.js +4 -0
  50. package/src/Token.js +6 -4
  51. package/src/Validator.js +1 -1
  52. package/src/Var.js +22 -6
  53. package/src/View/EarlyHints.js +43 -33
  54. package/src/View/Form.js +17 -11
  55. package/src/View.js +62 -6
  56. package/template/package.json +3 -1
  57. package/template/view/content/home.html +3 -3
  58. package/template/view/head/main.html +2 -2
  59. package/test/Client.test.js +168 -0
  60. package/test/Config.test.js +112 -0
  61. package/test/Lang.test.js +92 -0
  62. package/test/Odac.test.js +86 -0
  63. package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
  64. package/test/{framework/Route.test.js → Route.test.js} +1 -1
  65. package/test/{framework/View → View}/EarlyHints.test.js +1 -1
  66. package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
  67. package/test/scripts/check-coverage.js +4 -4
  68. package/test/cli/Cli.test.js +0 -36
  69. package/test/core/Commands.test.js +0 -538
  70. package/test/core/Config.test.js +0 -1432
  71. package/test/core/Lang.test.js +0 -250
  72. package/test/core/Odac.test.js +0 -234
  73. package/test/core/Process.test.js +0 -156
  74. package/test/server/Api.test.js +0 -647
  75. package/test/server/DNS.test.js +0 -2050
  76. package/test/server/DNS.test.js.bak +0 -2084
  77. package/test/server/Hub.test.js +0 -497
  78. package/test/server/Log.test.js +0 -73
  79. package/test/server/Mail.account.test_.js +0 -460
  80. package/test/server/Mail.init.test_.js +0 -411
  81. package/test/server/Mail.test_.js +0 -1340
  82. package/test/server/SSL.test_.js +0 -1491
  83. package/test/server/Server.test.js +0 -765
  84. package/test/server/Service.test_.js +0 -1127
  85. package/test/server/Subdomain.test.js +0 -440
  86. package/test/server/Web/Firewall.test.js +0 -175
  87. package/test/server/Web/Proxy.test.js +0 -397
  88. package/test/server/Web.test.js +0 -1494
  89. package/test/server/__mocks__/acme-client.js +0 -17
  90. package/test/server/__mocks__/bcrypt.js +0 -50
  91. package/test/server/__mocks__/child_process.js +0 -389
  92. package/test/server/__mocks__/crypto.js +0 -432
  93. package/test/server/__mocks__/fs.js +0 -450
  94. package/test/server/__mocks__/globalOdac.js +0 -227
  95. package/test/server/__mocks__/http.js +0 -575
  96. package/test/server/__mocks__/https.js +0 -272
  97. package/test/server/__mocks__/index.js +0 -249
  98. package/test/server/__mocks__/mail/server.js +0 -100
  99. package/test/server/__mocks__/mail/smtp.js +0 -31
  100. package/test/server/__mocks__/mailparser.js +0 -81
  101. package/test/server/__mocks__/net.js +0 -369
  102. package/test/server/__mocks__/node-forge.js +0 -328
  103. package/test/server/__mocks__/os.js +0 -320
  104. package/test/server/__mocks__/path.js +0 -291
  105. package/test/server/__mocks__/selfsigned.js +0 -8
  106. package/test/server/__mocks__/server/src/mail/server.js +0 -100
  107. package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
  108. package/test/server/__mocks__/smtp-server.js +0 -106
  109. package/test/server/__mocks__/sqlite3.js +0 -394
  110. package/test/server/__mocks__/testFactories.js +0 -299
  111. package/test/server/__mocks__/testHelpers.js +0 -363
  112. package/test/server/__mocks__/tls.js +0 -229
  113. /package/template/{config.json → odac.json} +0 -0
@@ -1,81 +0,0 @@
1
- /**
2
- * Mock implementation of mailparser for server tests
3
- */
4
-
5
- const {createMockEmailMessage} = require('./testFactories')
6
-
7
- const simpleParser = jest.fn((source, options, callback) => {
8
- if (typeof options === 'function') {
9
- callback = options
10
- options = {}
11
- }
12
-
13
- // Create a mock parsed email
14
- const mockParsedEmail = createMockEmailMessage()
15
-
16
- // Allow customization through options
17
- if (options.from) mockParsedEmail.from.value[0].address = options.from
18
- if (options.to) mockParsedEmail.to[0].address = options.to
19
- if (options.subject) mockParsedEmail.subject = options.subject
20
- if (options.text) mockParsedEmail.text = options.text
21
- if (options.html) mockParsedEmail.html = options.html
22
-
23
- // Simulate async parsing
24
- setTimeout(() => {
25
- if (callback) {
26
- callback(null, mockParsedEmail)
27
- }
28
- }, 0)
29
-
30
- // Return a promise if no callback provided
31
- if (!callback) {
32
- return Promise.resolve(mockParsedEmail)
33
- }
34
- })
35
-
36
- // Mock for streaming parser
37
- const parseHeaders = jest.fn((source, callback) => {
38
- const mockHeaders = new Map([
39
- ['from', 'sender@example.com'],
40
- ['to', 'recipient@example.com'],
41
- ['subject', 'Test Email'],
42
- ['date', new Date().toISOString()],
43
- ['message-id', '<test@example.com>']
44
- ])
45
-
46
- setTimeout(() => {
47
- if (callback) {
48
- callback(null, mockHeaders)
49
- }
50
- }, 0)
51
-
52
- if (!callback) {
53
- return Promise.resolve(mockHeaders)
54
- }
55
- })
56
-
57
- // Mock for attachment parsing
58
- const parseAttachment = jest.fn((attachment, callback) => {
59
- const mockAttachment = {
60
- filename: 'test.txt',
61
- contentType: 'text/plain',
62
- size: 100,
63
- content: Buffer.from('test attachment content')
64
- }
65
-
66
- setTimeout(() => {
67
- if (callback) {
68
- callback(null, mockAttachment)
69
- }
70
- }, 0)
71
-
72
- if (!callback) {
73
- return Promise.resolve(mockAttachment)
74
- }
75
- })
76
-
77
- module.exports = {
78
- simpleParser,
79
- parseHeaders,
80
- parseAttachment
81
- }
@@ -1,369 +0,0 @@
1
- /**
2
- * Mock implementation of the net module for server tests
3
- * Provides comprehensive mocking of TCP server and socket operations
4
- */
5
-
6
- const {createMockEventEmitter} = require('./testHelpers')
7
-
8
- // Track active servers and connections
9
- const activeServers = new Map()
10
- const activeConnections = new Map()
11
- let nextConnectionId = 1
12
-
13
- const createMockSocket = (options = {}) => {
14
- const socket = createMockEventEmitter()
15
- const connectionId = nextConnectionId++
16
-
17
- Object.assign(socket, {
18
- // Connection properties
19
- remoteAddress: options.remoteAddress || '127.0.0.1',
20
- remotePort: options.remotePort || Math.floor(Math.random() * 50000) + 10000,
21
- localAddress: options.localAddress || '127.0.0.1',
22
- localPort: options.localPort || 1453,
23
- remoteFamily: options.remoteFamily || 'IPv4',
24
- localFamily: options.localFamily || 'IPv4',
25
-
26
- // State properties
27
- readable: true,
28
- writable: true,
29
- destroyed: false,
30
- connecting: false,
31
- readyState: 'open',
32
-
33
- // Buffer properties
34
- bytesRead: 0,
35
- bytesWritten: 0,
36
-
37
- // Methods
38
- write: jest.fn((data, encoding, callback) => {
39
- if (socket.destroyed || !socket.writable) {
40
- const error = new Error('Cannot write to destroyed socket')
41
- if (callback) callback(error)
42
- return false
43
- }
44
-
45
- socket.bytesWritten += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data, encoding)
46
-
47
- if (callback) {
48
- setTimeout(callback, 0)
49
- }
50
-
51
- return true
52
- }),
53
-
54
- end: jest.fn((data, encoding, callback) => {
55
- if (data) {
56
- socket.write(data, encoding)
57
- }
58
-
59
- if (typeof encoding === 'function') {
60
- callback = encoding
61
- }
62
- if (typeof data === 'function') {
63
- callback = data
64
- }
65
-
66
- socket.writable = false
67
-
68
- setTimeout(() => {
69
- socket.emit('end')
70
- if (callback) callback()
71
- }, 0)
72
-
73
- return socket
74
- }),
75
-
76
- destroy: jest.fn(error => {
77
- if (socket.destroyed) return socket
78
-
79
- socket.destroyed = true
80
- socket.readable = false
81
- socket.writable = false
82
- socket.readyState = 'closed'
83
-
84
- activeConnections.delete(connectionId)
85
-
86
- setTimeout(() => {
87
- if (error) {
88
- socket.emit('error', error)
89
- }
90
- socket.emit('close', !!error)
91
- }, 0)
92
-
93
- return socket
94
- }),
95
-
96
- pause: jest.fn(() => {
97
- socket.readable = false
98
- return socket
99
- }),
100
-
101
- resume: jest.fn(() => {
102
- socket.readable = true
103
- return socket
104
- }),
105
-
106
- setTimeout: jest.fn((timeout, callback) => {
107
- if (callback) {
108
- socket.once('timeout', callback)
109
- }
110
-
111
- const timeoutId = setTimeout(() => {
112
- socket.emit('timeout')
113
- }, timeout)
114
-
115
- socket._timeout = timeoutId
116
- return socket
117
- }),
118
-
119
- setNoDelay: jest.fn((noDelay = true) => {
120
- socket._noDelay = noDelay
121
- return socket
122
- }),
123
-
124
- setKeepAlive: jest.fn((enable = false, initialDelay = 0) => {
125
- socket._keepAlive = enable
126
- socket._keepAliveInitialDelay = initialDelay
127
- return socket
128
- }),
129
-
130
- address: jest.fn(() => ({
131
- address: socket.localAddress,
132
- family: socket.localFamily,
133
- port: socket.localPort
134
- })),
135
-
136
- // Test helpers
137
- __simulateData: data => {
138
- if (!socket.destroyed && socket.readable) {
139
- socket.bytesRead += Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data)
140
- socket.emit('data', Buffer.isBuffer(data) ? data : Buffer.from(data))
141
- }
142
- },
143
-
144
- __simulateError: error => {
145
- socket.emit('error', error)
146
- },
147
-
148
- __simulateClose: (hadError = false) => {
149
- socket.destroy()
150
- }
151
- })
152
-
153
- activeConnections.set(connectionId, socket)
154
- return socket
155
- }
156
-
157
- const createMockServer = (options = {}) => {
158
- const server = createMockEventEmitter()
159
- const serverId = Math.floor(Math.random() * 1000)
160
-
161
- Object.assign(server, {
162
- // State properties
163
- listening: false,
164
- maxConnections: options.maxConnections || 0,
165
- connections: 0,
166
-
167
- // Server address info
168
- address: jest.fn(() => {
169
- if (!server.listening) return null
170
- return {
171
- address: server._address || '127.0.0.1',
172
- family: server._family || 'IPv4',
173
- port: server._port || 1453
174
- }
175
- }),
176
-
177
- // Methods
178
- listen: jest.fn((port, hostname, backlog, callback) => {
179
- // Handle different argument patterns
180
- if (typeof port === 'function') {
181
- callback = port
182
- port = 0
183
- } else if (typeof hostname === 'function') {
184
- callback = hostname
185
- hostname = undefined
186
- } else if (typeof backlog === 'function') {
187
- callback = backlog
188
- backlog = undefined
189
- }
190
-
191
- server._port = port || Math.floor(Math.random() * 50000) + 10000
192
- server._address = hostname || '127.0.0.1'
193
- server._family = 'IPv4'
194
- server.listening = true
195
-
196
- if (callback) {
197
- server.once('listening', callback)
198
- }
199
-
200
- setTimeout(() => {
201
- server.emit('listening')
202
- }, 0)
203
-
204
- return server
205
- }),
206
-
207
- close: jest.fn(callback => {
208
- if (!server.listening) {
209
- const error = new Error('Server is not running')
210
- if (callback) {
211
- setTimeout(() => callback(error), 0)
212
- }
213
- return server
214
- }
215
-
216
- server.listening = false
217
-
218
- // Close all connections
219
- activeConnections.forEach(socket => {
220
- if (socket.localPort === server._port) {
221
- socket.destroy()
222
- }
223
- })
224
-
225
- if (callback) {
226
- server.once('close', callback)
227
- }
228
-
229
- setTimeout(() => {
230
- server.emit('close')
231
- }, 0)
232
-
233
- activeServers.delete(serverId)
234
- return server
235
- }),
236
-
237
- getConnections: jest.fn(callback => {
238
- const count = Array.from(activeConnections.values()).filter(socket => socket.localPort === server._port && !socket.destroyed).length
239
-
240
- if (callback) {
241
- setTimeout(() => callback(null, count), 0)
242
- }
243
-
244
- return count
245
- }),
246
-
247
- ref: jest.fn(() => server),
248
- unref: jest.fn(() => server),
249
-
250
- // Test helpers
251
- __simulateConnection: (socketOptions = {}) => {
252
- if (!server.listening) {
253
- throw new Error('Server is not listening')
254
- }
255
-
256
- const socket = createMockSocket({
257
- localPort: server._port,
258
- localAddress: server._address,
259
- ...socketOptions
260
- })
261
-
262
- server.connections++
263
-
264
- setTimeout(() => {
265
- server.emit('connection', socket)
266
- }, 0)
267
-
268
- return socket
269
- },
270
-
271
- __simulateError: error => {
272
- server.emit('error', error)
273
- }
274
- })
275
-
276
- activeServers.set(serverId, server)
277
- return server
278
- }
279
-
280
- const net = {
281
- // Server creation
282
- createServer: jest.fn((options, connectionListener) => {
283
- if (typeof options === 'function') {
284
- connectionListener = options
285
- options = {}
286
- }
287
-
288
- const server = createMockServer(options)
289
-
290
- if (connectionListener) {
291
- server.on('connection', connectionListener)
292
- }
293
-
294
- return server
295
- }),
296
-
297
- // Socket creation
298
- createConnection: jest.fn((options, connectListener) => {
299
- const socket = createMockSocket()
300
-
301
- if (typeof options === 'number') {
302
- options = {port: options}
303
- }
304
-
305
- socket.connecting = true
306
- socket.readyState = 'opening'
307
-
308
- if (connectListener) {
309
- socket.once('connect', connectListener)
310
- }
311
-
312
- // Simulate connection
313
- setTimeout(() => {
314
- socket.connecting = false
315
- socket.readyState = 'open'
316
- socket.emit('connect')
317
- }, 0)
318
-
319
- return socket
320
- }),
321
-
322
- connect: jest.fn((...args) => net.createConnection(...args)),
323
-
324
- // Utility functions
325
- isIP: jest.fn(input => {
326
- if (typeof input !== 'string') return 0
327
-
328
- // Simple IPv4 check
329
- const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/
330
- if (ipv4Regex.test(input)) {
331
- const parts = input.split('.')
332
- if (parts.every(part => parseInt(part) >= 0 && parseInt(part) <= 255)) {
333
- return 4
334
- }
335
- }
336
-
337
- // Simple IPv6 check
338
- const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/
339
- if (ipv6Regex.test(input)) {
340
- return 6
341
- }
342
-
343
- return 0
344
- }),
345
-
346
- isIPv4: jest.fn(input => net.isIP(input) === 4),
347
- isIPv6: jest.fn(input => net.isIP(input) === 6),
348
-
349
- // Socket class
350
- Socket: jest.fn(function (options = {}) {
351
- return createMockSocket(options)
352
- }),
353
-
354
- // Server class
355
- Server: jest.fn(function (options = {}, connectionListener) {
356
- return createMockServer(options, connectionListener)
357
- }),
358
-
359
- // Test helpers
360
- __getActiveServers: () => new Map(activeServers),
361
- __getActiveConnections: () => new Map(activeConnections),
362
- __clearAll: () => {
363
- activeServers.clear()
364
- activeConnections.clear()
365
- nextConnectionId = 1
366
- }
367
- }
368
-
369
- module.exports = net