@evolu/common 8.9.0 → 8.10.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 (233) 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/Fs.d.ts +302 -0
  8. package/dist/src/Fs.d.ts.map +1 -0
  9. package/dist/src/Fs.js +111 -0
  10. package/dist/src/Number.d.ts +50 -7
  11. package/dist/src/Number.d.ts.map +1 -1
  12. package/dist/src/Number.js +47 -8
  13. package/dist/src/Object.d.ts +32 -0
  14. package/dist/src/Object.d.ts.map +1 -1
  15. package/dist/src/Object.js +41 -0
  16. package/dist/src/Sqlite.js +1 -1
  17. package/dist/src/Time.d.ts +74 -12
  18. package/dist/src/Time.d.ts.map +1 -1
  19. package/dist/src/Time.js +40 -2
  20. package/dist/src/Type.d.ts +1451 -79
  21. package/dist/src/Type.d.ts.map +1 -1
  22. package/dist/src/Type.js +1303 -96
  23. package/dist/src/index.d.ts +3 -1
  24. package/dist/src/index.d.ts.map +1 -1
  25. package/dist/src/index.js +3 -1
  26. package/dist/src/intl/_en.d.ts +24 -1
  27. package/dist/src/intl/_en.d.ts.map +1 -1
  28. package/dist/src/intl/_en.js +20 -0
  29. package/dist/src/intl/ar.d.ts +24 -1
  30. package/dist/src/intl/ar.d.ts.map +1 -1
  31. package/dist/src/intl/ar.js +20 -0
  32. package/dist/src/intl/bn.d.ts +24 -1
  33. package/dist/src/intl/bn.d.ts.map +1 -1
  34. package/dist/src/intl/bn.js +20 -0
  35. package/dist/src/intl/ca.d.ts +24 -1
  36. package/dist/src/intl/ca.d.ts.map +1 -1
  37. package/dist/src/intl/ca.js +20 -0
  38. package/dist/src/intl/cs.d.ts +24 -1
  39. package/dist/src/intl/cs.d.ts.map +1 -1
  40. package/dist/src/intl/cs.js +20 -0
  41. package/dist/src/intl/da.d.ts +24 -1
  42. package/dist/src/intl/da.d.ts.map +1 -1
  43. package/dist/src/intl/da.js +20 -0
  44. package/dist/src/intl/de.d.ts +24 -1
  45. package/dist/src/intl/de.d.ts.map +1 -1
  46. package/dist/src/intl/de.js +20 -0
  47. package/dist/src/intl/el.d.ts +24 -1
  48. package/dist/src/intl/el.d.ts.map +1 -1
  49. package/dist/src/intl/el.js +20 -0
  50. package/dist/src/intl/es.d.ts +24 -1
  51. package/dist/src/intl/es.d.ts.map +1 -1
  52. package/dist/src/intl/es.js +20 -0
  53. package/dist/src/intl/fa.d.ts +24 -1
  54. package/dist/src/intl/fa.d.ts.map +1 -1
  55. package/dist/src/intl/fa.js +20 -0
  56. package/dist/src/intl/fi.d.ts +24 -1
  57. package/dist/src/intl/fi.d.ts.map +1 -1
  58. package/dist/src/intl/fi.js +20 -0
  59. package/dist/src/intl/fil.d.ts +24 -1
  60. package/dist/src/intl/fil.d.ts.map +1 -1
  61. package/dist/src/intl/fil.js +20 -0
  62. package/dist/src/intl/fr.d.ts +24 -1
  63. package/dist/src/intl/fr.d.ts.map +1 -1
  64. package/dist/src/intl/fr.js +20 -0
  65. package/dist/src/intl/he.d.ts +24 -1
  66. package/dist/src/intl/he.d.ts.map +1 -1
  67. package/dist/src/intl/he.js +20 -0
  68. package/dist/src/intl/hi.d.ts +24 -1
  69. package/dist/src/intl/hi.d.ts.map +1 -1
  70. package/dist/src/intl/hi.js +20 -0
  71. package/dist/src/intl/hr.d.ts +24 -1
  72. package/dist/src/intl/hr.d.ts.map +1 -1
  73. package/dist/src/intl/hr.js +20 -0
  74. package/dist/src/intl/hu.d.ts +22 -1
  75. package/dist/src/intl/hu.d.ts.map +1 -1
  76. package/dist/src/intl/hu.js +18 -0
  77. package/dist/src/intl/id.d.ts +24 -1
  78. package/dist/src/intl/id.d.ts.map +1 -1
  79. package/dist/src/intl/id.js +20 -0
  80. package/dist/src/intl/it.d.ts +24 -1
  81. package/dist/src/intl/it.d.ts.map +1 -1
  82. package/dist/src/intl/it.js +20 -0
  83. package/dist/src/intl/ja.d.ts +24 -1
  84. package/dist/src/intl/ja.d.ts.map +1 -1
  85. package/dist/src/intl/ja.js +20 -0
  86. package/dist/src/intl/ko.d.ts +24 -1
  87. package/dist/src/intl/ko.d.ts.map +1 -1
  88. package/dist/src/intl/ko.js +20 -0
  89. package/dist/src/intl/ml.d.ts +24 -1
  90. package/dist/src/intl/ml.d.ts.map +1 -1
  91. package/dist/src/intl/ml.js +20 -0
  92. package/dist/src/intl/mr.d.ts +24 -1
  93. package/dist/src/intl/mr.d.ts.map +1 -1
  94. package/dist/src/intl/mr.js +20 -0
  95. package/dist/src/intl/ms.d.ts +24 -1
  96. package/dist/src/intl/ms.d.ts.map +1 -1
  97. package/dist/src/intl/ms.js +20 -0
  98. package/dist/src/intl/nb.d.ts +22 -1
  99. package/dist/src/intl/nb.d.ts.map +1 -1
  100. package/dist/src/intl/nb.js +18 -0
  101. package/dist/src/intl/nl.d.ts +24 -1
  102. package/dist/src/intl/nl.d.ts.map +1 -1
  103. package/dist/src/intl/nl.js +20 -0
  104. package/dist/src/intl/pa.d.ts +24 -1
  105. package/dist/src/intl/pa.d.ts.map +1 -1
  106. package/dist/src/intl/pa.js +20 -0
  107. package/dist/src/intl/pl.d.ts +23 -0
  108. package/dist/src/intl/pl.d.ts.map +1 -1
  109. package/dist/src/intl/pl.js +20 -0
  110. package/dist/src/intl/pt-BR.d.ts +24 -1
  111. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  112. package/dist/src/intl/pt-BR.js +20 -0
  113. package/dist/src/intl/pt.d.ts +24 -1
  114. package/dist/src/intl/pt.d.ts.map +1 -1
  115. package/dist/src/intl/pt.js +20 -0
  116. package/dist/src/intl/ro.d.ts +24 -1
  117. package/dist/src/intl/ro.d.ts.map +1 -1
  118. package/dist/src/intl/ro.js +20 -0
  119. package/dist/src/intl/sk.d.ts +24 -1
  120. package/dist/src/intl/sk.d.ts.map +1 -1
  121. package/dist/src/intl/sk.js +20 -0
  122. package/dist/src/intl/sl.d.ts +24 -1
  123. package/dist/src/intl/sl.d.ts.map +1 -1
  124. package/dist/src/intl/sl.js +20 -0
  125. package/dist/src/intl/sv.d.ts +24 -1
  126. package/dist/src/intl/sv.d.ts.map +1 -1
  127. package/dist/src/intl/sv.js +20 -0
  128. package/dist/src/intl/sw.d.ts +21 -0
  129. package/dist/src/intl/sw.d.ts.map +1 -1
  130. package/dist/src/intl/sw.js +18 -0
  131. package/dist/src/intl/ta.d.ts +24 -1
  132. package/dist/src/intl/ta.d.ts.map +1 -1
  133. package/dist/src/intl/ta.js +20 -0
  134. package/dist/src/intl/te.d.ts +24 -1
  135. package/dist/src/intl/te.d.ts.map +1 -1
  136. package/dist/src/intl/te.js +20 -0
  137. package/dist/src/intl/th.d.ts +24 -1
  138. package/dist/src/intl/th.d.ts.map +1 -1
  139. package/dist/src/intl/th.js +20 -0
  140. package/dist/src/intl/tr.d.ts +24 -1
  141. package/dist/src/intl/tr.d.ts.map +1 -1
  142. package/dist/src/intl/tr.js +20 -0
  143. package/dist/src/intl/uk.d.ts +80 -57
  144. package/dist/src/intl/uk.d.ts.map +1 -1
  145. package/dist/src/intl/uk.js +174 -149
  146. package/dist/src/intl/ur.d.ts +24 -1
  147. package/dist/src/intl/ur.d.ts.map +1 -1
  148. package/dist/src/intl/ur.js +20 -0
  149. package/dist/src/intl/vi.d.ts +24 -1
  150. package/dist/src/intl/vi.d.ts.map +1 -1
  151. package/dist/src/intl/vi.js +20 -0
  152. package/dist/src/intl/zh-CN.d.ts +24 -1
  153. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  154. package/dist/src/intl/zh-CN.js +20 -0
  155. package/dist/src/intl/zh-TW.d.ts +24 -1
  156. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  157. package/dist/src/intl/zh-TW.js +20 -0
  158. package/dist/src/local-first/Protocol.d.ts +1 -1
  159. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  160. package/dist/src/local-first/Protocol.js +1 -1
  161. package/dist/src/local-first/Schema.d.ts +10 -0
  162. package/dist/src/local-first/Schema.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +1 -1
  164. package/dist/src/local-first/Timestamp.js +1 -1
  165. package/package.json +1 -1
  166. package/src/{Binary.test.ts → Bytes.test.ts} +286 -1
  167. package/src/{Binary.ts → Bytes.ts} +652 -21
  168. package/src/Config.test.ts +672 -0
  169. package/src/Config.ts +410 -0
  170. package/src/Fs.test.ts +105 -0
  171. package/src/Fs.ts +414 -0
  172. package/src/Number.test.ts +82 -18
  173. package/src/Number.ts +76 -8
  174. package/src/Object.test.ts +115 -1
  175. package/src/Object.ts +44 -0
  176. package/src/Sqlite.ts +1 -1
  177. package/src/Time.test.ts +82 -11
  178. package/src/Time.ts +141 -16
  179. package/src/Type.test.ts +3114 -157
  180. package/src/Type.ts +2553 -196
  181. package/src/index.ts +3 -1
  182. package/src/intl/_en.ts +70 -0
  183. package/src/intl/ar.ts +71 -0
  184. package/src/intl/bn.ts +70 -0
  185. package/src/intl/ca.ts +70 -0
  186. package/src/intl/cs.ts +70 -0
  187. package/src/intl/da.ts +70 -0
  188. package/src/intl/de.ts +70 -0
  189. package/src/intl/el.ts +70 -0
  190. package/src/intl/es.ts +70 -0
  191. package/src/intl/fa.ts +70 -0
  192. package/src/intl/fi.ts +70 -0
  193. package/src/intl/fil.ts +70 -0
  194. package/src/intl/fr.ts +70 -0
  195. package/src/intl/he.ts +70 -0
  196. package/src/intl/hi.ts +70 -0
  197. package/src/intl/hr.ts +70 -0
  198. package/src/intl/hu.ts +69 -0
  199. package/src/intl/id.ts +70 -0
  200. package/src/intl/intl.test.ts +819 -1
  201. package/src/intl/it.ts +70 -0
  202. package/src/intl/ja.ts +70 -0
  203. package/src/intl/ko.ts +68 -0
  204. package/src/intl/ml.ts +70 -0
  205. package/src/intl/mr.ts +70 -0
  206. package/src/intl/ms.ts +71 -0
  207. package/src/intl/nb.ts +69 -0
  208. package/src/intl/nl.ts +70 -0
  209. package/src/intl/pa.ts +70 -0
  210. package/src/intl/pl.ts +63 -0
  211. package/src/intl/pt-BR.ts +70 -0
  212. package/src/intl/pt.ts +71 -0
  213. package/src/intl/ro.ts +70 -0
  214. package/src/intl/sk.ts +71 -0
  215. package/src/intl/sl.ts +70 -0
  216. package/src/intl/sv.ts +70 -0
  217. package/src/intl/sw.ts +62 -0
  218. package/src/intl/ta.ts +70 -0
  219. package/src/intl/te.ts +70 -0
  220. package/src/intl/th.ts +68 -0
  221. package/src/intl/tr.ts +70 -0
  222. package/src/intl/uk.ts +228 -155
  223. package/src/intl/ur.ts +70 -0
  224. package/src/intl/vi.ts +70 -0
  225. package/src/intl/zh-CN.ts +68 -0
  226. package/src/intl/zh-TW.ts +68 -0
  227. package/src/local-first/Protocol.test.ts +1 -1
  228. package/src/local-first/Protocol.ts +1 -1
  229. package/src/local-first/Schema.ts +11 -0
  230. package/src/local-first/Storage.ts +1 -1
  231. package/src/local-first/Timestamp.ts +1 -1
  232. package/dist/src/Binary.d.ts +0 -254
  233. package/dist/src/Binary.d.ts.map +0 -1
