odac 1.4.8 → 1.4.9

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 (35) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/docs/ai/README.md +2 -1
  3. package/docs/ai/skills/SKILL.md +2 -1
  4. package/docs/ai/skills/backend/authentication.md +12 -6
  5. package/docs/ai/skills/backend/database.md +85 -5
  6. package/docs/ai/skills/backend/migrations.md +23 -0
  7. package/docs/ai/skills/backend/odac-var.md +155 -0
  8. package/docs/ai/skills/backend/utilities.md +1 -1
  9. package/docs/ai/skills/frontend/forms.md +23 -1
  10. package/docs/backend/04-routing/09-websocket-quick-reference.md +21 -1
  11. package/docs/backend/04-routing/09-websocket.md +22 -1
  12. package/docs/backend/08-database/06-read-through-cache.md +206 -0
  13. package/docs/backend/10-authentication/01-authentication-basics.md +53 -0
  14. package/docs/backend/10-authentication/05-session-management.md +12 -3
  15. package/docs/backend/13-utilities/01-odac-var.md +13 -19
  16. package/docs/frontend/03-forms/01-form-handling.md +15 -2
  17. package/docs/index.json +1 -1
  18. package/package.json +1 -1
  19. package/src/Auth.js +17 -0
  20. package/src/Database/Migration.js +219 -3
  21. package/src/Database/ReadCache.js +174 -0
  22. package/src/Database.js +63 -0
  23. package/src/Validator.js +1 -1
  24. package/src/Var.js +1 -0
  25. package/src/WebSocket.js +80 -23
  26. package/test/Database/Migration/migrate_column.test.js +168 -0
  27. package/test/Database/ReadCache/crossTable.test.js +179 -0
  28. package/test/Database/ReadCache/get.test.js +128 -0
  29. package/test/Database/ReadCache/invalidate.test.js +103 -0
  30. package/test/Database/ReadCache/proxy.test.js +184 -0
  31. package/test/Database/insert.test.js +98 -0
  32. package/test/WebSocket/Client/fragmentation.test.js +130 -0
  33. package/test/WebSocket/Client/limits.test.js +10 -4
  34. package/test/WebSocket/Client/readyState.test.js +154 -0
  35. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +0 -55
