odac 1.0.0 → 1.1.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 (61) hide show
  1. package/.github/workflows/auto-pr-description.yml +3 -1
  2. package/CHANGELOG.md +127 -0
  3. package/README.md +39 -36
  4. package/bin/odac.js +1 -31
  5. package/client/odac.js +871 -994
  6. package/docs/backend/01-overview/03-development-server.md +7 -7
  7. package/docs/backend/02-structure/01-typical-project-layout.md +1 -0
  8. package/docs/backend/03-config/00-configuration-overview.md +9 -0
  9. package/docs/backend/03-config/01-database-connection.md +1 -1
  10. package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
  11. package/docs/backend/04-routing/09-websocket.md +29 -0
  12. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
  13. package/docs/backend/05-controllers/03-controller-classes.md +27 -41
  14. package/docs/backend/05-forms/01-custom-forms.md +103 -95
  15. package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
  16. package/docs/backend/07-views/02-rendering-a-view.md +1 -1
  17. package/docs/backend/07-views/03-variables.md +5 -5
  18. package/docs/backend/07-views/04-request-data.md +1 -1
  19. package/docs/backend/07-views/08-backend-javascript.md +1 -1
  20. package/docs/backend/08-database/01-getting-started.md +100 -0
  21. package/docs/backend/08-database/02-basics.md +136 -0
  22. package/docs/backend/08-database/03-advanced.md +84 -0
  23. package/docs/backend/08-database/04-migrations.md +48 -0
  24. package/docs/backend/09-validation/01-the-validator-service.md +1 -0
  25. package/docs/backend/10-authentication/03-register.md +8 -1
  26. package/docs/backend/10-authentication/04-odac-register-forms.md +46 -46
  27. package/docs/backend/10-authentication/05-session-management.md +1 -1
  28. package/docs/backend/10-authentication/06-odac-login-forms.md +48 -48
  29. package/docs/backend/10-authentication/07-magic-links.md +134 -0
  30. package/docs/backend/11-mail/01-the-mail-service.md +118 -28
  31. package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
  32. package/docs/backend/13-utilities/01-odac-var.md +7 -7
  33. package/docs/backend/13-utilities/02-ipc.md +73 -0
  34. package/docs/frontend/01-overview/01-introduction.md +5 -1
  35. package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
  36. package/docs/index.json +16 -124
  37. package/eslint.config.mjs +5 -47
  38. package/package.json +9 -4
  39. package/src/Auth.js +362 -104
  40. package/src/Config.js +7 -2
  41. package/src/Database.js +188 -0
  42. package/src/Ipc.js +330 -0
  43. package/src/Mail.js +408 -37
  44. package/src/Odac.js +65 -9
  45. package/src/Request.js +70 -48
  46. package/src/Route/Cron.js +4 -1
  47. package/src/Route/Internal.js +214 -11
  48. package/src/Route/Middleware.js +7 -2
  49. package/src/Route.js +106 -26
  50. package/src/Server.js +80 -11
  51. package/src/Storage.js +165 -0
  52. package/src/Validator.js +94 -2
  53. package/src/View/Form.js +193 -17
  54. package/src/View.js +46 -1
  55. package/src/WebSocket.js +18 -3
  56. package/template/config.json +1 -1
  57. package/template/route/www.js +12 -10
  58. package/test/core/{Candy.test.js → Odac.test.js} +2 -2
  59. package/docs/backend/08-database/01-database-connection.md +0 -99
  60. package/docs/backend/08-database/02-using-mysql.md +0 -322
  61. package/src/Mysql.js +0 -575
package/src/Mail.js CHANGED
@@ -1,5 +1,9 @@
1
- const axios = require('axios')
1
+ const net = require('net')
2
+ const nodeCrypto = require('crypto')
2
3
  const fs = require('fs')
4
+ // const Form = require('./View/Form')
5
+
6
+ const CACHE_DIR = './storage/.cache'
3
7
 
