effect-atom-svelte 0.0.0-stage → 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 +89 -2
- package/dist/Hooks.svelte.d.ts +396 -0
- package/dist/Hooks.svelte.js +1139 -0
- package/dist/HydrationBoundary.svelte +127 -0
- package/dist/HydrationBoundary.svelte.d.ts +36 -0
- package/dist/RegistryContext.d.ts +117 -0
- package/dist/RegistryContext.js +85 -0
- package/dist/RegistryProvider.svelte +60 -0
- package/dist/RegistryProvider.svelte.d.ts +31 -0
- package/dist/ScopedAtom.d.ts +73 -0
- package/dist/ScopedAtom.js +55 -0
- package/dist/SvelteKit.d.ts +78 -0
- package/dist/SvelteKit.js +90 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +50 -0
- package/dist/internal/hydration.d.ts +26 -0
- package/dist/internal/hydration.js +57 -0
- package/package.json +72 -4
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit `handleError` hooks that keep an Effect error's `_tag`, so a `<svelte:boundary>`
|
|
3
|
+
* `failed` snippet can tell typed errors apart. Import them from `effect-atom-svelte/sveltekit`.
|
|
4
|
+
*
|
|
5
|
+
* The module does not import SvelteKit: the hooks take the fields of SvelteKit's input they read
|
|
6
|
+
* and return the fields they set, so they type-check against `HandleClientError` and
|
|
7
|
+
* `HandleServerError` once `App.Error` declares `tag?: string`.
|
|
8
|
+
*
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*/
|
|
11
|
+
const tagOf = (error) => typeof error === "object" &&
|
|
12
|
+
error !== null &&
|
|
13
|
+
"_tag" in error &&
|
|
14
|
+
typeof error._tag === "string"
|
|
15
|
+
? error._tag
|
|
16
|
+
: undefined;
|
|
17
|
+
const messageOf = (error) => {
|
|
18
|
+
if (error instanceof Error) {
|
|
19
|
+
return error.message;
|
|
20
|
+
}
|
|
21
|
+
return typeof error === "string" ? error : undefined;
|
|
22
|
+
};
|
|
23
|
+
// SvelteKit 2 expects a message from the hook, and passes the one it would show. SvelteKit 3 fills
|
|
24
|
+
// in its own, and warns in development when the hook reads `message`, so it is read only without a
|
|
25
|
+
// `kind`.
|
|
26
|
+
const fallbackMessage = (input) => input.kind === undefined ? input.message : undefined;
|
|
27
|
+
// Leaves out empty fields, so SvelteKit's defaults apply, and returns nothing when both are empty.
|
|
28
|
+
const body = (message, tag) => {
|
|
29
|
+
if (!(message || tag)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return { ...(message ? { message } : {}), ...(tag ? { tag } : {}) };
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A client `handleError` hook (`src/hooks.client.ts`) that keeps the message and `_tag` of errors
|
|
36
|
+
* thrown by your code, so a boundary's `failed` snippet shows them instead of
|
|
37
|
+
* `{ status: 500, message: "Internal Error" }`. It logs those errors like SvelteKit's default
|
|
38
|
+
* hook, and leaves `error(...)` and SvelteKit's own errors as they are. Made for SvelteKit 3:
|
|
39
|
+
* SvelteKit 2 doesn't say which errors are its own, so there every error is kept and logged.
|
|
40
|
+
*
|
|
41
|
+
* **Example** (Using it as the client hook)
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* // src/hooks.client.ts
|
|
45
|
+
* export { handleClientError as handleError } from "effect-atom-svelte/sveltekit";
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @stability unstable
|
|
49
|
+
* @since 0.1.0
|
|
50
|
+
* @category hooks
|
|
51
|
+
*/
|
|
52
|
+
export const handleClientError = (input) => {
|
|
53
|
+
// Destructured here, not in the parameter list, so the API reference shows a named parameter.
|
|
54
|
+
const { error, kind = "unknown" } = input;
|
|
55
|
+
if (kind !== "unknown") {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
console.error(error);
|
|
59
|
+
return body(messageOf(error) ?? fallbackMessage(input), tagOf(error));
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* A server `handleError` hook (`src/hooks.server.ts`) that keeps the `_tag` of errors thrown by
|
|
63
|
+
* your code but not their message, which could expose details of the server to users: the
|
|
64
|
+
* message stays SvelteKit's `"Internal Error"`. It logs errors with `console.error`.
|
|
65
|
+
* Server errors reach a boundary's `failed` snippet when it renders on the server. Made for
|
|
66
|
+
* SvelteKit 3: SvelteKit 2 doesn't say which errors are its own, so there every error is logged.
|
|
67
|
+
*
|
|
68
|
+
* **Example** (Using it as the server hook)
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* // src/hooks.server.ts
|
|
72
|
+
* export { handleServerError as handleError } from "effect-atom-svelte/sveltekit";
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @stability unstable
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
* @category hooks
|
|
78
|
+
*/
|
|
79
|
+
export const handleServerError = (input) => {
|
|
80
|
+
const { error, issues, kind = "unknown" } = input;
|
|
81
|
+
if (kind === "validation") {
|
|
82
|
+
console.error("Remote function schema validation failed:", issues);
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
if (kind !== "unknown") {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
console.error(error);
|
|
89
|
+
return body(fallbackMessage(input), tagOf(error));
|
|
90
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte 5 bindings for Effect Atom: the hooks, components and registry an app uses, scoped atoms,
|
|
3
|
+
* and Effect Atom's own modules.
|
|
4
|
+
*
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* The hooks that read, write and await atoms from components.
|
|
9
|
+
*
|
|
10
|
+
* @stability unstable
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
* @category hooks
|
|
13
|
+
*/
|
|
14
|
+
export * from "./Hooks.svelte.ts";
|
|
15
|
+
/**
|
|
16
|
+
* The registry that hooks read from, and `provideRegistry` to give a component tree its own.
|
|
17
|
+
*
|
|
18
|
+
* @stability unstable
|
|
19
|
+
* @since 0.1.0
|
|
20
|
+
* @category registry
|
|
21
|
+
*/
|
|
22
|
+
export * from "./RegistryContext.ts";
|
|
23
|
+
/**
|
|
24
|
+
* @stability unstable
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
* @category components
|
|
27
|
+
*/
|
|
28
|
+
export { default as HydrationBoundary } from "./HydrationBoundary.svelte";
|
|
29
|
+
/**
|
|
30
|
+
* @stability unstable
|
|
31
|
+
* @since 0.1.0
|
|
32
|
+
* @category components
|
|
33
|
+
*/
|
|
34
|
+
export { default as RegistryProvider } from "./RegistryProvider.svelte";
|
|
35
|
+
/**
|
|
36
|
+
* Atoms created once per component subtree and read from context below it.
|
|
37
|
+
*
|
|
38
|
+
* @stability unstable
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
* @category scoped atoms
|
|
41
|
+
*/
|
|
42
|
+
export * as ScopedAtom from "./ScopedAtom.ts";
|
|
43
|
+
/**
|
|
44
|
+
* Effect Atom's modules, so an app can import everything from one place.
|
|
45
|
+
*
|
|
46
|
+
* @stability unstable
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
* @category re-exports
|
|
49
|
+
*/
|
|
50
|
+
export { AsyncResult, Atom, AtomHttpApi, AtomRef, AtomRegistry, AtomRpc, Hydration, } from "effect/reactivity";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte 5 bindings for Effect Atom: the hooks, components and registry an app uses, scoped atoms,
|
|
3
|
+
* and Effect Atom's own modules.
|
|
4
|
+
*
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* The hooks that read, write and await atoms from components.
|
|
9
|
+
*
|
|
10
|
+
* @stability unstable
|
|
11
|
+
* @since 0.1.0
|
|
12
|
+
* @category hooks
|
|
13
|
+
*/
|
|
14
|
+
export * from "./Hooks.svelte.js";
|
|
15
|
+
/**
|
|
16
|
+
* The registry that hooks read from, and `provideRegistry` to give a component tree its own.
|
|
17
|
+
*
|
|
18
|
+
* @stability unstable
|
|
19
|
+
* @since 0.1.0
|
|
20
|
+
* @category registry
|
|
21
|
+
*/
|
|
22
|
+
export * from "./RegistryContext.js";
|
|
23
|
+
/**
|
|
24
|
+
* @stability unstable
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
* @category components
|
|
27
|
+
*/
|
|
28
|
+
export { default as HydrationBoundary } from "./HydrationBoundary.svelte";
|
|
29
|
+
/**
|
|
30
|
+
* @stability unstable
|
|
31
|
+
* @since 0.1.0
|
|
32
|
+
* @category components
|
|
33
|
+
*/
|
|
34
|
+
export { default as RegistryProvider } from "./RegistryProvider.svelte";
|
|
35
|
+
/**
|
|
36
|
+
* Atoms created once per component subtree and read from context below it.
|
|
37
|
+
*
|
|
38
|
+
* @stability unstable
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
* @category scoped atoms
|
|
41
|
+
*/
|
|
42
|
+
export * as ScopedAtom from "./ScopedAtom.js";
|
|
43
|
+
/**
|
|
44
|
+
* Effect Atom's modules, so an app can import everything from one place.
|
|
45
|
+
*
|
|
46
|
+
* @stability unstable
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
* @category re-exports
|
|
49
|
+
*/
|
|
50
|
+
export { AsyncResult, Atom, AtomHttpApi, AtomRef, AtomRegistry, AtomRpc, Hydration, } from "effect/reactivity";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AsyncResult } from "effect/reactivity";
|
|
2
|
+
/** Sets the default for this component's children. Call during component init. */
|
|
3
|
+
export declare const setRevalidateOnHydrate: (value: boolean) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a hook runs its atom again once the page has hydrated: its own option, else the nearest
|
|
6
|
+
* provider's, else no (JND-19). Call during component init.
|
|
7
|
+
*/
|
|
8
|
+
export declare const revalidatesOnHydrate: (option: boolean | undefined) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Encodes the server's result for the page, or returns the no-seed marker for a result that must
|
|
11
|
+
* not or cannot travel:
|
|
12
|
+
*
|
|
13
|
+
* - A failure with a defect or an interruption. `Schema.Defect` keeps a defect's name, message and
|
|
14
|
+
* cause, which can hold details of the server (a connection string, say) that `handleServerError`
|
|
15
|
+
* keeps from the visitor. Typed errors are part of the atom's schema, so they are sent.
|
|
16
|
+
* - A result the schema can't encode, as Effect's `Hydration.dehydrate` skips it, rather than
|
|
17
|
+
* failing the whole render. Development builds warn.
|
|
18
|
+
*/
|
|
19
|
+
export declare const encodeSeed: (key: string, encode: (value: AsyncResult.AsyncResult<unknown, unknown>) => unknown, result: AsyncResult.AsyncResult<unknown, unknown>) => unknown;
|
|
20
|
+
/** The server's result, decoded, or undefined when the server sent none. */
|
|
21
|
+
export declare const decodeSeed: (value: unknown, decode: (value: unknown) => unknown) => unknown;
|
|
22
|
+
/**
|
|
23
|
+
* Whether a seed is still waiting, as a stream's result is between its values. The browser runs
|
|
24
|
+
* the atom again from it, as the server's run ended with the render.
|
|
25
|
+
*/
|
|
26
|
+
export declare const isWaiting: (value: unknown) => boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Hydration helpers shared by the hooks: the registry provider's default for revalidateOnHydrate,
|
|
2
|
+
// and how a seed is encoded on the server and read in the browser. Internal: not exported from the
|
|
3
|
+
// package.
|
|
4
|
+
import { Cause, Schema } from "effect";
|
|
5
|
+
import { AsyncResult } from "effect/reactivity";
|
|
6
|
+
import { DEV } from "esm-env";
|
|
7
|
+
import { createContext } from "svelte";
|
|
8
|
+
const [getDefault, setDefault, hasDefault] = createContext();
|
|
9
|
+
/** Sets the default for this component's children. Call during component init. */
|
|
10
|
+
export const setRevalidateOnHydrate = (value) => {
|
|
11
|
+
setDefault(value);
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Whether a hook runs its atom again once the page has hydrated: its own option, else the nearest
|
|
15
|
+
* provider's, else no (JND-19). Call during component init.
|
|
16
|
+
*/
|
|
17
|
+
export const revalidatesOnHydrate = (option) => option ?? (hasDefault() ? getDefault() : false);
|
|
18
|
+
/**
|
|
19
|
+
* What the server sends for a result it doesn't pass on, so the browser computes the atom itself.
|
|
20
|
+
* An encoded `AsyncResult` is an object, never `null`.
|
|
21
|
+
*/
|
|
22
|
+
const noSeed = null;
|
|
23
|
+
/**
|
|
24
|
+
* Encodes the server's result for the page, or returns the no-seed marker for a result that must
|
|
25
|
+
* not or cannot travel:
|
|
26
|
+
*
|
|
27
|
+
* - A failure with a defect or an interruption. `Schema.Defect` keeps a defect's name, message and
|
|
28
|
+
* cause, which can hold details of the server (a connection string, say) that `handleServerError`
|
|
29
|
+
* keeps from the visitor. Typed errors are part of the atom's schema, so they are sent.
|
|
30
|
+
* - A result the schema can't encode, as Effect's `Hydration.dehydrate` skips it, rather than
|
|
31
|
+
* failing the whole render. Development builds warn.
|
|
32
|
+
*/
|
|
33
|
+
export const encodeSeed = (key, encode, result) => {
|
|
34
|
+
if (AsyncResult.isFailure(result) &&
|
|
35
|
+
(Cause.hasDies(result.cause) || Cause.hasInterrupts(result.cause))) {
|
|
36
|
+
return noSeed;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return encode(result);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (!Schema.isSchemaError(error)) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
if (DEV) {
|
|
46
|
+
console.warn(`effect-atom-svelte: the result of the serializable atom "${key}" doesn't encode with its schema, so it isn't sent to the browser, which computes the atom again.`, error);
|
|
47
|
+
}
|
|
48
|
+
return noSeed;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/** The server's result, decoded, or undefined when the server sent none. */
|
|
52
|
+
export const decodeSeed = (value, decode) => (value === noSeed ? undefined : decode(value));
|
|
53
|
+
/**
|
|
54
|
+
* Whether a seed is still waiting, as a stream's result is between its values. The browser runs
|
|
55
|
+
* the atom again from it, as the server's run ended with the render.
|
|
56
|
+
*/
|
|
57
|
+
export const isWaiting = (value) => AsyncResult.isAsyncResult(value) && value.waiting;
|
package/package.json
CHANGED
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-atom-svelte",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Community-built Svelte 5 bindings for Effect Atom (not affiliated with the Effect team)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"effect",
|
|
7
|
+
"effect-atom",
|
|
8
|
+
"reactivity",
|
|
9
|
+
"state",
|
|
10
|
+
"svelte",
|
|
11
|
+
"sveltekit"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://atom.jarrednorris.dev",
|
|
14
|
+
"bugs": "https://github.com/jarrednorrisdev/effect-atom-svelte/issues",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Jarred Norris",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/jarrednorrisdev/effect-atom-svelte.git",
|
|
20
|
+
"directory": "packages/effect-atom-svelte"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"!dist/**/*.test.*"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"svelte": "./dist/index.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"svelte": "./dist/index.js",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./sveltekit": {
|
|
37
|
+
"types": "./dist/SvelteKit.d.ts",
|
|
38
|
+
"default": "./dist/SvelteKit.js"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "svelte-package -i src -o dist",
|
|
44
|
+
"check": "svelte-check --tsconfig ./tsconfig.json && bun scripts/check-docs.ts",
|
|
45
|
+
"test": "vitest run"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"esm-env": "^1.2.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@demo/domain": "workspace:*",
|
|
52
|
+
"@sveltejs/package": "^3.0.0",
|
|
53
|
+
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
|
54
|
+
"@vitest/browser": "^5.0.3",
|
|
55
|
+
"@vitest/browser-playwright": "^5.0.3",
|
|
56
|
+
"effect": "4.0.0",
|
|
57
|
+
"playwright": "^1.57.0",
|
|
58
|
+
"svelte": "^5.57.1",
|
|
59
|
+
"svelte-check": "^4.6.0",
|
|
60
|
+
"vite": "^8.3.0",
|
|
61
|
+
"vitest": "^5.0.3",
|
|
62
|
+
"vitest-browser-svelte": "^3.1.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@sveltejs/kit": "^2.0.0 || ^3.0.0",
|
|
66
|
+
"effect": "~4.0.0",
|
|
67
|
+
"svelte": "^5.57.0"
|
|
68
|
+
},
|
|
69
|
+
"peerDependenciesMeta": {
|
|
70
|
+
"@sveltejs/kit": {
|
|
71
|
+
"optional": true
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|