odac 1.4.6 → 1.4.8
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 +37 -0
- package/client/odac.js +1 -1
- package/docs/ai/README.md +1 -1
- package/docs/ai/skills/SKILL.md +1 -1
- package/docs/ai/skills/backend/database.md +103 -12
- package/docs/ai/skills/backend/ipc.md +71 -12
- package/docs/ai/skills/backend/views.md +6 -1
- package/docs/backend/00-getting-started/01-quick-start.md +77 -0
- package/docs/backend/07-views/03-template-syntax.md +5 -0
- package/docs/backend/07-views/04-request-data.md +13 -0
- package/docs/backend/08-database/05-write-behind-cache.md +230 -0
- package/docs/backend/13-utilities/02-ipc.md +117 -0
- package/docs/index.json +10 -0
- package/package.json +1 -1
- package/src/Database/WriteBuffer.js +605 -0
- package/src/Database.js +32 -1
- package/src/Ipc.js +343 -81
- package/src/Odac.js +2 -1
- package/src/Storage.js +4 -2
- package/src/View.js +33 -18
- package/test/Database/WriteBuffer/_recoverFromCheckpoint.test.js +207 -0
- package/test/Database/WriteBuffer/buffer.test.js +143 -0
- package/test/Database/WriteBuffer/flush.test.js +192 -0
- package/test/Database/WriteBuffer/get.test.js +72 -0
- package/test/Database/WriteBuffer/increment.test.js +118 -0
- package/test/Database/WriteBuffer/update.test.js +178 -0
- package/test/Ipc/hset.test.js +59 -0
- package/test/Ipc/incrBy.test.js +65 -0
- package/test/Ipc/lock.test.js +62 -0
- package/test/Ipc/rpush.test.js +68 -0
- package/test/Ipc/sadd.test.js +68 -0
- package/test/View/addNavigateAttribute.test.js +53 -0
- package/test/View/print.test.js +45 -1
- package/test/View/tags.test.js +132 -0
package/test/View/print.test.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
const path = require('path')
|
|
1
3
|
const View = require('../../src/View')
|
|
2
4
|
|
|
3
5
|
describe('View.print()', () => {
|
|
@@ -5,15 +7,57 @@ describe('View.print()', () => {
|
|
|
5
7
|
let mockOdac
|
|
6
8
|
|
|
7
9
|
beforeEach(() => {
|
|
10
|
+
global.__dir = path.resolve(__dirname, '../../')
|
|
8
11
|
mockOdac = {
|
|
9
12
|
Config: {view: {earlyHints: {enabled: false}}},
|
|
10
13
|
View: {},
|
|
11
|
-
Request: {
|
|
14
|
+
Request: {
|
|
15
|
+
req: {url: '/test'},
|
|
16
|
+
res: {finished: false},
|
|
17
|
+
isAjaxLoad: false,
|
|
18
|
+
header: jest.fn(),
|
|
19
|
+
end: jest.fn(),
|
|
20
|
+
get: jest.fn(),
|
|
21
|
+
hasEarlyHints: jest.fn().mockReturnValue(false)
|
|
22
|
+
},
|
|
23
|
+
Lang: {get: jest.fn()}
|
|
12
24
|
}
|
|
13
25
|
view = new View(mockOdac)
|
|
14
26
|
})
|
|
15
27
|
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
jest.restoreAllMocks()
|
|
30
|
+
delete global.__dir
|
|
31
|
+
})
|
|
32
|
+
|
|
16
33
|
it('should be a function', () => {
|
|
17
34
|
expect(typeof view.print).toBe('function')
|
|
18
35
|
})
|
|
36
|
+
|
|
37
|
+
describe('AJAX Rendering Edge Cases', () => {
|
|
38
|
+
it("should extract title from priority parts even if their view path hasn't changed", async () => {
|
|
39
|
+
const headDir = path.join(global.__dir, 'view/head/inc')
|
|
40
|
+
const contentDir = path.join(global.__dir, 'view/content/pages')
|
|
41
|
+
await fs.mkdir(headDir, {recursive: true})
|
|
42
|
+
await fs.mkdir(contentDir, {recursive: true})
|
|
43
|
+
|
|
44
|
+
await fs.writeFile(path.join(headDir, 'head.html'), '<title>Dynamic Title</title>')
|
|
45
|
+
await fs.writeFile(path.join(contentDir, 'test.html'), '<h1>Test</h1>')
|
|
46
|
+
|
|
47
|
+
mockOdac.Request.isAjaxLoad = true
|
|
48
|
+
mockOdac.Request.ajaxLoad = ['head', 'content']
|
|
49
|
+
mockOdac.Request.clientParts = {head: 'inc/head'}
|
|
50
|
+
mockOdac.Request.page = 'test_page'
|
|
51
|
+
|
|
52
|
+
view.set({head: 'inc/head', content: 'pages/test'})
|
|
53
|
+
|
|
54
|
+
await view.print()
|
|
55
|
+
|
|
56
|
+
expect(mockOdac.Request.end).toHaveBeenCalled()
|
|
57
|
+
|
|
58
|
+
const payload = mockOdac.Request.end.mock.calls[0][0]
|
|
59
|
+
expect(payload.output.head).toBeUndefined()
|
|
60
|
+
expect(payload.title).toBe('Dynamic Title')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
19
63
|
})
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const fsPromises = fs.promises
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const View = require('../../src/View')
|
|
5
|
+
|
|
6
|
+
const FIXTURE_DIR = path.resolve(__dirname, '_fixtures_get')
|
|
7
|
+
|
|
8
|
+
describe('View odac tags (var, get, translate) raw attribute', () => {
|
|
9
|
+
let originalDir
|
|
10
|
+
let originalCwd
|
|
11
|
+
|
|
12
|
+
beforeAll(async () => {
|
|
13
|
+
await fsPromises.mkdir(path.join(FIXTURE_DIR, 'skeleton'), {recursive: true})
|
|
14
|
+
await fsPromises.mkdir(path.join(FIXTURE_DIR, 'view', 'content'), {recursive: true})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
await fsPromises.rm(FIXTURE_DIR, {recursive: true, force: true})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
originalDir = global.__dir
|
|
23
|
+
originalCwd = process.cwd()
|
|
24
|
+
global.__dir = FIXTURE_DIR
|
|
25
|
+
process.chdir(FIXTURE_DIR)
|
|
26
|
+
|
|
27
|
+
if (global.Odac?.View?.cache) {
|
|
28
|
+
global.Odac.View.cache = {}
|
|
29
|
+
}
|
|
30
|
+
if (global.Odac?.View?.skeletons) {
|
|
31
|
+
global.Odac.View.skeletons = {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const key of Object.keys(require.cache)) {
|
|
35
|
+
if (key.includes('.cache')) {
|
|
36
|
+
delete require.cache[key]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
global.__dir = originalDir
|
|
43
|
+
process.chdir(originalCwd)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
let testCounter = 0
|
|
47
|
+
|
|
48
|
+
async function renderTemplate(templateContent, getValues = {}) {
|
|
49
|
+
const uniqueId = `test_get_${++testCounter}`
|
|
50
|
+
const viewFile = path.join(FIXTURE_DIR, 'view', 'content', `${uniqueId}.html`)
|
|
51
|
+
await fsPromises.writeFile(viewFile, templateContent, 'utf8')
|
|
52
|
+
|
|
53
|
+
const skeletonFile = path.join(FIXTURE_DIR, 'skeleton', 'main.html')
|
|
54
|
+
await fsPromises.writeFile(skeletonFile, '{{ CONTENT }}', 'utf8')
|
|
55
|
+
|
|
56
|
+
let capturedOutput = ''
|
|
57
|
+
const mockOdac = {
|
|
58
|
+
Config: {debug: true},
|
|
59
|
+
Var: value => {
|
|
60
|
+
const str = value === null || value === undefined ? '' : String(value)
|
|
61
|
+
return {
|
|
62
|
+
html: () => str.replace(/[&<>"']/g, m => ({'&': '&', '<': '<', '>': '>', '"': '"', "'": '''})[m])
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
Request: {
|
|
66
|
+
get: key => getValues[key] || '',
|
|
67
|
+
req: {url: '/test'},
|
|
68
|
+
res: {finished: false, headersSent: false},
|
|
69
|
+
isAjaxLoad: false,
|
|
70
|
+
ajaxLoad: [],
|
|
71
|
+
variables: {},
|
|
72
|
+
sharedData: {},
|
|
73
|
+
page: '',
|
|
74
|
+
header: () => {},
|
|
75
|
+
end: output => {
|
|
76
|
+
capturedOutput = output
|
|
77
|
+
},
|
|
78
|
+
hasEarlyHints: () => false,
|
|
79
|
+
setEarlyHints: () => {}
|
|
80
|
+
},
|
|
81
|
+
Lang: {
|
|
82
|
+
get: (...args) => args[0]
|
|
83
|
+
},
|
|
84
|
+
View: {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const view = new View(mockOdac)
|
|
88
|
+
view.skeleton('main')
|
|
89
|
+
view.set('content', uniqueId)
|
|
90
|
+
await view.print()
|
|
91
|
+
|
|
92
|
+
return capturedOutput
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
it('should escape <odac get> by default', async () => {
|
|
96
|
+
const result = await renderTemplate('<odac get="html" />', {html: '<b>bold</b>'})
|
|
97
|
+
expect(result).toContain('<b>bold</b>')
|
|
98
|
+
expect(result).not.toContain('<b>')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should not escape <odac get> when raw attribute is present (self-closing)', async () => {
|
|
102
|
+
const result = await renderTemplate('<odac get="html" raw />', {html: '<b>bold</b>'})
|
|
103
|
+
expect(result).toContain('<b>bold</b>')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should not escape <odac get> when raw attribute is present (block-level)', async () => {
|
|
107
|
+
const result = await renderTemplate('<odac get="html" raw></odac>', {html: '<b>bold</b>'})
|
|
108
|
+
expect(result).toContain('<b>bold</b>')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should handle missing parameters gracefully with raw', async () => {
|
|
112
|
+
const result = await renderTemplate('<odac get="missing" raw />', {})
|
|
113
|
+
expect(result).toContain('data-odac-navigate="content"')
|
|
114
|
+
expect(result).toContain('id="odac-data"')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should not escape <odac translate> when raw attribute is present', async () => {
|
|
118
|
+
const result = await renderTemplate('<odac translate raw><b>bold</b></odac>')
|
|
119
|
+
expect(result).toContain('<b>bold</b>')
|
|
120
|
+
expect(result).not.toContain('<b>')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('should escape <odac var> by default', async () => {
|
|
124
|
+
const result = await renderTemplate('<script:odac>var htmlVar = "<em>test</em>";</script:odac><odac var="htmlVar" />')
|
|
125
|
+
expect(result).toContain('<em>test</em>')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('should not escape <odac var> when raw attribute is present', async () => {
|
|
129
|
+
const result = await renderTemplate('<script:odac>var htmlVar = "<em>test</em>";</script:odac><odac var="htmlVar" raw />')
|
|
130
|
+
expect(result).toContain('<em>test</em>')
|
|
131
|
+
})
|
|
132
|
+
})
|