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.
Files changed (34) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/client/odac.js +1 -1
  3. package/docs/ai/README.md +1 -1
  4. package/docs/ai/skills/SKILL.md +1 -1
  5. package/docs/ai/skills/backend/database.md +103 -12
  6. package/docs/ai/skills/backend/ipc.md +71 -12
  7. package/docs/ai/skills/backend/views.md +6 -1
  8. package/docs/backend/00-getting-started/01-quick-start.md +77 -0
  9. package/docs/backend/07-views/03-template-syntax.md +5 -0
  10. package/docs/backend/07-views/04-request-data.md +13 -0
  11. package/docs/backend/08-database/05-write-behind-cache.md +230 -0
  12. package/docs/backend/13-utilities/02-ipc.md +117 -0
  13. package/docs/index.json +10 -0
  14. package/package.json +1 -1
  15. package/src/Database/WriteBuffer.js +605 -0
  16. package/src/Database.js +32 -1
  17. package/src/Ipc.js +343 -81
  18. package/src/Odac.js +2 -1
  19. package/src/Storage.js +4 -2
  20. package/src/View.js +33 -18
  21. package/test/Database/WriteBuffer/_recoverFromCheckpoint.test.js +207 -0
  22. package/test/Database/WriteBuffer/buffer.test.js +143 -0
  23. package/test/Database/WriteBuffer/flush.test.js +192 -0
  24. package/test/Database/WriteBuffer/get.test.js +72 -0
  25. package/test/Database/WriteBuffer/increment.test.js +118 -0
  26. package/test/Database/WriteBuffer/update.test.js +178 -0
  27. package/test/Ipc/hset.test.js +59 -0
  28. package/test/Ipc/incrBy.test.js +65 -0
  29. package/test/Ipc/lock.test.js +62 -0
  30. package/test/Ipc/rpush.test.js +68 -0
  31. package/test/Ipc/sadd.test.js +68 -0
  32. package/test/View/addNavigateAttribute.test.js +53 -0
  33. package/test/View/print.test.js +45 -1
  34. package/test/View/tags.test.js +132 -0
@@ -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: {data: {all: {}}}
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 => ({'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'})[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('&lt;b&gt;bold&lt;/b&gt;')
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('&lt;b&gt;')
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('&lt;em&gt;test&lt;/em&gt;')
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
+ })