Releases: evoluhq/evolu
Release list
@evolu/web@3.1.2
Patch Changes
-
9fc736f: Updated internal Evolu peer dependency requirements
Aligned internal peer dependency minimums with the current workspace releases. Upgrade the Evolu packages together when updating an adapter or framework integration.
-
Updated dependencies [532feaa]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [3d84543]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [76554bd]
-
Updated dependencies [918d77b]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [91ff875]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [140c4cf]
-
Updated dependencies [3d84543]
-
Updated dependencies [3d84543]
-
Updated dependencies [f3b5829]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
@evolu/vue@2.0.3
Patch Changes
-
9fc736f: Updated internal Evolu peer dependency requirements
Aligned internal peer dependency minimums with the current workspace releases. Upgrade the Evolu packages together when updating an adapter or framework integration.
-
Updated dependencies [532feaa]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [3d84543]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [76554bd]
-
Updated dependencies [918d77b]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [91ff875]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [140c4cf]
-
Updated dependencies [3d84543]
-
Updated dependencies [3d84543]
-
Updated dependencies [f3b5829]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
@evolu/vitest@2.0.2
Patch Changes
-
9fc736f: Updated internal Evolu peer dependency requirements
Aligned internal peer dependency minimums with the current workspace releases. Upgrade the Evolu packages together when updating an adapter or framework integration.
-
Updated dependencies [532feaa]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [3d84543]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [76554bd]
-
Updated dependencies [918d77b]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [91ff875]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [140c4cf]
-
Updated dependencies [3d84543]
-
Updated dependencies [3d84543]
-
Updated dependencies [f3b5829]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
@evolu/svelte@3.0.3
Patch Changes
-
9fc736f: Updated internal Evolu peer dependency requirements
Aligned internal peer dependency minimums with the current workspace releases. Upgrade the Evolu packages together when updating an adapter or framework integration.
-
Updated dependencies [532feaa]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [3d84543]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [76554bd]
-
Updated dependencies [918d77b]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9fc736f]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [91ff875]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [140c4cf]
-
Updated dependencies [3d84543]
-
Updated dependencies [3d84543]
-
Updated dependencies [f3b5829]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
- @evolu/web@3.1.2
@evolu/relay@4.0.0
Major Changes
-
6269b91: Published the configurable relay package
The relay was a private workspace app that only shipped as a Docker image. It
is now published to npm with theevolu-relayexecutable, so it can run with
npx @evolu/relay. Theevoluhq/relayDocker image installs the published
package and is built as part of every release that publishes a new relay
version.The relay reads its port from
PORTand its per-owner storage quota
fromEVOLU_RELAY_MAX_OWNER_BYTES, as a size literal such as10MiB.
Bare byte counts such as1048576are not accepted; use1MiBinstead.
PORTsupports hosting platforms that supply the listening port. There is no
EVOLU_RELAY_PORToverride. The port still defaults to 4000. Leaving the quota
unset imposes no per-owner storage limit;0Bpermits no stored bytes.
Invalid supplied values never fall back to defaults or disable the quota.Ports must be integers from 0 through 65535; zero requests an automatically
assigned listening port. An empty, invalid, or unknownEVOLU_RELAY_*
variable stops the relay with a formatted validation error, before creating
the data directory or initializing its database. Startup reports the first
validation message and preserves all collected errors in the Error'scause.
The Docker health check uses the configuredPORT; use a fixed, nonzero port
when running the image.Environment names must exactly match the declared CONSTANT_CASE names. Incorrect
casing within theEVOLU_RELAY_namespace is reported as an unknown variable.
Casing is not repaired, and double underscores do not introduce nested settings.
Patch Changes
- Updated dependencies [532feaa]
- Updated dependencies [6bd0a36]
- Updated dependencies [f4d9ad7]
- Updated dependencies [3d84543]
- Updated dependencies [6bd0a36]
- Updated dependencies [76554bd]
- Updated dependencies [2183ca3]
- Updated dependencies [918d77b]
- Updated dependencies [f4d9ad7]
- Updated dependencies [9fc736f]
- Updated dependencies [918d77b]
- Updated dependencies [6bd0a36]
- Updated dependencies [ad85bdb]
- Updated dependencies [f4d9ad7]
- Updated dependencies [91ff875]
- Updated dependencies [ad85bdb]
- Updated dependencies [8ac49c8]
- Updated dependencies [6bd0a36]
- Updated dependencies [140c4cf]
- Updated dependencies [3d84543]
- Updated dependencies [3d84543]
- Updated dependencies [f3b5829]
- Updated dependencies [ad85bdb]
- Updated dependencies [f4d9ad7]
- Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
- @evolu/nodejs@4.0.0
@evolu/react@11.0.3
Patch Changes
-
9fc736f: Updated internal Evolu peer dependency requirements
Aligned internal peer dependency minimums with the current workspace releases. Upgrade the Evolu packages together when updating an adapter or framework integration.
-
Updated dependencies [532feaa]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [3d84543]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [76554bd]
-
Updated dependencies [918d77b]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [91ff875]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [140c4cf]
-
Updated dependencies [3d84543]
-
Updated dependencies [3d84543]
-
Updated dependencies [f3b5829]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
@evolu/react-web@3.0.3
Patch Changes
-
9fc736f: Updated internal Evolu peer dependency requirements
Aligned internal peer dependency minimums with the current workspace releases. Upgrade the Evolu packages together when updating an adapter or framework integration.
-
Updated dependencies [532feaa]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [3d84543]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [76554bd]
-
Updated dependencies [918d77b]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9fc736f]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [91ff875]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [140c4cf]
-
Updated dependencies [3d84543]
-
Updated dependencies [3d84543]
-
Updated dependencies [f3b5829]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
- @evolu/web@3.1.2
@evolu/react-native@16.0.2
Patch Changes
-
9fc736f: Updated internal Evolu peer dependency requirements
Aligned internal peer dependency minimums with the current workspace releases. Upgrade the Evolu packages together when updating an adapter or framework integration.
-
Updated dependencies [532feaa]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [3d84543]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [76554bd]
-
Updated dependencies [918d77b]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9fc736f]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [91ff875]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [140c4cf]
-
Updated dependencies [3d84543]
-
Updated dependencies [3d84543]
-
Updated dependencies [f3b5829]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
- @evolu/react@11.0.3
@evolu/nodejs@4.0.0
Major Changes
-
2183ca3: Required validated Port values in relay configuration
NodeJsRelayConfig.portremains optional, but a supplied value must now be a
validatedPortinstead of a plain number. This ensures ports are integers
from 0 through 65535 before starting the server. Zero requests an automatically
assigned listening port.Use
Port.orThrowfor startup constants or handle the Result from
Port.fromUnknownorPortFromString.fromUnknownfor external input.import { assertType, Port, type Task } from "@evolu/common"; import type { Relay } from "@evolu/common/local-first"; import { createRelay, type RelayDeps } from "@evolu/nodejs"; // @ts-expect-error Relay configuration requires a validated Port, not a number. const _old = createRelay({ port: 4000, isOwnerWithinQuota: () => true }); const main = createRelay({ port: Port.orThrow(4000), isOwnerWithinQuota: () => true, }); assertType<typeof main, Task<Relay, never, RelayDeps>>(); const defaultPort = createRelay({ isOwnerWithinQuota: () => true }); assertType<typeof defaultPort, Task<Relay, never, RelayDeps>>();
Minor Changes
-
918d77b: Added file system operations for Tasks
FsprovidesreadFile,writeFile,readDirectory,createDirectory,copy,
copyFile,rename,remove,getMetadata,exists, andcreateTempDirectory.
Each operation returns a Task.Tasks can sequence file operations without synchronous I/O. Node.js's synchronous
methods are intentionally omitted to avoid accidentally blocking the event loop.Inject
createNodeFs()throughrunMainorcreateRun. Tasks declareFsDep
and access the file system throughrun.deps.fs.On Node.js,
readFileandwriteFilepass the Run's abort signal to the native
operation. If it rejects after cancellation, the Task propagates the Run's abort
reason. Cancellation can leave a write partially completed. Successful operations
return their values even if an abort was requested. Other operations run to
completion and return their results once started.File system errors include a reason such as
NotFoundorIsDirectory, a
diagnostic message, and the operation's path. URL paths are represented byhref.
Copy and rename errors also include the destination.existsreturnsfalse
forNotFoundand preserves other errors, including permission failures.getMetadatareturns data with atypefield such as"File"or"Directory".
readDirectorylists relative entry names and supports recursive listing.
On Node.js,copydelegates to recursivenode:fs/promises.cp. By default it
merges directories and replaces files. Its options are Node'sforce,
errorOnExist, andpreserveTimestamps:force: falseskips existing files,
and addingerrorOnExist: truerejects existing files and directories.
Symbolic links retain Node's behavior and may still be replaced with both of
those options set. Tree copying provides no exclusive-creation or atomicity
guarantee, and a failure can leave a partial copy.copyFilecopies a single file and fails withAlreadyExistsif the destination
exists, unlessoverwrite: trueis set. Its default exclusive creation also
protects against competing copies. File contents are not published atomically.
renameuses the platform's rename semantics and can replace an existing file.createTempDirectory()uses the system temporary directory by default. Its
directoryoption selects an existing parent directory, andprefixsets a
prefix for the generated directory name. The returned directory supports cleanup
withawait using. Once creation starts, it returns its result even if the Run
aborts, so the caller can dispose the directory.
On Node.js, the parent is resolved through the file system before creation, so
symbolic links followed by..retain their file system meaning. The returned
path is absolute, so cleanup still removes the created directory if the process
changes its working directory before disposal. Resolution errors identify the
supplied parent; creation errors identify the resolved parent and name prefix.import { assertEqual, assertErr, ok, type FsDep, type FsError, type Task, } from "@evolu/common"; import { createNodeFs, runMain } from "@evolu/nodejs"; import { join } from "node:path"; const main: Task<void, FsError, FsDep> = async (run) => { const { fs } = run.deps; const temp = await run(fs.createTempDirectory({ prefix: "evolu-fs-" })); if (!temp.ok) return temp; await using directory = temp.value; const path = join(directory.path, "message.txt"); const result = await run(fs.writeFile(path, "hello")); if (!result.ok) return result; const text = await run(fs.readFile(path, "utf8")); if (!text.ok) return text; assertEqual(text.value, "hello"); const metadata = await run(fs.getMetadata(path)); if (!metadata.ok) return metadata; assertEqual(metadata.value.type, "File"); const copyPath = join(directory.path, "copy.txt"); const copied = await run(fs.copy(path, copyPath)); if (!copied.ok) return copied; const copyConflict = await run( fs.copy(path, copyPath, { force: false, errorOnExist: true }), ); assertErr(copyConflict); assertEqual(copyConflict.error.reason, "AlreadyExists"); const conflict = await run(fs.copyFile(path, copyPath)); assertErr(conflict); assertEqual(conflict.error.reason, "AlreadyExists"); return ok(); }; await runMain({ fs: createNodeFs() }, { mode: "command" })(main);
Use
testCreateFs(overrides)to supply file system behavior in application tests.
Unconfigured operations throw a defect naming the method when their Task runs.import { assertErr, testCreateFs, testCreateRun, err, type FsError, } from "@evolu/common"; const error: FsError = { type: "FsError", reason: "PermissionDenied", path: "protected.txt", syscall: "open", message: "Permission denied", }; await using run = testCreateRun({ fs: testCreateFs({ readFile: () => () => err(error) }), }); assertErr(await run(run.deps.fs.readFile("protected.txt", "utf8")), error);
-
8ac49c8: Allowed main Tasks to return errors
runMainaccepts fallible Tasks. A returned error is fatal: it is preserved in
Error.cause, reported throughreportDefect, and setsprocess.exitCodeto 1.
Cleanup finishes beforerunMainresolves.import { assertEqual, IntFromString, ok, type Task, type TypeError, } from "@evolu/common"; import { runMain } from "@evolu/nodejs"; const main: Task<void, TypeError> = () => { const port = IntFromString.fromUnknown("4000"); if (!port.ok) return port; assertEqual(port.value, 4000); return ok(); }; await runMain(main, { mode: "command" });
Patch Changes
-
9fc736f: Updated internal Evolu peer dependency requirements
Aligned internal peer dependency minimums with the current workspace releases. Upgrade the Evolu packages together when updating an adapter or framework integration.
-
918d77b: Fixed type resolution in documentation examples
testJSDocExamplesnow applies package aliases when type-checking imports
within aliased modules. Examples can use branded types across common and
platform packages without mixing source and built declarations. -
Updated dependencies [532feaa]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [3d84543]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [76554bd]
-
Updated dependencies [918d77b]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [91ff875]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [6bd0a36]
-
Updated dependencies [140c4cf]
-
Updated dependencies [3d84543]
-
Updated dependencies [3d84543]
-
Updated dependencies [f3b5829]
-
Updated dependencies [ad85bdb]
-
Updated dependencies [f4d9ad7]
-
Updated dependencies [9ee6f15]
- @evolu/common@8.10.0
@evolu/common@8.10.0
Minor Changes
-
532feaa: Added
ByteLengthwith human-readable literalsByteLengthis the canonical non-negative safe integer number of bytes, as
Millisis for time. It rejects JavaScript's negative zero so zero has one
canonical representation.ByteSizeLiteralvalidates sizes such as
"1023MiB"or"1.5GiB"at compile time and runtime using the binary units
B,KiB,MiB,GiB, andTiB. Each unit stays below 1024, so
1024KiBis written as"1MiB", and a half is the only decimal because it
is the only one that is exact in every binary unit. APIs can acceptByteSizeand normalize it with
byteSizeToByteLength, andByteLengthFromStringparses either a number of
bytes or a literal from text such as an environment variable.import { assertEqual, assertErr, assertOk, ByteLength, ByteLengthFromString, byteSizeToByteLength, type ByteSize, } from "@evolu/common"; const quota: ByteSize = "1MiB"; assertEqual(byteSizeToByteLength(quota), 1048576); assertEqual(byteSizeToByteLength(ByteLength.orThrow(1000)), 1000); assertErr(ByteLength.fromUnknown(-0)); assertOk(ByteLengthFromString.fromUnknown("10MiB"), 10485760);
-
6bd0a36: Added strict environment configuration codecs
Use
env({ ... })to decode CONSTANT_CASE environment variables into typed
camelCase settings. Declare fields directly for unprefixed variables such as
PORT, and put related fields in one-level CONSTANT_CASE namespace groups
such asAPP. The decoded output stays flat. Duplicate output fields and
external names fail during construction.TypeScript rejects fields declared as namespace groups and groups declared as
fields. Full identifier spelling and name lengths are checked during construction.Field codecs validate values and encode them back to canonical strings. Field
Types can usewithDefaultfor explicit defaults. Resolve source precedence
before replacing absence, or use thepreservestrategy when later composition
needs supplied-input evidence. Environment access and source merging remain
application code.Unprefixed fields select exact names. Namespace groups select all names with
their prefix and an underscore separator, ignoring casing during selection.
Incorrectly cased or unknown selected names fail validation. Unrelated variables
are ignored; misspelled unprefixed names or namespace prefixes may therefore
still look absent. Namespaces must omit the trailing underscore.Optional fields may be absent; explicit
undefinedvalues are rejected.
Empty strings remain present and must satisfy the field Type.
Malformed and unknown selected names fail instead of silently using defaults.
Errors retain the original environment names. Non-object inputs fail with
standard object errors. Names followEnvName: a
CONSTANT_CASE identifier of at most 255 characters including the prefix.Matching own string properties are read from any non-null object, including
arrays andprocess.env. Prototypes and internal contents, such as Map
entries, are ignored. Missing required settings still fail validation.import { assertEqual, assertErr, assertOk, ByteLengthFromString, env, optional, PortFromString, typeErrorToIssues, } from "@evolu/common"; const AppEnv = env({ port: optional(PortFromString), APP: { maxOwnerBytes: optional(ByteLengthFromString), }, }); const config = AppEnv.fromUnknown({ PORT: "04000", APP_MAX_OWNER_BYTES: "1MiB", HOME: "/home/evolu", }); assertOk(config, { port: 4000, maxOwnerBytes: 1048576 }); assertEqual(AppEnv.to(config.value), { PORT: "4000", APP_MAX_OWNER_BYTES: "1048576", }); const invalid = AppEnv.fromUnknown({ APP_POTR: "4000" }); assertErr(invalid); assertEqual(typeErrorToIssues(AppEnv, invalid.error)[0]?.path, ["APP_POTR"]); assertErr(AppEnv.fromUnknown({ PORT: "" })); assertErr(AppEnv.fromUnknown({ PORT: undefined })); assertOk(AppEnv.fromUnknown([]), {});
-
f4d9ad7: Added custom error wrappers for validation Types
createTypeWithErrorcreates a root Type from an existing validator, an error
mapper, and a formatter. It forwards error-collection options automatically.
The source must use identity encoding; the wrapper preserves its valid values
and exposes its Output as Input.import { assertEqual, assertErr, createTypeWithError, Number, String, union, type TypeError, type UnionError, } from "@evolu/common"; interface ValueError extends TypeError<"Value"> { readonly cause: UnionError; } const Value = createTypeWithError( "Value", union(String, Number), (cause): ValueError => ({ type: "Value", cause }), () => "Enter text or a number.", ); const result = Value.fromUnknown(false, { errors: "all" }); assertErr(result); assertEqual(result.error.cause.errors.length, 2); assertEqual(Value.formatError(result.error), "Enter text or a number.");
-
3d84543: Added a prefixed string codec
Use
prefixed(prefix)(Type)to remove an exact, case-sensitive prefix when
decoding and restore it when encoding. The wrapped Type validates the suffix
and preserves its decoded output, including brands. Encoding uses its
canonical string representation.import { assertEqual, assertErr, assertOk, assertType, ConstantCaseIdentifier, prefixed, PortFromString, type Port, } from "@evolu/common"; const EnvName = prefixed("APP_")(ConstantCaseIdentifier); const name = EnvName.fromUnknown("APP_PORT"); assertOk(name, "PORT"); assertType<typeof name.value, ConstantCaseIdentifier>(); assertEqual(EnvName.to(name.value), "APP_PORT"); assertErr(EnvName.fromUnknown("OTHER_PORT")); assertErr(EnvName.fromUnknown("APP_port")); const PortSetting = prefixed("port:")(PortFromString); const port = PortSetting.fromUnknown("port:04000"); assertOk(port, 4000); assertType<typeof port.value, Port>(); assertEqual(PortSetting.to(port.value), "port:4000");
The prefix must be a concrete string literal. The wrapped Type must accept a
string Input and encode to strings. An empty prefix leaves its representation
unchanged; an empty suffix is validated by the wrapped Type. -
6bd0a36: Added reversible object key codecs
objectKeys(keyType)(objectType)gives a strict object's fields external
names using the key Type's canonical encoding. Field Types, optionality, and
semantic Output are preserved. Decoding accepts exact canonical names and
reports errors at those names. Invalid schema keys and conflicting encodings
fail during construction.Typed property errors include missing required properties and unexpected input
keys.CamelCaseIdentifierFromConstantCaseIdentifierconverts between the two
identifier conventions without losing word boundaries. Compose it with
prefixedto adapt namespaced keys.import { assertEqual, assertErr, assertOk, CamelCaseIdentifierFromConstantCaseIdentifier, object, objectKeys, PortFromString, prefixed, } from "@evolu/common"; const Key = prefixed("APP_")(CamelCaseIdentifierFromConstantCaseIdentifier); const Settings = objectKeys(Key)(object({ http2Port: PortFromString })); const result = Settings.fromUnknown({ APP_HTTP2_PORT: "04000" }); assertOk(result, { http2Port: 4000 }); assertEqual(Settings.to(result.value), { APP_HTTP2_PORT: "4000" }); assertErr(Settings.fromUnknown({ APP_HTTP_2_PORT: "4000" }));
-
76554bd: Added explicit defaults
Use
withDefault(type, value)to fill missing optional properties or accepted
nullandundefinedvalues. Invalid supplied values still fail validation.Pass
{ strategy: "preserve" }as the third argument to track whether the
default was used and restore the original absence when encoding. An explicitly
supplied value remains distinguishable even when it equals the default.Configured defaults are reused by reference, including when Types are localized
withlocalizeTypes.partialretains localized field errors while disabling
defaults for missing properties.Prefer defaults in the view over defaults in your database schema. Use
nullable columns and apply??when reading or displaying values unless the
default needs to be stored. Replacingnullcan enlarge rows unnecessarily and
erase the distinction between "not specified" and an explicit user decision.import { assertEqual, assertOk, Boolean, nullOr, object, optional, withDefault, } from "@evolu/common"; const Enabled = withDefault(nullOr(Boolean), true); assertOk(Enabled.fromUnknown(null), true); assertEqual(Enabled.to(true), true); const Settings = object({ enabled: withDefault(optional(Boolean), true, { strategy: "preserve" }), }); const missing = Settings.fromUnknown({}); assertOk(missing, { enabled: { value: true, defaultUsed: true, original: "missing" }, }); assertEqual(Settings.to(missing.value), {}); assertOk(Settings.fromUnknown({ enabled: true }), { enabled: { value: true, defaultUsed: false }, });
-
918d77b: Added file system operations for Tasks
FsprovidesreadFile,writeFile,readDirectory,createDirectory,copy,
copyFile,rename,remove,getMetadata,exists, andcreateTempDirectory.
Each operation returns a Task.Tasks can sequence file operations without synchronous I/O. Node.js's synchronous
methods are intentionally omitted to avoid accidentally blocking the event loop.Inject
createNodeFs()throughrunMainorcreateRun. Tasks declareFsDep
and access the file system throughrun.deps.fs.On Node.js,
readFileandwriteFilepass the Run's abort signal to the native
operation. If it rejects after cancellation,...