@@ -0,0 +1,181 @@
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
+ import { assert } from "./Assert.js";
22
+ import { createMutableRecord, filterObjectKeys, isPlainObject, } from "./Object.js";
23
+ import { ok } from "./Result.js";
24
+ import { CamelCaseIdentifier, camelCaseToConstantCase, ConstantCaseIdentifier, EvoluType, maxLength, object, objectKeys, String, transform, Unknown, } from "./Type.js";
25
+ /**
26
+ * An environment variable name in Evolu's configuration convention.
27
+ *
28
+ * Names are {@link ConstantCaseIdentifier}s of at most 255 characters, including
29
+ * the application prefix. This is an Evolu convention, not an operating-system
30
+ * limit. Double underscores do not introduce nesting.
31
+ *
32
+ * ### Example
33
+ *
34
+ * ```ts
35
+ * import { assertErr, assertOk, EnvName } from "@evolu/common";
36
+ *
37
+ * assertOk(
38
+ * EnvName.fromUnknown("APP_MAX_OWNER_BYTES"),
39
+ * "APP_MAX_OWNER_BYTES",
40
+ * );
41
+ * assertErr(EnvName.fromUnknown("APP_maxOwnerBytes"));
42
+ * assertErr(EnvName.fromUnknown("APP_DB__PORT"));
43
+ * ```
44
+ */
45
+ export const EnvName = /*#__PURE__*/ maxLength(255)(ConstantCaseIdentifier);
46
+ /**
47
+ * Creates a reversible environment-variable codec with a flat decoded output.
48
+ *
49
+ * Declare camelCase fields directly, or put them in one-level CONSTANT_CASE
50
+ * namespace groups. A direct `port` field reads `PORT`; a `maxOwnerBytes` field
51
+ * in an `EVOLU_RELAY` group reads `EVOLU_RELAY_MAX_OWNER_BYTES`. Groups affect
52
+ * external names only. Duplicate decoded fields or encoded names are rejected
53
+ * during construction, even when their Types are identical.
54
+ *
55
+ * Selects exact declared unprefixed names and all keys within declared
56
+ * namespaces. Namespace selection ignores casing so incorrectly cased names
57
+ * fail validation instead of being ignored. Selected names must match their
58
+ * exact declared spelling. Unrelated variables are ignored; misspelled
59
+ * unprefixed names or namespace prefixes can therefore still look absent.
60
+ *
61
+ * Field Types must encode to strings. Optional fields may be absent, but
62
+ * explicit `undefined` values are rejected. Empty strings remain present and
63
+ * must satisfy the field Type. Compose {@link withDefault} to supply defaults.
64
+ * Encoding emits canonical external names and string values, preserving absence
65
+ * when the field codec does. Errors retain external names and nested paths.
66
+ *
67
+ * Reads own string properties from any non-null object, including
68
+ * `process.env`. Selected properties must be enumerable data properties;
69
+ * getters are never read. Prototypes and internal contents such as Map entries
70
+ * are ignored. Non-object inputs fail with standard object errors. Schema names
71
+ * must satisfy {@link EnvName}; groups cannot be nested. Loading and source
72
+ * precedence remain explicit application code.
73
+ *
74
+ * ### Example
75
+ *
76
+ * ```ts
77
+ * import {
78
+ * assertEqual,
79
+ * assertErr,
80
+ * assertOk,
81
+ * assertType,
82
+ * ByteSizeLiteral,
83
+ * env,
84
+ * optional,
85
+ * Port,
86
+ * PortFromString,
87
+ * withDefault,
88
+ * } from "@evolu/common";
89
+ *
90
+ * const RelayEnv = env({
91
+ * port: withDefault(optional(PortFromString), Port.orThrow(4000)),
92
+ * EVOLU_RELAY: {
93
+ * maxOwnerBytes: optional(ByteSizeLiteral),
94
+ * },
95
+ * });
96
+ *
97
+ * const result = RelayEnv.fromUnknown({
98
+ * PORT: "04000",
99
+ * EVOLU_RELAY_MAX_OWNER_BYTES: "512KiB",
100
+ * HOME: "/home/evolu",
101
+ * });
102
+ *
103
+ * assertOk(result, { port: 4000, maxOwnerBytes: "512KiB" });
104
+ * assertType<typeof result.value.port, Port>();
105
+ * assertEqual(RelayEnv.to(result.value), {
106
+ * PORT: "4000",
107
+ * EVOLU_RELAY_MAX_OWNER_BYTES: "512KiB",
108
+ * });
109
+ *
110
+ * assertOk(RelayEnv.fromUnknown({}), { port: 4000 });
111
+ * assertErr(RelayEnv.fromUnknown({ PORT: "" }));
112
+ * assertErr(RelayEnv.fromUnknown({ EVOLU_RELAY_MAX_OWENR_BYTES: "1MiB" }));
113
+ * assertErr(RelayEnv.fromUnknown({ evolu_relay_max_owner_bytes: "1MiB" }));
114
+ * ```
115
+ */
116
+ export const env = (props, ..._validation) => {
117
+ assert(isPlainObject(props), "Environment properties must be a plain object.");
118
+ const flat = createMutableRecord();
119
+ const inputNameByOutputName = new Map();
120
+ const outputNameByInputName = new Map();
121
+ const prefixes = [];
122
+ const unprefixedNames = new Set();
123
+ for (const rootName of Reflect.ownKeys(props)) {
124
+ const descriptor = Object.getOwnPropertyDescriptor(props, rootName);
125
+ assert(typeof rootName === "string" &&
126
+ descriptor.enumerable &&
127
+ "value" in descriptor, "Environment properties must be enumerable string data properties.");
128
+ const grouped = ConstantCaseIdentifier.is(rootName);
129
+ assert(grouped || CamelCaseIdentifier.is(rootName), `Invalid environment schema name ${JSON.stringify(rootName)}. Expected a camelCase field or CONSTANT_CASE namespace.`);
130
+ const rootValue = descriptor.value;
131
+ const fields = grouped ? rootValue : { [rootName]: rootValue };
132
+ assert(isPlainObject(fields) && !EvoluType.is(fields), "Environment groups must be plain objects containing fields.");
133
+ if (grouped) {
134
+ assert(EnvName.is(rootName + "_A"), `Invalid environment namespace ${JSON.stringify(rootName)}. Names must fit within 255 characters.`);
135
+ prefixes.push(rootName + "_");
136
+ }
137
+ for (const name of Reflect.ownKeys(fields)) {
138
+ const field = Object.getOwnPropertyDescriptor(fields, name);
139
+ assert(typeof name === "string" && field.enumerable && "value" in field, "Environment fields must be enumerable string data properties.");
140
+ assert(CamelCaseIdentifier.is(name), `Invalid environment field ${JSON.stringify(name)}. Expected a camelCase identifier; groups cannot be nested.`);
141
+ assert(!inputNameByOutputName.has(name), `Duplicate environment field ${JSON.stringify(name)}.`);
142
+ const encoded = (grouped ? rootName + "_" : "") + camelCaseToConstantCase(name);
143
+ assert(EnvName.is(encoded), `Invalid environment schema name ${JSON.stringify(encoded)}. Expected a CONSTANT_CASE identifier of at most 255 characters.`);
144
+ assert(!outputNameByInputName.has(encoded), `Duplicate environment variable ${JSON.stringify(encoded)}.`);
145
+ const property = field.value;
146
+ const fieldType = EvoluType.is(property)
147
+ ? property
148
+ : isPlainObject(property)
149
+ ? Object.getOwnPropertyDescriptor(property, "type")?.value
150
+ : undefined;
151
+ assert(EvoluType.is(fieldType), `Invalid environment field ${JSON.stringify(name)}. Expected a Type or optional/defaulted property; groups must use CONSTANT_CASE names.`);
152
+ flat[name] = property;
153
+ inputNameByOutputName.set(name, encoded);
154
+ outputNameByInputName.set(encoded, name);
155
+ if (!grouped)
156
+ unprefixedNames.add(encoded);
157
+ }
158
+ }
159
+ // Runtime construction validates the flattened fields; the public signature
160
+ // checks their concrete Types and string encodings before flattening.
161
+ const output = object(flat);
162
+ const key = transform("EnvKey", String, CamelCaseIdentifier, {
163
+ // Identity fallback keeps this exposed codec round-trippable for other
164
+ // valid camelCase identifiers. objectKeys accepts only declared fields.
165
+ from: (value) => ok(outputNameByInputName.get(value) ?? value),
166
+ to: (value) => inputNameByOutputName.get(value) ?? value,
167
+ });
168
+ const withKeys = objectKeys(key)(output);
169
+ const type = transform("Env", Unknown, withKeys, {
170
+ from: (value) => {
171
+ if (typeof value !== "object" || value === null)
172
+ return ok(value);
173
+ return ok(filterObjectKeys(value, (key) => unprefixedNames.has(key) ||
174
+ prefixes.some((prefix) => key.toUpperCase().startsWith(prefix))));
175
+ },
176
+ to: (value) => value,
177
+ });
178
+ // The maps preserve each field's name and codec; only the static flattening
179
+ // cannot be expressed by the runtime loops above.
180
+ return type;
181
+ };
@@ -0,0 +1,302 @@
1
+ /**
2
+ * File system operations for {@link Task}s.
3
+ *
4
+ * {@link Fs} reads, writes, copies, and renames files; lists and manages
5
+ * directories; and provides metadata and existence checks. Each operation
6
+ * returns a Task.
7
+ *
8
+ * Tasks can sequence file operations without synchronous I/O. Node.js's
9
+ * synchronous methods are intentionally omitted to avoid accidentally blocking
10
+ * the event loop.
11
+ *
12
+ * Inject {@link @evolu/nodejs!createNodeFs | createNodeFs} through
13
+ * {@link @evolu/nodejs!runMain | runMain} or {@link createRun}. Tasks declare
14
+ * {@link FsDep} and access the file system through `run.deps.fs`.
15
+ *
16
+ * {@link FsError} includes the path, a diagnostic message, and a
17
+ * {@link FsErrorReason} such as `NotFound` or `PermissionDenied`.
18
+ * {@link Fs.exists} returns `false` for `NotFound` and preserves other errors.
19
+ *
20
+ * ### Example
21
+ *
22
+ * ```ts
23
+ * import {
24
+ * assertEqual,
25
+ * ok,
26
+ * type FsDep,
27
+ * type FsError,
28
+ * type Task,
29
+ * } from "@evolu/common";
30
+ * import { createNodeFs, runMain } from "@evolu/nodejs";
31
+ * import { join } from "node:path";
32
+ *
33
+ * const main: Task<void, FsError, FsDep> = async (run) => {
34
+ * const { fs } = run.deps;
35
+ * const temp = await run(
36
+ * fs.createTempDirectory({ prefix: "evolu-fs-" }),
37
+ * );
38
+ * if (!temp.ok) return temp;
39
+ *
40
+ * await using directory = temp.value;
41
+ * const path = join(directory.path, "message.txt");
42
+ *
43
+ * const result = await run(fs.writeFile(path, "hello"));
44
+ * if (!result.ok) return result;
45
+ *
46
+ * const text = await run(fs.readFile(path, "utf8"));
47
+ * if (!text.ok) return text;
48
+ * assertEqual(text.value, "hello");
49
+ *
50
+ * return ok();
51
+ * };
52
+ *
53
+ * await runMain({ fs: createNodeFs() }, { mode: "command" })(main);
54
+ * ```
55
+ *
56
+ * @module
57
+ */
58
+ import type { ByteLength } from "./Bytes.ts";
59
+ import type { Task } from "./Task.ts";
60
+ import type { Typed } from "./Type.ts";
61
+ /** Asynchronous file system operations. */
62
+ export interface Fs {
63
+ /**
64
+ * Reads a whole file, as bytes by default or as a string with an encoding. An
65
+ * abort requests cancellation; pending operating system reads may still
66
+ * finish.
67
+ */
68
+ readonly readFile: FsReadFile;
69
+ /**
70
+ * Writes a file, creating it or truncating an existing file by default. An
71
+ * abort requests cancellation and can leave the file partially written or
72
+ * truncated.
73
+ */
74
+ readonly writeFile: (path: FsPath, data: string | Uint8Array, options?: FsWriteFileOptions) => Task<void, FsError>;
75
+ /**
76
+ * Lists entry names relative to `path`, in unspecified order. With
77
+ * `recursive`, includes nested entries with their relative paths.
78
+ */
79
+ readonly readDirectory: (path: FsPath, options?: FsReadDirectoryOptions) => Task<ReadonlyArray<string>, FsError>;
80
+ /**
81
+ * Creates a directory. Without `recursive`, an existing directory fails with
82
+ * `AlreadyExists` and a missing parent with `NotFound`.
83
+ */
84
+ readonly createDirectory: (path: FsPath, options?: FsCreateDirectoryOptions) => Task<void, FsError>;
85
+ /**
86
+ * Copies a file or directory tree using Node's recursive `cp` semantics. By
87
+ * default, existing directories are merged and files are replaced. Symbolic
88
+ * links follow Node's rules: `force: false` and `errorOnExist` do not
89
+ * guarantee that destination links are preserved.
90
+ *
91
+ * Copying is neither exclusive nor atomic, and a failure can leave a partial
92
+ * copy. Use {@link Fs.copyFile} for exclusive creation of a single file.
93
+ */
94
+ readonly copy: (source: FsPath, destination: FsPath, options?: FsCopyOptions) => Task<void, FsError>;
95
+ /**
96
+ * Copies a single file. An existing destination fails with `AlreadyExists`
97
+ * unless `overwrite` is enabled. Without overwrite, destination creation is
98
+ * exclusive even when other copies run concurrently. File contents are not
99
+ * published atomically; a failure can leave a partial copy.
100
+ */
101
+ readonly copyFile: (source: FsPath, destination: FsPath, options?: FsCopyFileOptions) => Task<void, FsError>;
102
+ /**
103
+ * Renames or moves a file or directory using the platform's rename semantics.
104
+ * An existing destination file can be replaced.
105
+ */
106
+ readonly rename: (source: FsPath, destination: FsPath) => Task<void, FsError>;
107
+ /**
108
+ * Removes a file, or a directory with `recursive`. Removing a directory
109
+ * without `recursive` fails with `IsDirectory`. With `force`, a missing path
110
+ * succeeds.
111
+ */
112
+ readonly remove: (path: FsPath, options?: FsRemoveOptions) => Task<void, FsError>;
113
+ /** Reads file metadata, following symbolic links. */
114
+ readonly getMetadata: (path: FsPath) => Task<FsMetadata, FsError>;
115
+ /**
116
+ * Checks whether a path exists. `NotFound` produces `false`; other errors are
117
+ * returned. A `true` result does not establish read or write permission.
118
+ */
119
+ readonly exists: (path: FsPath) => Task<boolean, FsError>;
120
+ /**
121
+ * Creates a unique directory in the system temporary directory by default.
122
+ * Options can specify a parent directory and a name prefix. The returned
123
+ * resource removes the directory and its contents on disposal. Use `await
124
+ * using` for cleanup; disposal can throw if removal fails.
125
+ *
126
+ * Once started, this operation returns its result even if its Run aborts.
127
+ */
128
+ readonly createTempDirectory: (options?: FsCreateTempDirectoryOptions) => Task<FsTempDirectory, FsError>;
129
+ }
130
+ /** Dependency wrapper for {@link Fs}. */
131
+ export interface FsDep {
132
+ readonly fs: Fs;
133
+ }
134
+ /** A file system path, or a `file:` URL. */
135
+ export type FsPath = string | URL;
136
+ /** Supported text encodings. */
137
+ export type FsEncoding = "ascii" | "utf8" | "utf-8" | "utf16le" | "utf-16le" | "ucs2" | "ucs-2" | "base64" | "base64url" | "latin1" | "binary" | "hex";
138
+ /** Supported file opening modes for {@link Fs.writeFile}. */
139
+ export type FsOpenFlag = "a" | "ax" | "a+" | "ax+" | "as" | "as+" | "r" | "r+" | "rs+" | "w" | "wx" | "w+" | "wx+";
140
+ /** Reads bytes by default, or text when an encoding is specified. */
141
+ export interface FsReadFile {
142
+ (path: FsPath): Task<Uint8Array, FsError>;
143
+ (path: FsPath, encoding: FsEncoding | {
144
+ readonly encoding: FsEncoding;
145
+ }): Task<string, FsError>;
146
+ }
147
+ /** Options for {@link Fs.writeFile}. */
148
+ export interface FsWriteFileOptions {
149
+ /** Encoding of string data. Defaults to `utf8`. */
150
+ readonly encoding?: FsEncoding;
151
+ /** File mode of a created file. Defaults to `0o666`. */
152
+ readonly mode?: number;
153
+ /** Open flag. Defaults to `w`; use `wx` to fail when the file exists. */
154
+ readonly flag?: FsOpenFlag;
155
+ }
156
+ /** Options for {@link Fs.readDirectory}. */
157
+ export interface FsReadDirectoryOptions {
158
+ /** Includes entries from nested directories. Defaults to `false`. */
159
+ readonly recursive?: boolean;
160
+ }
161
+ /** Options for {@link Fs.createDirectory}. */
162
+ export interface FsCreateDirectoryOptions {
163
+ /** Creates missing parents and accepts an existing directory. */
164
+ readonly recursive?: boolean;
165
+ /** Directory mode. Defaults to `0o777`. */
166
+ readonly mode?: number;
167
+ }
168
+ /** Options for {@link Fs.copy}. */
169
+ export interface FsCopyOptions {
170
+ /**
171
+ * Node's `force` option. Replaces existing files; `false` skips them unless
172
+ * `errorOnExist` is enabled. Defaults to `true`. This does not protect
173
+ * destination symbolic links.
174
+ */
175
+ readonly force?: boolean;
176
+ /**
177
+ * Node's `errorOnExist` option. With `force: false`, existing files and
178
+ * directories fail with `AlreadyExists`. Defaults to `false`. Symbolic links
179
+ * retain Node's behavior and may still be replaced.
180
+ */
181
+ readonly errorOnExist?: boolean;
182
+ /** Preserves access and modification times. Defaults to `false`. */
183
+ readonly preserveTimestamps?: boolean;
184
+ }
185
+ /** Options for {@link Fs.copyFile}. */
186
+ export interface FsCopyFileOptions {
187
+ /** Replaces an existing destination file. Defaults to `false`. */
188
+ readonly overwrite?: boolean;
189
+ }
190
+ /** Options for {@link Fs.remove}. */
191
+ export interface FsRemoveOptions {
192
+ /** Removes directories and their contents. */
193
+ readonly recursive?: boolean;
194
+ /** Ignores a missing path. */
195
+ readonly force?: boolean;
196
+ /**
197
+ * Number of retries for `EBUSY`, `EMFILE`, `ENFILE`, `ENOTEMPTY`, or `EPERM`
198
+ * on Node.js. Applies only with `recursive: true`. Defaults to `0`.
199
+ */
200
+ readonly maxRetries?: number;
201
+ /**
202
+ * Base retry delay in milliseconds. Each retry waits one additional interval.
203
+ * Applies only with `recursive: true`. Defaults to `100`.
204
+ */
205
+ readonly retryDelay?: number;
206
+ }
207
+ /** File metadata as data, with Node's numeric and timestamp field names. */
208
+ export interface FsMetadata {
209
+ readonly type: FsEntryType;
210
+ readonly dev: number;
211
+ readonly ino: number;
212
+ readonly mode: number;
213
+ readonly nlink: number;
214
+ readonly uid: number;
215
+ readonly gid: number;
216
+ readonly rdev: number;
217
+ readonly size: ByteLength;
218
+ readonly blksize: number;
219
+ readonly blocks: number;
220
+ readonly atimeMs: number;
221
+ readonly mtimeMs: number;
222
+ readonly ctimeMs: number;
223
+ readonly birthtimeMs: number;
224
+ readonly atime: Date;
225
+ readonly mtime: Date;
226
+ readonly ctime: Date;
227
+ readonly birthtime: Date;
228
+ }
229
+ /** The kind of file system entry described by {@link FsMetadata}. */
230
+ export type FsEntryType = "File" | "Directory" | "SymbolicLink" | "BlockDevice" | "CharacterDevice" | "FIFO" | "Socket" | "Unknown";
231
+ /** Options for {@link Fs.createTempDirectory}. */
232
+ export interface FsCreateTempDirectoryOptions {
233
+ /** Existing parent directory. Defaults to the system temporary directory. */
234
+ readonly directory?: string;
235
+ /** Prefix for the directory name. Defaults to an empty string. */
236
+ readonly prefix?: string;
237
+ }
238
+ /** A temporary directory removed, with its contents, on asynchronous disposal. */
239
+ export interface FsTempDirectory extends AsyncDisposable {
240
+ readonly path: string;
241
+ }
242
+ /** A failed file system operation. */
243
+ export interface FsError extends Typed<"FsError"> {
244
+ readonly reason: FsErrorReason;
245
+ /**
246
+ * The operation's path, or source for copying and renaming. URL inputs use
247
+ * their `href`. Temporary directories use the supplied parent if its
248
+ * resolution fails, or the resolved parent combined with the name prefix if
249
+ * creation fails.
250
+ */
251
+ readonly path: string;
252
+ /**
253
+ * Destination for copying and renaming, with URL inputs represented by
254
+ * `href`.
255
+ */
256
+ readonly destination?: string;
257
+ /** The failing system call, or the method name when the platform reports none. */
258
+ readonly syscall: string;
259
+ /** The platform's diagnostic message. */
260
+ readonly message: string;
261
+ }
262
+ /** Why a file system operation failed, mapped from the platform's error code. */
263
+ export type FsErrorReason = "NotFound" | "AlreadyExists" | "PermissionDenied" | "IsDirectory" | "NotDirectory" | "NotEmpty" | "Busy" | "Unknown";
264
+ /**
265
+ * Creates a test {@link Fs} with the supplied operation overrides.
266
+ *
267
+ * Unconfigured operations throw a defect naming the method when their Task
268
+ * runs. Constructing a Task does not execute it. This helper performs no file
269
+ * system I/O; overrides provide the behavior needed by each test.
270
+ *
271
+ * ### Example
272
+ *
273
+ * ```ts
274
+ * import {
275
+ * assertEqual,
276
+ * assertOk,
277
+ * ok,
278
+ * testCreateFs,
279
+ * testCreateRun,
280
+ * type FsDep,
281
+ * type FsError,
282
+ * type Task,
283
+ * } from "@evolu/common";
284
+ *
285
+ * const saveMessage: Task<void, FsError, FsDep> = (run) =>
286
+ * run(run.deps.fs.writeFile("message.txt", "hello"));
287
+ *
288
+ * await using run = testCreateRun({
289
+ * fs: testCreateFs({
290
+ * writeFile: (path, data) => () => {
291
+ * assertEqual(path, "message.txt");
292
+ * assertEqual(data, "hello");
293
+ * return ok();
294
+ * },
295
+ * }),
296
+ * });
297
+ *
298
+ * assertOk(await run(saveMessage));
299
+ * ```
300
+ */
301
+ export declare const testCreateFs: (overrides?: Partial<Fs>) => Fs;
302
+ //# sourceMappingURL=Fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Fs.d.ts","sourceRoot":"","sources":["../../src/Fs.ts"],"names":[],"mappings":"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"}
package/dist/src/Fs.js ADDED
@@ -0,0 +1,111 @@
1
+ /**
2
+ * File system operations for {@link Task}s.
3
+ *
4
+ * {@link Fs} reads, writes, copies, and renames files; lists and manages
5
+ * directories; and provides metadata and existence checks. Each operation
6
+ * returns a Task.
7
+ *
8
+ * Tasks can sequence file operations without synchronous I/O. Node.js's
9
+ * synchronous methods are intentionally omitted to avoid accidentally blocking
10
+ * the event loop.
11
+ *
12
+ * Inject {@link @evolu/nodejs!createNodeFs | createNodeFs} through
13
+ * {@link @evolu/nodejs!runMain | runMain} or {@link createRun}. Tasks declare
14
+ * {@link FsDep} and access the file system through `run.deps.fs`.
15
+ *
16
+ * {@link FsError} includes the path, a diagnostic message, and a
17
+ * {@link FsErrorReason} such as `NotFound` or `PermissionDenied`.
18
+ * {@link Fs.exists} returns `false` for `NotFound` and preserves other errors.
19
+ *
20
+ * ### Example
21
+ *
22
+ * ```ts
23
+ * import {
24
+ * assertEqual,
25
+ * ok,
26
+ * type FsDep,
27
+ * type FsError,
28
+ * type Task,
29
+ * } from "@evolu/common";
30
+ * import { createNodeFs, runMain } from "@evolu/nodejs";
31
+ * import { join } from "node:path";
32
+ *
33
+ * const main: Task<void, FsError, FsDep> = async (run) => {
34
+ * const { fs } = run.deps;
35
+ * const temp = await run(
36
+ * fs.createTempDirectory({ prefix: "evolu-fs-" }),
37
+ * );
38
+ * if (!temp.ok) return temp;
39
+ *
40
+ * await using directory = temp.value;
41
+ * const path = join(directory.path, "message.txt");
42
+ *
43
+ * const result = await run(fs.writeFile(path, "hello"));
44
+ * if (!result.ok) return result;
45
+ *
46
+ * const text = await run(fs.readFile(path, "utf8"));
47
+ * if (!text.ok) return text;
48
+ * assertEqual(text.value, "hello");
49
+ *
50
+ * return ok();
51
+ * };
52
+ *
53
+ * await runMain({ fs: createNodeFs() }, { mode: "command" })(main);
54
+ * ```
55
+ *
56
+ * @module
57
+ */
58
+ /**
59
+ * Creates a test {@link Fs} with the supplied operation overrides.
60
+ *
61
+ * Unconfigured operations throw a defect naming the method when their Task
62
+ * runs. Constructing a Task does not execute it. This helper performs no file
63
+ * system I/O; overrides provide the behavior needed by each test.
64
+ *
65
+ * ### Example
66
+ *
67
+ * ```ts
68
+ * import {
69
+ * assertEqual,
70
+ * assertOk,
71
+ * ok,
72
+ * testCreateFs,
73
+ * testCreateRun,
74
+ * type FsDep,
75
+ * type FsError,
76
+ * type Task,
77
+ * } from "@evolu/common";
78
+ *
79
+ * const saveMessage: Task<void, FsError, FsDep> = (run) =>
80
+ * run(run.deps.fs.writeFile("message.txt", "hello"));
81
+ *
82
+ * await using run = testCreateRun({
83
+ * fs: testCreateFs({
84
+ * writeFile: (path, data) => () => {
85
+ * assertEqual(path, "message.txt");
86
+ * assertEqual(data, "hello");
87
+ * return ok();
88
+ * },
89
+ * }),
90
+ * });
91
+ *
92
+ * assertOk(await run(saveMessage));
93
+ * ```
94
+ */
95
+ export const testCreateFs = (overrides = {}) => ({
96
+ readFile: createUnexpectedFsOperation("readFile"),
97
+ writeFile: createUnexpectedFsOperation("writeFile"),
98
+ readDirectory: createUnexpectedFsOperation("readDirectory"),
99
+ createDirectory: createUnexpectedFsOperation("createDirectory"),
100
+ copy: createUnexpectedFsOperation("copy"),
101
+ copyFile: createUnexpectedFsOperation("copyFile"),
102
+ rename: createUnexpectedFsOperation("rename"),
103
+ remove: createUnexpectedFsOperation("remove"),
104
+ getMetadata: createUnexpectedFsOperation("getMetadata"),
105
+ exists: createUnexpectedFsOperation("exists"),
106
+ createTempDirectory: createUnexpectedFsOperation("createTempDirectory"),
107
+ ...overrides,
108
+ });
109
+ const createUnexpectedFsOperation = (method) => () => () => {
110
+ throw new Error(`Unexpected Fs.${method} call`);
111
+ };