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,402 @@
1
+ /**
2
+ * @fileoverview Upload Tag Utilities for Socket Receive Handler
3
+ *
4
+ * This module provides utilities for detecting and processing special binary data
5
+ * tags in api-ape messages. The tag system enables binary data (files, ArrayBuffers)
6
+ * to be referenced in JSON messages and transferred separately via HTTP.
7
+ *
8
+ * Tag Types:
9
+ * - `<!B>` (Buffer): Client will upload binary data (Buffer/ArrayBuffer)
10
+ * - `<!A>` (ArrayBuffer): Client will upload an ArrayBuffer
11
+ * - `<!F>` (File): References a file for streaming client-to-client transfer
12
+ * - `<!L>` (Link): Server returns a download link for binary data
13
+ *
14
+ * Data Flow:
15
+ * 1. Client sends message with tagged keys: `{ "image<!B>": "hash123" }`
16
+ * 2. Server extracts upload tags using `findUploadTags()`
17
+ * 3. Server waits for binary data upload via HTTP PUT
18
+ * 4. Server cleans tags and injects actual data using `cleanUploadTags()`
19
+ * 5. Controller receives clean object: `{ image: <Buffer> }`
20
+ *
21
+ * @module server/socket/tagUtils
22
+ * @see {@link module:server/socket/receive} - Socket receive handler using these utilities
23
+ * @see {@link module:server/lib/fileTransfer} - File transfer management
24
+ *
25
+ * @example
26
+ * const { findUploadTags, cleanUploadTags, setValueAtPath } = require('./tagUtils')
27
+ *
28
+ * // Message from client with binary upload tag
29
+ * const message = { "avatar<!B>": "abc123", name: "Alice" }
30
+ *
31
+ * // Find all upload tags
32
+ * const uploads = findUploadTags(message)
33
+ * // [{ path: 'avatar', hash: 'abc123', tag: 'B', originalKey: 'avatar<!B>' }]
34
+ *
35
+ * // After receiving the binary data, clean the object
36
+ * const cleaned = cleanUploadTags(message)
37
+ * // { avatar: 'abc123', name: 'Alice' }
38
+ *
39
+ * // Inject the actual binary data
40
+ * setValueAtPath(cleaned, 'avatar', actualBuffer)
41
+ * // { avatar: <Buffer ...>, name: 'Alice' }
42
+ */
43
+
44
+ /**
45
+ * @typedef {Object} UploadTag
46
+ * Represents a detected upload tag in a message object.
47
+ *
48
+ * @property {string} path - Dot-notation path to the value (e.g., 'user.profile.avatar')
49
+ * @property {string} hash - The hash/identifier for the upload
50
+ * @property {'B'|'A'} tag - Tag type: 'B' for Buffer, 'A' for ArrayBuffer
51
+ * @property {string} originalKey - The original key including the tag (e.g., 'avatar<!B>')
52
+ */
53
+
54
+ /**
55
+ * @typedef {Object} FileTag
56
+ * Represents a detected file tag for streaming transfers.
57
+ *
58
+ * @property {string} path - Dot-notation path to the value
59
+ * @property {string} hash - The file identifier/hash
60
+ * @property {string} originalKey - The original key including the tag
61
+ */
62
+
63
+ /**
64
+ * Recursively finds all upload tags (`<!B>` and `<!A>`) in an object.
65
+ *
66
+ * Traverses the object tree and identifies keys ending with `<!B>` or `<!A>`,
67
+ * which indicate that the client will upload binary data for that field.
68
+ *
69
+ * The function handles:
70
+ * - Nested objects: `{ user: { "avatar<!B>": "hash" } }` → path: 'user.avatar'
71
+ * - Arrays: `{ files: [{ "data<!B>": "h1" }, { "data<!B>": "h2" }] }` → paths: 'files.0.data', 'files.1.data'
72
+ * - Multiple tags at any level
73
+ *
74
+ * @function findUploadTags
75
+ * @param {Object} obj - The object to search for upload tags
76
+ * @param {string} [path=''] - Current path prefix (used in recursion)
77
+ * @returns {UploadTag[]} Array of detected upload tags with their paths and metadata
78
+ *
79
+ * @example
80
+ * // Simple upload tag
81
+ * const uploads = findUploadTags({ "image<!B>": "hash123" })
82
+ * // [{ path: 'image', hash: 'hash123', tag: 'B', originalKey: 'image<!B>' }]
83
+ *
84
+ * @example
85
+ * // Nested upload tags
86
+ * const uploads = findUploadTags({
87
+ * user: {
88
+ * "avatar<!B>": "hash1",
89
+ * profile: {
90
+ * "banner<!A>": "hash2"
91
+ * }
92
+ * }
93
+ * })
94
+ * // [
95
+ * // { path: 'user.avatar', hash: 'hash1', tag: 'B', originalKey: 'avatar<!B>' },
96
+ * // { path: 'user.profile.banner', hash: 'hash2', tag: 'A', originalKey: 'banner<!A>' }
97
+ * // ]
98
+ *
99
+ * @example
100
+ * // Tags in arrays
101
+ * const uploads = findUploadTags({
102
+ * attachments: [
103
+ * { "file<!B>": "hash1" },
104
+ * { "file<!B>": "hash2" }
105
+ * ]
106
+ * })
107
+ * // [
108
+ * // { path: 'attachments.0.file', hash: 'hash1', tag: 'B', originalKey: 'file<!B>' },
109
+ * // { path: 'attachments.1.file', hash: 'hash2', tag: 'B', originalKey: 'file<!B>' }
110
+ * // ]
111
+ */
112
+ function findUploadTags(obj, path = "") {
113
+ const uploads = [];
114
+
115
+ // Base case: null, undefined, or primitives have no tags
116
+ if (obj === null || obj === undefined || typeof obj !== "object") {
117
+ return uploads;
118
+ }
119
+
120
+ // Handle arrays - recurse into each element with index as path segment
121
+ if (Array.isArray(obj)) {
122
+ for (let i = 0; i < obj.length; i++) {
123
+ uploads.push(
124
+ ...findUploadTags(obj[i], path ? `${path}.${i}` : String(i)),
125
+ );
126
+ }
127
+ return uploads;
128
+ }
129
+
130
+ // Handle objects - check each key for tags or recurse
131
+ for (const key of Object.keys(obj)) {
132
+ // Check for <!B> (Buffer) tag
133
+ const bMatch = key.match(/^(.+)<!B>$/);
134
+ // Check for <!A> (ArrayBuffer) tag
135
+ const aMatch = key.match(/^(.+)<!A>$/);
136
+
137
+ if (bMatch) {
138
+ uploads.push({
139
+ path: path ? `${path}.${bMatch[1]}` : bMatch[1],
140
+ hash: obj[key],
141
+ tag: "B",
142
+ originalKey: key,
143
+ });
144
+ } else if (aMatch) {
145
+ uploads.push({
146
+ path: path ? `${path}.${aMatch[1]}` : aMatch[1],
147
+ hash: obj[key],
148
+ tag: "A",
149
+ originalKey: key,
150
+ });
151
+ } else {
152
+ // No tag on this key, recurse into the value
153
+ uploads.push(...findUploadTags(obj[key], path ? `${path}.${key}` : key));
154
+ }
155
+ }
156
+
157
+ return uploads;
158
+ }
159
+
160
+ /**
161
+ * Recursively finds all file tags (`<!F>`) in an object.
162
+ *
163
+ * File tags are used for streaming client-to-client file transfers.
164
+ * Unlike upload tags, file tags reference files that are streamed
165
+ * between clients without the server storing the entire file.
166
+ *
167
+ * @function findFileTags
168
+ * @param {Object} obj - The object to search for file tags
169
+ * @param {string} [path=''] - Current path prefix (used in recursion)
170
+ * @returns {FileTag[]} Array of detected file tags with their paths and metadata
171
+ *
172
+ * @example
173
+ * const files = findFileTags({
174
+ * "document<!F>": "file123",
175
+ * metadata: { name: "report.pdf" }
176
+ * })
177
+ * // [{ path: 'document', hash: 'file123', originalKey: 'document<!F>' }]
178
+ *
179
+ * @example
180
+ * // Nested file tags
181
+ * const files = findFileTags({
182
+ * attachments: {
183
+ * "primary<!F>": "f1",
184
+ * "secondary<!F>": "f2"
185
+ * }
186
+ * })
187
+ * // [
188
+ * // { path: 'attachments.primary', hash: 'f1', originalKey: 'primary<!F>' },
189
+ * // { path: 'attachments.secondary', hash: 'f2', originalKey: 'secondary<!F>' }
190
+ * // ]
191
+ */
192
+ function findFileTags(obj, path = "") {
193
+ const files = [];
194
+
195
+ // Base case: null, undefined, or primitives have no tags
196
+ if (obj === null || obj === undefined || typeof obj !== "object") {
197
+ return files;
198
+ }
199
+
200
+ // Handle arrays - recurse into each element with index as path segment
201
+ if (Array.isArray(obj)) {
202
+ for (let i = 0; i < obj.length; i++) {
203
+ files.push(...findFileTags(obj[i], path ? `${path}.${i}` : String(i)));
204
+ }
205
+ return files;
206
+ }
207
+
208
+ // Handle objects - check each key for file tags or recurse
209
+ for (const key of Object.keys(obj)) {
210
+ // Check for <!F> (File) tag
211
+ const fMatch = key.match(/^(.+)<!F>$/);
212
+
213
+ if (fMatch) {
214
+ files.push({
215
+ path: path ? `${path}.${fMatch[1]}` : fMatch[1],
216
+ hash: obj[key],
217
+ originalKey: key,
218
+ });
219
+ } else {
220
+ // No tag on this key, recurse into the value
221
+ files.push(...findFileTags(obj[key], path ? `${path}.${key}` : key));
222
+ }
223
+ }
224
+
225
+ return files;
226
+ }
227
+
228
+ /**
229
+ * Recursively cleans upload tags from an object.
230
+ *
231
+ * Transforms keys like `"avatar<!B>"` to `"avatar"` throughout the object tree.
232
+ * This prepares the object for controller consumption after binary data
233
+ * has been collected.
234
+ *
235
+ * The function:
236
+ * - Removes `<!B>` and `<!A>` suffixes from keys
237
+ * - Preserves the structure of the object
238
+ * - Recursively processes nested objects and arrays
239
+ * - Returns a new object (does not mutate the original)
240
+ *
241
+ * @function cleanUploadTags
242
+ * @param {Object} obj - The object with tagged keys to clean
243
+ * @returns {Object} New object with tags removed from keys
244
+ *
245
+ * @example
246
+ * // Clean a simple object
247
+ * const cleaned = cleanUploadTags({
248
+ * "image<!B>": "hash123",
249
+ * name: "Photo"
250
+ * })
251
+ * // { image: 'hash123', name: 'Photo' }
252
+ *
253
+ * @example
254
+ * // Clean nested objects
255
+ * const cleaned = cleanUploadTags({
256
+ * user: {
257
+ * "avatar<!B>": "hash1",
258
+ * profile: {
259
+ * "banner<!A>": "hash2",
260
+ * bio: "Hello"
261
+ * }
262
+ * }
263
+ * })
264
+ * // {
265
+ * // user: {
266
+ * // avatar: 'hash1',
267
+ * // profile: {
268
+ * // banner: 'hash2',
269
+ * // bio: 'Hello'
270
+ * // }
271
+ * // }
272
+ * // }
273
+ *
274
+ * @example
275
+ * // Clean arrays
276
+ * const cleaned = cleanUploadTags({
277
+ * files: [
278
+ * { "data<!B>": "h1", name: "file1" },
279
+ * { "data<!B>": "h2", name: "file2" }
280
+ * ]
281
+ * })
282
+ * // {
283
+ * // files: [
284
+ * // { data: 'h1', name: 'file1' },
285
+ * // { data: 'h2', name: 'file2' }
286
+ * // ]
287
+ * // }
288
+ */
289
+ function cleanUploadTags(obj) {
290
+ // Base case: null, undefined, or primitives pass through unchanged
291
+ if (obj === null || obj === undefined || typeof obj !== "object") {
292
+ return obj;
293
+ }
294
+
295
+ // Handle arrays - recursively clean each element
296
+ if (Array.isArray(obj)) {
297
+ return obj.map(cleanUploadTags);
298
+ }
299
+
300
+ // Handle objects - clean tagged keys and recurse into values
301
+ const cleaned = {};
302
+
303
+ for (const key of Object.keys(obj)) {
304
+ // Check for <!B> (Buffer) tag
305
+ const bMatch = key.match(/^(.+)<!B>$/);
306
+ // Check for <!A> (ArrayBuffer) tag
307
+ const aMatch = key.match(/^(.+)<!A>$/);
308
+
309
+ if (bMatch) {
310
+ // Remove <!B> tag, keep the value (hash for now, will be replaced with data)
311
+ cleaned[bMatch[1]] = obj[key];
312
+ } else if (aMatch) {
313
+ // Remove <!A> tag, keep the value
314
+ cleaned[aMatch[1]] = obj[key];
315
+ } else {
316
+ // No tag, recursively clean the value
317
+ cleaned[key] = cleanUploadTags(obj[key]);
318
+ }
319
+ }
320
+
321
+ return cleaned;
322
+ }
323
+
324
+ /**
325
+ * Sets a value at a dot-notation path in an object.
326
+ *
327
+ * Used to inject binary data into the cleaned message object after
328
+ * the upload has been received. Navigates to the specified path
329
+ * and sets the value.
330
+ *
331
+ * **Note**: This function mutates the original object.
332
+ *
333
+ * @function setValueAtPath
334
+ * @param {Object} obj - The object to modify
335
+ * @param {string} path - Dot-notation path (e.g., 'user.profile.avatar')
336
+ * @param {*} value - The value to set at the path
337
+ *
338
+ * @example
339
+ * // Set a simple path
340
+ * const obj = { avatar: 'hash123' }
341
+ * setValueAtPath(obj, 'avatar', Buffer.from([1, 2, 3]))
342
+ * // obj is now: { avatar: <Buffer 01 02 03> }
343
+ *
344
+ * @example
345
+ * // Set a nested path
346
+ * const obj = { user: { profile: { avatar: 'hash' } } }
347
+ * setValueAtPath(obj, 'user.profile.avatar', Buffer.from('image'))
348
+ * // obj.user.profile.avatar is now a Buffer
349
+ *
350
+ * @example
351
+ * // Set a path in an array
352
+ * const obj = { files: [{ data: 'h1' }, { data: 'h2' }] }
353
+ * setValueAtPath(obj, 'files.0.data', Buffer.from('content1'))
354
+ * // obj.files[0].data is now a Buffer
355
+ *
356
+ * @example
357
+ * // Usage in upload processing flow
358
+ * const message = cleanUploadTags({ "image<!B>": "hash123" })
359
+ * // message: { image: 'hash123' }
360
+ *
361
+ * // After receiving the binary upload...
362
+ * setValueAtPath(message, 'image', uploadedBuffer)
363
+ * // message: { image: <Buffer ...> }
364
+ */
365
+ function setValueAtPath(obj, path, value) {
366
+ const parts = path.split(".");
367
+ let current = obj;
368
+
369
+ // Navigate to the parent of the target
370
+ for (let i = 0; i < parts.length - 1; i++) {
371
+ current = current[parts[i]];
372
+ }
373
+
374
+ // Set the value at the final key
375
+ current[parts[parts.length - 1]] = value;
376
+ }
377
+
378
+ module.exports = {
379
+ /**
380
+ * Find all upload tags (<!B> and <!A>) in an object.
381
+ * @function
382
+ */
383
+ findUploadTags,
384
+
385
+ /**
386
+ * Find all file tags (<!F>) in an object.
387
+ * @function
388
+ */
389
+ findFileTags,
390
+
391
+ /**
392
+ * Clean upload tags from object keys.
393
+ * @function
394
+ */
395
+ cleanUploadTags,
396
+
397
+ /**
398
+ * Set a value at a dot-notation path.
399
+ * @function
400
+ */
401
+ setValueAtPath,
402
+ };
@@ -0,0 +1,19 @@
1
+ # Server Utils Module
2
+
3
+ ## Overview
4
+
5
+ Server-side utility functions used throughout the api-ape server implementation. These utilities provide essential functionality for controller loading, unique ID generation, and client identification via User-Agent parsing.
6
+
7
+ **Key capabilities:**
8
+
9
+ - **Controller loading** — Recursively load JavaScript modules and map file paths to API endpoints
10
+ - **ID generation** — Generate unique, URL-safe identifiers using Crockford Base32 encoding
11
+ - **User-Agent parsing** — Zero-dependency parsing for browser, OS, device, and bot detection
12
+
13
+ > **Contributing?** See [`files.md`](./files.md) for directory structure and file descriptions.
14
+
15
+ ## See Also
16
+
17
+ - [`userAgent/README.md`](./userAgent/README.md) — User-agent detection patterns
18
+ - [`../lib/loader.js`](../lib/loader.js) — Controller loader using deepRequire
19
+ - [`../lib/wiring.js`](../lib/wiring.js) — Uses parseUserAgent for client info