odac 1.4.10 → 1.4.12
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/CHANGELOG.md +31 -0
- package/bin/odac.js +230 -1
- package/docs/ai/skills/SKILL.md +3 -1
- package/docs/ai/skills/backend/config.md +21 -1
- package/docs/ai/skills/backend/database.md +40 -0
- package/docs/ai/skills/backend/request_response.md +21 -1
- package/docs/ai/skills/frontend/scripts.md +36 -0
- package/docs/backend/03-config/00-configuration-overview.md +25 -6
- package/docs/backend/03-config/01-database-connection.md +41 -4
- package/docs/backend/03-config/04-environment-variables.md +3 -2
- package/docs/backend/06-request-and-response/03-proxy-cache.md +77 -0
- package/docs/backend/07-views/12-scripts-and-typescript.md +156 -0
- package/docs/backend/08-database/01-getting-started.md +11 -0
- package/docs/backend/10-authentication/04-odac-register-forms.md +7 -4
- package/docs/backend/10-authentication/06-odac-login-forms.md +7 -4
- package/docs/index.json +8 -0
- package/eslint.config.mjs +20 -1
- package/package.json +2 -1
- package/src/Config.js +7 -0
- package/src/Database/Migration.js +32 -11
- package/src/Mail.js +61 -0
- package/src/Odac.js +3 -0
- package/src/Request.js +21 -0
- package/src/Route/Cron.js +10 -0
- package/src/Route.js +1 -0
- package/template/public/.gitkeep +0 -0
- package/template/{public/assets → view}/js/app.js +8 -2
- package/test/Odac/cache.test.js +54 -0
- package/test/Request/cache.test.js +95 -0
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
/* global Odac */
|
|
2
1
|
/**
|
|
3
2
|
* ODAC Template - Client-Side Application
|
|
4
3
|
*
|
|
5
|
-
* This file
|
|
4
|
+
* This file is automatically compiled by ODAC's JS/TS pipeline.
|
|
5
|
+
* Write your frontend logic here — TypeScript (.ts) and plain JavaScript (.js) both work.
|
|
6
|
+
*
|
|
7
|
+
* - Place files in view/js/ to create entry points
|
|
8
|
+
* - Files starting with _ are ignored (use them as shared imports)
|
|
9
|
+
* - Output goes to public/assets/js/{name}.js
|
|
10
|
+
*
|
|
11
|
+
* Features demonstrated:
|
|
6
12
|
* - AJAX page loading with Odac.loader() for smooth navigation
|
|
7
13
|
* - History API integration
|
|
8
14
|
* - Event delegation
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const Odac = require('../../src/Odac')
|
|
2
|
+
|
|
3
|
+
describe('Odac.cache()', () => {
|
|
4
|
+
let mockOdac
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
mockOdac = {
|
|
8
|
+
Config: {request: {timeout: 1000}},
|
|
9
|
+
Route: {routes: {www: {}}},
|
|
10
|
+
Storage: {get: jest.fn(), put: jest.fn()}
|
|
11
|
+
}
|
|
12
|
+
global.Odac = mockOdac
|
|
13
|
+
global.__dir = '/mock'
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
delete global.Odac
|
|
18
|
+
delete global.__dir
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should expose cache() as a shorthand on the instance', () => {
|
|
22
|
+
const mockReq = {
|
|
23
|
+
method: 'GET',
|
|
24
|
+
url: '/',
|
|
25
|
+
headers: {host: 'www.example.com'},
|
|
26
|
+
connection: {remoteAddress: '127.0.0.1'},
|
|
27
|
+
on: jest.fn()
|
|
28
|
+
}
|
|
29
|
+
const mockRes = {on: jest.fn(), writeHead: jest.fn(), end: jest.fn()}
|
|
30
|
+
|
|
31
|
+
const ctx = Odac.instance('id', mockReq, mockRes)
|
|
32
|
+
|
|
33
|
+
expect(typeof ctx.cache).toBe('function')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should delegate to Request.cache()', () => {
|
|
37
|
+
const mockReq = {
|
|
38
|
+
method: 'GET',
|
|
39
|
+
url: '/',
|
|
40
|
+
headers: {host: 'www.example.com'},
|
|
41
|
+
connection: {remoteAddress: '127.0.0.1'},
|
|
42
|
+
on: jest.fn()
|
|
43
|
+
}
|
|
44
|
+
const mockRes = {on: jest.fn(), writeHead: jest.fn(), end: jest.fn(), finished: false}
|
|
45
|
+
|
|
46
|
+
const ctx = Odac.instance('id', mockReq, mockRes)
|
|
47
|
+
ctx.cache(3600)
|
|
48
|
+
|
|
49
|
+
ctx.Request.print()
|
|
50
|
+
const headers = mockRes.writeHead.mock.calls[0][1]
|
|
51
|
+
expect(headers['X-ODAC-Cache']).toBe(3600)
|
|
52
|
+
expect(headers['Cache-Control']).toBe('public, max-age=3600')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const OdacRequest = require('../../src/Request')
|
|
2
|
+
|
|
3
|
+
describe('Request.cache()', () => {
|
|
4
|
+
let req, res, request
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
global.Odac = {
|
|
8
|
+
Config: {request: {timeout: 5000}},
|
|
9
|
+
Route: {routes: {www: {}}},
|
|
10
|
+
Storage: {get: jest.fn(), put: jest.fn()}
|
|
11
|
+
}
|
|
12
|
+
global.__dir = '/mock'
|
|
13
|
+
|
|
14
|
+
req = {
|
|
15
|
+
method: 'GET',
|
|
16
|
+
url: '/test',
|
|
17
|
+
headers: {host: 'www.example.com'},
|
|
18
|
+
connection: {remoteAddress: '127.0.0.1'},
|
|
19
|
+
on: jest.fn()
|
|
20
|
+
}
|
|
21
|
+
res = {
|
|
22
|
+
writeHead: jest.fn(),
|
|
23
|
+
end: jest.fn(),
|
|
24
|
+
finished: false
|
|
25
|
+
}
|
|
26
|
+
request = new OdacRequest('test-id', req, res, {
|
|
27
|
+
setTimeout: (fn, ms) => setTimeout(fn, ms)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
delete global.Odac
|
|
33
|
+
delete global.__dir
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should set X-ODAC-Cache header with the given TTL', () => {
|
|
37
|
+
request.cache(3600)
|
|
38
|
+
|
|
39
|
+
// Verify headers by printing them
|
|
40
|
+
request.print()
|
|
41
|
+
const headers = res.writeHead.mock.calls[0][1]
|
|
42
|
+
expect(headers['X-ODAC-Cache']).toBe(3600)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('should set Cache-Control to public with correct max-age', () => {
|
|
46
|
+
request.cache(7200)
|
|
47
|
+
|
|
48
|
+
request.print()
|
|
49
|
+
const headers = res.writeHead.mock.calls[0][1]
|
|
50
|
+
expect(headers['Cache-Control']).toBe('public, max-age=7200')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should override a previously set Cache-Control header', () => {
|
|
54
|
+
request.header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
|
55
|
+
request.cache(1800)
|
|
56
|
+
|
|
57
|
+
request.print()
|
|
58
|
+
const headers = res.writeHead.mock.calls[0][1]
|
|
59
|
+
expect(headers['Cache-Control']).toBe('public, max-age=1800')
|
|
60
|
+
expect(headers['X-ODAC-Cache']).toBe(1800)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should throw TypeError for non-integer values', () => {
|
|
64
|
+
expect(() => request.cache(3.5)).toThrow(TypeError)
|
|
65
|
+
expect(() => request.cache('3600')).toThrow(TypeError)
|
|
66
|
+
expect(() => request.cache(null)).toThrow(TypeError)
|
|
67
|
+
expect(() => request.cache(undefined)).toThrow(TypeError)
|
|
68
|
+
expect(() => request.cache(NaN)).toThrow(TypeError)
|
|
69
|
+
expect(() => request.cache(Infinity)).toThrow(TypeError)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should throw TypeError for zero or negative values', () => {
|
|
73
|
+
expect(() => request.cache(0)).toThrow(TypeError)
|
|
74
|
+
expect(() => request.cache(-1)).toThrow(TypeError)
|
|
75
|
+
expect(() => request.cache(-3600)).toThrow(TypeError)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should work with small TTL values', () => {
|
|
79
|
+
request.cache(1)
|
|
80
|
+
|
|
81
|
+
request.print()
|
|
82
|
+
const headers = res.writeHead.mock.calls[0][1]
|
|
83
|
+
expect(headers['X-ODAC-Cache']).toBe(1)
|
|
84
|
+
expect(headers['Cache-Control']).toBe('public, max-age=1')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should work with large TTL values', () => {
|
|
88
|
+
request.cache(86400)
|
|
89
|
+
|
|
90
|
+
request.print()
|
|
91
|
+
const headers = res.writeHead.mock.calls[0][1]
|
|
92
|
+
expect(headers['X-ODAC-Cache']).toBe(86400)
|
|
93
|
+
expect(headers['Cache-Control']).toBe('public, max-age=86400')
|
|
94
|
+
})
|
|
95
|
+
})
|