@evolu/common 8.9.0 → 8.11.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 (337) hide show
  1. package/dist/src/Bytes.d.ts +647 -0
  2. package/dist/src/Bytes.d.ts.map +1 -0
  3. package/dist/src/{Binary.js → Bytes.js} +266 -16
  4. package/dist/src/Config.d.ts +142 -0
  5. package/dist/src/Config.d.ts.map +1 -0
  6. package/dist/src/Config.js +181 -0
  7. package/dist/src/Console.d.ts +62 -7
  8. package/dist/src/Console.d.ts.map +1 -1
  9. package/dist/src/Console.js +20 -4
  10. package/dist/src/Crypto.d.ts +76 -4
  11. package/dist/src/Crypto.d.ts.map +1 -1
  12. package/dist/src/Crypto.js +55 -4
  13. package/dist/src/Error.d.ts +45 -0
  14. package/dist/src/Error.d.ts.map +1 -1
  15. package/dist/src/Error.js +69 -0
  16. package/dist/src/Fs.d.ts +376 -0
  17. package/dist/src/Fs.d.ts.map +1 -0
  18. package/dist/src/Fs.js +113 -0
  19. package/dist/src/Identicon.d.ts +2 -2
  20. package/dist/src/Identicon.js +2 -2
  21. package/dist/src/LeakDetector.d.ts +22 -3
  22. package/dist/src/LeakDetector.d.ts.map +1 -1
  23. package/dist/src/LeakDetector.js +12 -2
  24. package/dist/src/LockManager.d.ts +8 -0
  25. package/dist/src/LockManager.d.ts.map +1 -1
  26. package/dist/src/LockManager.js +6 -0
  27. package/dist/src/Number.d.ts +50 -7
  28. package/dist/src/Number.d.ts.map +1 -1
  29. package/dist/src/Number.js +47 -8
  30. package/dist/src/Object.d.ts +32 -0
  31. package/dist/src/Object.d.ts.map +1 -1
  32. package/dist/src/Object.js +46 -0
  33. package/dist/src/Platform.d.ts +47 -7
  34. package/dist/src/Platform.d.ts.map +1 -1
  35. package/dist/src/Platform.js +24 -5
  36. package/dist/src/Random.d.ts +25 -2
  37. package/dist/src/Random.d.ts.map +1 -1
  38. package/dist/src/Random.js +14 -2
  39. package/dist/src/Resource.d.ts +156 -1
  40. package/dist/src/Resource.d.ts.map +1 -1
  41. package/dist/src/Resource.js +201 -72
  42. package/dist/src/Schedule.d.ts +11 -10
  43. package/dist/src/Schedule.d.ts.map +1 -1
  44. package/dist/src/Schedule.js +1 -1
  45. package/dist/src/Sqlite.d.ts +132 -16
  46. package/dist/src/Sqlite.d.ts.map +1 -1
  47. package/dist/src/Sqlite.js +64 -10
  48. package/dist/src/Task.d.ts +15 -4
  49. package/dist/src/Task.d.ts.map +1 -1
  50. package/dist/src/Task.js +41 -15
  51. package/dist/src/Test.d.ts +9 -0
  52. package/dist/src/Test.d.ts.map +1 -1
  53. package/dist/src/Test.js +4 -0
  54. package/dist/src/Time.d.ts +179 -20
  55. package/dist/src/Time.d.ts.map +1 -1
  56. package/dist/src/Time.js +95 -6
  57. package/dist/src/Type.d.ts +3056 -1539
  58. package/dist/src/Type.d.ts.map +1 -1
  59. package/dist/src/Type.js +2548 -584
  60. package/dist/src/WebSocket.d.ts +164 -13
  61. package/dist/src/WebSocket.d.ts.map +1 -1
  62. package/dist/src/WebSocket.js +133 -24
  63. package/dist/src/Worker.d.ts +90 -8
  64. package/dist/src/Worker.d.ts.map +1 -1
  65. package/dist/src/Worker.js +28 -2
  66. package/dist/src/index.d.ts +9 -8
  67. package/dist/src/index.d.ts.map +1 -1
  68. package/dist/src/index.js +5 -4
  69. package/dist/src/intl/_en.d.ts +24 -1
  70. package/dist/src/intl/_en.d.ts.map +1 -1
  71. package/dist/src/intl/_en.js +20 -0
  72. package/dist/src/intl/ar.d.ts +24 -1
  73. package/dist/src/intl/ar.d.ts.map +1 -1
  74. package/dist/src/intl/ar.js +20 -0
  75. package/dist/src/intl/bn.d.ts +24 -1
  76. package/dist/src/intl/bn.d.ts.map +1 -1
  77. package/dist/src/intl/bn.js +20 -0
  78. package/dist/src/intl/ca.d.ts +24 -1
  79. package/dist/src/intl/ca.d.ts.map +1 -1
  80. package/dist/src/intl/ca.js +20 -0
  81. package/dist/src/intl/cs.d.ts +24 -1
  82. package/dist/src/intl/cs.d.ts.map +1 -1
  83. package/dist/src/intl/cs.js +20 -0
  84. package/dist/src/intl/da.d.ts +24 -1
  85. package/dist/src/intl/da.d.ts.map +1 -1
  86. package/dist/src/intl/da.js +20 -0
  87. package/dist/src/intl/de.d.ts +24 -1
  88. package/dist/src/intl/de.d.ts.map +1 -1
  89. package/dist/src/intl/de.js +20 -0
  90. package/dist/src/intl/el.d.ts +24 -1
  91. package/dist/src/intl/el.d.ts.map +1 -1
  92. package/dist/src/intl/el.js +20 -0
  93. package/dist/src/intl/es.d.ts +24 -1
  94. package/dist/src/intl/es.d.ts.map +1 -1
  95. package/dist/src/intl/es.js +20 -0
  96. package/dist/src/intl/fa.d.ts +24 -1
  97. package/dist/src/intl/fa.d.ts.map +1 -1
  98. package/dist/src/intl/fa.js +20 -0
  99. package/dist/src/intl/fi.d.ts +24 -1
  100. package/dist/src/intl/fi.d.ts.map +1 -1
  101. package/dist/src/intl/fi.js +20 -0
  102. package/dist/src/intl/fil.d.ts +24 -1
  103. package/dist/src/intl/fil.d.ts.map +1 -1
  104. package/dist/src/intl/fil.js +20 -0
  105. package/dist/src/intl/fr.d.ts +24 -1
  106. package/dist/src/intl/fr.d.ts.map +1 -1
  107. package/dist/src/intl/fr.js +20 -0
  108. package/dist/src/intl/he.d.ts +24 -1
  109. package/dist/src/intl/he.d.ts.map +1 -1
  110. package/dist/src/intl/he.js +20 -0
  111. package/dist/src/intl/hi.d.ts +24 -1
  112. package/dist/src/intl/hi.d.ts.map +1 -1
  113. package/dist/src/intl/hi.js +20 -0
  114. package/dist/src/intl/hr.d.ts +24 -1
  115. package/dist/src/intl/hr.d.ts.map +1 -1
  116. package/dist/src/intl/hr.js +20 -0
  117. package/dist/src/intl/hu.d.ts +22 -1
  118. package/dist/src/intl/hu.d.ts.map +1 -1
  119. package/dist/src/intl/hu.js +18 -0
  120. package/dist/src/intl/id.d.ts +24 -1
  121. package/dist/src/intl/id.d.ts.map +1 -1
  122. package/dist/src/intl/id.js +20 -0
  123. package/dist/src/intl/it.d.ts +24 -1
  124. package/dist/src/intl/it.d.ts.map +1 -1
  125. package/dist/src/intl/it.js +20 -0
  126. package/dist/src/intl/ja.d.ts +24 -1
  127. package/dist/src/intl/ja.d.ts.map +1 -1
  128. package/dist/src/intl/ja.js +20 -0
  129. package/dist/src/intl/ko.d.ts +24 -1
  130. package/dist/src/intl/ko.d.ts.map +1 -1
  131. package/dist/src/intl/ko.js +20 -0
  132. package/dist/src/intl/ml.d.ts +24 -1
  133. package/dist/src/intl/ml.d.ts.map +1 -1
  134. package/dist/src/intl/ml.js +20 -0
  135. package/dist/src/intl/mr.d.ts +24 -1
  136. package/dist/src/intl/mr.d.ts.map +1 -1
  137. package/dist/src/intl/mr.js +20 -0
  138. package/dist/src/intl/ms.d.ts +24 -1
  139. package/dist/src/intl/ms.d.ts.map +1 -1
  140. package/dist/src/intl/ms.js +20 -0
  141. package/dist/src/intl/nb.d.ts +22 -1
  142. package/dist/src/intl/nb.d.ts.map +1 -1
  143. package/dist/src/intl/nb.js +18 -0
  144. package/dist/src/intl/nl.d.ts +24 -1
  145. package/dist/src/intl/nl.d.ts.map +1 -1
  146. package/dist/src/intl/nl.js +20 -0
  147. package/dist/src/intl/pa.d.ts +24 -1
  148. package/dist/src/intl/pa.d.ts.map +1 -1
  149. package/dist/src/intl/pa.js +20 -0
  150. package/dist/src/intl/pl.d.ts +23 -0
  151. package/dist/src/intl/pl.d.ts.map +1 -1
  152. package/dist/src/intl/pl.js +20 -0
  153. package/dist/src/intl/pt-BR.d.ts +24 -1
  154. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  155. package/dist/src/intl/pt-BR.js +20 -0
  156. package/dist/src/intl/pt.d.ts +24 -1
  157. package/dist/src/intl/pt.d.ts.map +1 -1
  158. package/dist/src/intl/pt.js +20 -0
  159. package/dist/src/intl/ro.d.ts +24 -1
  160. package/dist/src/intl/ro.d.ts.map +1 -1
  161. package/dist/src/intl/ro.js +20 -0
  162. package/dist/src/intl/sk.d.ts +24 -1
  163. package/dist/src/intl/sk.d.ts.map +1 -1
  164. package/dist/src/intl/sk.js +20 -0
  165. package/dist/src/intl/sl.d.ts +24 -1
  166. package/dist/src/intl/sl.d.ts.map +1 -1
  167. package/dist/src/intl/sl.js +20 -0
  168. package/dist/src/intl/sv.d.ts +24 -1
  169. package/dist/src/intl/sv.d.ts.map +1 -1
  170. package/dist/src/intl/sv.js +20 -0
  171. package/dist/src/intl/sw.d.ts +21 -0
  172. package/dist/src/intl/sw.d.ts.map +1 -1
  173. package/dist/src/intl/sw.js +18 -0
  174. package/dist/src/intl/ta.d.ts +24 -1
  175. package/dist/src/intl/ta.d.ts.map +1 -1
  176. package/dist/src/intl/ta.js +20 -0
  177. package/dist/src/intl/te.d.ts +24 -1
  178. package/dist/src/intl/te.d.ts.map +1 -1
  179. package/dist/src/intl/te.js +20 -0
  180. package/dist/src/intl/th.d.ts +24 -1
  181. package/dist/src/intl/th.d.ts.map +1 -1
  182. package/dist/src/intl/th.js +20 -0
  183. package/dist/src/intl/tr.d.ts +24 -1
  184. package/dist/src/intl/tr.d.ts.map +1 -1
  185. package/dist/src/intl/tr.js +20 -0
  186. package/dist/src/intl/uk.d.ts +80 -57
  187. package/dist/src/intl/uk.d.ts.map +1 -1
  188. package/dist/src/intl/uk.js +174 -149
  189. package/dist/src/intl/ur.d.ts +24 -1
  190. package/dist/src/intl/ur.d.ts.map +1 -1
  191. package/dist/src/intl/ur.js +20 -0
  192. package/dist/src/intl/vi.d.ts +24 -1
  193. package/dist/src/intl/vi.d.ts.map +1 -1
  194. package/dist/src/intl/vi.js +20 -0
  195. package/dist/src/intl/zh-CN.d.ts +24 -1
  196. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  197. package/dist/src/intl/zh-CN.js +20 -0
  198. package/dist/src/intl/zh-TW.d.ts +24 -1
  199. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  200. package/dist/src/intl/zh-TW.js +20 -0
  201. package/dist/src/local-first/Db.d.ts +52 -3
  202. package/dist/src/local-first/Db.d.ts.map +1 -1
  203. package/dist/src/local-first/Db.js +412 -137
  204. package/dist/src/local-first/Evolu.d.ts +336 -211
  205. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  206. package/dist/src/local-first/Evolu.js +102 -15
  207. package/dist/src/local-first/Owner.d.ts +13 -30
  208. package/dist/src/local-first/Owner.d.ts.map +1 -1
  209. package/dist/src/local-first/Owner.js +13 -30
  210. package/dist/src/local-first/Protocol.d.ts +95 -17
  211. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  212. package/dist/src/local-first/Protocol.js +119 -39
  213. package/dist/src/local-first/Query.d.ts +8 -15
  214. package/dist/src/local-first/Query.d.ts.map +1 -1
  215. package/dist/src/local-first/Schema.d.ts +345 -21
  216. package/dist/src/local-first/Schema.d.ts.map +1 -1
  217. package/dist/src/local-first/Schema.js +214 -17
  218. package/dist/src/local-first/Shared.d.ts +537 -22
  219. package/dist/src/local-first/Shared.d.ts.map +1 -1
  220. package/dist/src/local-first/Shared.js +1437 -234
  221. package/dist/src/local-first/Storage.d.ts +192 -14
  222. package/dist/src/local-first/Storage.d.ts.map +1 -1
  223. package/dist/src/local-first/Storage.js +82 -21
  224. package/dist/src/local-first/Timestamp.d.ts +392 -41
  225. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  226. package/dist/src/local-first/Timestamp.js +404 -82
  227. package/dist/src/local-first/index.d.ts +0 -1
  228. package/dist/src/local-first/index.d.ts.map +1 -1
  229. package/dist/src/local-first/index.js +0 -1
  230. package/package.json +1 -1
  231. package/src/Assert.test.ts +2 -5
  232. package/src/{Binary.test.ts → Bytes.test.ts} +286 -1
  233. package/src/{Binary.ts → Bytes.ts} +652 -21
  234. package/src/Config.test.ts +668 -0
  235. package/src/Config.ts +410 -0
  236. package/src/Console.ts +62 -7
  237. package/src/Crypto.ts +76 -4
  238. package/src/Eq.test.ts +2 -3
  239. package/src/Error.test.ts +76 -3
  240. package/src/Error.ts +71 -0
  241. package/src/Fs.test.ts +105 -0
  242. package/src/Fs.ts +488 -0
  243. package/src/Identicon.ts +2 -2
  244. package/src/LeakDetector.ts +22 -3
  245. package/src/LockManager.ts +8 -0
  246. package/src/Number.test.ts +82 -18
  247. package/src/Number.ts +76 -8
  248. package/src/Object.test.ts +139 -10
  249. package/src/Object.ts +49 -0
  250. package/src/Platform.ts +50 -8
  251. package/src/Random.ts +25 -2
  252. package/src/Resource.test.ts +837 -0
  253. package/src/Resource.ts +235 -15
  254. package/src/Schedule.test.ts +50 -12
  255. package/src/Schedule.ts +24 -14
  256. package/src/Sqlite.ts +138 -18
  257. package/src/Task.test.ts +189 -8
  258. package/src/Task.ts +56 -17
  259. package/src/Test.ts +9 -0
  260. package/src/Time.test.ts +82 -11
  261. package/src/Time.ts +246 -24
  262. package/src/Type.test.ts +3994 -1119
  263. package/src/Type.ts +7258 -3842
  264. package/src/Types.test.ts +4 -14
  265. package/src/WebSocket.ts +313 -40
  266. package/src/Worker.ts +90 -8
  267. package/src/index.ts +18 -7
  268. package/src/intl/_en.ts +70 -0
  269. package/src/intl/ar.ts +71 -0
  270. package/src/intl/bn.ts +70 -0
  271. package/src/intl/ca.ts +70 -0
  272. package/src/intl/cs.ts +70 -0
  273. package/src/intl/da.ts +70 -0
  274. package/src/intl/de.ts +70 -0
  275. package/src/intl/el.ts +70 -0
  276. package/src/intl/es.ts +70 -0
  277. package/src/intl/fa.ts +70 -0
  278. package/src/intl/fi.ts +70 -0
  279. package/src/intl/fil.ts +70 -0
  280. package/src/intl/fr.ts +70 -0
  281. package/src/intl/he.ts +70 -0
  282. package/src/intl/hi.ts +70 -0
  283. package/src/intl/hr.ts +70 -0
  284. package/src/intl/hu.ts +69 -0
  285. package/src/intl/id.ts +70 -0
  286. package/src/intl/intl.test.ts +819 -1
  287. package/src/intl/it.ts +70 -0
  288. package/src/intl/ja.ts +70 -0
  289. package/src/intl/ko.ts +68 -0
  290. package/src/intl/ml.ts +70 -0
  291. package/src/intl/mr.ts +70 -0
  292. package/src/intl/ms.ts +71 -0
  293. package/src/intl/nb.ts +69 -0
  294. package/src/intl/nl.ts +70 -0
  295. package/src/intl/pa.ts +70 -0
  296. package/src/intl/pl.ts +63 -0
  297. package/src/intl/pt-BR.ts +70 -0
  298. package/src/intl/pt.ts +71 -0
  299. package/src/intl/ro.ts +70 -0
  300. package/src/intl/sk.ts +71 -0
  301. package/src/intl/sl.ts +70 -0
  302. package/src/intl/sv.ts +70 -0
  303. package/src/intl/sw.ts +62 -0
  304. package/src/intl/ta.ts +70 -0
  305. package/src/intl/te.ts +70 -0
  306. package/src/intl/th.ts +68 -0
  307. package/src/intl/tr.ts +70 -0
  308. package/src/intl/uk.ts +228 -155
  309. package/src/intl/ur.ts +70 -0
  310. package/src/intl/vi.ts +70 -0
  311. package/src/intl/zh-CN.ts +68 -0
  312. package/src/intl/zh-TW.ts +68 -0
  313. package/src/local-first/Db.ts +644 -339
  314. package/src/local-first/Evolu.test.ts +686 -21
  315. package/src/local-first/Evolu.ts +450 -228
  316. package/src/local-first/Owner.ts +13 -30
  317. package/src/local-first/Protocol.test.ts +618 -11
  318. package/src/local-first/Protocol.ts +197 -73
  319. package/src/local-first/Query.ts +8 -15
  320. package/src/local-first/Schema.test.ts +143 -0
  321. package/src/local-first/Schema.ts +374 -24
  322. package/src/local-first/Shared.test.ts +7731 -559
  323. package/src/local-first/Shared.ts +2036 -267
  324. package/src/local-first/Storage.ts +219 -33
  325. package/src/local-first/Timestamp.test.ts +344 -70
  326. package/src/local-first/Timestamp.ts +435 -119
  327. package/src/local-first/index.ts +0 -1
  328. package/dist/src/Binary.d.ts +0 -254
  329. package/dist/src/Binary.d.ts.map +0 -1
  330. package/dist/src/local-first/Error.d.ts +0 -12
  331. package/dist/src/local-first/Error.d.ts.map +0 -1
  332. package/dist/src/local-first/Error.js +0 -6
  333. package/dist/src/local-first/LocalAuth.d.ts +0 -150
  334. package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
  335. package/dist/src/local-first/LocalAuth.js +0 -179
  336. package/src/local-first/Error.ts +0 -17
  337. package/src/local-first/LocalAuth.ts +0 -457
