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.
@@ -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
+ };
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }