odac 1.1.0 → 1.3.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.
- package/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +42 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/release.yml +68 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +1 -1
- package/CHANGELOG.md +99 -0
- package/README.md +16 -0
- package/bin/odac.js +196 -38
- package/client/odac.js +238 -171
- package/docs/backend/01-overview/03-development-server.md +38 -45
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -26
- package/docs/backend/03-config/00-configuration-overview.md +6 -6
- package/docs/backend/03-config/01-database-connection.md +2 -2
- package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
- package/docs/backend/03-config/03-request-timeout.md +1 -1
- package/docs/backend/03-config/04-environment-variables.md +4 -4
- package/docs/backend/03-config/05-early-hints.md +2 -2
- package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
- package/docs/backend/04-routing/07-cron-jobs.md +17 -1
- package/docs/backend/04-routing/09-websocket.md +14 -1
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/03-controller-classes.md +40 -20
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +2 -2
- package/docs/backend/10-authentication/03-register.md +1 -1
- package/docs/backend/10-authentication/04-odac-register-forms.md +2 -2
- package/docs/backend/10-authentication/05-session-management.md +15 -1
- package/docs/backend/10-authentication/06-odac-login-forms.md +2 -2
- package/docs/backend/10-authentication/07-magic-links.md +1 -1
- package/docs/index.json +5 -1
- package/jest.config.js +1 -1
- package/package.json +18 -14
- package/src/Auth.js +58 -23
- package/src/Config.js +7 -7
- package/src/Database.js +1 -1
- package/src/Env.js +3 -1
- package/src/Ipc.js +7 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +19 -9
- package/src/Odac.js +56 -44
- package/src/Request.js +6 -2
- package/src/Route/Cron.js +58 -17
- package/src/Route/Internal.js +1 -1
- package/src/Route.js +375 -125
- package/src/Server.js +40 -3
- package/src/Storage.js +4 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +24 -15
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +17 -11
- package/src/View.js +68 -12
- package/src/WebSocket.js +45 -12
- package/template/package.json +3 -1
- package/template/view/content/home.html +3 -3
- package/template/view/head/main.html +2 -2
- package/test/Client.test.js +168 -0
- package/test/Config.test.js +112 -0
- package/test/Lang.test.js +92 -0
- package/test/Odac.test.js +88 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +105 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/WebSocket.test.js +238 -0
- package/test/scripts/check-coverage.js +4 -4
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Commands.test.js +0 -538
- package/test/core/Config.test.js +0 -1432
- package/test/core/Lang.test.js +0 -250
- package/test/core/Odac.test.js +0 -234
- package/test/core/Process.test.js +0 -156
- package/test/framework/WebSocket.test.js +0 -100
- package/test/server/Api.test.js +0 -647
- package/test/server/DNS.test.js +0 -2050
- package/test/server/DNS.test.js.bak +0 -2084
- package/test/server/Hub.test.js +0 -497
- package/test/server/Log.test.js +0 -73
- package/test/server/Mail.account.test_.js +0 -460
- package/test/server/Mail.init.test_.js +0 -411
- package/test/server/Mail.test_.js +0 -1340
- package/test/server/SSL.test_.js +0 -1491
- package/test/server/Server.test.js +0 -765
- package/test/server/Service.test_.js +0 -1127
- package/test/server/Subdomain.test.js +0 -440
- package/test/server/Web/Firewall.test.js +0 -175
- package/test/server/Web/Proxy.test.js +0 -397
- package/test/server/Web.test.js +0 -1494
- package/test/server/__mocks__/acme-client.js +0 -17
- package/test/server/__mocks__/bcrypt.js +0 -50
- package/test/server/__mocks__/child_process.js +0 -389
- package/test/server/__mocks__/crypto.js +0 -432
- package/test/server/__mocks__/fs.js +0 -450
- package/test/server/__mocks__/globalOdac.js +0 -227
- package/test/server/__mocks__/http.js +0 -575
- package/test/server/__mocks__/https.js +0 -272
- package/test/server/__mocks__/index.js +0 -249
- package/test/server/__mocks__/mail/server.js +0 -100
- package/test/server/__mocks__/mail/smtp.js +0 -31
- package/test/server/__mocks__/mailparser.js +0 -81
- package/test/server/__mocks__/net.js +0 -369
- package/test/server/__mocks__/node-forge.js +0 -328
- package/test/server/__mocks__/os.js +0 -320
- package/test/server/__mocks__/path.js +0 -291
- package/test/server/__mocks__/selfsigned.js +0 -8
- package/test/server/__mocks__/server/src/mail/server.js +0 -100
- package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
- package/test/server/__mocks__/smtp-server.js +0 -106
- package/test/server/__mocks__/sqlite3.js +0 -394
- package/test/server/__mocks__/testFactories.js +0 -299
- package/test/server/__mocks__/testHelpers.js +0 -363
- package/test/server/__mocks__/tls.js +0 -229
- /package/template/{config.json → odac.json} +0 -0
package/src/Odac.js
CHANGED
|
@@ -64,36 +64,41 @@ module.exports = {
|
|
|
64
64
|
_odac.Var = (...args) => new (require('./Var.js'))(...args)
|
|
65
65
|
|
|
66
66
|
if (req) {
|
|
67
|
-
_odac.Request = new (require('./Request.js'))(id, req, res, _odac)
|
|
68
|
-
_odac.Auth = new (require('./Auth.js'))(_odac.Request)
|
|
69
|
-
_odac.Token = new (require('./Token.js'))(_odac.Request)
|
|
70
|
-
_odac.Lang = new (require('./Lang.js'))(_odac)
|
|
71
|
-
if (res) {
|
|
72
|
-
_odac.View = new (require('./View.js'))(_odac)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
_odac._intervals = []
|
|
76
|
-
_odac._timeouts = []
|
|
77
67
|
_odac.setInterval = function (callback, delay, ...args) {
|
|
78
68
|
const id = setInterval(callback, delay, ...args)
|
|
69
|
+
if (!_odac._intervals) _odac._intervals = []
|
|
79
70
|
_odac._intervals.push(id)
|
|
80
71
|
return id
|
|
81
72
|
}
|
|
82
73
|
_odac.setTimeout = function (callback, delay, ...args) {
|
|
83
74
|
const id = setTimeout(callback, delay, ...args)
|
|
75
|
+
if (!_odac._timeouts) _odac._timeouts = []
|
|
84
76
|
_odac._timeouts.push(id)
|
|
85
77
|
return id
|
|
86
78
|
}
|
|
87
79
|
_odac.clearInterval = function (id) {
|
|
88
|
-
const index = _odac._intervals
|
|
80
|
+
const index = _odac._intervals?.indexOf(id) ?? -1
|
|
89
81
|
if (index > -1) _odac._intervals.splice(index, 1)
|
|
90
82
|
clearInterval(id)
|
|
91
83
|
}
|
|
92
84
|
_odac.clearTimeout = function (id) {
|
|
93
|
-
const index = _odac._timeouts
|
|
85
|
+
const index = _odac._timeouts?.indexOf(id) ?? -1
|
|
94
86
|
if (index > -1) _odac._timeouts.splice(index, 1)
|
|
95
87
|
clearTimeout(id)
|
|
96
88
|
}
|
|
89
|
+
|
|
90
|
+
_odac._intervals = []
|
|
91
|
+
_odac._timeouts = []
|
|
92
|
+
|
|
93
|
+
if (typeof req === 'object') {
|
|
94
|
+
_odac.Request = new (require('./Request.js'))(id, req, res, _odac)
|
|
95
|
+
_odac.Auth = new (require('./Auth.js'))(_odac.Request)
|
|
96
|
+
_odac.Token = new (require('./Token.js'))(_odac.Request)
|
|
97
|
+
}
|
|
98
|
+
_odac.Lang = new (require('./Lang.js'))(_odac)
|
|
99
|
+
if (res) {
|
|
100
|
+
_odac.View = new (require('./View.js'))(_odac)
|
|
101
|
+
}
|
|
97
102
|
_odac.cleanup = function () {
|
|
98
103
|
for (const id of _odac._intervals) clearInterval(id)
|
|
99
104
|
for (const id of _odac._timeouts) clearTimeout(id)
|
|
@@ -110,42 +115,49 @@ module.exports = {
|
|
|
110
115
|
_odac.__ = function (...args) {
|
|
111
116
|
return _odac.Lang.get(...args)
|
|
112
117
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
if (typeof req === 'object') {
|
|
119
|
+
_odac.abort = function (code) {
|
|
120
|
+
return _odac.Request.abort(code)
|
|
121
|
+
}
|
|
122
|
+
_odac.cookie = function (key, value, options) {
|
|
123
|
+
return _odac.Request.cookie(key, value, options)
|
|
124
|
+
}
|
|
125
|
+
_odac.direct = function (url) {
|
|
126
|
+
return _odac.Request.redirect(url)
|
|
127
|
+
}
|
|
121
128
|
}
|
|
122
129
|
_odac.env = function (key, defaultValue) {
|
|
123
130
|
return _odac.Env.get(key, defaultValue)
|
|
124
131
|
}
|
|
125
|
-
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
_odac.
|
|
148
|
-
|
|
132
|
+
if (typeof req === 'object') {
|
|
133
|
+
_odac.return = function (data) {
|
|
134
|
+
return _odac.Request.end(data)
|
|
135
|
+
}
|
|
136
|
+
_odac.request = function (key) {
|
|
137
|
+
return _odac.Request.request(key)
|
|
138
|
+
}
|
|
139
|
+
_odac.set = function (key, value) {
|
|
140
|
+
return _odac.Request.set(key, value)
|
|
141
|
+
}
|
|
142
|
+
_odac.session = function (key, value) {
|
|
143
|
+
return _odac.Request.session(key, value)
|
|
144
|
+
}
|
|
145
|
+
_odac.share = function (key, value) {
|
|
146
|
+
return _odac.Request.share(key, value)
|
|
147
|
+
}
|
|
148
|
+
_odac.token = function (hash) {
|
|
149
|
+
return hash ? _odac.Token.check(hash) : _odac.Token.generate()
|
|
150
|
+
}
|
|
151
|
+
_odac.validator = function () {
|
|
152
|
+
return new (require('./Validator.js'))(_odac.Request)
|
|
153
|
+
}
|
|
154
|
+
_odac.write = function (value) {
|
|
155
|
+
return _odac.Request.write(value)
|
|
156
|
+
}
|
|
157
|
+
_odac.stream = function (input) {
|
|
158
|
+
_odac.Request.clearTimeout()
|
|
159
|
+
return new (require('./Stream'))(_odac.Request.req, _odac.Request.res, input, _odac)
|
|
160
|
+
}
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
if (global.Odac?.Route?.class) {
|
package/src/Request.js
CHANGED
|
@@ -35,7 +35,11 @@ class OdacRequest {
|
|
|
35
35
|
if (!Odac.Route.routes[route]) route = 'www'
|
|
36
36
|
this.route = route
|
|
37
37
|
if (this.res) {
|
|
38
|
-
|
|
38
|
+
if (typeof this.#odac.setTimeout === 'function') {
|
|
39
|
+
this.#timeout = this.#odac.setTimeout(() => !this.res.finished && this.abort(408), Odac.Config.request.timeout)
|
|
40
|
+
} else {
|
|
41
|
+
this.#timeout = setTimeout(() => !this.res.finished && this.abort(408), Odac.Config.request.timeout)
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
this.#data()
|
|
41
45
|
if (!Odac.Request) Odac.Request = {}
|
|
@@ -71,7 +75,7 @@ class OdacRequest {
|
|
|
71
75
|
if (options.expires === undefined) options.expires = new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString()
|
|
72
76
|
if (options.secure === undefined) options.secure = true
|
|
73
77
|
if (options.httpOnly === undefined) options.httpOnly = true
|
|
74
|
-
if (options.sameSite === undefined) options.sameSite = '
|
|
78
|
+
if (options.sameSite === undefined) options.sameSite = 'Lax'
|
|
75
79
|
if (typeof value === 'object') value = JSON.stringify(value)
|
|
76
80
|
let cookie = `${key}=${value}`
|
|
77
81
|
for (const option of Object.keys(options)) if (options[option]) cookie += `; ${option}=${options[option]}`
|
package/src/Route/Cron.js
CHANGED
|
@@ -60,7 +60,7 @@ class Cron {
|
|
|
60
60
|
if (job.lastRun && Math.floor(unix / 86400) % condition.value !== 0) shouldRun = false
|
|
61
61
|
break
|
|
62
62
|
case 'everyWeekDay':
|
|
63
|
-
if (
|
|
63
|
+
if (condition.value !== weekDay) shouldRun = false
|
|
64
64
|
break
|
|
65
65
|
case 'everyMonth':
|
|
66
66
|
if (job.lastRun && (year * 12 + month) % condition.value !== 0) shouldRun = false
|
|
@@ -81,7 +81,9 @@ class Cron {
|
|
|
81
81
|
if (job.function || fs.existsSync(job.path)) {
|
|
82
82
|
if (!job.function) job.function = require(job.path)
|
|
83
83
|
if (job.function && typeof job.function === 'function') {
|
|
84
|
-
|
|
84
|
+
const _odac = global.Odac.instance(null, 'cron')
|
|
85
|
+
job.function(_odac)
|
|
86
|
+
if (_odac.cleanup) _odac.cleanup()
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
} catch (error) {
|
|
@@ -109,22 +111,61 @@ class Cron {
|
|
|
109
111
|
updated: new Date()
|
|
110
112
|
})
|
|
111
113
|
let id = this.#jobs.length - 1
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
114
|
+
const addCondition = (type, value) => {
|
|
115
|
+
this.#jobs[id].condition.push({type, value})
|
|
116
|
+
return chain
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const chain = {
|
|
120
|
+
minute: value => addCondition('minute', value),
|
|
121
|
+
hour: value => addCondition('hour', value),
|
|
122
|
+
day: value => addCondition('day', value),
|
|
123
|
+
at: time => {
|
|
124
|
+
if (!/^\d{1,2}:\d{1,2}$/.test(time)) throw new Error('Invalid time format for .at(). Use HH:MM')
|
|
125
|
+
const [h, m] = time.split(':')
|
|
126
|
+
addCondition('hour', parseInt(h))
|
|
127
|
+
addCondition('minute', parseInt(m))
|
|
128
|
+
return chain
|
|
129
|
+
},
|
|
130
|
+
raw: pattern => {
|
|
131
|
+
const parts = pattern.split(' ').filter(p => p.trim() !== '')
|
|
132
|
+
if (parts.length !== 5) throw new Error('Invalid cron expression. Expected 5 fields (min hour day month weekDay)')
|
|
133
|
+
|
|
134
|
+
const [min, hour, day, month, weekDay] = parts
|
|
135
|
+
const parse = (val, type, everyType) => {
|
|
136
|
+
if (val === '*') return
|
|
137
|
+
if (val.startsWith('*/') && everyType) {
|
|
138
|
+
addCondition(everyType, parseInt(val.split('/')[1]))
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (!isNaN(val)) {
|
|
142
|
+
addCondition(type, parseInt(val))
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
throw new Error(`Unsupported cron value '${val}' for ${type}`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
parse(min, 'minute', 'everyMinute')
|
|
149
|
+
parse(hour, 'hour', 'everyHour')
|
|
150
|
+
parse(day, 'day', 'everyDay')
|
|
151
|
+
parse(month, 'month', 'everyMonth')
|
|
152
|
+
parse(weekDay, 'weekDay', null)
|
|
153
|
+
|
|
154
|
+
return chain
|
|
155
|
+
},
|
|
156
|
+
weekDay: value => addCondition('weekDay', value),
|
|
157
|
+
month: value => addCondition('month', value),
|
|
158
|
+
year: value => addCondition('year', value),
|
|
159
|
+
yearDay: value => addCondition('yearDay', value),
|
|
160
|
+
everyMinute: value => addCondition('everyMinute', value),
|
|
161
|
+
everyHour: value => addCondition('everyHour', value),
|
|
162
|
+
everyDay: value => addCondition('everyDay', value),
|
|
163
|
+
everyWeekDay: value => addCondition('everyWeekDay', value),
|
|
164
|
+
everyMonth: value => addCondition('everyMonth', value),
|
|
165
|
+
everyYear: value => addCondition('everyYear', value),
|
|
166
|
+
everyYearDay: value => addCondition('everyYearDay', value)
|
|
127
167
|
}
|
|
168
|
+
return chain
|
|
128
169
|
}
|
|
129
170
|
}
|
|
130
171
|
|
package/src/Route/Internal.js
CHANGED
|
@@ -528,7 +528,7 @@ class Internal {
|
|
|
528
528
|
if (error.message === 'Database connection failed') {
|
|
529
529
|
return Odac.return({
|
|
530
530
|
result: {success: false},
|
|
531
|
-
errors: {_odac_form: 'Database not configured. Please check your
|
|
531
|
+
errors: {_odac_form: 'Database not configured. Please check your odac.json'}
|
|
532
532
|
})
|
|
533
533
|
}
|
|
534
534
|
|