@timothyrusso/effect-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/config/makeConfig.d.ts +43 -0
- package/dist/config/makeConfig.d.ts.map +1 -0
- package/dist/config/makeConfig.js +44 -0
- package/dist/config/makeConfig.js.map +1 -0
- package/dist/errors/appError.d.ts +55 -0
- package/dist/errors/appError.d.ts.map +1 -0
- package/dist/errors/appError.js +31 -0
- package/dist/errors/appError.js.map +1 -0
- package/dist/errors/messageKeys.d.ts +36 -0
- package/dist/errors/messageKeys.d.ts.map +1 -0
- package/dist/errors/messageKeys.js +26 -0
- package/dist/errors/messageKeys.js.map +1 -0
- package/dist/errors/unexpected.d.ts +26 -0
- package/dist/errors/unexpected.d.ts.map +1 -0
- package/dist/errors/unexpected.js +16 -0
- package/dist/errors/unexpected.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/logger.d.ts +25 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +27 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/react/EffectRuntimeProvider.d.ts +22 -0
- package/dist/react/EffectRuntimeProvider.d.ts.map +1 -0
- package/dist/react/EffectRuntimeProvider.js +17 -0
- package/dist/react/EffectRuntimeProvider.js.map +1 -0
- package/dist/react/index.d.ts +10 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +9 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/register.d.ts +30 -0
- package/dist/react/register.d.ts.map +1 -0
- package/dist/react/register.js +2 -0
- package/dist/react/register.js.map +1 -0
- package/dist/react/runAtBoundary.d.ts +13 -0
- package/dist/react/runAtBoundary.d.ts.map +1 -0
- package/dist/react/runAtBoundary.js +23 -0
- package/dist/react/runAtBoundary.js.map +1 -0
- package/dist/react/useEffectMutation.d.ts +19 -0
- package/dist/react/useEffectMutation.d.ts.map +1 -0
- package/dist/react/useEffectMutation.js +21 -0
- package/dist/react/useEffectMutation.js.map +1 -0
- package/dist/react/useEffectQuery.d.ts +20 -0
- package/dist/react/useEffectQuery.d.ts.map +1 -0
- package/dist/react/useEffectQuery.js +22 -0
- package/dist/react/useEffectQuery.js.map +1 -0
- package/dist/react/useErrorMessage.d.ts +13 -0
- package/dist/react/useErrorMessage.d.ts.map +1 -0
- package/dist/react/useErrorMessage.js +15 -0
- package/dist/react/useErrorMessage.js.map +1 -0
- package/dist/runtime/makeAppRuntime.d.ts +38 -0
- package/dist/runtime/makeAppRuntime.d.ts.map +1 -0
- package/dist/runtime/makeAppRuntime.js +26 -0
- package/dist/runtime/makeAppRuntime.js.map +1 -0
- package/dist/sqlite/migrations.d.ts +33 -0
- package/dist/sqlite/migrations.d.ts.map +1 -0
- package/dist/sqlite/migrations.js +49 -0
- package/dist/sqlite/migrations.js.map +1 -0
- package/dist/sqlite/sqliteClient.d.ts +63 -0
- package/dist/sqlite/sqliteClient.d.ts.map +1 -0
- package/dist/sqlite/sqliteClient.js +37 -0
- package/dist/sqlite/sqliteClient.js.map +1 -0
- package/dist/testing/clock.d.ts +16 -0
- package/dist/testing/clock.d.ts.map +1 -0
- package/dist/testing/clock.js +18 -0
- package/dist/testing/clock.js.map +1 -0
- package/dist/testing/collectLogs.d.ts +32 -0
- package/dist/testing/collectLogs.d.ts.map +1 -0
- package/dist/testing/collectLogs.js +31 -0
- package/dist/testing/collectLogs.js.map +1 -0
- package/dist/testing/index.d.ts +12 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/nodeSqlite.d.ts +27 -0
- package/dist/testing/nodeSqlite.d.ts.map +1 -0
- package/dist/testing/nodeSqlite.js +74 -0
- package/dist/testing/nodeSqlite.js.map +1 -0
- package/dist/testing/runTest.d.ts +25 -0
- package/dist/testing/runTest.d.ts.map +1 -0
- package/dist/testing/runTest.js +18 -0
- package/dist/testing/runTest.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Timothy Russo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @timothyrusso/effect-core
|
|
2
|
+
|
|
3
|
+
The shared Effect layer an app builds on: the error model, the Logger and Config services, the
|
|
4
|
+
one app runtime, the SQLite interface and migration runner, the React boundary hooks and the
|
|
5
|
+
jest helpers.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @timothyrusso/effect-core effect @tanstack/react-query
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies: `effect` 3.x, and for `/react` also `react` and `@tanstack/react-query` v5.
|
|
12
|
+
The package has no native dependency: the app passes in `expo-constants` and `expo-sqlite`.
|
|
13
|
+
|
|
14
|
+
## Entry points
|
|
15
|
+
|
|
16
|
+
| Import | Contents |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `@timothyrusso/effect-core` | Errors, Logger, Config, runtime, SQLite (safe for Metro) |
|
|
19
|
+
| `@timothyrusso/effect-core/react` | `EffectRuntimeProvider`, `useEffectQuery`, `useEffectMutation`, `useErrorMessage` |
|
|
20
|
+
| `@timothyrusso/effect-core/testing` | Jest helpers; loads `node:sqlite`, so import it only from tests |
|
|
21
|
+
|
|
22
|
+
### `@timothyrusso/effect-core`
|
|
23
|
+
|
|
24
|
+
| Export | What it is |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `AppErrorBase(tag, messageKey)` | Declares a `Data.TaggedError` class with a `messageKey` and an optional `cause` |
|
|
27
|
+
| `UnexpectedError` | The error every defect becomes; `cause: unknown` |
|
|
28
|
+
| `toAppError(cause, isKnown?)` | For the `catch` of `Effect.tryPromise`: wraps unknown values in `UnexpectedError` |
|
|
29
|
+
| `isAppError(value)` | Shape check: a string `_tag` and `messageKey` |
|
|
30
|
+
| `defineAppErrors<AppError>()` | Binds the closed union; `.assertExhaustiveMessageKeys(mapper)` fails to compile on a missing or unknown tag |
|
|
31
|
+
| `resolveMessageKey(error, mapper)` | The key the mapper gives an error |
|
|
32
|
+
| `Logger`, `ConsoleLogger`, `NoopLogger` | The logging Tag (`error(error, context)`, `warn`, `info`, `debug`) and two Layers |
|
|
33
|
+
| `Clock` | Effect's clock, re-exported |
|
|
34
|
+
| `makeConfig(schema)`, `ConfigError` | A `Config` Tag and a Live Layer decoding the app's `extra`; boot fails naming the field |
|
|
35
|
+
| `makeAppRuntime(layer)`, `AppServicesOf` | The one `ManagedRuntime`: `boot`, `runPromise`, `runPromiseExit`, `runFork`, `dispose` |
|
|
36
|
+
| `SqliteClient`, `SqliteDatabase`, `SqlError` | The connection Tag, shaped on expo-sqlite's async API, and its error |
|
|
37
|
+
| `withSqlite(operation, query)`, `trySql` | Run a query as an Effect failing with `SqlError` |
|
|
38
|
+
| `runMigrations(client, migrations)` | `{ version, up }` steps keyed by `PRAGMA user_version`, each in its own transaction |
|
|
39
|
+
|
|
40
|
+
### `@timothyrusso/effect-core/react`
|
|
41
|
+
|
|
42
|
+
| Export | What it is |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `EffectRuntimeProvider` | Puts the app runtime in context, inside `QueryClientProvider` |
|
|
45
|
+
| `useEffectQuery(options)` | `useQuery` with `queryFn` an `Effect<A, AppError, AppServices>`; interrupts on unmount or key change |
|
|
46
|
+
| `useEffectMutation(options)` | `useMutation` with `mutationFn` returning an Effect; `onError` gets an `AppError` |
|
|
47
|
+
| `useErrorMessage(error, mapper, t)` | The translated message for an error |
|
|
48
|
+
| `Register` | The interface the app augments with its services and error union |
|
|
49
|
+
|
|
50
|
+
A defect becomes an `UnexpectedError` with the defect as `cause`; each failure is logged once
|
|
51
|
+
through `Logger` (`error` for an `UnexpectedError`, `warn` with the tag otherwise).
|
|
52
|
+
|
|
53
|
+
### `@timothyrusso/effect-core/testing`
|
|
54
|
+
|
|
55
|
+
| Export | What it is |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `itEffect(name, effect, layer?)`, `runTest(effect, layer?)` | Run an Effect as a jest test, with `TestContext` |
|
|
58
|
+
| `makeNodeSqliteLayer()`, `makeNodeSqliteDatabase()` | `SqliteClient` over an in-memory `node:sqlite` database |
|
|
59
|
+
| `collectLogs()` | A `Logger` Layer recording into `entries` |
|
|
60
|
+
| `advanceClock(duration)`, `TestClock`, `TestContext` | Move time in tests |
|
|
61
|
+
|
|
62
|
+
## Wiring an app
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// features/core/config
|
|
66
|
+
import Constants from 'expo-constants';
|
|
67
|
+
export const AppConfig = makeConfig(Schema.Struct({ apiUrl: Schema.String }));
|
|
68
|
+
export const ConfigLive = AppConfig.layer(() => Constants.expoConfig?.extra);
|
|
69
|
+
|
|
70
|
+
// features/core/runtime
|
|
71
|
+
export const runtime = makeAppRuntime(Layer.mergeAll(ConsoleLogger, ConfigLive, SqliteLive));
|
|
72
|
+
export type AppServices = AppServicesOf<typeof runtime>;
|
|
73
|
+
|
|
74
|
+
declare module '@timothyrusso/effect-core/react' {
|
|
75
|
+
interface Register {
|
|
76
|
+
services: AppServices;
|
|
77
|
+
error: AppError;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Render `<EffectRuntimeProvider runtime={runtime}>` inside the `QueryClientProvider`. Once
|
|
83
|
+
`Register` is filled in, a facade passing an Effect that needs a service the runtime does not
|
|
84
|
+
provide, or that fails with an error outside `AppError`, does not compile.
|
|
85
|
+
|
|
86
|
+
The expo-sqlite Live Layer is one line in the app:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
export const SqliteLive = Layer.effect(SqliteClient, Effect.promise(() => SQLite.openDatabaseAsync('app.db')));
|
|
90
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Context, Layer, Schema } from 'effect';
|
|
2
|
+
declare const ConfigError_base: import("../errors/appError.js").AppErrorClass<"ConfigError", "errors.config">;
|
|
3
|
+
/**
|
|
4
|
+
* The app configuration failed to decode at boot. `field` is the dotted path of the first bad
|
|
5
|
+
* field (`api.baseUrl`); `message` lists every problem.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ConfigError extends ConfigError_base<{
|
|
8
|
+
readonly field: string;
|
|
9
|
+
readonly message: string;
|
|
10
|
+
}> {
|
|
11
|
+
}
|
|
12
|
+
/** The identifier of the app's config service in the runtime's context. */
|
|
13
|
+
export interface AppConfig {
|
|
14
|
+
readonly _AppConfig: 'AppConfig';
|
|
15
|
+
}
|
|
16
|
+
/** What {@link makeConfig} returns. */
|
|
17
|
+
export interface ConfigDefinition<A> {
|
|
18
|
+
/** The Tag use cases read the decoded config from: `const config = yield* Config.Config`. */
|
|
19
|
+
readonly Config: Context.Tag<AppConfig, A>;
|
|
20
|
+
/**
|
|
21
|
+
* The Live Layer. `readExtra` is called once, when the runtime boots; its result is decoded
|
|
22
|
+
* with the schema, and a missing or invalid field fails the boot with a {@link ConfigError}.
|
|
23
|
+
* An `undefined` result counts as an empty object, so the error still names the field.
|
|
24
|
+
*/
|
|
25
|
+
readonly layer: (readExtra: () => unknown) => Layer.Layer<AppConfig, ConfigError>;
|
|
26
|
+
/** A Layer holding an already decoded value, for tests. */
|
|
27
|
+
readonly layerOf: (config: A) => Layer.Layer<AppConfig>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Declares the app's config from a Schema. The kit has no native dependency, so the app passes
|
|
31
|
+
* where the values come from; in an Expo app that is the `extra` block of `app.config.ts`:
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import Constants from 'expo-constants';
|
|
36
|
+
*
|
|
37
|
+
* export const AppConfig = makeConfig(Schema.Struct({ apiUrl: Schema.String }));
|
|
38
|
+
* export const ConfigLive = AppConfig.layer(() => Constants.expoConfig?.extra);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function makeConfig<A, I>(schema: Schema.Schema<A, I, never>): ConfigDefinition<A>;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=makeConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"makeConfig.d.ts","sourceRoot":"","sources":["../../src/config/makeConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,KAAK,EAAe,MAAM,EAAE,MAAM,QAAQ,CAAC;;AAGrE;;;GAGG;AACH,qBAAa,WAAY,SAAQ,iBAA6C;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAEL,2EAA2E;AAC3E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;CAClC;AAED,uCAAuC;AACvC,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,6FAA6F;IAC7F,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,MAAM,OAAO,KAAK,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAClF,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;CACzD;AAcD;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAgBxF"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Context, Effect, Layer, ParseResult, Schema } from 'effect';
|
|
2
|
+
import { AppErrorBase } from '../errors/appError.js';
|
|
3
|
+
/**
|
|
4
|
+
* The app configuration failed to decode at boot. `field` is the dotted path of the first bad
|
|
5
|
+
* field (`api.baseUrl`); `message` lists every problem.
|
|
6
|
+
*/
|
|
7
|
+
export class ConfigError extends AppErrorBase('ConfigError', 'errors.config') {
|
|
8
|
+
}
|
|
9
|
+
function describe(error) {
|
|
10
|
+
const issues = ParseResult.ArrayFormatter.formatErrorSync(error);
|
|
11
|
+
const field = issues[0]?.path.map(String).join('.') ?? '';
|
|
12
|
+
const message = issues
|
|
13
|
+
.map(issue => {
|
|
14
|
+
const path = issue.path.map(String).join('.');
|
|
15
|
+
return path === '' ? issue.message : `${path}: ${issue.message}`;
|
|
16
|
+
})
|
|
17
|
+
.join('; ');
|
|
18
|
+
return new ConfigError({ field, message: `Invalid app config: ${message}`, cause: error });
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Declares the app's config from a Schema. The kit has no native dependency, so the app passes
|
|
22
|
+
* where the values come from; in an Expo app that is the `extra` block of `app.config.ts`:
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import Constants from 'expo-constants';
|
|
27
|
+
*
|
|
28
|
+
* export const AppConfig = makeConfig(Schema.Struct({ apiUrl: Schema.String }));
|
|
29
|
+
* export const ConfigLive = AppConfig.layer(() => Constants.expoConfig?.extra);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function makeConfig(schema) {
|
|
33
|
+
const Config = Context.GenericTag('@timothyrusso/effect-core/Config');
|
|
34
|
+
const decode = Schema.decodeUnknown(schema, { errors: 'all' });
|
|
35
|
+
return {
|
|
36
|
+
Config,
|
|
37
|
+
layer: readExtra => Layer.effect(Config, Effect.try({
|
|
38
|
+
try: () => readExtra() ?? {},
|
|
39
|
+
catch: cause => new ConfigError({ field: '', message: 'Invalid app config: reading the source threw', cause }),
|
|
40
|
+
}).pipe(Effect.flatMap(extra => Effect.mapError(decode(extra), describe)))),
|
|
41
|
+
layerOf: config => Layer.succeed(Config, config),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=makeConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"makeConfig.js","sourceRoot":"","sources":["../../src/config/makeConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,YAAY,CAAC,aAAa,EAAE,eAAe,CAG1E;CAAG;AAqBL,SAAS,QAAQ,CAAC,KAA6B;IAC7C,MAAM,MAAM,GAAG,WAAW,CAAC,cAAc,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IAC1D,MAAM,OAAO,GAAG,MAAM;SACnB,GAAG,CAAC,KAAK,CAAC,EAAE;QACX,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9C,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;IACnE,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,IAAI,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,uBAAuB,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7F,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAO,MAAkC;IACjE,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAe,kCAAkC,CAAC,CAAC;IACpF,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,OAAO;QACL,MAAM;QACN,KAAK,EAAE,SAAS,CAAC,EAAE,CACjB,KAAK,CAAC,MAAM,CACV,MAAM,EACN,MAAM,CAAC,GAAG,CAAC;YACT,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE;YAC5B,KAAK,EAAE,KAAK,CAAC,EAAE,CACb,IAAI,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,8CAA8C,EAAE,KAAK,EAAE,CAAC;SACjG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAC3E;QACH,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC;KACjD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type Cause } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* The shape every app error shares: a `_tag` naming the class, a `messageKey` into the app's
|
|
4
|
+
* catalog, and an optional `cause` (the original exception, a parse error, a failed response).
|
|
5
|
+
* An app's closed `AppError` union is a union of classes declared with {@link AppErrorBase}.
|
|
6
|
+
*/
|
|
7
|
+
export interface AnyAppError {
|
|
8
|
+
readonly _tag: string;
|
|
9
|
+
readonly messageKey: string;
|
|
10
|
+
readonly cause?: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** The fields a subclass declares, minus the ones {@link AppErrorBase} owns. */
|
|
13
|
+
type AppErrorFields<A> = {
|
|
14
|
+
readonly [P in keyof A as P extends '_tag' | 'messageKey' ? never : P]: A[P];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Constructor arguments: the declared fields plus an optional `cause`. The argument itself is
|
|
18
|
+
* optional when every declared field is.
|
|
19
|
+
*/
|
|
20
|
+
export type AppErrorArgs<A> = Record<never, never> extends AppErrorFields<A> ? [args?: AppErrorFields<A> & {
|
|
21
|
+
readonly cause?: unknown;
|
|
22
|
+
}] : [args: AppErrorFields<A> & {
|
|
23
|
+
readonly cause?: unknown;
|
|
24
|
+
}];
|
|
25
|
+
/** An instance of a class declared with {@link AppErrorBase}. */
|
|
26
|
+
export type AppErrorInstance<Tag extends string, Key extends string, A> = Cause.YieldableError & {
|
|
27
|
+
readonly _tag: Tag;
|
|
28
|
+
readonly messageKey: Key;
|
|
29
|
+
} & Readonly<A>;
|
|
30
|
+
/**
|
|
31
|
+
* The class {@link AppErrorBase} returns. Extend it with the error's own fields as the type
|
|
32
|
+
* argument; `messageKey` is available on the class and on every instance.
|
|
33
|
+
*/
|
|
34
|
+
export interface AppErrorClass<Tag extends string, Key extends string> {
|
|
35
|
+
new <A extends Record<string, unknown> = Record<never, never>>(...args: AppErrorArgs<A>): AppErrorInstance<Tag, Key, A>;
|
|
36
|
+
readonly messageKey: Key;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Declares a `Data.TaggedError` class that carries a catalog `messageKey` and accepts an optional
|
|
40
|
+
* `cause`. Instances are yieldable in `Effect.gen`, compare structurally and are real `Error`s.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* export class TripNotFound extends AppErrorBase('TripNotFound', 'errors.tripNotFound')<{
|
|
45
|
+
* readonly tripId: string;
|
|
46
|
+
* }> {}
|
|
47
|
+
*
|
|
48
|
+
* yield* new TripNotFound({ tripId, cause: response });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function AppErrorBase<const Tag extends string, const Key extends string>(tag: Tag, messageKey: Key): AppErrorClass<Tag, Key>;
|
|
52
|
+
/** True for any value shaped like an app error: an object with a string `_tag` and `messageKey`. */
|
|
53
|
+
export declare function isAppError(value: unknown): value is AnyAppError;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=appError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appError.d.ts","sourceRoot":"","sources":["../../src/errors/appError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAQ,MAAM,QAAQ,CAAC;AAE1C;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,gFAAgF;AAChF,KAAK,cAAc,CAAC,CAAC,IAAI;IAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,YAAY,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAE1G;;;GAGG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IACxB,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,cAAc,CAAC,CAAC,CAAC,GAC1C,CAAC,IAAI,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,GACzD,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAE/D,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAAC,IAAI,KAAK,CAAC,cAAc,GAAG;IAC/F,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC;IACnB,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC;CAC1B,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAEhB;;;GAGG;AACH,MAAM,WAAW,aAAa,CAAC,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM;IACnE,KAAK,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAC3D,GAAG,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GACvB,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,GAAG,SAAS,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS,MAAM,EAC7E,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,GAAG,GACd,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAQzB;AAED,oGAAoG;AACpG,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAI/D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Data } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* Declares a `Data.TaggedError` class that carries a catalog `messageKey` and accepts an optional
|
|
4
|
+
* `cause`. Instances are yieldable in `Effect.gen`, compare structurally and are real `Error`s.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* export class TripNotFound extends AppErrorBase('TripNotFound', 'errors.tripNotFound')<{
|
|
9
|
+
* readonly tripId: string;
|
|
10
|
+
* }> {}
|
|
11
|
+
*
|
|
12
|
+
* yield* new TripNotFound({ tripId, cause: response });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function AppErrorBase(tag, messageKey) {
|
|
16
|
+
class Base extends Data.TaggedError(tag) {
|
|
17
|
+
static messageKey = messageKey;
|
|
18
|
+
messageKey = messageKey;
|
|
19
|
+
}
|
|
20
|
+
// NOTE: Data.TaggedError's constructor is generic in the fields; this class keeps that
|
|
21
|
+
// signature and adds messageKey, which TypeScript cannot express on the class expression itself.
|
|
22
|
+
return Base;
|
|
23
|
+
}
|
|
24
|
+
/** True for any value shaped like an app error: an object with a string `_tag` and `messageKey`. */
|
|
25
|
+
export function isAppError(value) {
|
|
26
|
+
if (typeof value !== 'object' || value === null)
|
|
27
|
+
return false;
|
|
28
|
+
const candidate = value;
|
|
29
|
+
return typeof candidate._tag === 'string' && typeof candidate.messageKey === 'string';
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=appError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appError.js","sourceRoot":"","sources":["../../src/errors/appError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,IAAI,EAAE,MAAM,QAAQ,CAAC;AA0C1C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAQ,EACR,UAAe;IAEf,MAAM,IAAK,SAAQ,IAAI,CAAC,WAAW,CAAC,GAAG,CAA+B;QACpE,MAAM,CAAU,UAAU,GAAG,UAAU,CAAC;QAC/B,UAAU,GAAG,UAAU,CAAC;;IAEnC,uFAAuF;IACvF,iGAAiG;IACjG,OAAO,IAA0C,CAAC;AACpD,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAAmE,CAAC;IACtF,OAAO,OAAO,SAAS,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,SAAS,CAAC,UAAU,KAAK,QAAQ,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AnyAppError } from './appError.js';
|
|
2
|
+
import type { UnexpectedError } from './unexpected.js';
|
|
3
|
+
/**
|
|
4
|
+
* One catalog key per error tag of `E`. Being a mapped type over the tags, a missing tag is a
|
|
5
|
+
* compile error and so is a tag that is not in the union.
|
|
6
|
+
*/
|
|
7
|
+
export type MessageKeyMapper<E extends AnyAppError, K extends string = string> = {
|
|
8
|
+
readonly [Tag in E['_tag']]: K;
|
|
9
|
+
};
|
|
10
|
+
/** What {@link defineAppErrors} returns: helpers bound to the app's closed error union. */
|
|
11
|
+
export interface AppErrors<E extends AnyAppError> {
|
|
12
|
+
/**
|
|
13
|
+
* Returns `mapper` unchanged. Its only job is the type check: every tag of the union, plus
|
|
14
|
+
* `UnexpectedError`, has a key, and no other tag does.
|
|
15
|
+
*/
|
|
16
|
+
assertExhaustiveMessageKeys<K extends string>(mapper: MessageKeyMapper<E | UnexpectedError, K>): MessageKeyMapper<E | UnexpectedError, K>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Declares the app's closed error union once, in `features/core/error`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* export type AppError = TripNotFound | SqlError | ConfigError | UnexpectedError;
|
|
24
|
+
* const appErrors = defineAppErrors<AppError>();
|
|
25
|
+
* export const errorTagToMessageKey = appErrors.assertExhaustiveMessageKeys({
|
|
26
|
+
* TripNotFound: 'errors.tripNotFound',
|
|
27
|
+
* SqlError: 'errors.storage',
|
|
28
|
+
* ConfigError: 'errors.config',
|
|
29
|
+
* UnexpectedError: 'errors.unexpected',
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function defineAppErrors<E extends AnyAppError>(): AppErrors<E>;
|
|
34
|
+
/** The catalog key `mapper` gives `error`. */
|
|
35
|
+
export declare function resolveMessageKey<E extends AnyAppError, K extends string>(error: E, mapper: MessageKeyMapper<E, K>): K;
|
|
36
|
+
//# sourceMappingURL=messageKeys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messageKeys.d.ts","sourceRoot":"","sources":["../../src/errors/messageKeys.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IAC/E,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;CAC/B,CAAC;AAEF,2FAA2F;AAC3F,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,WAAW;IAC9C;;;OAGG;IACH,2BAA2B,CAAC,CAAC,SAAS,MAAM,EAC1C,MAAM,EAAE,gBAAgB,CAAC,CAAC,GAAG,eAAe,EAAE,CAAC,CAAC,GAC/C,gBAAgB,CAAC,CAAC,GAAG,eAAe,EAAE,CAAC,CAAC,CAAC;CAC7C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,CAIrE;AAED,8CAA8C;AAC9C,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC,SAAS,MAAM,EACvE,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC7B,CAAC,CAGH"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declares the app's closed error union once, in `features/core/error`.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* export type AppError = TripNotFound | SqlError | ConfigError | UnexpectedError;
|
|
7
|
+
* const appErrors = defineAppErrors<AppError>();
|
|
8
|
+
* export const errorTagToMessageKey = appErrors.assertExhaustiveMessageKeys({
|
|
9
|
+
* TripNotFound: 'errors.tripNotFound',
|
|
10
|
+
* SqlError: 'errors.storage',
|
|
11
|
+
* ConfigError: 'errors.config',
|
|
12
|
+
* UnexpectedError: 'errors.unexpected',
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function defineAppErrors() {
|
|
17
|
+
return {
|
|
18
|
+
assertExhaustiveMessageKeys: mapper => mapper,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** The catalog key `mapper` gives `error`. */
|
|
22
|
+
export function resolveMessageKey(error, mapper) {
|
|
23
|
+
const tag = error._tag;
|
|
24
|
+
return mapper[tag];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=messageKeys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messageKeys.js","sourceRoot":"","sources":["../../src/errors/messageKeys.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO;QACL,2BAA2B,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM;KAC9C,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,iBAAiB,CAC/B,KAAQ,EACR,MAA8B;IAE9B,MAAM,GAAG,GAAc,KAAK,CAAC,IAAI,CAAC;IAClC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AnyAppError } from './appError.js';
|
|
2
|
+
declare const UnexpectedError_base: import("./appError.js").AppErrorClass<"UnexpectedError", "errors.unexpected">;
|
|
3
|
+
/**
|
|
4
|
+
* The one error that is not a domain failure: a defect, a thrown exception or a rejection nobody
|
|
5
|
+
* anticipated. The runtime boundary turns every defect into this, and `toAppError` wraps unknown
|
|
6
|
+
* values in it. `cause` holds the original value.
|
|
7
|
+
*/
|
|
8
|
+
export declare class UnexpectedError extends UnexpectedError_base<{
|
|
9
|
+
readonly cause: unknown;
|
|
10
|
+
}> {
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Turns an unknown thrown value into an app error, for the `catch` callback of
|
|
14
|
+
* `Effect.tryPromise` and `Effect.try`. An `UnexpectedError` passes through unchanged; anything
|
|
15
|
+
* else is wrapped as its `cause`. Pass `isKnown` to let the app's own errors through as they are.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* Effect.tryPromise({ try: () => api.trip(id), catch: toAppError });
|
|
20
|
+
* Effect.tryPromise({ try: () => repo.save(trip), catch: cause => toAppError(cause, isTripError) });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function toAppError(cause: unknown): UnexpectedError;
|
|
24
|
+
export declare function toAppError<E extends AnyAppError>(cause: unknown, isKnown: (value: unknown) => value is E): E | UnexpectedError;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=unexpected.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unexpected.d.ts","sourceRoot":"","sources":["../../src/errors/unexpected.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,eAAe,CAAC;;AAE/D;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,qBAAqD;IACxF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,CAAC;CAAG;AAEL;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAAC;AAC5D,wBAAgB,UAAU,CAAC,CAAC,SAAS,WAAW,EAC9C,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,GACtC,CAAC,GAAG,eAAe,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AppErrorBase } from './appError.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one error that is not a domain failure: a defect, a thrown exception or a rejection nobody
|
|
4
|
+
* anticipated. The runtime boundary turns every defect into this, and `toAppError` wraps unknown
|
|
5
|
+
* values in it. `cause` holds the original value.
|
|
6
|
+
*/
|
|
7
|
+
export class UnexpectedError extends AppErrorBase('UnexpectedError', 'errors.unexpected') {
|
|
8
|
+
}
|
|
9
|
+
export function toAppError(cause, isKnown) {
|
|
10
|
+
if (cause instanceof UnexpectedError)
|
|
11
|
+
return cause;
|
|
12
|
+
if (isKnown?.(cause))
|
|
13
|
+
return cause;
|
|
14
|
+
return new UnexpectedError({ cause });
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=unexpected.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unexpected.js","sourceRoot":"","sources":["../../src/errors/unexpected.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,YAAY,EAAE,MAAM,eAAe,CAAC;AAE/D;;;;GAIG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY,CAAC,iBAAiB,EAAE,mBAAmB,CAEtF;CAAG;AAkBL,MAAM,UAAU,UAAU,CACxB,KAAc,EACd,OAAwC;IAExC,IAAI,KAAK,YAAY,eAAe;QAAE,OAAO,KAAK,CAAC;IACnD,IAAI,OAAO,EAAE,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,OAAO,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACxC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @timothyrusso/effect-core. Shared Effect layer: AppError, Logger, Config, runtime, SqliteClient,
|
|
3
|
+
* React hooks (`/react`) and testing helpers (`/testing`).
|
|
4
|
+
*/
|
|
5
|
+
/** Effect's `Clock` service, re-exported so use cases read time through it and tests control it. */
|
|
6
|
+
export { Clock } from 'effect';
|
|
7
|
+
export { type AppConfig, type ConfigDefinition, ConfigError, makeConfig } from './config/makeConfig.js';
|
|
8
|
+
export { type AnyAppError, type AppErrorArgs, AppErrorBase, type AppErrorClass, type AppErrorInstance, isAppError, } from './errors/appError.js';
|
|
9
|
+
export { type AppErrors, defineAppErrors, type MessageKeyMapper, resolveMessageKey } from './errors/messageKeys.js';
|
|
10
|
+
export { toAppError, UnexpectedError } from './errors/unexpected.js';
|
|
11
|
+
export { ConsoleLogger, type LogContext, Logger, type LoggerService, NoopLogger } from './logger/logger.js';
|
|
12
|
+
export { type AppRuntime, type AppServicesOf, makeAppRuntime, type RunOptions } from './runtime/makeAppRuntime.js';
|
|
13
|
+
export { type Migration, type MigrationReport, runMigrations } from './sqlite/migrations.js';
|
|
14
|
+
export { SqlError, SqliteClient, type SqliteDatabase, type SqlParams, type SqlRunResult, type SqlValue, trySql, withSqlite, } from './sqlite/sqliteClient.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,oGAAoG;AACpG,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACxG,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,UAAU,GACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,SAAS,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACpH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5G,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACnH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC7F,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,MAAM,EACN,UAAU,GACX,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @timothyrusso/effect-core. Shared Effect layer: AppError, Logger, Config, runtime, SqliteClient,
|
|
3
|
+
* React hooks (`/react`) and testing helpers (`/testing`).
|
|
4
|
+
*/
|
|
5
|
+
/** Effect's `Clock` service, re-exported so use cases read time through it and tests control it. */
|
|
6
|
+
export { Clock } from 'effect';
|
|
7
|
+
export { ConfigError, makeConfig } from './config/makeConfig.js';
|
|
8
|
+
export { AppErrorBase, isAppError, } from './errors/appError.js';
|
|
9
|
+
export { defineAppErrors, resolveMessageKey } from './errors/messageKeys.js';
|
|
10
|
+
export { toAppError, UnexpectedError } from './errors/unexpected.js';
|
|
11
|
+
export { ConsoleLogger, Logger, NoopLogger } from './logger/logger.js';
|
|
12
|
+
export { makeAppRuntime } from './runtime/makeAppRuntime.js';
|
|
13
|
+
export { runMigrations } from './sqlite/migrations.js';
|
|
14
|
+
export { SqlError, SqliteClient, trySql, withSqlite, } from './sqlite/sqliteClient.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,oGAAoG;AACpG,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAyC,WAAW,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACxG,OAAO,EAGL,YAAY,EAGZ,UAAU,GACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAkB,eAAe,EAAyB,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACpH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAmB,MAAM,EAAsB,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5G,OAAO,EAAuC,cAAc,EAAmB,MAAM,6BAA6B,CAAC;AACnH,OAAO,EAAwC,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC7F,OAAO,EACL,QAAQ,EACR,YAAY,EAKZ,MAAM,EACN,UAAU,GACX,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from 'effect';
|
|
2
|
+
/** Structured data attached to a log line: a query key, an id, the operation that failed. */
|
|
3
|
+
export type LogContext = Readonly<Record<string, unknown>>;
|
|
4
|
+
/** The logging service. Every method returns an Effect that never fails. */
|
|
5
|
+
export interface LoggerService {
|
|
6
|
+
/** An error worth reporting (a Sentry Layer sends these). */
|
|
7
|
+
readonly error: (error: unknown, context?: LogContext) => Effect.Effect<void>;
|
|
8
|
+
readonly warn: (message: string, context?: LogContext) => Effect.Effect<void>;
|
|
9
|
+
readonly info: (message: string, context?: LogContext) => Effect.Effect<void>;
|
|
10
|
+
readonly debug: (message: string, context?: LogContext) => Effect.Effect<void>;
|
|
11
|
+
}
|
|
12
|
+
declare const Logger_base: Context.TagClass<Logger, "@timothyrusso/effect-core/Logger", LoggerService>;
|
|
13
|
+
/**
|
|
14
|
+
* The app's logger. Use cases do not log: the runtime boundary (`useEffectQuery`,
|
|
15
|
+
* `useEffectMutation`) logs each failure once. Apps provide {@link ConsoleLogger} in development
|
|
16
|
+
* and their own Layer (Sentry, for example) in release builds.
|
|
17
|
+
*/
|
|
18
|
+
export declare class Logger extends Logger_base {
|
|
19
|
+
}
|
|
20
|
+
/** Writes to the console, with the context as a second argument. */
|
|
21
|
+
export declare const ConsoleLogger: Layer.Layer<Logger>;
|
|
22
|
+
/** Discards everything. For tests that do not assert on logs, and for silent builds. */
|
|
23
|
+
export declare const NoopLogger: Layer.Layer<Logger>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAEhD,6FAA6F;AAC7F,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE3D,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9E,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9E,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC9E,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAChF;;AAED;;;;GAIG;AACH,qBAAa,MAAO,SAAQ,WAAwE;CAAG;AAEvG,oEAAoE;AACpE,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAS5C,CAAC;AAEH,wFAAwF;AACxF,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAKzC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from 'effect';
|
|
2
|
+
/**
|
|
3
|
+
* The app's logger. Use cases do not log: the runtime boundary (`useEffectQuery`,
|
|
4
|
+
* `useEffectMutation`) logs each failure once. Apps provide {@link ConsoleLogger} in development
|
|
5
|
+
* and their own Layer (Sentry, for example) in release builds.
|
|
6
|
+
*/
|
|
7
|
+
export class Logger extends Context.Tag('@timothyrusso/effect-core/Logger')() {
|
|
8
|
+
}
|
|
9
|
+
/** Writes to the console, with the context as a second argument. */
|
|
10
|
+
export const ConsoleLogger = Layer.succeed(Logger, {
|
|
11
|
+
// biome-ignore lint/suspicious/noConsole: this Layer is the console sink
|
|
12
|
+
error: (error, context = {}) => Effect.sync(() => console.error(error, context)),
|
|
13
|
+
// biome-ignore lint/suspicious/noConsole: this Layer is the console sink
|
|
14
|
+
warn: (message, context = {}) => Effect.sync(() => console.warn(message, context)),
|
|
15
|
+
// biome-ignore lint/suspicious/noConsole: this Layer is the console sink
|
|
16
|
+
info: (message, context = {}) => Effect.sync(() => console.info(message, context)),
|
|
17
|
+
// biome-ignore lint/suspicious/noConsole: this Layer is the console sink
|
|
18
|
+
debug: (message, context = {}) => Effect.sync(() => console.debug(message, context)),
|
|
19
|
+
});
|
|
20
|
+
/** Discards everything. For tests that do not assert on logs, and for silent builds. */
|
|
21
|
+
export const NoopLogger = Layer.succeed(Logger, {
|
|
22
|
+
error: () => Effect.void,
|
|
23
|
+
warn: () => Effect.void,
|
|
24
|
+
info: () => Effect.void,
|
|
25
|
+
debug: () => Effect.void,
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAchD;;;;GAIG;AACH,MAAM,OAAO,MAAO,SAAQ,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,EAAyB;CAAG;AAEvG,oEAAoE;AACpE,MAAM,CAAC,MAAM,aAAa,GAAwB,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE;IACtE,yEAAyE;IACzE,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAChF,yEAAyE;IACzE,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClF,yEAAyE;IACzE,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClF,yEAAyE;IACzE,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;CACrF,CAAC,CAAC;AAEH,wFAAwF;AACxF,MAAM,CAAC,MAAM,UAAU,GAAwB,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE;IACnE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI;IACxB,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI;IACvB,IAAI,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI;IACvB,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI;CACzB,CAAC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { Logger } from '../logger/logger.js';
|
|
3
|
+
import type { AppRuntime } from '../runtime/makeAppRuntime.js';
|
|
4
|
+
import type { RegisteredServices } from './register.js';
|
|
5
|
+
/**
|
|
6
|
+
* The runtime the hooks run through: it provides the registered services, and `Logger`, which the
|
|
7
|
+
* boundary logs failures with.
|
|
8
|
+
*/
|
|
9
|
+
export type ProvidedRuntime = AppRuntime<RegisteredServices | Logger, unknown>;
|
|
10
|
+
/** Props of {@link EffectRuntimeProvider}. */
|
|
11
|
+
export interface EffectRuntimeProviderProps {
|
|
12
|
+
readonly runtime: ProvidedRuntime;
|
|
13
|
+
readonly children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Makes the app runtime available to `useEffectQuery` and `useEffectMutation`. Render it once,
|
|
17
|
+
* inside the `QueryClientProvider`, with the runtime from `makeAppRuntime`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function EffectRuntimeProvider({ runtime, children }: EffectRuntimeProviderProps): ReactNode;
|
|
20
|
+
/** The runtime from the nearest {@link EffectRuntimeProvider}; throws outside one. */
|
|
21
|
+
export declare function useAppRuntime(): ProvidedRuntime;
|
|
22
|
+
//# sourceMappingURL=EffectRuntimeProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EffectRuntimeProvider.d.ts","sourceRoot":"","sources":["../../src/react/EffectRuntimeProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,SAAS,EAAc,MAAM,OAAO,CAAC;AACjF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,kBAAkB,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC;AAI/E,8CAA8C;AAC9C,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,0BAA0B,GAAG,SAAS,CAElG;AAED,sFAAsF;AACtF,wBAAgB,aAAa,IAAI,eAAe,CAI/C"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createContext, createElement, useContext } from 'react';
|
|
2
|
+
const RuntimeContext = createContext(null);
|
|
3
|
+
/**
|
|
4
|
+
* Makes the app runtime available to `useEffectQuery` and `useEffectMutation`. Render it once,
|
|
5
|
+
* inside the `QueryClientProvider`, with the runtime from `makeAppRuntime`.
|
|
6
|
+
*/
|
|
7
|
+
export function EffectRuntimeProvider({ runtime, children }) {
|
|
8
|
+
return createElement(RuntimeContext.Provider, { value: runtime }, children);
|
|
9
|
+
}
|
|
10
|
+
/** The runtime from the nearest {@link EffectRuntimeProvider}; throws outside one. */
|
|
11
|
+
export function useAppRuntime() {
|
|
12
|
+
const runtime = useContext(RuntimeContext);
|
|
13
|
+
if (runtime === null)
|
|
14
|
+
throw new Error('useEffectQuery and useEffectMutation need an EffectRuntimeProvider');
|
|
15
|
+
return runtime;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=EffectRuntimeProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EffectRuntimeProvider.js","sourceRoot":"","sources":["../../src/react/EffectRuntimeProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAkB,UAAU,EAAE,MAAM,OAAO,CAAC;AAWjF,MAAM,cAAc,GAAG,aAAa,CAAyB,IAAI,CAAC,CAAC;AAQnE;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAA8B;IACrF,OAAO,aAAa,CAAC,cAAc,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC9E,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAC3C,IAAI,OAAO,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IAC5G,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @timothyrusso/effect-core/react. The runtime provider and the TanStack Query hooks that run
|
|
3
|
+
* Effects at the React boundary.
|
|
4
|
+
*/
|
|
5
|
+
export { EffectRuntimeProvider, type EffectRuntimeProviderProps, type ProvidedRuntime, } from './EffectRuntimeProvider.js';
|
|
6
|
+
export type { EffectHookError, Register, RegisteredError, RegisteredServices } from './register.js';
|
|
7
|
+
export { type UseEffectMutationOptions, useEffectMutation } from './useEffectMutation.js';
|
|
8
|
+
export { type UseEffectQueryOptions, useEffectQuery } from './useEffectQuery.js';
|
|
9
|
+
export { useErrorMessage } from './useErrorMessage.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,GACrB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,KAAK,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,KAAK,qBAAqB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @timothyrusso/effect-core/react. The runtime provider and the TanStack Query hooks that run
|
|
3
|
+
* Effects at the React boundary.
|
|
4
|
+
*/
|
|
5
|
+
export { EffectRuntimeProvider, } from './EffectRuntimeProvider.js';
|
|
6
|
+
export { useEffectMutation } from './useEffectMutation.js';
|
|
7
|
+
export { useEffectQuery } from './useEffectQuery.js';
|
|
8
|
+
export { useErrorMessage } from './useErrorMessage.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,qBAAqB,GAGtB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAiC,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAA8B,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|