4
8
  class Mail {
5
9
  #header = {}
@@ -7,11 +11,273 @@ class Mail {
7
11
  #subject = ''
8
12
  #template
9
13
  #to
14
+ #htmlContent
15
+ #textContent
10
16
 
11
17
  constructor(template) {
12
18
  this.#template = template
13
19
  }
14
20
 
21
+ #functions = {
22
+ '{!!': {
23
+ function: '${await ',
24
+ close: '!!}',
25
+ end: '}'
26
+ },
27
+ '{{--': {
28
+ function: '`; /*',
29
+ close: '--}}',
30
+ end: '*/ html += `'
31
+ },
32
+ '{{': {
33
+ function: '${Odac.Var(await ',
34
+ close: '}}',
35
+ end: ').html().replace(/\\n/g, "<br>")}'
36
+ },
37
+ break: {
38
+ function: 'break;',
39
+ arguments: {}
40
+ },
41
+ component: {},
42
+ continue: {
43
+ function: 'continue;',
44
+ arguments: {}
45
+ },
46
+ mysql: {},
47
+ elseif: {
48
+ function: '} else if(await ($condition)){',
49
+ arguments: {
50
+ condition: true
51
+ }
52
+ },
53
+ else: {
54
+ function: '} else {'
55
+ },
56
+ fetch: {},
57
+ for: {
58
+ function: '{ let _arr = $constructor; for(let $key in _arr){ let $value = _arr[$key];',
59
+ end: '}}',
60
+ arguments: {
61
+ var: null,
62
+ get: null,
63
+ key: 'key',
64
+ value: 'value'
65
+ }
66
+ },
67
+ if: {
68
+ function: 'if(await ($condition)){',
69
+ arguments: {
70
+ condition: true
71
+ }
72
+ },
73
+ '<odac:js>': {
74
+ end: ' html += `',
75
+ function: '`; ',
76
+ close: '</odac:js>'
77
+ },
78
+ lazy: {},
79
+ list: {
80
+ arguments: {
81
+ var: null,
82
+ get: null,
83
+ key: 'key',
84
+ value: 'value'
85
+ },
86
+ end: '}}',
87
+ function: '{ let _arr = $constructor; for(let $key in _arr){ let $value = _arr[$key];',
88
+ replace: 'ul'
89
+ },
90
+ while: {
91
+ function: 'while(await ($condition)){',
92
+ arguments: {
93
+ condition: true
94
+ }
95
+ }
96
+ }
97
+
98
+ #parseOdacTag(content) {
99
+ // Parse backend comments
100
+ content = content.replace(/<!--odac([\s\S]*?)(?:odac-->|-->)/g, () => '')
101
+
102
+ // Parse <script:odac> tags
103
+ content = content.replace(/<script:odac([^>]*)>([\s\S]*?)<\/script:odac>/g, (fullMatch, attributes, jsContent) => {
104
+ return `<odac:js>${jsContent}</odac:js>`
105
+ })
106
+
107
+ content = content.replace(/<odac:else\s*\/>/g, '<odac:else>')
108
+ content = content.replace(/<odac:elseif\s+([^>]*?)\/>/g, '<odac:elseif $1>')
109
+
110
+ content = content.replace(/<odac([^>]*?)\/>/g, (fullMatch, attributes) => {
111
+ attributes = attributes.trim()
112
+ const attrs = {}
113
+ const attrRegex = /(\w+)(?:=(["'])((?:(?!\2).)*)\2|=([^\s>]+))?/g
114
+ let match
115
+ while ((match = attrRegex.exec(attributes))) {
116
+ const key = match[1]
117
+ const value = match[3] !== undefined ? match[3] : match[4] !== undefined ? match[4] : true
118
+ attrs[key] = value
119
+ }
120
+
121
+ if (attrs.get) return `{{ get('${attrs.get}') || '' }}`
122
+ else if (attrs.var) return attrs.raw ? `{!! ${attrs.var} !!}` : `{{ ${attrs.var} }}`
123
+
124
+ return fullMatch
125
+ })
126
+
127
+ let depth = 0
128
+ let maxDepth = 10
129
+ while (depth < maxDepth && content.includes('<odac')) {
130
+ const before = content
131
+ content = content.replace(/<odac([^>]*)>((?:(?!<odac)[\s\S])*?)<\/odac>/g, (fullMatch, attributes, innerContent) => {
132
+ attributes = attributes.trim()
133
+ innerContent = innerContent.trim()
134
+
135
+ const attrs = {}
136
+ const attrRegex = /(\w+)(?:=(["'])((?:(?!\2).)*)\2|=([^\s>]+))?/g
137
+ let match
138
+ while ((match = attrRegex.exec(attributes))) {
139
+ const key = match[1]
140
+ const value = match[3] !== undefined ? match[3] : match[4] !== undefined ? match[4] : true
141
+ attrs[key] = value
142
+ }
143
+
144
+ if (attrs.get) return `{{ get('${attrs.get}') || '' }}`
145
+ else if (attrs.var) return attrs.raw ? `{!! ${attrs.var} !!}` : `{{ ${attrs.var} }}`
146
+ else if (attrs.t || attrs.translate)
147
+ return `{{ '${innerContent}' }}` // Simple fallback for mail
148
+ else return `{{ '${innerContent}' }}`
149
+ })
150
+ if (before === content) break
151
+ depth++
152
+ }
153
+
154
+ return content
155
+ }
156
+
157
+ async #render(file, data) {
158
+ const fd = fs.openSync(file, 'r')
159
+ let mtime, content
160
+ try {
161
+ mtime = fs.fstatSync(fd).mtimeMs
162
+ content = fs.readFileSync(fd, 'utf8')
163
+ } finally {
164
+ fs.closeSync(fd)
165
+ }
166
+
167
+ // Since mail doesn't have a persistent Odac instance access like View cache, we manage a simple cache or just re-compile.
168
+ // For performance in emails (usually background), re-compiling is okay, but caching is better.
169
+ // Let's use global Odac.View.cache if available or local.
170
+ if (!Odac.View) Odac.View = {}
171
+ if (!Odac.View.cache) Odac.View.cache = {}
172
+
173
+ if (Odac.View.cache[file]?.mtime !== mtime) {
174
+ // No Form options needed normally for simplified email templates, but keeping Form.parse for consistency if needed
175
+ // content = Form.parse(content, {Request: {}, ...Odac}) // Partially mock if Form needs it, but Form usually needs full Request.
176
+ // Skipping Form.parse for mail for now unless requested, as it relies on Session/Request heavily.
177
+ // User asked for "View file rendering", usually meaning logic tags.
178
+
179
+ const jsBlocks = []
180
+ content = content.replace(/<script:odac([^>]*)>([\s\S]*?)<\/script:odac>/g, (match, attrs, jsContent) => {
181
+ const placeholder = `___ODAC_JS_BLOCK_${jsBlocks.length}___`
182
+ jsBlocks.push(jsContent)
183
+ return `<script:odac${attrs}>${placeholder}</script:odac>`
184
+ })
185
+
186
+ content = this.#parseOdacTag(content)
187
+ content = content.replace(/`/g, '\\\\`').replace(/\$\{/g, '\\\\${')
188
+
189
+ jsBlocks.forEach((jsContent, index) => {
190
+ content = content.replace(`___ODAC_JS_BLOCK_${index}___`, jsContent)
191
+ })
192
+
193
+ let result = 'html += `\n' + content + '\n`'
194
+ content = content.split('\n')
195
+
196
+ for (let key in this.#functions) {
197
+ let att = ''
198
+ let func = this.#functions[key]
199
+ let matches = func.close
200
+ ? result.match(new RegExp(`${key}[\\s\\S]*?${func.close}`, 'g'))
201
+ : result.match(new RegExp(`<odac:${key}(?:\\s+[^>]*?(?:"[^"]*"|'[^']*'|[^"'>])*)?>`, 'g'))
202
+ if (!matches) continue
203
+ for (let match of matches) {
204
+ let matchForParsing = match
205
+ if (!func.close) matchForParsing = matchForParsing.replace(/^<odac:/, '').replace(/>$/, '')
206
+ const attrRegex = /(\w+)(?:=(["'])((?:(?!\2).)*)\2|=([^\s>]+))?/g
207
+ let attrMatch
208
+ const args = []
209
+ while ((attrMatch = attrRegex.exec(matchForParsing))) {
210
+ args.push(attrMatch[0])
211
+ }
212
+ let vars = {}
213
+ if (func.arguments)
214
+ for (let arg of args) {
215
+ const argRegex = /(\w+)(?:=(["'])((?:(?!\2).)*)\2|=([^\s>]+))?/
216
+ const argMatch = argRegex.exec(arg)
217
+ if (!argMatch) continue
218
+ const argKey = argMatch[1]
219
+ const value = argMatch[3] !== undefined ? argMatch[3] : argMatch[4] !== undefined ? argMatch[4] : true
220
+ if (func.arguments[argKey] === undefined) {
221
+ att += `${argKey}="${value}"`
222
+ continue
223
+ }
224
+ vars[argKey] = value
225
+ }
226
+ if (!func.function) continue
227
+ let fun = func.function
228
+
229
+ // Simplified logic for loop
230
+ if (key === 'for' || key === 'list') {
231
+ let constructor
232
+ if (vars.var) {
233
+ constructor = `await ${vars.var}`
234
+ delete vars.var
235
+ }
236
+ fun = fun.replace(/\$constructor/g, constructor)
237
+ }
238
+
239
+ for (let argKey in func.arguments) {
240
+ if (argKey === 'var' || argKey === 'get') continue
241
+ if (vars[argKey] === undefined) vars[argKey] = func.arguments[argKey]
242
+ fun = fun.replace(new RegExp(`\\$${argKey}`, 'g'), vars[argKey])
243
+ }
244
+ if (func.close) {
245
+ result = result.replace(match, fun + match.substring(key.length, match.length - func.close.length) + func.end)
246
+ } else {
247
+ result = result.replace(match, (func.replace ? `<${[func.replace, att].join(' ')}>` : '') + '`; ' + fun + ' html += `')
248
+ result = result.replace(`</odac:${key}>`, '`; ' + (func.end ?? '}') + ' html += `' + (func.replace ? `</${func.replace}>` : ''))
249
+ }
250
+ }
251
+ }
252
+
253
+ let cache = `${nodeCrypto.createHash('md5').update(file).digest('hex')}`
254
+ if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, {recursive: true})
255
+ fs.writeFileSync(
256
+ `${CACHE_DIR}/${cache}`,
257
+ `module.exports = async (Odac, data, get, __) => {\n
258
+ // Destructure data keys into local scope variables
259
+ ${Object.keys(data)
260
+ .map(k => `let ${k} = data['${k}'];`)
261
+ .join('\n')}
262
+ let html = '';\n${result}\nreturn html.trim()\n}`
263
+ )
264
+ delete require.cache[require.resolve(`${__dir}/${CACHE_DIR}/${cache}`)]
265
+ Odac.View.cache[file] = {mtime: mtime, cache: cache}
266
+ }
267
+
268
+ try {
269
+ return await require(`${__dir}/${CACHE_DIR}/${Odac.View.cache[file].cache}`)(
270
+ Odac,
271
+ data,
272
+ key => data[key],
273
+ (...args) => (Odac.Lang ? Odac.Lang.get(...args) : args[0])
274
+ )
275
+ } catch (e) {
276
+ console.error(e)
277
+ return ''
278
+ }
279
+ }
280
+
15
281
  header(header) {
16
282
  this.#header = header
17
283
  return this
@@ -27,34 +293,108 @@ class Mail {
27
293
  return this
28
294
  }
29
295
 
30
- to(email) {
31
- email = {value: [{address: email}]}
32
- this.#to = email
296
+ to(email, name = '') {
297
+ this.#to = {value: [{address: email, name: name}]}
298
+ return this
299
+ }
300
+
301
+ html(content) {
302
+ this.#htmlContent = content
33
303
  return this
34
304
  }
35
305
 
36
- send(data) {
306
+ text(content) {
307
+ this.#textContent = content
308
+ return this
309
+ }
310
+
311
+ #encode(text) {
312
+ if (!text) return ''
313
+ // eslint-disable-next-line
314
+ if (/^[\x00-\x7F]*$/.test(text)) return text
315
+ return '=?UTF-8?B?' + Buffer.from(text).toString('base64') + '?='
316
+ }
317
+
318
+ #stripHtml(html) {
319
+ if (!html) return ''
320
+
321
+ let text = html
322
+ // Recursively remove script and style tags to handle nested injections
323
+ // Single-pass removal for plain text generation.
324
+ // Recursive removal (do-while) is dangerous (ReDoS) and unnecessary for text/plain output.
325
+ text = text.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gim, '')
326
+ text = text.replace(/<[^>]+>/g, '')
327
+
328
+ return text.replace(/\s+/g, ' ').trim()
329
+ }
330
+
331
+ send(data = {}) {
37
332
  return new Promise(resolve => {
38
- if (!fs.existsSync(__dir + '/view/mail/' + this.#template + '.html')) return console.log('Template not found') && false
39
- if (!this.#from || !this.#subject || !this.#to) return console.log('From, Subject and To fields are required') && false
40
- if (!Odac.Var(this.#from.email).is('email')) return console.log('From field is not a valid e-mail address') && false
41
- if (!Odac.Var(this.#to.value[0].address).is('email')) return console.log('To field is not a valid e-mail address') && false
42
- if (!this.#header['From']) this.#header['From'] = `${this.#from.name} <${this.#from.email}>`
43
- if (!this.#header['To']) this.#header['To'] = this.#to
44
- if (!this.#header['Subject']) this.#header['Subject'] = this.#subject
45
- if (!this.#header['Message-ID']) this.#header['Message-ID'] = `<${crypto.randomBytes(16).toString('hex')}-${Date.now()}@odac>`
46
- if (!this.#header['Content-Transfer-Encoding']) this.#header['Content-Transfer-Encoding'] = 'quoted-printable'
47
- if (!this.#header['Date']) this.#header['Date'] = new Date().toUTCString()
48
- if (!this.#header['Content-Type'])
49
- this.#header['Content-Type'] = 'multipart/alternative; boundary="----=' + crypto.randomBytes(32).toString('hex') + '"'
50
- if (!this.#header['X-Mailer']) this.#header['X-Mailer'] = 'Odac'
51
- if (!this.#header['MIME-Version']) this.#header['MIME-Version'] = '1.0'
52
- let content = fs.readFileSync(__dir + '/view/mail/' + this.#template + '.html').toString()
53
- for (const iterator of Object.keys(data)) content = content.replace(new RegExp(`{${iterator}}`, 'g'), data[iterator])
54
- axios
55
- .post(
56
- 'http://127.0.0.1:1453',
57
- {
333
+ ;(async () => {
334
+ try {
335
+ if (!this.#from || !this.#subject || !this.#to) {
336
+ console.error('[Mail] Missing required fields: From, Subject, or To')
337
+ return resolve(false)
338
+ }
339
+
340
+ if (!Odac.Var(this.#from.email).is('email')) {
341
+ console.error('[Mail] From field is not a valid e-mail address')
342
+ return resolve(false)
343
+ }
344
+
345
+ if (!Odac.Var(this.#to.value[0].address).is('email')) {
346
+ console.error('[Mail] To field is not a valid e-mail address')
347
+ return resolve(false)
348
+ }
349
+
350
+ let htmlContent = ''
351
+ let textContent = ''
352
+
353
+ if (this.#template) {
354
+ if (!fs.existsSync(__dir + '/view/mail/' + this.#template + '.html')) {
355
+ console.error(`[Mail] Template not found: ${__dir}/view/mail/${this.#template}.html`)
356
+ return resolve(false)
357
+ }
358
+ htmlContent = await this.#render(__dir + '/view/mail/' + this.#template + '.html', data)
359
+ textContent = this.#stripHtml(htmlContent)
360
+ } else {
361
+ if (this.#htmlContent) htmlContent = this.#htmlContent
362
+ if (this.#textContent) textContent = this.#textContent
363
+
364
+ if (!htmlContent && !textContent) {
365
+ console.error('[Mail] No content provided (Template, HTML, or Text)')
366
+ return resolve(false)
367
+ }
368
+
369
+ // If only HTML is provided, auto-generate text
370
+ if (htmlContent && !textContent) {
371
+ textContent = this.#stripHtml(htmlContent)
372
+ }
373
+ }
374
+
375
+ if (!this.#header['From']) this.#header['From'] = `${this.#encode(this.#from.name)} <${this.#from.email}>`
376
+ if (!this.#header['To']) {
377
+ const t = this.#to.value[0]
378
+ this.#header['To'] = t.name ? `${this.#encode(t.name)} <${t.address}>` : t.address
379
+ }
380
+ if (!this.#header['Subject']) this.#header['Subject'] = this.#encode(this.#subject)
381
+ if (!this.#header['Message-ID']) this.#header['Message-ID'] = `<${nodeCrypto.randomBytes(16).toString('hex')}-${Date.now()}@odac>`
382
+
383
+ if (!this.#header['Date']) this.#header['Date'] = new Date().toUTCString()
384
+ if (!this.#header['Content-Type']) {
385
+ if (htmlContent) {
386
+ this.#header['Content-Type'] =
387
+ 'multipart/alternative; charset=UTF-8; boundary="----=' + nodeCrypto.randomBytes(32).toString('hex') + '"'
388
+ } else {
389
+ this.#header['Content-Type'] = 'text/plain; charset=UTF-8'
390
+ }
391
+ }
392
+ if (!this.#header['X-Mailer']) this.#header['X-Mailer'] = 'ODAC'
393
+ if (!this.#header['MIME-Version']) this.#header['MIME-Version'] = '1.0'
394
+
395
+ const client = new net.Socket()
396
+ const payload = {
397
+ auth: process.env.ODAC_API_KEY,
58
398
  action: 'mail.send',
59
399
  data: [
60
400
  {
@@ -62,22 +402,53 @@ class Mail {
62
402
  from: {value: [{address: this.#from.email, name: this.#from.name}]},
63
403
  to: this.#to,
64
404
  header: this.#header,
65
- html: content,
66
- text: content.replace(/<[^>]*>?/gm, '')
405
+ html: htmlContent,
406
+ text: textContent,
407
+ attachments: []
67
408
  }
68
409
  ]
69
- },
70
- {headers: {Authorization: Odac.Config.system.api.auth}}
71
- )
72
- .then(response => {
73
- resolve(response.data)
74
- })
75
- .catch(error => {
76
- console.log(error)
410
+ }
411
+
412
+ const socketPath = process.env.ODAC_API_SOCKET || '/var/run/odac.sock'
413
+
414
+ if (Odac.Config.debug) console.log(`[Mail] Connecting to Odac Core via Unix Socket: ${socketPath}...`)
415
+
416
+ client.connect(socketPath, () => {
417
+ if (Odac.Config.debug) console.log('[Mail] Connected to Odac Core. Sending payload...')
418
+ client.write(JSON.stringify(payload))
419
+ })
420
+
421
+ client.on('data', data => {
422
+ if (Odac.Config.debug) console.log('[Mail] Received data from server:', data.toString())
423
+ try {
424
+ const response = JSON.parse(data.toString())
425
+ resolve(response)
426
+ } catch (error) {
427
+ console.error('[Mail] Error parsing response:', error)
428
+ resolve(false)
429
+ }
430
+ client.destroy()
431
+ })
432
+
433
+ client.on('error', error => {
434
+ console.error('[Mail] Socket Error:', error)
435
+ resolve(false)
436
+ })
437
+
438
+ client.on('close', () => {
439
+ if (Odac.Config.debug) console.log('[Mail] Connection closed')
440
+ })
441
+ } catch (error) {
442
+ console.error('[Mail] Unexpected error:', error)
77
443
  resolve(false)
78
- })
444
+ }
445
+ })()
79
446
  })
80
447
  }
81
448
  }
82
449
 
83
- module.exports = Mail
450
+ module.exports = new Proxy(Mail, {
451
+ apply(target, thisArg, args) {
452
+ return new target(...args)
453
+ }
454
+ })
package/src/Odac.js CHANGED
@@ -1,9 +1,16 @@
1
1
  module.exports = {
2
2
  init: async function () {
3
3
  global.Odac = this.instance()
4
+ global.Odac.Storage = require('./Storage.js')
5
+ global.Odac.Storage.init()
6
+
4
7
  await global.Odac.Env.init()
5
8
  await global.Odac.Config.init()
6
- await global.Odac.Mysql.init()
9
+ await global.Odac.Database.init()
10
+
11
+ global.Odac.Ipc = require('./Ipc.js')
12
+ await global.Odac.Ipc.init()
13
+
7
14
  await global.Odac.Route.init()
8
15
  await global.Odac.Server.init()
9
16
  global.Odac.instance = this.instance
@@ -18,9 +25,42 @@ module.exports = {
18
25
  _odac.Config = require('./Config.js')
19
26
  _odac.Env = require('./Env.js')
20
27
  _odac.Mail = (...args) => new (require('./Mail.js'))(...args)
21
- _odac.Mysql = require('./Mysql.js')
28
+ _odac.Database = require('./Database.js')
29
+ _odac.DB = _odac.Database
22
30
  _odac.Route = global.Odac?.Route ?? new (require('./Route.js'))()
31
+
32
+ _odac._ipcSubs = []
33
+ const ipcSingleton = require('./Ipc.js')
34
+
35
+ _odac.Ipc = new Proxy(ipcSingleton, {
36
+ get(target, prop) {
37
+ if (prop === 'subscribe') {
38
+ return async (channel, callback) => {
39
+ const res = await target.subscribe(channel, callback)
40
+ _odac._ipcSubs.push({channel, callback})
41
+ return res
42
+ }
43
+ }
44
+ if (prop === 'unsubscribe') {
45
+ return async (channel, callback) => {
46
+ const res = await target.unsubscribe(channel, callback)
47
+ const index = _odac._ipcSubs.findIndex(s => s.channel === channel && s.callback === callback)
48
+ if (index > -1) _odac._ipcSubs.splice(index, 1)
49
+ return res
50
+ }
51
+ }
52
+ const value = target[prop]
53
+ if (typeof value === 'function') return value.bind(target)
54
+ return value
55
+ },
56
+ set(target, prop, value) {
57
+ target[prop] = value
58
+ return true
59
+ }
60
+ })
61
+
23
62
  _odac.Server = require('./Server.js')
63
+ _odac.Storage = require('./Storage.js')
24
64
  _odac.Var = (...args) => new (require('./Var.js'))(...args)
25
65
 
26
66
  if (req) {
@@ -57,15 +97,14 @@ module.exports = {
57
97
  _odac.cleanup = function () {
58
98
  for (const id of _odac._intervals) clearInterval(id)
59
99
  for (const id of _odac._timeouts) clearTimeout(id)
100
+ if (_odac._ipcSubs) {
101
+ for (const sub of _odac._ipcSubs) {
102
+ ipcSingleton.unsubscribe(sub.channel, sub.callback).catch(console.error)
103
+ }
104
+ }
60
105
  _odac._intervals = []
61
106
  _odac._timeouts = []
62
- }
63
-
64
- if (global.Odac?.Route?.class) {
65
- for (const name in global.Odac.Route.class) {
66
- const Module = global.Odac.Route.class[name].module
67
- _odac[name] = typeof Module === 'function' ? new Module(_odac) : Module
68
- }
107
+ _odac._ipcSubs = []
69
108
  }
70
109
 
71
110
  _odac.__ = function (...args) {
@@ -92,6 +131,9 @@ module.exports = {
92
131
  _odac.set = function (key, value) {
93
132
  return _odac.Request.set(key, value)
94
133
  }
134
+ _odac.share = function (key, value) {
135
+ return _odac.Request.share(key, value)
136
+ }
95
137
  _odac.token = function (hash) {
96
138
  return hash ? _odac.Token.check(hash) : _odac.Token.generate()
97
139
  }
@@ -105,6 +147,20 @@ module.exports = {
105
147
  _odac.Request.clearTimeout()
106
148
  return new (require('./Stream'))(_odac.Request.req, _odac.Request.res, input, _odac)
107
149
  }
150
+
151
+ if (global.Odac?.Route?.class) {
152
+ _odac.App = {}
153
+ for (const name in global.Odac.Route.class) {
154
+ const Module = global.Odac.Route.class[name].module
155
+ const instance = typeof Module === 'function' ? new Module(_odac) : Module
156
+
157
+ if (_odac[name]) {
158
+ _odac.App[name] = instance
159
+ } else {
160
+ _odac[name] = instance
161
+ }
162
+ }
163
+ }
108
164
  }
109
165
 
110
166
  return _odac