package/src/Config.ts ADDED
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Strict configuration Types with explicit application policy.
3
+ *
4
+ * Decode configuration at startup, then pass validated values to constructors
5
+ * or Tasks. The application decides how to load configuration, combine sources,
6
+ * and apply defaults. Declare defaults with {@link withDefault}, or use `??`
7
+ * where a value is consumed. Invalid supplied values still fail decoding.
8
+ *
9
+ * Resolve source precedence before replacing absence with defaults. If a source
10
+ * Type already provides defaults and later composition needs to distinguish
11
+ * them from supplied values, use `strategy: "preserve"` and inspect
12
+ * `defaultUsed` explicitly. Preserving this evidence does not choose a merging
13
+ * policy: the application still decides which source wins.
14
+ *
15
+ * {@link env} creates a pure, reversible codec. It does not read global state,
16
+ * load files, merge sources, or introduce a Run dependency. Tests can pass
17
+ * ordinary objects; a Node.js entry point can pass `process.env`.
18
+ *
19
+ * @module
20
+ */
21
+
22
+ import { assert } from "./Assert.ts";
23
+ import {
24
+ createMutableRecord,
25
+ filterObjectKeys,
26
+ isPlainObject,
27
+ } from "./Object.ts";
28
+ import { ok } from "./Result.ts";
29
+ import {
30
+ CamelCaseIdentifier,
31
+ camelCaseToConstantCase,
32
+ ConstantCaseIdentifier,
33
+ EvoluType,
34
+ maxLength,
35
+ object,
36
+ objectKeys,
37
+ String,
38
+ transform,
39
+ Unknown,
40
+ type AnyType,
41
+ type InferErrors,
42
+ type ValidateLiteral,
43
+ type ObjectKeysType,
44
+ type ObjectProps,
45
+ type ObjectType,
46
+ type TransformType,
47
+ type TypeNode,
48
+ type withDefault,
49
+ } from "./Type.ts";
50
+ import type {
51
+ CompileTimeError,
52
+ IsUnion,
53
+ UnionToIntersection,
54
+ } from "./Types.ts";
55
+
56
+ /**
57
+ * An environment variable name in Evolu's configuration convention.
58
+ *
59
+ * Names are {@link ConstantCaseIdentifier}s of at most 255 characters, including
60
+ * the application prefix. This is an Evolu convention, not an operating-system
61
+ * limit. Double underscores do not introduce nesting.
62
+ *
63
+ * ### Example
64
+ *
65
+ * ```ts
66
+ * import { assertErr, assertOk, EnvName } from "@evolu/common";
67
+ *
68
+ * assertOk(
69
+ * EnvName.fromUnknown("APP_MAX_OWNER_BYTES"),
70
+ * "APP_MAX_OWNER_BYTES",
71
+ * );
72
+ * assertErr(EnvName.fromUnknown("APP_maxOwnerBytes"));
73
+ * assertErr(EnvName.fromUnknown("APP_DB__PORT"));
74
+ * ```
75
+ */
76
+ export const EnvName = /*#__PURE__*/ maxLength(255)(ConstantCaseIdentifier);
77
+ export type EnvName = typeof EnvName.Output;
78
+
79
+ /** The configuration codec returned by {@link env}. */
80
+ export interface EnvType<Props extends EnvProps> extends TransformType<
81
+ typeof Unknown,
82
+ ObjectKeysType<EnvKeyType, ObjectType<EnvFlatProps<Props>>>,
83
+ "Env",
84
+ never,
85
+ Readonly<
86
+ Record<
87
+ string,
88
+ ObjectType<EnvFlatProps<Props>>["CanonicalInput"][keyof ObjectType<
89
+ EnvFlatProps<Props>
90
+ >["CanonicalInput"]]
91
+ >
92
+ >
93
+ > {}
94
+
95
+ /** Fields and one-level namespace groups used to construct an {@link env} Type. */
96
+ export type EnvProps = Readonly<
97
+ Record<string, ObjectProps[string] | ObjectProps>
98
+ >;
99
+
100
+ /**
101
+ * Creates a reversible environment-variable codec with a flat decoded output.
102
+ *
103
+ * Declare camelCase fields directly, or put them in one-level CONSTANT_CASE
104
+ * namespace groups. A direct `port` field reads `PORT`; a `maxOwnerBytes` field
105
+ * in an `EVOLU_RELAY` group reads `EVOLU_RELAY_MAX_OWNER_BYTES`. Groups affect
106
+ * external names only. Duplicate decoded fields or encoded names are rejected
107
+ * during construction, even when their Types are identical.
108
+ *
109
+ * Selects exact declared unprefixed names and all keys within declared
110
+ * namespaces. Namespace selection ignores casing so incorrectly cased names
111
+ * fail validation instead of being ignored. Selected names must match their
112
+ * exact declared spelling. Unrelated variables are ignored; misspelled
113
+ * unprefixed names or namespace prefixes can therefore still look absent.
114
+ *
115
+ * Field Types must encode to strings. Optional fields may be absent, but
116
+ * explicit `undefined` values are rejected. Empty strings remain present and
117
+ * must satisfy the field Type. Compose {@link withDefault} to supply defaults.
118
+ * Encoding emits canonical external names and string values, preserving absence
119
+ * when the field codec does. Errors retain external names and nested paths.
120
+ *
121
+ * Reads own string properties from any non-null object, including
122
+ * `process.env`. Selected properties must be enumerable data properties;
123
+ * getters are never read. Prototypes and internal contents such as Map entries
124
+ * are ignored. Non-object inputs fail with standard object errors. Schema names
125
+ * must satisfy {@link EnvName}; groups cannot be nested. Loading and source
126
+ * precedence remain explicit application code.
127
+ *
128
+ * ### Example
129
+ *
130
+ * ```ts
131
+ * import {
132
+ * assertEqual,
133
+ * assertErr,
134
+ * assertOk,
135
+ * assertType,
136
+ * ByteSizeLiteral,
137
+ * env,
138
+ * optional,
139
+ * Port,
140
+ * PortFromString,
141
+ * withDefault,
142
+ * } from "@evolu/common";
143
+ *
144
+ * const RelayEnv = env({
145
+ * port: withDefault(optional(PortFromString), Port.orThrow(4000)),
146
+ * EVOLU_RELAY: {
147
+ * maxOwnerBytes: optional(ByteSizeLiteral),
148
+ * },
149
+ * });
150
+ *
151
+ * const result = RelayEnv.fromUnknown({
152
+ * PORT: "04000",
153
+ * EVOLU_RELAY_MAX_OWNER_BYTES: "512KiB",
154
+ * HOME: "/home/evolu",
155
+ * });
156
+ *
157
+ * assertOk(result, { port: 4000, maxOwnerBytes: "512KiB" });
158
+ * assertType<typeof result.value.port, Port>();
159
+ * assertEqual(RelayEnv.to(result.value), {
160
+ * PORT: "4000",
161
+ * EVOLU_RELAY_MAX_OWNER_BYTES: "512KiB",
162
+ * });
163
+ *
164
+ * assertOk(RelayEnv.fromUnknown({}), { port: 4000 });
165
+ * assertErr(RelayEnv.fromUnknown({ PORT: "" }));
166
+ * assertErr(RelayEnv.fromUnknown({ EVOLU_RELAY_MAX_OWENR_BYTES: "1MiB" }));
167
+ * assertErr(RelayEnv.fromUnknown({ evolu_relay_max_owner_bytes: "1MiB" }));
168
+ * ```
169
+ */
170
+ export const env = <const Props extends EnvProps>(
171
+ props: Props,
172
+ ..._validation: [EnvValidation<Props>] extends [never]
173
+ ? []
174
+ : [EnvValidation<Props>]
175
+ ): EnvType<Props> => {
176
+ assert(
177
+ isPlainObject(props),
178
+ "Environment properties must be a plain object.",
179
+ );
180
+ const flat = createMutableRecord<string, ObjectProps[string]>();
181
+ const inputNameByOutputName = new Map<string, string>();
182
+ const outputNameByInputName = new Map<string, string>();
183
+ const prefixes: Array<string> = [];
184
+ const unprefixedNames = new Set<string>();
185
+
186
+ for (const rootName of Reflect.ownKeys(props)) {
187
+ const descriptor = Object.getOwnPropertyDescriptor(props, rootName)!;
188
+ assert(
189
+ typeof rootName === "string" &&
190
+ descriptor.enumerable &&
191
+ "value" in descriptor,
192
+ "Environment properties must be enumerable string data properties.",
193
+ );
194
+ const grouped = ConstantCaseIdentifier.is(rootName);
195
+ assert(
196
+ grouped || CamelCaseIdentifier.is(rootName),
197
+ `Invalid environment schema name ${JSON.stringify(rootName)}. Expected a camelCase field or CONSTANT_CASE namespace.`,
198
+ );
199
+ const rootValue: unknown = descriptor.value;
200
+ const fields = grouped ? rootValue : { [rootName]: rootValue };
201
+ assert(
202
+ isPlainObject(fields) && !EvoluType.is(fields),
203
+ "Environment groups must be plain objects containing fields.",
204
+ );
205
+ if (grouped) {
206
+ assert(
207
+ EnvName.is(rootName + "_A"),
208
+ `Invalid environment namespace ${JSON.stringify(rootName)}. Names must fit within 255 characters.`,
209
+ );
210
+ prefixes.push(rootName + "_");
211
+ }
212
+ for (const name of Reflect.ownKeys(fields)) {
213
+ const field = Object.getOwnPropertyDescriptor(fields, name)!;
214
+ assert(
215
+ typeof name === "string" && field.enumerable && "value" in field,
216
+ "Environment fields must be enumerable string data properties.",
217
+ );
218
+ assert(
219
+ CamelCaseIdentifier.is(name),
220
+ `Invalid environment field ${JSON.stringify(name)}. Expected a camelCase identifier; groups cannot be nested.`,
221
+ );
222
+ assert(
223
+ !inputNameByOutputName.has(name),
224
+ `Duplicate environment field ${JSON.stringify(name)}.`,
225
+ );
226
+ const encoded =
227
+ (grouped ? rootName + "_" : "") + camelCaseToConstantCase(name);
228
+ assert(
229
+ EnvName.is(encoded),
230
+ `Invalid environment schema name ${JSON.stringify(encoded)}. Expected a CONSTANT_CASE identifier of at most 255 characters.`,
231
+ );
232
+ assert(
233
+ !outputNameByInputName.has(encoded),
234
+ `Duplicate environment variable ${JSON.stringify(encoded)}.`,
235
+ );
236
+ const property: unknown = field.value;
237
+ const fieldType: unknown = EvoluType.is(property)
238
+ ? property
239
+ : isPlainObject(property)
240
+ ? Object.getOwnPropertyDescriptor(property, "type")?.value
241
+ : undefined;
242
+ assert(
243
+ EvoluType.is(fieldType),
244
+ `Invalid environment field ${JSON.stringify(name)}. Expected a Type or optional/defaulted property; groups must use CONSTANT_CASE names.`,
245
+ );
246
+ flat[name] = property as ObjectProps[string];
247
+ inputNameByOutputName.set(name, encoded);
248
+ outputNameByInputName.set(encoded, name);
249
+ if (!grouped) unprefixedNames.add(encoded);
250
+ }
251
+ }
252
+
253
+ // Runtime construction validates the flattened fields; the public signature
254
+ // checks their concrete Types and string encodings before flattening.
255
+ const output = (
256
+ object as unknown as (props: ObjectProps) => ObjectType<ObjectProps>
257
+ )(flat);
258
+ const key = transform("EnvKey", String, CamelCaseIdentifier, {
259
+ // Identity fallback keeps this exposed codec round-trippable for other
260
+ // valid camelCase identifiers. objectKeys accepts only declared fields.
261
+ from: (value) => ok(outputNameByInputName.get(value) ?? value),
262
+ to: (value) => inputNameByOutputName.get(value) ?? value,
263
+ });
264
+ const withKeys = objectKeys(key)(output);
265
+ const type = transform("Env", Unknown, withKeys, {
266
+ from: (value) => {
267
+ if (typeof value !== "object" || value === null) return ok(value);
268
+ return ok(
269
+ filterObjectKeys(
270
+ value,
271
+ (key) =>
272
+ unprefixedNames.has(key) ||
273
+ prefixes.some((prefix) => key.toUpperCase().startsWith(prefix)),
274
+ ),
275
+ );
276
+ },
277
+ to: (value) => value,
278
+ });
279
+ // The maps preserve each field's name and codec; only the static flattening
280
+ // cannot be expressed by the runtime loops above.
281
+ return type as unknown as EnvType<Props>;
282
+ };
283
+
284
+ type EnvFlatProps<Props extends EnvProps> =
285
+ UnionToIntersection<
286
+ {
287
+ [Key in keyof Props]: EnvDeclarationKind<Key> extends "group"
288
+ ? Props[Key]
289
+ : EnvDeclarationKind<Key> extends "field"
290
+ ? { readonly [Field in Key]: Props[Key] }
291
+ : never;
292
+ }[keyof Props]
293
+ > extends infer Flat extends ObjectProps
294
+ ? Flat
295
+ : {};
296
+
297
+ type EnvKeyType = TransformType<
298
+ typeof String,
299
+ typeof CamelCaseIdentifier,
300
+ "EnvKey",
301
+ never,
302
+ string
303
+ >;
304
+
305
+ type EnvValidation<Props extends EnvProps> =
306
+ | EnvKeysValidation<Props>
307
+ | {
308
+ [Key in keyof Props]: EnvDeclarationKind<Key> extends "group"
309
+ ? Props[Key] extends ObjectProps
310
+ ? EnvFieldsValidation<Props[Key]>
311
+ : CompileTimeError<
312
+ "Env",
313
+ "Environment namespaces must contain a group of fields."
314
+ >
315
+ : EnvDeclarationKind<Key> extends "field"
316
+ ? Props[Key] extends ObjectProps[string]
317
+ ? EnvFieldValidation<Props[Key]>
318
+ : CompileTimeError<
319
+ "Env",
320
+ "Environment fields must be Types, optional properties, or defaulted properties."
321
+ >
322
+ : CompileTimeError<
323
+ "Env",
324
+ "Environment declarations must use camelCase field names or CONSTANT_CASE namespace names."
325
+ >;
326
+ }[keyof Props];
327
+
328
+ type EnvFieldsValidation<Props extends ObjectProps> =
329
+ | EnvKeysValidation<Props>
330
+ | {
331
+ [Key in keyof Props]: EnvDeclarationKind<Key> extends "field"
332
+ ? EnvFieldValidation<Props[Key]>
333
+ : CompileTimeError<
334
+ "Env",
335
+ "Environment fields must use camelCase names."
336
+ >;
337
+ }[keyof Props];
338
+
339
+ type EnvFieldValidation<Field> =
340
+ IsUnion<Field> extends true
341
+ ? CompileTimeError<"Env", "Environment fields must use one concrete Type.">
342
+ : EnvFieldType<Field> extends infer T extends AnyType
343
+ ? IsUnion<T> extends true
344
+ ? CompileTimeError<
345
+ "Env",
346
+ "Environment fields must use one concrete Type."
347
+ >
348
+ : [T["CanonicalInput"]] extends [string]
349
+ ? Extract<
350
+ | "ObjectMissingProperty"
351
+ | "ObjectPropertyAccess"
352
+ | "ObjectExcessProperty",
353
+ InferErrors<T>["type"]
354
+ > extends never
355
+ ? never
356
+ : CompileTimeError<
357
+ "Env",
358
+ "Environment fields must not use error tags reserved for Object structure."
359
+ >
360
+ : CompileTimeError<
361
+ "Env",
362
+ "Environment fields must encode to strings."
363
+ >
364
+ : CompileTimeError<
365
+ "Env",
366
+ "Environment fields must use one concrete Type."
367
+ >;
368
+
369
+ type EnvFieldType<Field> = Field extends TypeNode
370
+ ? Field
371
+ : Field extends { readonly type: infer T extends TypeNode }
372
+ ? T
373
+ : never;
374
+
375
+ type EnvKeysValidation<Props> =
376
+ | (string extends keyof Props
377
+ ? CompileTimeError<
378
+ "Env",
379
+ "Environment properties must use fixed string keys."
380
+ >
381
+ : never)
382
+ | (IsUnion<Props> extends true
383
+ ? CompileTimeError<
384
+ "Env",
385
+ "Environment properties must use one concrete schema."
386
+ >
387
+ : never)
388
+ | {
389
+ [Key in keyof Props]: Key extends string
390
+ ? ValidateLiteral<Key> extends Key
391
+ ? never
392
+ : CompileTimeError<
393
+ "Env",
394
+ "Environment properties must use fixed string keys."
395
+ >
396
+ : CompileTimeError<
397
+ "Env",
398
+ "Environment properties must use fixed string keys."
399
+ >;
400
+ }[keyof Props];
401
+
402
+ // Classify declarations by casing; identifier Types validate the full grammar
403
+ // and name lengths during construction.
404
+ type EnvDeclarationKind<Key> = Key extends string
405
+ ? Key extends Uppercase<Key>
406
+ ? "group"
407
+ : Key extends Uncapitalize<Key>
408
+ ? "field"
409
+ : "invalid"
410
+ : "invalid";
package/src/Console.ts CHANGED
@@ -93,6 +93,7 @@ import {
93
93
  * For testing, use {@link testCreateConsole} which creates a {@link TestConsole}
94
94
  * with array output and snapshot helpers.
95
95
  *
96
+ * @group Core
96
97
  * @see {@link createConsole}
97
98
  */
98
99
  export interface Console {
@@ -176,6 +177,11 @@ export interface Console {
176
177
  readonly write: (entry: ConsoleEntry) => void;
177
178
  }
178
179
 
180
+ /**
181
+ * Dependency wrapper for {@link Console}.
182
+ *
183
+ * @group Core
184
+ */
179
185
  export interface ConsoleDep {
180
186
  readonly console: Console;
181
187
  }
@@ -193,6 +199,8 @@ export interface ConsoleDep {
193
199
  * - `"warn"` — Recoverable issues that may need attention
194
200
  * - `"error"` — Failures requiring immediate attention
195
201
  * - `"silent"` — Disables all logging
202
+ *
203
+ * @group Core
196
204
  */
197
205
  export type ConsoleLevel =
198
206
  "trace" | "debug" | "log" | "info" | "warn" | "error" | "silent";
@@ -202,6 +210,8 @@ export type ConsoleLevel =
202
210
  *
203
211
  * Contains all information needed for outputs to route the log: method for
204
212
  * routing, path for context, and the original arguments.
213
+ *
214
+ * @group Core
205
215
  */
206
216
  export interface ConsoleEntry {
207
217
  /** The console method that was called. */
@@ -219,6 +229,8 @@ export interface ConsoleEntry {
219
229
  *
220
230
  * Used in {@link ConsoleEntry} to identify which console method was invoked.
221
231
  * Outputs can route or format differently based on the method.
232
+ *
233
+ * @group Core
222
234
  */
223
235
  export type ConsoleMethod =
224
236
  | "trace"
@@ -242,6 +254,8 @@ export type ConsoleMethod =
242
254
  * array for testing, etc.).
243
255
  *
244
256
  * Use {@link createNativeConsoleOutput} for native console output.
257
+ *
258
+ * @group Output
245
259
  */
246
260
  export interface ConsoleOutput {
247
261
  /** Write a log entry to this output. */
@@ -253,10 +267,16 @@ export interface ConsoleOutput {
253
267
  *
254
268
  * Used by {@link ConsoleConfig.formatter} and {@link ConsoleOutput.write}. Create
255
269
  * one with {@link createConsoleFormatter}.
270
+ *
271
+ * @group Output
256
272
  */
257
273
  export type ConsoleFormatter = (entry: ConsoleEntry) => ReadonlyArray<unknown>;
258
274
 
259
- /** Configuration for {@link createConsole}. */
275
+ /**
276
+ * Configuration for {@link createConsole}.
277
+ *
278
+ * @group Core
279
+ */
260
280
  export interface ConsoleConfig {
261
281
  /** Name of this console. Defaults to empty string. */
262
282
  readonly name?: string;
@@ -283,7 +303,11 @@ export interface ConsoleConfig {
283
303
  readonly formatter?: ConsoleFormatter;
284
304
  }
285
305
 
286
- /** Configuration for {@link createConsoleFormatter}. */
306
+ /**
307
+ * Configuration for {@link createConsoleFormatter}.
308
+ *
309
+ * @group Output
310
+ */
287
311
  export interface ConsoleFormatterConfig {
288
312
  /**
289
313
  * Timestamp format to prepend to log messages.
@@ -304,7 +328,11 @@ export interface ConsoleFormatterConfig {
304
328
  readonly startTime?: Millis;
305
329
  }
306
330
 
307
- /** Timestamp format for {@link ConsoleFormatterConfig}. */
331
+ /**
332
+ * Timestamp format for {@link ConsoleFormatterConfig}.
333
+ *
334
+ * @group Output
335
+ */
308
336
  export type ConsoleEntryTimestampFormat =
309
337
  "relative" | "absolute" | "iso" | "none";
310
338
 
@@ -341,6 +369,8 @@ export type ConsoleEntryTimestampFormat =
341
369
  * args: ["connected"],
342
370
  * });
343
371
  * ```
372
+ *
373
+ * @group Output
344
374
  */
345
375
  export interface ConsoleStoreOutput extends ConsoleOutput {
346
376
  /** Latest entry written to this output. */
@@ -350,6 +380,8 @@ export interface ConsoleStoreOutput extends ConsoleOutput {
350
380
  /**
351
381
  * Dependency providing the latest {@link ConsoleEntry} from a
352
382
  * {@link ConsoleStoreOutput}.
383
+ *
384
+ * @group Output
353
385
  */
354
386
  export interface ConsoleStoreOutputEntryDep {
355
387
  readonly consoleStoreOutputEntry: ReadonlyStore<ConsoleEntry | null>;
@@ -359,6 +391,8 @@ export interface ConsoleStoreOutputEntryDep {
359
391
  * A test console that captures all output for assertions.
360
392
  *
361
393
  * Use as a drop-in replacement for {@link Console} in tests.
394
+ *
395
+ * @group Testing
362
396
  */
363
397
  export interface TestConsole extends Console {
364
398
  /** Gets all captured entries and clears the internal buffer. */
@@ -368,6 +402,11 @@ export interface TestConsole extends Console {
368
402
  readonly clearEntries: () => void;
369
403
  }
370
404
 
405
+ /**
406
+ * Dependency wrapper for {@link TestConsole}.
407
+ *
408
+ * @group Testing
409
+ */
371
410
  export interface TestConsoleDep {
372
411
  readonly console: TestConsole;
373
412
  }
@@ -382,7 +421,11 @@ const levelOrder: Record<ConsoleLevel, number> = {
382
421
  silent: 6,
383
422
  };
384
423
 
385
- /** Creates a {@link Console}. */
424
+ /**
425
+ * Creates a {@link Console}.
426
+ *
427
+ * @group Core
428
+ */
386
429
  export const createConsole = ({
387
430
  name = "",
388
431
  level = "log",
@@ -458,7 +501,6 @@ export const createConsole = ({
458
501
  *
459
502
  * ```ts
460
503
  * import {
461
- * assertEqual,
462
504
  * assertType,
463
505
  * createNativeConsoleOutput,
464
506
  * type ConsoleOutput,
@@ -467,8 +509,9 @@ export const createConsole = ({
467
509
  * const output = createNativeConsoleOutput();
468
510
  *
469
511
  * assertType<typeof output, ConsoleOutput>();
470
- * assertEqual(typeof output.write, "function");
471
512
  * ```
513
+ *
514
+ * @group Output
472
515
  */
473
516
  export const createNativeConsoleOutput = (): ConsoleOutput => ({
474
517
  write: (entry, formatter) => {
@@ -530,6 +573,8 @@ export const createNativeConsoleOutput = (): ConsoleOutput => ({
530
573
  * assertType(Data, absolute);
531
574
  * assertEqual(absolute, ["14:30:15.123 [relay]", "connected"]);
532
575
  * ```
576
+ *
577
+ * @group Output
533
578
  */
534
579
  export const createConsoleFormatter =
535
580
  ({ time = createTime() }: Partial<TimeDep> = {}) =>
@@ -566,7 +611,11 @@ export const createConsoleFormatter =
566
611
  };
567
612
  };
568
613
 
569
- /** Creates a {@link ConsoleStoreOutput}. */
614
+ /**
615
+ * Creates a {@link ConsoleStoreOutput}.
616
+ *
617
+ * @group Output
618
+ */
570
619
  export const createConsoleStoreOutput = (): ConsoleStoreOutput => {
571
620
  const entry = createStore<ConsoleEntry | null>(null);
572
621
  return {
@@ -600,6 +649,8 @@ export const createConsoleStoreOutput = (): ConsoleStoreOutput => {
600
649
  * { method: "info", path: [], args: ["connected"] },
601
650
  * ]);
602
651
  * ```
652
+ *
653
+ * @group Output
603
654
  */
604
655
  export const createConsoleArrayOutput = (
605
656
  entries: Array<ConsoleEntry>,
@@ -644,6 +695,8 @@ export const createConsoleArrayOutput = (
644
695
  * assertType(Data, entries[0]);
645
696
  * assertEqual(storedEntry, entries[0]);
646
697
  * ```
698
+ *
699
+ * @group Output
647
700
  */
648
701
  export const createMultiOutput = (
649
702
  outputs: ReadonlyArray<ConsoleOutput>,
@@ -672,6 +725,8 @@ export const createMultiOutput = (
672
725
  * { method: "info", path: ["relay"], args: ["connected"] },
673
726
  * ]);
674
727
  * ```
728
+ *
729
+ * @group Testing
675
730
  */
676
731
  export const testCreateConsole = ({
677
732
  level = "trace",