api-ape 3.0.1 → 4.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 (186) hide show
  1. package/README.md +58 -570
  2. package/client/README.md +73 -14
  3. package/client/auth/crypto/aead.js +214 -0
  4. package/client/auth/crypto/constants.js +32 -0
  5. package/client/auth/crypto/encoding.js +104 -0
  6. package/client/auth/crypto/files.md +27 -0
  7. package/client/auth/crypto/kdf.js +217 -0
  8. package/client/auth/crypto-utils.js +118 -0
  9. package/client/auth/files.md +52 -0
  10. package/client/auth/key-recovery.js +288 -0
  11. package/client/auth/recovery/constants.js +37 -0
  12. package/client/auth/recovery/files.md +23 -0
  13. package/client/auth/recovery/key-derivation.js +61 -0
  14. package/client/auth/recovery/sss-browser.js +189 -0
  15. package/client/auth/share-storage.js +205 -0
  16. package/client/auth/storage/constants.js +18 -0
  17. package/client/auth/storage/db.js +132 -0
  18. package/client/auth/storage/files.md +27 -0
  19. package/client/auth/storage/keys.js +173 -0
  20. package/client/auth/storage/shares.js +200 -0
  21. package/client/browser.js +190 -23
  22. package/client/connectSocket.js +418 -988
  23. package/client/connection/README.md +23 -0
  24. package/client/connection/fileDownload.js +256 -0
  25. package/client/connection/fileHandling.js +450 -0
  26. package/client/connection/fileUtils.js +346 -0
  27. package/client/connection/files.md +71 -0
  28. package/client/connection/messageHandler.js +105 -0
  29. package/client/connection/network.js +350 -0
  30. package/client/connection/proxy.js +233 -0
  31. package/client/connection/sender.js +333 -0
  32. package/client/connection/state.js +321 -0
  33. package/client/connection/subscriptions.js +151 -0
  34. package/client/files.md +53 -0
  35. package/client/index.js +298 -142
  36. package/client/transports/README.md +50 -0
  37. package/client/transports/files.md +41 -0
  38. package/client/transports/streamParser.js +195 -0
  39. package/client/transports/streaming.js +555 -202
  40. package/dist/ape.js +6 -1
  41. package/dist/ape.js.map +4 -4
  42. package/index.d.ts +38 -16
  43. package/package.json +32 -7
  44. package/server/README.md +287 -53
  45. package/server/adapters/README.md +28 -19
  46. package/server/adapters/files.md +68 -0
  47. package/server/adapters/firebase.js +543 -160
  48. package/server/adapters/index.js +362 -112
  49. package/server/adapters/mongo.js +530 -140
  50. package/server/adapters/postgres.js +534 -155
  51. package/server/adapters/redis.js +508 -143
  52. package/server/adapters/supabase.js +555 -186
  53. package/server/client/README.md +43 -0
  54. package/server/client/connection.js +586 -0
  55. package/server/client/files.md +40 -0
  56. package/server/client/index.js +342 -0
  57. package/server/files.md +54 -0
  58. package/server/index.js +332 -27
  59. package/server/lib/README.md +26 -0
  60. package/server/lib/broadcast/clients.js +219 -0
  61. package/server/lib/broadcast/files.md +58 -0
  62. package/server/lib/broadcast/index.js +57 -0
  63. package/server/lib/broadcast/publishProxy.js +110 -0
  64. package/server/lib/broadcast/pubsub.js +137 -0
  65. package/server/lib/broadcast/sendProxy.js +103 -0
  66. package/server/lib/bun.js +315 -99
  67. package/server/lib/fileTransfer/README.md +63 -0
  68. package/server/lib/fileTransfer/files.md +30 -0
  69. package/server/lib/fileTransfer/streaming.js +435 -0
  70. package/server/lib/fileTransfer.js +710 -326
  71. package/server/lib/files.md +111 -0
  72. package/server/lib/httpUtils.js +283 -0
  73. package/server/lib/loader.js +208 -7
  74. package/server/lib/longPolling/README.md +63 -0
  75. package/server/lib/longPolling/files.md +44 -0
  76. package/server/lib/longPolling/getHandler.js +365 -0
  77. package/server/lib/longPolling/postHandler.js +327 -0
  78. package/server/lib/longPolling.js +174 -221
  79. package/server/lib/main.js +369 -532
  80. package/server/lib/runtimes/README.md +42 -0
  81. package/server/lib/runtimes/bun.js +586 -0
  82. package/server/lib/runtimes/files.md +56 -0
  83. package/server/lib/runtimes/node.js +511 -0
  84. package/server/lib/wiring.js +539 -98
  85. package/server/lib/ws/README.md +35 -0
  86. package/server/lib/ws/adapters/README.md +54 -0
  87. package/server/lib/ws/adapters/bun.js +538 -170
  88. package/server/lib/ws/adapters/deno.js +623 -149
  89. package/server/lib/ws/adapters/files.md +42 -0
  90. package/server/lib/ws/files.md +74 -0
  91. package/server/lib/ws/frames.js +532 -154
  92. package/server/lib/ws/index.js +207 -10
  93. package/server/lib/ws/server.js +385 -92
  94. package/server/lib/ws/socket.js +549 -181
  95. package/server/lib/wsProvider.js +363 -89
  96. package/server/plugins/binary.js +282 -0
  97. package/server/security/README.md +92 -0
  98. package/server/security/auth/README.md +319 -0
  99. package/server/security/auth/adapters/files.md +95 -0
  100. package/server/security/auth/adapters/ldap/constants.js +37 -0
  101. package/server/security/auth/adapters/ldap/files.md +19 -0
  102. package/server/security/auth/adapters/ldap/helpers.js +111 -0
  103. package/server/security/auth/adapters/ldap.js +353 -0
  104. package/server/security/auth/adapters/oauth2/constants.js +41 -0
  105. package/server/security/auth/adapters/oauth2/files.md +19 -0
  106. package/server/security/auth/adapters/oauth2/helpers.js +123 -0
  107. package/server/security/auth/adapters/oauth2.js +273 -0
  108. package/server/security/auth/adapters/opaque-handlers.js +314 -0
  109. package/server/security/auth/adapters/opaque.js +205 -0
  110. package/server/security/auth/adapters/saml/constants.js +52 -0
  111. package/server/security/auth/adapters/saml/files.md +19 -0
  112. package/server/security/auth/adapters/saml/helpers.js +74 -0
  113. package/server/security/auth/adapters/saml.js +173 -0
  114. package/server/security/auth/adapters/totp.js +703 -0
  115. package/server/security/auth/adapters/webauthn.js +625 -0
  116. package/server/security/auth/files.md +61 -0
  117. package/server/security/auth/framework/constants.js +27 -0
  118. package/server/security/auth/framework/files.md +23 -0
  119. package/server/security/auth/framework/handlers.js +272 -0
  120. package/server/security/auth/framework/socket-auth.js +177 -0
  121. package/server/security/auth/handlers/auth-messages.js +143 -0
  122. package/server/security/auth/handlers/files.md +28 -0
  123. package/server/security/auth/index.js +290 -0
  124. package/server/security/auth/mfa/crypto/aead.js +148 -0
  125. package/server/security/auth/mfa/crypto/constants.js +35 -0
  126. package/server/security/auth/mfa/crypto/files.md +27 -0
  127. package/server/security/auth/mfa/crypto/kdf.js +120 -0
  128. package/server/security/auth/mfa/crypto/utils.js +68 -0
  129. package/server/security/auth/mfa/crypto-utils.js +80 -0
  130. package/server/security/auth/mfa/files.md +77 -0
  131. package/server/security/auth/mfa/ledger/constants.js +75 -0
  132. package/server/security/auth/mfa/ledger/errors.js +73 -0
  133. package/server/security/auth/mfa/ledger/files.md +23 -0
  134. package/server/security/auth/mfa/ledger/share-record.js +32 -0
  135. package/server/security/auth/mfa/ledger.js +255 -0
  136. package/server/security/auth/mfa/recovery/constants.js +67 -0
  137. package/server/security/auth/mfa/recovery/files.md +19 -0
  138. package/server/security/auth/mfa/recovery/handlers.js +216 -0
  139. package/server/security/auth/mfa/recovery.js +191 -0
  140. package/server/security/auth/mfa/sss/constants.js +21 -0
  141. package/server/security/auth/mfa/sss/files.md +23 -0
  142. package/server/security/auth/mfa/sss/gf256.js +103 -0
  143. package/server/security/auth/mfa/sss/serialization.js +82 -0
  144. package/server/security/auth/mfa/sss.js +161 -0
  145. package/server/security/auth/mfa/two-of-three/constants.js +58 -0
  146. package/server/security/auth/mfa/two-of-three/files.md +23 -0
  147. package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
  148. package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
  149. package/server/security/auth/mfa/two-of-three.js +136 -0
  150. package/server/security/auth/nonce-manager.js +89 -0
  151. package/server/security/auth/state-machine-mfa.js +269 -0
  152. package/server/security/auth/state-machine.js +257 -0
  153. package/server/security/extractRootDomain.js +144 -16
  154. package/server/security/files.md +51 -0
  155. package/server/security/origin.js +197 -15
  156. package/server/security/reply.js +274 -16
  157. package/server/socket/README.md +119 -0
  158. package/server/socket/authMiddleware.js +299 -0
  159. package/server/socket/files.md +86 -0
  160. package/server/socket/open.js +154 -8
  161. package/server/socket/pluginHooks.js +334 -0
  162. package/server/socket/receive.js +184 -225
  163. package/server/socket/receiveContext.js +117 -0
  164. package/server/socket/send.js +416 -78
  165. package/server/socket/tagUtils.js +402 -0
  166. package/server/utils/README.md +19 -0
  167. package/server/utils/deepRequire.js +255 -30
  168. package/server/utils/files.md +57 -0
  169. package/server/utils/genId.js +182 -20
  170. package/server/utils/parseUserAgent.js +313 -251
  171. package/server/utils/userAgent/README.md +65 -0
  172. package/server/utils/userAgent/files.md +46 -0
  173. package/server/utils/userAgent/patterns.js +545 -0
  174. package/utils/README.md +21 -0
  175. package/utils/files.md +66 -0
  176. package/utils/jss/README.md +21 -0
  177. package/utils/jss/decode.js +471 -0
  178. package/utils/jss/encode.js +312 -0
  179. package/utils/jss/files.md +68 -0
  180. package/utils/jss/plugins.js +210 -0
  181. package/utils/jss.js +219 -273
  182. package/utils/messageHash.js +238 -35
  183. package/dist/api-ape.min.js +0 -2
  184. package/dist/api-ape.min.js.map +0 -7
  185. package/server/client.js +0 -308
  186. package/server/lib/broadcast.js +0 -146