@@ -0,0 +1,154 @@
1
+ const {WebSocketServer, WebSocketClient, READY_STATE} = require('../../../src/WebSocket.js')
2
+
3
+ /**
4
+ * Helper: creates a mock TCP socket with the minimum interface
5
+ * required by WebSocketClient.
6
+ */
7
+ function createMockSocket() {
8
+ return {
9
+ pause: jest.fn(),
10
+ resume: jest.fn(),
11
+ on: jest.fn(),
12
+ write: jest.fn(),
13
+ end: jest.fn(),
14
+ removeAllListeners: jest.fn(),
15
+ writable: true
16
+ }
17
+ }
18
+
19
+ describe('WebSocketClient readyState', () => {
20
+ let server
21
+
22
+ beforeEach(() => {
23
+ server = new WebSocketServer()
24
+ })
25
+
26
+ it('should expose static state constants matching READY_STATE enum', () => {
27
+ expect(WebSocketClient.CONNECTING).toBe(0)
28
+ expect(WebSocketClient.OPEN).toBe(1)
29
+ expect(WebSocketClient.CLOSING).toBe(2)
30
+ expect(WebSocketClient.CLOSED).toBe(3)
31
+ })
32
+
33
+ it('should export READY_STATE enum', () => {
34
+ expect(READY_STATE).toEqual({
35
+ CONNECTING: 0,
36
+ OPEN: 1,
37
+ CLOSING: 2,
38
+ CLOSED: 3
39
+ })
40
+ })
41
+
42
+ it('should start in CONNECTING state', () => {
43
+ const socket = createMockSocket()
44
+ const client = new WebSocketClient(socket, server, 'rs-1')
45
+ expect(client.readyState).toBe(READY_STATE.CONNECTING)
46
+ })
47
+
48
+ it('should transition to OPEN on resume()', () => {
49
+ const socket = createMockSocket()
50
+ const client = new WebSocketClient(socket, server, 'rs-2')
51
+
52
+ client.resume()
53
+
54
+ expect(client.readyState).toBe(READY_STATE.OPEN)
55
+ expect(socket.resume).toHaveBeenCalled()
56
+ })
57
+
58
+ it('should transition to CLOSED after close()', () => {
59
+ const socket = createMockSocket()
60
+ const client = new WebSocketClient(socket, server, 'rs-3')
61
+ client.resume()
62
+
63
+ client.close()
64
+
65
+ expect(client.readyState).toBe(READY_STATE.CLOSED)
66
+ expect(socket.end).toHaveBeenCalled()
67
+ })
68
+
69
+ it('should be idempotent — second close() is a no-op', () => {
70
+ const socket = createMockSocket()
71
+ const client = new WebSocketClient(socket, server, 'rs-4')
72
+ client.resume()
73
+
74
+ client.close()
75
+ client.close()
76
+
77
+ expect(socket.end).toHaveBeenCalledTimes(1)
78
+ })
79
+
80
+ it('should not send data when in CONNECTING state', () => {
81
+ const socket = createMockSocket()
82
+ const client = new WebSocketClient(socket, server, 'rs-5')
83
+
84
+ client.send('hello')
85
+
86
+ expect(socket.write).not.toHaveBeenCalled()
87
+ })
88
+
89
+ it('should not send data when in CLOSED state', () => {
90
+ const socket = createMockSocket()
91
+ const client = new WebSocketClient(socket, server, 'rs-6')
92
+ client.resume()
93
+ client.close()
94
+
95
+ client.send('hello')
96
+
97
+ // Only the close frame write should exist, no data frame
98
+ const writes = socket.write.mock.calls
99
+ const lastWrite = writes[writes.length - 1]
100
+ // Close frame starts with 0x88
101
+ expect(lastWrite[0][0]).toBe(0x88)
102
+ })
103
+
104
+ it('should not send ping when not OPEN', () => {
105
+ const socket = createMockSocket()
106
+ const client = new WebSocketClient(socket, server, 'rs-7')
107
+
108
+ client.ping()
109
+
110
+ expect(socket.write).not.toHaveBeenCalled()
111
+ })
112
+
113
+ it('should not write when socket is not writable', () => {
114
+ const socket = createMockSocket()
115
+ socket.writable = false
116
+ const client = new WebSocketClient(socket, server, 'rs-8')
117
+ client.resume()
118
+
119
+ client.send('hello')
120
+
121
+ expect(socket.write).not.toHaveBeenCalled()
122
+ })
123
+
124
+ it('should transition to CLOSED when socket fires close event', () => {
125
+ const socket = createMockSocket()
126
+ const client = new WebSocketClient(socket, server, 'rs-9')
127
+ server.clients.set('rs-9', client)
128
+ client.resume()
129
+
130
+ // Simulate socket 'close' event
131
+ const closeHandler = socket.on.mock.calls.find(c => c[0] === 'close')[1]
132
+ closeHandler()
133
+
134
+ expect(client.readyState).toBe(READY_STATE.CLOSED)
135
+ })
136
+
137
+ it('should emit close event only once on double cleanup', () => {
138
+ const socket = createMockSocket()
139
+ const client = new WebSocketClient(socket, server, 'rs-10')
140
+ server.clients.set('rs-10', client)
141
+ client.resume()
142
+
143
+ const closeSpy = jest.fn()
144
+ client.on('close', closeSpy)
145
+
146
+ client.close()
147
+
148
+ // Simulate socket 'close' event firing after close() already cleaned up
149
+ const closeHandler = socket.on.mock.calls.find(c => c[0] === 'close')
150
+ if (closeHandler) closeHandler[1]()
151
+
152
+ expect(closeSpy).toHaveBeenCalledTimes(1)
153
+ })
154
+ })
@@ -1,55 +0,0 @@
1
- ## 🔐 User Logins with `Auth.js`
2
-
3
- The `Odac.Auth` service is your bouncer, managing who gets in and who stays out. It handles user login sessions for you.
4
-
5
- #### Letting a User In
6
-
7
- `Odac.Auth.login(userId, userData)`
8
-
9
- * `userId`: A unique ID for the user (like their database ID).
10
- * `userData`: An object with any user info you want to remember, like their username or role.
11
-
12
- When you call this, `Auth` creates a secure session for the user.
13
-
14
- > **💡 Enterprise Security:** ODAC automatically handles **Token Rotation** every 15 minutes (configurable) and includes built-in **CSRF protection** for all forms. Sessions are persistent across browser restarts by default.
15
-
16
- #### Checking the Guest List
17
-
18
- * `Odac.Auth.isLogin()`: Is the current user logged in? Returns `true` or `false`.
19
- * `Odac.Auth.getId()`: Gets the ID of the logged-in user.
20
- * `Odac.Auth.get('some-key')`: Grabs a specific piece of info from the `userData` you stored.
21
-
22
- #### Showing a User Out
23
-
24
- * `Odac.Auth.logout()`: Ends the user's session and logs them out.
25
-
26
- #### Example: A Login Flow
27
- ```javascript
28
- // Controller for your login form
29
- module.exports = async function (Odac) {
30
- const { username, password } = Odac.Request.post;
31
-
32
- // IMPORTANT: You need to write your own code to find the user in your database!
33
- const user = await yourDatabase.findUser(username, password);
34
-
35
- if (user) {
36
- // User is valid! Log them in.
37
- Odac.Auth.login(user.id, { username: user.username });
38
- return Odac.direct('/dashboard'); // Send them to their dashboard
39
- } else {
40
- // Bad credentials, send them back to the login page
41
- return Odac.direct('/login?error=1');
42
- }
43
- }
44
-
45
- // A protected dashboard page
46
- module.exports = function (Odac) {
47
- // If they're not logged in, kick them back to the login page.
48
- if (!Odac.Auth.isLogin()) {
49
- return Odac.direct('/login');
50
- }
51
-
52
- const username = Odac.Auth.get('username');
53
- return `Welcome back, ${username}!`;
54
- }
55
- ```