jazz-svelte 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
package/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright 2024, Garden Computing, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # Jazz Svelte
2
+
3
+ > Modern Svelte bindings for Jazz 🎵
4
+
5
+ ## ⚠️ Experimental Status
6
+
7
+ <div align="center">
8
+
9
+ ```
10
+ 🐉 Here be dragons! 🐉
11
+ ```
12
+
13
+ </div>
14
+
15
+ ### 🚧 Development Notice
16
+
17
+ Greetings, brave explorer! 🌟 You've caught us in our early stages - this package is currently experimental and under active development.
18
+
19
+ ---
20
+
21
+ > **Note**: You've been warned, but we're glad you're here! Let's build something amazing together. 🚀
@@ -0,0 +1,69 @@
1
+ <script lang="ts" generics="Acc extends Account">
2
+ import { createJazzBrowserContext } from "jazz-browser";
3
+ import type { AccountClass, AuthMethod } from "jazz-tools";
4
+ import { Account } from "jazz-tools";
5
+ import { type Snippet, setContext, untrack } from "svelte";
6
+ import { JAZZ_CTX, type JazzContext } from "./jazz.svelte";
7
+
8
+ type Props = {
9
+ children: Snippet;
10
+ auth: AuthMethod | "guest";
11
+ peer: `wss://${string}` | `ws://${string}`;
12
+ storage?: "indexedDB" | "singleTabOPFS";
13
+ schema?: AccountClass<Acc>;
14
+ };
15
+
16
+ let {
17
+ children,
18
+ auth,
19
+ peer,
20
+ storage,
21
+ schema = Account as unknown as AccountClass<Acc>,
22
+ }: Props = $props();
23
+
24
+ const ctx = $state<JazzContext<Acc>>({ current: undefined });
25
+ setContext<JazzContext<Acc>>(JAZZ_CTX, ctx);
26
+ let sessionCount = $state(0);
27
+
28
+ $effect(() => {
29
+ schema;
30
+ auth;
31
+ peer;
32
+ storage;
33
+ sessionCount;
34
+ return untrack(() => {
35
+ if (!auth || !peer) return;
36
+
37
+ const promiseWithDoneCallback = createJazzBrowserContext<Acc>(
38
+ auth === "guest"
39
+ ? {
40
+ peer,
41
+ storage,
42
+ }
43
+ : {
44
+ AccountSchema: schema,
45
+ auth,
46
+ peer,
47
+ storage,
48
+ },
49
+ ).then((context) => {
50
+ ctx.current = {
51
+ ...context,
52
+ logOut: () => {
53
+ context.logOut();
54
+ ctx.current = undefined;
55
+ sessionCount = sessionCount + 1;
56
+ },
57
+ };
58
+ return context.done;
59
+ });
60
+ return () => {
61
+ void promiseWithDoneCallback.then((done) => done());
62
+ };
63
+ });
64
+ });
65
+ </script>
66
+
67
+ {#if ctx.current}
68
+ {@render children?.()}
69
+ {/if}
@@ -0,0 +1,26 @@
1
+ import type { AccountClass, AuthMethod } from "jazz-tools";
2
+ import { Account } from "jazz-tools";
3
+ import { type Snippet } from "svelte";
4
+ declare class __sveltets_Render<Acc extends Account> {
5
+ props(): {
6
+ children: Snippet;
7
+ auth: AuthMethod | "guest";
8
+ peer: `wss://${string}` | `ws://${string}`;
9
+ storage?: "indexedDB" | "singleTabOPFS";
10
+ schema?: AccountClass<Acc> | undefined;
11
+ };
12
+ events(): {};
13
+ slots(): {};
14
+ bindings(): "";
15
+ exports(): {};
16
+ }
17
+ interface $$IsomorphicComponent {
18
+ new <Acc extends Account>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Acc>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Acc>['props']>, ReturnType<__sveltets_Render<Acc>['events']>, ReturnType<__sveltets_Render<Acc>['slots']>> & {
19
+ $$bindings?: ReturnType<__sveltets_Render<Acc>['bindings']>;
20
+ } & ReturnType<__sveltets_Render<Acc>['exports']>;
21
+ <Acc extends Account>(internal: unknown, props: ReturnType<__sveltets_Render<Acc>['props']> & {}): ReturnType<__sveltets_Render<Acc>['exports']>;
22
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
23
+ }
24
+ declare const JazzProvider: $$IsomorphicComponent;
25
+ type JazzProvider<Acc extends Account> = InstanceType<typeof JazzProvider<Acc>>;
26
+ export default JazzProvider;
@@ -0,0 +1,24 @@
1
+ import { BrowserPasskeyAuth } from "jazz-browser";
2
+ export type PasskeyAuthState = ({
3
+ state: "uninitialized";
4
+ } | {
5
+ state: "loading";
6
+ } | {
7
+ state: "ready";
8
+ logIn: () => void;
9
+ signUp: (username: string) => void;
10
+ } | {
11
+ state: "signedIn";
12
+ logOut: () => void;
13
+ }) & {
14
+ errors: string[];
15
+ };
16
+ export type PasskeyAuth = {
17
+ current?: BrowserPasskeyAuth;
18
+ state: PasskeyAuthState;
19
+ };
20
+ /** @category Auth Providers */
21
+ export declare function usePasskeyAuth({ appName, appHostname, }: {
22
+ appName: string;
23
+ appHostname?: string;
24
+ }): PasskeyAuth;
@@ -0,0 +1,55 @@
1
+ import { BrowserPasskeyAuth } from "jazz-browser";
2
+ import { onMount } from "svelte";
3
+ /** @category Auth Providers */
4
+ export function usePasskeyAuth({ appName, appHostname, }) {
5
+ let instance = $state();
6
+ let state = $state({
7
+ state: "loading",
8
+ errors: [],
9
+ });
10
+ // Function to create a new auth instance
11
+ function createAuthInstance() {
12
+ instance = new BrowserPasskeyAuth({
13
+ onReady(next) {
14
+ state = {
15
+ state: "ready",
16
+ logIn: next.logIn,
17
+ signUp: next.signUp,
18
+ errors: [],
19
+ };
20
+ },
21
+ onSignedIn(next) {
22
+ state = {
23
+ state: "signedIn",
24
+ logOut: () => {
25
+ // First set state to loading
26
+ state = { state: "loading", errors: [] };
27
+ // Then trigger logout
28
+ next.logOut();
29
+ // Create new instance to trigger onReady
30
+ createAuthInstance();
31
+ },
32
+ errors: [],
33
+ };
34
+ },
35
+ onError(error) {
36
+ state = {
37
+ ...state,
38
+ errors: [...state.errors, error.toString()],
39
+ };
40
+ },
41
+ }, appName, appHostname);
42
+ }
43
+ // Initialize the auth instance on mount
44
+ onMount(() => {
45
+ createAuthInstance();
46
+ });
47
+ return {
48
+ get current() {
49
+ return instance;
50
+ },
51
+ get state() {
52
+ return state;
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ import { type PasskeyAuthState } from 'jazz-svelte';
3
+
4
+ let { state: authState }: { state: PasskeyAuthState } = $props();
5
+
6
+ let name = $state('');
7
+
8
+ function signUp(e: Event) {
9
+ e.preventDefault();
10
+ if (!name.trim() || authState.state !== 'ready') return;
11
+ authState.signUp(name);
12
+ }
13
+
14
+ function logIn(e: Event) {
15
+ e.preventDefault();
16
+ e.stopPropagation();
17
+ if (authState.state !== 'ready') return;
18
+ authState.logIn();
19
+ }
20
+ </script>
21
+
22
+ <div style="max-width: 18rem; display: flex; flex-direction: column; gap: 2rem;">
23
+ {#if authState.state === 'loading'}
24
+ <div>Loading...</div>
25
+ {:else if authState.state === 'ready'}
26
+ {#if authState.errors?.length > 0}
27
+ <div style="color: red;">
28
+ {#each authState.errors as error}
29
+ <div>{error}</div>
30
+ {/each}
31
+ </div>
32
+ {/if}
33
+ <form onsubmit={signUp}>
34
+ <input type="text" placeholder="Display name" bind:value={name} autocomplete="name" />
35
+ <input type="submit" value="Sign up" />
36
+ </form>
37
+ <button onclick={logIn}> Log in with existing account </button>
38
+ {/if}
39
+ </div>
40
+
41
+ <style>
42
+ form {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 0.5rem;
46
+ }
47
+
48
+ button,
49
+ input[type='submit'] {
50
+ background: #000;
51
+ color: #fff;
52
+ padding: 6px 12px;
53
+ border: none;
54
+ border-radius: 6px;
55
+ min-height: 38px;
56
+ cursor: pointer;
57
+ }
58
+
59
+ input[type='text'] {
60
+ border: 2px solid #000;
61
+ padding: 6px 12px;
62
+ border-radius: 6px;
63
+ min-height: 24px;
64
+ }
65
+ </style>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ state: PasskeyAuthState;
3
+ };
4
+ import { type PasskeyAuthState } from 'jazz-svelte';
5
+ declare const PasskeyAuthBasicUi: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type PasskeyAuthBasicUi = ReturnType<typeof PasskeyAuthBasicUi>;
7
+ export default PasskeyAuthBasicUi;
@@ -0,0 +1,2 @@
1
+ export * from "./PasskeyAuth.svelte";
2
+ export { default as PasskeyAuthBasicUI } from "./PasskeyAuthBasicUI.svelte";
@@ -0,0 +1,2 @@
1
+ export * from "./PasskeyAuth.svelte";
2
+ export { default as PasskeyAuthBasicUI } from "./PasskeyAuthBasicUI.svelte";
@@ -0,0 +1,5 @@
1
+ import JazzProvider from "./JazzProvider.svelte";
2
+ export { createJazzApp } from "./jazz.svelte.js";
3
+ export { JazzProvider };
4
+ export { createInviteLink, parseInviteLink } from "jazz-browser";
5
+ export * from "./auth/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import JazzProvider from "./JazzProvider.svelte";
2
+ export { createJazzApp } from "./jazz.svelte.js";
3
+ export { JazzProvider };
4
+ export { createInviteLink, parseInviteLink } from "jazz-browser";
5
+ export * from "./auth/index.js";
@@ -0,0 +1,50 @@
1
+ import { type BrowserContext, type BrowserGuestContext } from "jazz-browser";
2
+ import type { AnonymousJazzAgent, CoValue, CoValueClass, DeeplyLoaded, DepthsIn, ID } from "jazz-tools";
3
+ import { Account } from "jazz-tools";
4
+ /**
5
+ * The key for the Jazz context.
6
+ */
7
+ export declare const JAZZ_CTX: {};
8
+ /**
9
+ * The Jazz context.
10
+ */
11
+ export type JazzContext<Acc extends Account> = {
12
+ current?: BrowserContext<Acc> | BrowserGuestContext;
13
+ };
14
+ /**
15
+ * Get the current Jazz context.
16
+ * @returns The current Jazz context.
17
+ */
18
+ export declare function getJazzContext<Acc extends Account>(): JazzContext<Acc>;
19
+ /**
20
+ * Create a Jazz app.
21
+ * @returns The Jazz app.
22
+ */
23
+ export declare function createJazzApp<Acc extends Account>(): {
24
+ useAccount: {
25
+ (): {
26
+ me: Acc | undefined;
27
+ logOut: () => void;
28
+ };
29
+ <D extends DepthsIn<Acc>>(depth: D): {
30
+ me: DeeplyLoaded<Acc, D> | undefined;
31
+ logOut: () => void;
32
+ };
33
+ };
34
+ useAccountOrGuest: {
35
+ (): {
36
+ me: Acc | AnonymousJazzAgent;
37
+ };
38
+ <D extends DepthsIn<Acc>>(depth: D): {
39
+ me: DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent;
40
+ };
41
+ };
42
+ useCoState: <V extends CoValue, D extends DepthsIn<V> = []>(Schema: CoValueClass<V>, id: ID<V> | undefined, depth?: D) => {
43
+ current?: DeeplyLoaded<V, D>;
44
+ };
45
+ useAcceptInvite: <V extends CoValue>({ invitedObjectSchema, onAccept, forValueHint, }: {
46
+ invitedObjectSchema: CoValueClass<V>;
47
+ onAccept: (projectID: ID<V>) => void;
48
+ forValueHint?: string;
49
+ }) => void;
50
+ };
@@ -0,0 +1,154 @@
1
+ import { consumeInviteLinkFromWindowLocation, } from "jazz-browser";
2
+ import { Account, createCoValueObservable } from "jazz-tools";
3
+ import { getContext, untrack } from "svelte";
4
+ /**
5
+ * The key for the Jazz context.
6
+ */
7
+ export const JAZZ_CTX = {};
8
+ /**
9
+ * Get the current Jazz context.
10
+ * @returns The current Jazz context.
11
+ */
12
+ export function getJazzContext() {
13
+ return getContext(JAZZ_CTX);
14
+ }
15
+ /**
16
+ * Create a Jazz app.
17
+ * @returns The Jazz app.
18
+ */
19
+ export function createJazzApp() {
20
+ /**
21
+ * Use the current account with a optional depth.
22
+ * @param depth - The depth.
23
+ * @returns The current account.
24
+ */
25
+ function useAccount(depth) {
26
+ const ctx = getJazzContext();
27
+ if (!ctx?.current) {
28
+ throw new Error("useAccount must be used within a JazzProvider");
29
+ }
30
+ if (!("me" in ctx.current)) {
31
+ throw new Error("useAccount can't be used in a JazzProvider with auth === 'guest' - consider using useAccountOrGuest()");
32
+ }
33
+ const me = useCoState(ctx.current.me.constructor, ctx.current.me.id, depth);
34
+ return {
35
+ get me() {
36
+ if (!ctx.current || !("me" in ctx.current))
37
+ return;
38
+ return depth === undefined ? me.current || ctx.current.me : me.current;
39
+ },
40
+ logOut() {
41
+ return ctx.current?.logOut();
42
+ },
43
+ };
44
+ }
45
+ /**
46
+ * Use the current account or guest with a optional depth.
47
+ * @param depth - The depth.
48
+ * @returns The current account or guest.
49
+ */
50
+ function useAccountOrGuest(depth) {
51
+ const ctx = getJazzContext();
52
+ if (!ctx?.current) {
53
+ throw new Error("useAccountOrGuest must be used within a JazzProvider");
54
+ }
55
+ const contextMe = "me" in ctx.current ? ctx.current.me : undefined;
56
+ const me = useCoState(contextMe?.constructor, contextMe?.id, depth);
57
+ // If the context has a me, return the account.
58
+ if ("me" in ctx.current) {
59
+ return {
60
+ get me() {
61
+ return depth === undefined
62
+ ? me.current || ctx.current?.me
63
+ : me.current;
64
+ },
65
+ };
66
+ }
67
+ // If the context has no me, return the guest.
68
+ else {
69
+ return {
70
+ get me() {
71
+ return ctx.current?.guest;
72
+ },
73
+ };
74
+ }
75
+ }
76
+ /**
77
+ * Use a CoValue with a optional depth.
78
+ * @param Schema - The CoValue schema.
79
+ * @param id - The CoValue id.
80
+ * @param depth - The depth.
81
+ * @returns The CoValue.
82
+ */
83
+ function useCoState(Schema, id, depth = []) {
84
+ const ctx = getJazzContext();
85
+ // Create state and a stable observable
86
+ let state = $state.raw(undefined);
87
+ const observable = $state.raw(createCoValueObservable());
88
+ // Effect to handle subscription
89
+ $effect(() => {
90
+ // Reset state when dependencies change
91
+ state = undefined;
92
+ // Get latest values
93
+ const currentCtx = ctx.current;
94
+ // Return early if no context or id, effectively cleaning up any previous subscription
95
+ if (!currentCtx || !id)
96
+ return;
97
+ // Setup subscription with current values
98
+ return observable.subscribe(Schema, id, "me" in currentCtx ? currentCtx.me : currentCtx.guest, depth, () => {
99
+ // Get current value from our stable observable
100
+ state = observable.getCurrentValue();
101
+ });
102
+ });
103
+ return {
104
+ get current() {
105
+ return state;
106
+ },
107
+ };
108
+ }
109
+ /**
110
+ * Use the accept invite hook.
111
+ * @param invitedObjectSchema - The invited object schema.
112
+ * @param onAccept - Function to call when the invite is accepted.
113
+ * @param forValueHint - Hint for the value.
114
+ * @returns The accept invite hook.
115
+ */
116
+ function useAcceptInvite({ invitedObjectSchema, onAccept, forValueHint, }) {
117
+ const ctx = getJazzContext();
118
+ const _onAccept = onAccept;
119
+ if (!ctx.current) {
120
+ throw new Error("useAcceptInvite must be used within a JazzProvider");
121
+ }
122
+ if (!("me" in ctx.current)) {
123
+ throw new Error("useAcceptInvite can't be used in a JazzProvider with auth === 'guest'.");
124
+ }
125
+ // Subscribe to the onAccept function.
126
+ $effect(() => {
127
+ _onAccept;
128
+ // Subscribe to the onAccept function.
129
+ untrack(() => {
130
+ // If there is no context, return.
131
+ if (!ctx.current)
132
+ return;
133
+ // Consume the invite link from the window location.
134
+ const result = consumeInviteLinkFromWindowLocation({
135
+ as: ctx.current.me,
136
+ invitedObjectSchema,
137
+ forValueHint,
138
+ });
139
+ // If the result is valid, call the onAccept function.
140
+ result
141
+ .then((result) => result && _onAccept(result?.valueID))
142
+ .catch((e) => {
143
+ console.error("Failed to accept invite", e);
144
+ });
145
+ });
146
+ });
147
+ }
148
+ return {
149
+ useAccount,
150
+ useAccountOrGuest,
151
+ useCoState,
152
+ useAcceptInvite,
153
+ };
154
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "jazz-svelte",
3
+ "version": "0.0.1",
4
+ "files": [
5
+ "dist",
6
+ "!dist/**/*.test.*",
7
+ "!dist/**/*.spec.*"
8
+ ],
9
+ "sideEffects": [
10
+ "**/*.css"
11
+ ],
12
+ "svelte": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "type": "module",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "svelte": "./dist/index.js"
19
+ }
20
+ },
21
+ "peerDependencies": {
22
+ "svelte": "^5.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@sveltejs/adapter-auto": "^3.0.0",
26
+ "@sveltejs/kit": "^2.0.0",
27
+ "@sveltejs/package": "^2.0.0",
28
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
29
+ "@types/eslint": "^9.6.0",
30
+ "eslint": "^9.7.0",
31
+ "eslint-config-prettier": "^9.1.0",
32
+ "eslint-plugin-svelte": "^2.36.0",
33
+ "globals": "^15.0.0",
34
+ "prettier": "^3.3.2",
35
+ "prettier-plugin-svelte": "^3.2.6",
36
+ "publint": "^0.2.0",
37
+ "svelte": "^5.0.0",
38
+ "svelte-check": "^4.0.0",
39
+ "typescript": "^5.0.0",
40
+ "typescript-eslint": "^8.0.0",
41
+ "vite": "^5.0.11"
42
+ },
43
+ "dependencies": {
44
+ "jazz-browser": "0.8.34",
45
+ "jazz-tools": "0.8.34"
46
+ },
47
+ "scripts": {
48
+ "dev": "vite dev",
49
+ "build": "vite build && npm run package",
50
+ "preview": "vite preview",
51
+ "package": "svelte-kit sync && svelte-package && publint",
52
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
53
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
54
+ "format": "prettier --write .",
55
+ "lint": "prettier --check . && eslint ."
56
+ }
57
+ }