@@ -0,0 +1,342 @@
1
+ /**
2
+ * @fileoverview api-ape Node.js Client - Server-Side WebSocket Client
3
+ *
4
+ * This module provides a Node.js client for connecting to an api-ape server.
5
+ * It mirrors the browser client API exactly, allowing server-to-server communication
6
+ * or backend services to connect to api-ape servers.
7
+ *
8
+ * The client uses a Proxy-based API that allows natural method chaining
9
+ * for constructing API paths. Any property access on the client becomes
10
+ * part of the request path.
11
+ *
12
+ * Features:
13
+ * - **Proxy-based API**: Natural method chaining (`api.users.list()`)
14
+ * - **Auto-reconnect**: Automatically reconnects on connection loss
15
+ * - **Connection state tracking**: Subscribe to connection state changes
16
+ * - **Request queuing**: Requests made while disconnected are queued
17
+ * - **Promise-based**: All API calls return promises
18
+ *
19
+ * @module server/client
20
+ * @see {@link module:server/client/connection} - Connection management implementation
21
+ * @see {@link module:client/index} - Browser client (same API)
22
+ *
23
+ * @example
24
+ * // Basic usage
25
+ * const api = require('api-ape/server/client')
26
+ *
27
+ * // Connect to an api-ape server
28
+ * api.connect('localhost', 3000)
29
+ *
30
+ * // Make API calls using natural method chaining
31
+ * const users = await api.users.list()
32
+ * const user = await api.users.get({ id: '123' })
33
+ *
34
+ * // Subscribe to connection state changes
35
+ * api.onConnectionChange(state => {
36
+ * console.log('Connection state:', state)
37
+ * })
38
+ *
39
+ * // Subscribe to server-sent events
40
+ * api.on('notification', data => {
41
+ * console.log('Notification received:', data)
42
+ * })
43
+ *
44
+ * @example
45
+ * // Using with environment variables
46
+ * process.env.APE_SERVER = 'ws://localhost:3000/api/ape'
47
+ * const api = require('api-ape/server/client')
48
+ * api.connect() // Uses APE_SERVER environment variable
49
+ *
50
+ * @example
51
+ * // Clean shutdown
52
+ * process.on('SIGTERM', () => {
53
+ * api.close()
54
+ * process.exit(0)
55
+ * })
56
+ */
57
+
58
+ const {
59
+ ConnectionState,
60
+ connect,
61
+ close,
62
+ queueOrSend,
63
+ on,
64
+ onConnectionChange,
65
+ isReady,
66
+ } = require("./connection");
67
+
68
+ /**
69
+ * Path separator used when building API paths.
70
+ * @private
71
+ * @constant {string}
72
+ */
73
+ const joinKey = "/";
74
+
75
+ /**
76
+ * Proxy handler for the api-ape client.
77
+ *
78
+ * This handler intercepts property access and function calls to build
79
+ * API paths dynamically. For example:
80
+ * - `api.users` returns a new proxy with path "/users"
81
+ * - `api.users.list()` calls the "/users/list" endpoint
82
+ * - `api.users("/123").profile()` calls the "/users/123/profile" endpoint
83
+ *
84
+ * @private
85
+ * @type {ProxyHandler}
86
+ */
87
+ const handler = {
88
+ /**
89
+ * Intercepts property access on the proxy.
90
+ *
91
+ * - Reserved properties (`on`, `onConnectionChange`, `transport`, `connect`, `close`)
92
+ * return their actual implementations
93
+ * - `then` and `catch` return undefined to prevent promise coercion
94
+ * - All other properties return a new proxy function that extends the path
95
+ *
96
+ * @param {Object} target - The proxy target
97
+ * @param {string} prop - The property being accessed
98
+ * @returns {*} The property value or a new proxy
99
+ */
100
+ get(target, prop) {
101
+ // Return actual property if it exists on the target
102
+ if (Reflect.has(target, prop)) return Reflect.get(target, prop);
103
+
104
+ // Reserved methods - return actual implementations
105
+ if (prop === "on") return on;
106
+ if (prop === "onConnectionChange") return onConnectionChange;
107
+ if (prop === "transport") return isReady() ? "websocket" : null;
108
+ if (prop === "connect") return connect;
109
+ if (prop === "close") return close;
110
+
111
+ // Prevent promise coercion (don't let proxy be awaited directly)
112
+ if (prop === "then" || prop === "catch") return undefined;
113
+
114
+ /**
115
+ * Creates a wrapper function that can be called or chained further.
116
+ *
117
+ * When called with two arguments where the first is a string,
118
+ * it appends to the path: `api.users("/123", data)` → path "/users/123"
119
+ *
120
+ * When called with one argument, it uses that as the request body:
121
+ * `api.users.create({ name: 'Alice' })` → POST to "/users/create"
122
+ *
123
+ * @param {string|Object} [a] - Path segment or request body
124
+ * @param {Object} [b] - Request body (when a is a path segment)
125
+ * @returns {Promise} Promise resolving to the server response
126
+ */
127
+ const wrapperFn = function (a, b) {
128
+ let path = joinKey + prop,
129
+ body;
130
+
131
+ if (arguments.length === 2 && typeof a === "string") {
132
+ // Two args with string first: append to path
133
+ // e.g., api.users("/123", { name: 'Bob' })
134
+ path += a;
135
+ body = b;
136
+ } else {
137
+ // Single arg: use as body
138
+ // e.g., api.users.create({ name: 'Alice' })
139
+ body = a;
140
+ }
141
+
142
+ return queueOrSend(path, body);
143
+ };
144
+
145
+ // Return a new proxy wrapping the function, allowing further chaining
146
+ return new Proxy(wrapperFn, handler);
147
+ },
148
+ };
149
+
150
+ /**
151
+ * The api-ape client proxy object.
152
+ *
153
+ * This is the main export of the module. It's a Proxy that allows
154
+ * natural method chaining for API calls.
155
+ *
156
+ * @type {Proxy}
157
+ *
158
+ * @property {function} on - Subscribe to server events
159
+ * @property {function} onConnectionChange - Subscribe to connection state changes
160
+ * @property {string|null} transport - Current transport type ('websocket' or null)
161
+ * @property {function} connect - Establish connection to server
162
+ * @property {function} close - Close the connection
163
+ *
164
+ * @example
165
+ * // The proxy allows any property access to become an API path
166
+ * api.users.list() // Calls /users/list
167
+ * api.posts.comments.recent() // Calls /posts/comments/recent
168
+ * api.auth.login({ user, pass }) // Calls /auth/login with body
169
+ */
170
+ const api = new Proxy({}, handler);
171
+
172
+ // Define non-enumerable properties for reserved methods
173
+ // This ensures they can't be overwritten and don't show in Object.keys()
174
+
175
+ /**
176
+ * Subscribe to server-sent events of a specific type.
177
+ *
178
+ * @function on
179
+ * @memberof module:server/client
180
+ * @param {string|null} type - Event type to listen for, or null for all events
181
+ * @param {function} handler - Callback function receiving event data
182
+ *
183
+ * @example
184
+ * // Listen for specific event type
185
+ * api.on('notification', (data) => {
186
+ * console.log('Notification:', data)
187
+ * })
188
+ *
189
+ * @example
190
+ * // Listen for all events
191
+ * api.on(null, (event) => {
192
+ * console.log('Event:', event.type, event.data)
193
+ * })
194
+ */
195
+ Object.defineProperty(api, "on", {
196
+ value: on,
197
+ writable: false,
198
+ enumerable: false,
199
+ configurable: false,
200
+ });
201
+
202
+ /**
203
+ * Subscribe to connection state changes.
204
+ *
205
+ * @function onConnectionChange
206
+ * @memberof module:server/client
207
+ * @param {function} handler - Callback receiving the new ConnectionState
208
+ * @returns {function} Unsubscribe function
209
+ *
210
+ * @example
211
+ * const unsubscribe = api.onConnectionChange((state) => {
212
+ * switch (state) {
213
+ * case 'connected':
214
+ * console.log('Connected to server')
215
+ * break
216
+ * case 'disconnected':
217
+ * console.log('Disconnected from server')
218
+ * break
219
+ * }
220
+ * })
221
+ *
222
+ * // Later, stop listening
223
+ * unsubscribe()
224
+ */
225
+ Object.defineProperty(api, "onConnectionChange", {
226
+ value: onConnectionChange,
227
+ writable: false,
228
+ enumerable: false,
229
+ configurable: false,
230
+ });
231
+
232
+ /**
233
+ * Establish a connection to an api-ape server.
234
+ *
235
+ * @function connect
236
+ * @memberof module:server/client
237
+ * @param {string} [host] - Server hostname
238
+ * @param {number} [port] - Server port
239
+ *
240
+ * @example
241
+ * // Connect with explicit host and port
242
+ * api.connect('localhost', 3000)
243
+ *
244
+ * @example
245
+ * // Connect using APE_SERVER environment variable
246
+ * process.env.APE_SERVER = 'ws://api.example.com/api/ape'
247
+ * api.connect()
248
+ */
249
+ Object.defineProperty(api, "connect", {
250
+ value: connect,
251
+ writable: false,
252
+ enumerable: false,
253
+ configurable: false,
254
+ });
255
+
256
+ /**
257
+ * Close the WebSocket connection.
258
+ * Disables auto-reconnect until connect() is called again.
259
+ *
260
+ * @function close
261
+ * @memberof module:server/client
262
+ *
263
+ * @example
264
+ * // Clean shutdown
265
+ * process.on('SIGTERM', () => {
266
+ * api.close()
267
+ * process.exit(0)
268
+ * })
269
+ */
270
+ Object.defineProperty(api, "close", {
271
+ value: close,
272
+ writable: false,
273
+ enumerable: false,
274
+ configurable: false,
275
+ });
276
+
277
+ // Module exports
278
+
279
+ /**
280
+ * Default export - the api-ape client proxy
281
+ * @type {Proxy}
282
+ */
283
+ module.exports = api;
284
+
285
+ /**
286
+ * Default export (for ES module interop)
287
+ * @type {Proxy}
288
+ */
289
+ module.exports.default = api;
290
+
291
+ /**
292
+ * Subscribe to server events
293
+ * @function
294
+ */
295
+ module.exports.on = on;
296
+
297
+ /**
298
+ * Subscribe to connection state changes
299
+ * @function
300
+ */
301
+ module.exports.onConnectionChange = onConnectionChange;
302
+
303
+ /**
304
+ * Establish connection to server
305
+ * @function
306
+ */
307
+ module.exports.connect = connect;
308
+
309
+ /**
310
+ * Close the connection
311
+ * @function
312
+ */
313
+ module.exports.close = close;
314
+
315
+ /**
316
+ * Connection state enumeration.
317
+ *
318
+ * @type {Object}
319
+ * @property {string} Disconnected - Not connected to server
320
+ * @property {string} Connecting - Connection attempt in progress
321
+ * @property {string} Connected - Successfully connected
322
+ * @property {string} Closing - Connection is being closed
323
+ *
324
+ * @example
325
+ * const { ConnectionState } = require('api-ape/server/client')
326
+ *
327
+ * api.onConnectionChange((state) => {
328
+ * if (state === ConnectionState.Connected) {
329
+ * console.log('Ready to make API calls')
330
+ * }
331
+ * })
332
+ */
333
+ module.exports.ConnectionState = ConnectionState;
334
+
335
+ /**
336
+ * Internal function for queuing or sending requests.
337
+ * Exposed for advanced use cases and testing.
338
+ *
339
+ * @private
340
+ * @function
341
+ */
342
+ module.exports._queueOrSend = queueOrSend;
@@ -0,0 +1,54 @@
1
+ # Server Module Files
2
+
3
+ This module provides the backend infrastructure for api-ape's WebSocket-based Remote Procedure Events (RPE) system. It transforms standard HTTP servers into real-time API servers with automatic controller routing.
4
+
5
+ ## Guidelines
6
+
7
+ - **Multi-runtime support** — Code must work on Node.js, Bun, and Deno; use runtime detection via `lib/wsProvider.js`
8
+ - **Zero dependencies** — Avoid external packages; use built-in WebSocket polyfill when native unavailable
9
+ - **JSS encoding** — Always use `utils/jss` for message serialization to preserve extended types
10
+ - **Controller context** — Controllers receive `this` context with `clientId`, `broadcast`, `send`, and embedded data
11
+ - **Binary transfers** — Use the tag system (`<!B>`, `<!A>`, `<!F>`) for file handling
12
+
13
+ ## Directory Structure
14
+
15
+ ```
16
+ server/
17
+ ├── index.js # Entry point (exports lib/main)
18
+ ├── client/ # Server-side client (index.js + connection management)
19
+ ├── lib/ # Core server implementation
20
+ ├── adapters/ # 🌲 Forest distributed mesh adapters
21
+ ├── socket/ # WebSocket message handlers
22
+ ├── security/ # Origin validation & CSRF protection
23
+ └── utils/ # Server utilities
24
+ ```
25
+
26
+ ## Files
27
+
28
+ ### `index.js`
29
+
30
+ Main entry point that re-exports the `ape` initializer from `lib/main.js`. This is what users get when they `require('api-ape')` or `import { ape } from 'api-ape'`.
31
+
32
+ ### `client/`
33
+
34
+ Server-side WebSocket client module for outbound connections. Contains the main client (`index.js`) and connection management. See [`client/files.md`](./client/files.md).
35
+
36
+ ### `lib/`
37
+
38
+ Core server implementation including initialization, controller loading, broadcasting, and WebSocket handling. See [`lib/files.md`](./lib/files.md).
39
+
40
+ ### `adapters/`
41
+
42
+ Database adapters for the Forest distributed mesh system (Redis, MongoDB, PostgreSQL, Supabase, Firebase). See [`adapters/files.md`](./adapters/files.md).
43
+
44
+ ### `socket/`
45
+
46
+ WebSocket message handlers for connection validation, incoming message processing, and response serialization. See [`socket/files.md`](./socket/files.md).
47
+
48
+ ### `security/`
49
+
50
+ Origin validation and CSRF protection for WebSocket connections. See [`security/files.md`](./security/files.md).
51
+
52
+ ### `utils/`
53
+
54
+ Server utilities including controller loading, ID generation, and User-Agent parsing. See [`utils/files.md`](./utils/files.md).