odac 1.0.1 → 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 (143) 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/auto-pr-description.yml +3 -1
  6. package/.github/workflows/release.yml +42 -1
  7. package/.github/workflows/test-coverage.yml +6 -5
  8. package/.github/workflows/test-publish.yml +36 -0
  9. package/.husky/pre-commit +10 -0
  10. package/.husky/pre-push +13 -0
  11. package/.releaserc.js +3 -3
  12. package/CHANGELOG.md +184 -0
  13. package/README.md +53 -34
  14. package/bin/odac.js +181 -49
  15. package/client/odac.js +878 -995
  16. package/docs/backend/01-overview/03-development-server.md +39 -46
  17. package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
  18. package/docs/backend/03-config/00-configuration-overview.md +15 -6
  19. package/docs/backend/03-config/01-database-connection.md +3 -3
  20. package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
  21. package/docs/backend/03-config/03-request-timeout.md +1 -1
  22. package/docs/backend/03-config/04-environment-variables.md +4 -4
  23. package/docs/backend/03-config/05-early-hints.md +2 -2
  24. package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
  25. package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
  26. package/docs/backend/04-routing/07-cron-jobs.md +17 -1
  27. package/docs/backend/04-routing/09-websocket.md +29 -0
  28. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
  29. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
  30. package/docs/backend/05-controllers/03-controller-classes.md +61 -55
  31. package/docs/backend/05-forms/01-custom-forms.md +103 -95
  32. package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
  33. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
  34. package/docs/backend/07-views/02-rendering-a-view.md +1 -1
  35. package/docs/backend/07-views/03-variables.md +5 -5
  36. package/docs/backend/07-views/04-request-data.md +1 -1
  37. package/docs/backend/07-views/08-backend-javascript.md +1 -1
  38. package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
  39. package/docs/backend/08-database/01-getting-started.md +100 -0
  40. package/docs/backend/08-database/02-basics.md +136 -0
  41. package/docs/backend/08-database/03-advanced.md +84 -0
  42. package/docs/backend/08-database/04-migrations.md +48 -0
  43. package/docs/backend/09-validation/01-the-validator-service.md +1 -0
  44. package/docs/backend/10-authentication/03-register.md +9 -2
  45. package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
  46. package/docs/backend/10-authentication/05-session-management.md +16 -2
  47. package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
  48. package/docs/backend/10-authentication/07-magic-links.md +134 -0
  49. package/docs/backend/11-mail/01-the-mail-service.md +118 -28
  50. package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
  51. package/docs/backend/13-utilities/01-odac-var.md +7 -7
  52. package/docs/backend/13-utilities/02-ipc.md +73 -0
  53. package/docs/frontend/01-overview/01-introduction.md +5 -1
  54. package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
  55. package/docs/index.json +21 -125
  56. package/eslint.config.mjs +5 -47
  57. package/jest.config.js +1 -1
  58. package/package.json +16 -7
  59. package/src/Auth.js +414 -121
  60. package/src/Config.js +12 -7
  61. package/src/Database.js +188 -0
  62. package/src/Env.js +3 -1
  63. package/src/Ipc.js +337 -0
  64. package/src/Lang.js +9 -2
  65. package/src/Mail.js +408 -37
  66. package/src/Odac.js +105 -40
  67. package/src/Request.js +71 -49
  68. package/src/Route/Cron.js +62 -18
  69. package/src/Route/Internal.js +215 -12
  70. package/src/Route/Middleware.js +7 -2
  71. package/src/Route.js +372 -109
  72. package/src/Server.js +118 -12
  73. package/src/Storage.js +169 -0
  74. package/src/Token.js +6 -4
  75. package/src/Validator.js +95 -3
  76. package/src/Var.js +22 -6
  77. package/src/View/EarlyHints.js +43 -33
  78. package/src/View/Form.js +210 -28
  79. package/src/View.js +108 -7
  80. package/src/WebSocket.js +18 -3
  81. package/template/odac.json +5 -0
  82. package/template/package.json +3 -1
  83. package/template/route/www.js +12 -10
  84. package/template/view/content/home.html +3 -3
  85. package/template/view/head/main.html +2 -2
  86. package/test/Client.test.js +168 -0
  87. package/test/Config.test.js +112 -0
  88. package/test/Lang.test.js +92 -0
  89. package/test/Odac.test.js +86 -0
  90. package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
  91. package/test/{framework/Route.test.js → Route.test.js} +1 -1
  92. package/test/{framework/View → View}/EarlyHints.test.js +1 -1
  93. package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
  94. package/test/scripts/check-coverage.js +4 -4
  95. package/docs/backend/08-database/01-database-connection.md +0 -99
  96. package/docs/backend/08-database/02-using-mysql.md +0 -322
  97. package/src/Mysql.js +0 -575
  98. package/template/config.json +0 -5
  99. package/test/cli/Cli.test.js +0 -36
  100. package/test/core/Candy.test.js +0 -234
  101. package/test/core/Commands.test.js +0 -538
  102. package/test/core/Config.test.js +0 -1432
  103. package/test/core/Lang.test.js +0 -250
  104. package/test/core/Process.test.js +0 -156
  105. package/test/server/Api.test.js +0 -647
  106. package/test/server/DNS.test.js +0 -2050
  107. package/test/server/DNS.test.js.bak +0 -2084
  108. package/test/server/Hub.test.js +0 -497
  109. package/test/server/Log.test.js +0 -73
  110. package/test/server/Mail.account.test_.js +0 -460
  111. package/test/server/Mail.init.test_.js +0 -411
  112. package/test/server/Mail.test_.js +0 -1340
  113. package/test/server/SSL.test_.js +0 -1491
  114. package/test/server/Server.test.js +0 -765
  115. package/test/server/Service.test_.js +0 -1127
  116. package/test/server/Subdomain.test.js +0 -440
  117. package/test/server/Web/Firewall.test.js +0 -175
  118. package/test/server/Web/Proxy.test.js +0 -397
  119. package/test/server/Web.test.js +0 -1494
  120. package/test/server/__mocks__/acme-client.js +0 -17
  121. package/test/server/__mocks__/bcrypt.js +0 -50
  122. package/test/server/__mocks__/child_process.js +0 -389
  123. package/test/server/__mocks__/crypto.js +0 -432
  124. package/test/server/__mocks__/fs.js +0 -450
  125. package/test/server/__mocks__/globalOdac.js +0 -227
  126. package/test/server/__mocks__/http.js +0 -575
  127. package/test/server/__mocks__/https.js +0 -272
  128. package/test/server/__mocks__/index.js +0 -249
  129. package/test/server/__mocks__/mail/server.js +0 -100
  130. package/test/server/__mocks__/mail/smtp.js +0 -31
  131. package/test/server/__mocks__/mailparser.js +0 -81
  132. package/test/server/__mocks__/net.js +0 -369
  133. package/test/server/__mocks__/node-forge.js +0 -328
  134. package/test/server/__mocks__/os.js +0 -320
  135. package/test/server/__mocks__/path.js +0 -291
  136. package/test/server/__mocks__/selfsigned.js +0 -8
  137. package/test/server/__mocks__/server/src/mail/server.js +0 -100
  138. package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
  139. package/test/server/__mocks__/smtp-server.js +0 -106
  140. package/test/server/__mocks__/sqlite3.js +0 -394
  141. package/test/server/__mocks__/testFactories.js +0 -299
  142. package/test/server/__mocks__/testHelpers.js +0 -363
  143. package/test/server/__mocks__/tls.js +0 -229
@@ -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