feat: simplify app setup and make hooks available as top-level imports
This commit is contained in:
7
.changeset/manual-svelte-change.md
Normal file
7
.changeset/manual-svelte-change.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"jazz-svelte": minor
|
||||
---
|
||||
|
||||
Change the way the JazzProvider is created and make the API available as top-level imports.
|
||||
|
||||
This is a breaking change.
|
||||
7
.changeset/manual-vue-changelog.md
Normal file
7
.changeset/manual-vue-changelog.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"jazz-vue": minor
|
||||
---
|
||||
|
||||
Change the way the JazzProvider is created and make the composables available as top-level imports.
|
||||
|
||||
This is a breaking change.
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
"jazz-react": minor
|
||||
"jazz-svelte": minor
|
||||
---
|
||||
|
||||
Change the way the JazzProvider is created and make the hooks available as top-level imports.
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAccount } from "jazz-vue";
|
||||
import AppContainer from "./components/AppContainer.vue";
|
||||
import TopBar from "./components/TopBar.vue";
|
||||
import { useAccount } from "./main";
|
||||
|
||||
const { me, logOut } = useAccount();
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { DemoAuthBasicUI, createJazzVueApp, useDemoAuth } from "jazz-vue";
|
||||
import { DemoAuthBasicUI, JazzProvider, useDemoAuth } from "jazz-vue";
|
||||
import { createApp, defineComponent, h } from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./index.css";
|
||||
import router from "./router";
|
||||
|
||||
const Jazz = createJazzVueApp();
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
const { JazzProvider } = Jazz;
|
||||
|
||||
const RootComponent = defineComponent({
|
||||
name: "RootComponent",
|
||||
setup() {
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
|
||||
<script lang="ts">
|
||||
import type { ID } from "jazz-tools";
|
||||
import { useCoState } from "jazz-vue";
|
||||
import { type PropType, computed, defineComponent, ref } from "vue";
|
||||
import ChatBody from "../components/ChatBody.vue";
|
||||
import ChatBubble from "../components/ChatBubble.vue";
|
||||
import ChatInput from "../components/ChatInput.vue";
|
||||
import EmptyChatMessage from "../components/EmptyChatMessage.vue";
|
||||
import { useCoState } from "../main";
|
||||
import { Chat, Message } from "../schema";
|
||||
|
||||
export default defineComponent({
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Group } from "jazz-tools";
|
||||
import { useAccount } from "jazz-vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useAccount } from "../main";
|
||||
import { Chat } from "../schema";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -63,7 +63,7 @@ main {
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAccount } from "./main";
|
||||
import { useAccount } from "jazz-vue";
|
||||
|
||||
const { me, logOut } = useAccount();
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { DemoAuthBasicUI, createJazzVueApp, useDemoAuth } from "jazz-vue";
|
||||
import { DemoAuthBasicUI, JazzProvider, useDemoAuth } from "jazz-vue";
|
||||
import { createApp, defineComponent, h } from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./assets/main.css";
|
||||
import router from "./router";
|
||||
import { ToDoAccount } from "./schema";
|
||||
|
||||
const Jazz = createJazzVueApp<ToDoAccount>({ AccountSchema: ToDoAccount });
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
const { JazzProvider } = Jazz;
|
||||
declare module "jazz-vue" {
|
||||
interface Register {
|
||||
Account: ToDoAccount;
|
||||
}
|
||||
}
|
||||
|
||||
const RootComponent = defineComponent({
|
||||
name: "RootComponent",
|
||||
@@ -18,6 +20,7 @@ const RootComponent = defineComponent({
|
||||
h(
|
||||
JazzProvider,
|
||||
{
|
||||
AccountSchema: ToDoAccount,
|
||||
auth: authMethod.value,
|
||||
peer: "wss://cloud.jazz.tools/?key=vue-todo-example-jazz@garden.co",
|
||||
},
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Group, type ID } from "jazz-tools";
|
||||
import { useAccount, useCoState } from "jazz-vue";
|
||||
import { ref, toRaw, watch } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useAccount, useCoState } from "../main";
|
||||
import { Folder, FolderList, ToDoItem, ToDoList } from "../schema";
|
||||
|
||||
const { me } = useAccount();
|
||||
|
||||
@@ -82,48 +82,3 @@ useAcceptInvite({
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
`useAcceptInvite` is imported from `jazz-react`.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
import { useAcceptInvite } from "jazz-react";
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
`useAcceptInvite` is exported from your Jazz app.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const Jazz = createJazzReactNativeApp();
|
||||
export const { useAcceptInvite } = Jazz;
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="vue">
|
||||
`useAcceptInvite` is exported from your Jazz app.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const Jazz = createJazzVueApp();
|
||||
export const { useAcceptInvite } = Jazz;
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="svelte">
|
||||
`useAcceptInvite` is exported from your Jazz app.
|
||||
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const Jazz = createJazzApp();
|
||||
export const { useAcceptInvite } = Jazz;
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
...more docs coming soon
|
||||
|
||||
@@ -108,15 +108,17 @@ Update the `src/main.ts` file to integrate Jazz:
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
import "./assets/main.css";
|
||||
import { DemoAuthBasicUI, createJazzVueApp, useDemoAuth } from "jazz-vue";
|
||||
import { DemoAuthBasicUI, useDemoAuth, JazzProvider } from "jazz-vue";
|
||||
import { createApp, defineComponent, h } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { ToDoAccount } from "./schema";
|
||||
|
||||
const Jazz = createJazzVueApp<ToDoAccount>({ AccountSchema: ToDoAccount });
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
const { JazzProvider } = Jazz;
|
||||
declare module "jazz-vue" {
|
||||
interface Register {
|
||||
Account: ToDoAccount;
|
||||
}
|
||||
}
|
||||
|
||||
const RootComponent = defineComponent({
|
||||
name: "RootComponent",
|
||||
@@ -127,6 +129,7 @@ const RootComponent = defineComponent({
|
||||
h(
|
||||
JazzProvider,
|
||||
{
|
||||
AccountSchema: ToDoAccount,
|
||||
auth: authMethod.value,
|
||||
peer: "wss://cloud.jazz.tools/?key=vue-todo-example-jazz@garden.co",
|
||||
},
|
||||
@@ -198,7 +201,7 @@ Update the `App.vue` file to include logout functionality:
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAccount } from "./main";
|
||||
import { useAccount } from "jazz-vue";
|
||||
|
||||
const { me, logOut } = useAccount();
|
||||
</script>
|
||||
@@ -215,7 +218,7 @@ Subscribe to a CoValue inside `src/views/HomeView.vue`:
|
||||
import { Group, type ID } from "jazz-tools";
|
||||
import { ref, toRaw, watch } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useAccount, useCoState } from "../main";
|
||||
import { useAccount, useCoState } from "jazz-vue";
|
||||
import { Folder, FolderList, ToDoItem, ToDoList } from "../schema";
|
||||
|
||||
const { me } = useAccount();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pre-commit:
|
||||
commands:
|
||||
check:
|
||||
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
|
||||
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc,vue,svelte}"
|
||||
run: npx @biomejs/biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files}
|
||||
stage_fixed: true
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "jazz-monorepo",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": ["packages/*", "examples/*"],
|
||||
"workspaces": ["packages/*", "examples/*", "starters/*"],
|
||||
"packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c",
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
|
||||
@@ -5,14 +5,24 @@
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./testing": {
|
||||
"types": "./dist/testing.d.ts",
|
||||
"default": "./dist/testing.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@scure/bip39": "^1.3.0",
|
||||
"cojson": "workspace:*",
|
||||
"jazz-browser": "workspace:*",
|
||||
"jazz-tools": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"rollup-plugin-node-externals": "^8.0.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10",
|
||||
"vite-plugin-dts": "^4.2.4",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AgentSecret } from "cojson";
|
||||
import { BrowserDemoAuth } from "jazz-browser";
|
||||
import { Account, ID } from "jazz-tools";
|
||||
import { onUnmounted, reactive, ref } from "vue";
|
||||
import { logoutHandler } from "../createJazzVueApp.js";
|
||||
import { logoutHandler } from "../provider.js";
|
||||
|
||||
export type DemoAuthState = (
|
||||
| {
|
||||
|
||||
250
packages/jazz-vue/src/composables.ts
Normal file
250
packages/jazz-vue/src/composables.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import {
|
||||
BrowserContext,
|
||||
BrowserGuestContext,
|
||||
consumeInviteLinkFromWindowLocation,
|
||||
} from "jazz-browser";
|
||||
import {
|
||||
Account,
|
||||
AnonymousJazzAgent,
|
||||
CoValue,
|
||||
CoValueClass,
|
||||
DeeplyLoaded,
|
||||
DepthsIn,
|
||||
ID,
|
||||
subscribeToCoValue,
|
||||
} from "jazz-tools";
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
ComputedRef,
|
||||
MaybeRef,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
computed,
|
||||
inject,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
unref,
|
||||
watch,
|
||||
} from "vue";
|
||||
import { JazzContextSymbol, RegisteredAccount } from "./provider.js";
|
||||
|
||||
export const logoutHandler = ref<() => void>();
|
||||
|
||||
function useJazzContext() {
|
||||
const context =
|
||||
inject<Ref<BrowserContext<RegisteredAccount> | BrowserGuestContext>>(
|
||||
JazzContextSymbol,
|
||||
);
|
||||
if (!context) {
|
||||
throw new Error("useJazzContext must be used within a JazzProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function createUseAccountComposables<Acc extends Account>() {
|
||||
function useAccount(): {
|
||||
me: ComputedRef<Acc>;
|
||||
logOut: () => void;
|
||||
};
|
||||
function useAccount<D extends DepthsIn<Acc>>(
|
||||
depth: D,
|
||||
): {
|
||||
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined>;
|
||||
logOut: () => void;
|
||||
};
|
||||
function useAccount<D extends DepthsIn<Acc>>(
|
||||
depth?: D,
|
||||
): {
|
||||
me: ComputedRef<Acc | DeeplyLoaded<Acc, D> | undefined>;
|
||||
logOut: () => void;
|
||||
} {
|
||||
const context = useJazzContext();
|
||||
|
||||
if (!context.value) {
|
||||
throw new Error("useAccount must be used within a JazzProvider");
|
||||
}
|
||||
|
||||
if (!("me" in context.value)) {
|
||||
throw new Error(
|
||||
"useAccount can't be used in a JazzProvider with auth === 'guest' - consider using useAccountOrGuest()",
|
||||
);
|
||||
}
|
||||
|
||||
const contextMe = context.value.me as Acc;
|
||||
|
||||
const me = useCoState<Acc, D>(
|
||||
contextMe.constructor as CoValueClass<Acc>,
|
||||
contextMe.id,
|
||||
depth,
|
||||
);
|
||||
|
||||
return {
|
||||
me: computed(() => {
|
||||
const value =
|
||||
depth === undefined
|
||||
? me.value || toRaw((context.value as BrowserContext<Acc>).me)
|
||||
: me.value;
|
||||
|
||||
return value ? toRaw(value) : value;
|
||||
}),
|
||||
logOut: context.value.logOut,
|
||||
};
|
||||
}
|
||||
|
||||
function useAccountOrGuest(): {
|
||||
me: ComputedRef<Acc | AnonymousJazzAgent>;
|
||||
};
|
||||
function useAccountOrGuest<D extends DepthsIn<Acc>>(
|
||||
depth: D,
|
||||
): {
|
||||
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent>;
|
||||
};
|
||||
function useAccountOrGuest<D extends DepthsIn<Acc>>(
|
||||
depth?: D,
|
||||
): {
|
||||
me: ComputedRef<
|
||||
Acc | DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent
|
||||
>;
|
||||
} {
|
||||
const context = useJazzContext();
|
||||
|
||||
if (!context.value) {
|
||||
throw new Error("useAccountOrGuest must be used within a JazzProvider");
|
||||
}
|
||||
|
||||
const contextMe = computed(() =>
|
||||
"me" in context.value ? (context.value.me as Acc) : undefined,
|
||||
);
|
||||
|
||||
const me = useCoState<Acc, D>(
|
||||
contextMe.value?.constructor as CoValueClass<Acc>,
|
||||
contextMe.value?.id,
|
||||
depth,
|
||||
);
|
||||
|
||||
if ("me" in context.value) {
|
||||
return {
|
||||
me: computed(() =>
|
||||
depth === undefined
|
||||
? me.value || toRaw((context.value as BrowserContext<Acc>).me)
|
||||
: me.value,
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
me: computed(() => toRaw((context.value as BrowserGuestContext).guest)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
useAccount,
|
||||
useAccountOrGuest,
|
||||
};
|
||||
}
|
||||
|
||||
const { useAccount, useAccountOrGuest } =
|
||||
createUseAccountComposables<RegisteredAccount>();
|
||||
|
||||
export { useAccount, useAccountOrGuest };
|
||||
|
||||
export function useCoState<V extends CoValue, D>(
|
||||
Schema: CoValueClass<V>,
|
||||
id: MaybeRef<ID<V> | undefined>,
|
||||
depth: D & DepthsIn<V> = [] as D & DepthsIn<V>,
|
||||
): Ref<DeeplyLoaded<V, D> | undefined> {
|
||||
const state: ShallowRef<DeeplyLoaded<V, D> | undefined> =
|
||||
shallowRef(undefined);
|
||||
const context = useJazzContext();
|
||||
|
||||
if (!context.value) {
|
||||
throw new Error("useCoState must be used within a JazzProvider");
|
||||
}
|
||||
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
|
||||
watch(
|
||||
[() => unref(id), () => context, () => Schema, () => depth],
|
||||
() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
|
||||
const idValue = unref(id);
|
||||
if (!idValue) return;
|
||||
|
||||
unsubscribe = subscribeToCoValue(
|
||||
Schema,
|
||||
idValue,
|
||||
"me" in context.value
|
||||
? toRaw(context.value.me)
|
||||
: toRaw(context.value.guest),
|
||||
depth,
|
||||
(value) => {
|
||||
state.value = value;
|
||||
},
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
});
|
||||
|
||||
const computedState = computed(() => state.value);
|
||||
|
||||
return computedState;
|
||||
}
|
||||
|
||||
export function useAcceptInvite<V extends CoValue>({
|
||||
invitedObjectSchema,
|
||||
onAccept,
|
||||
forValueHint,
|
||||
}: {
|
||||
invitedObjectSchema: CoValueClass<V>;
|
||||
onAccept: (projectID: ID<V>) => void;
|
||||
forValueHint?: string;
|
||||
}): void {
|
||||
const context = useJazzContext();
|
||||
|
||||
if (!context.value) {
|
||||
throw new Error("useAcceptInvite must be used within a JazzProvider");
|
||||
}
|
||||
|
||||
if (!("me" in context.value)) {
|
||||
throw new Error(
|
||||
"useAcceptInvite can't be used in a JazzProvider with auth === 'guest'.",
|
||||
);
|
||||
}
|
||||
|
||||
const runInviteAcceptance = () => {
|
||||
const result = consumeInviteLinkFromWindowLocation({
|
||||
as: toRaw((context.value as BrowserContext<RegisteredAccount>).me),
|
||||
invitedObjectSchema,
|
||||
forValueHint,
|
||||
});
|
||||
|
||||
result
|
||||
.then((res) => res && onAccept(res.valueID))
|
||||
.catch((e) => {
|
||||
console.error("Failed to accept invite", e);
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
runInviteAcceptance();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => onAccept,
|
||||
(newOnAccept, oldOnAccept) => {
|
||||
if (newOnAccept !== oldOnAccept) {
|
||||
runInviteAcceptance();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
import {
|
||||
BrowserContext,
|
||||
BrowserGuestContext,
|
||||
consumeInviteLinkFromWindowLocation,
|
||||
createJazzBrowserContext,
|
||||
} from "jazz-browser";
|
||||
import {
|
||||
Account,
|
||||
AnonymousJazzAgent,
|
||||
AuthMethod,
|
||||
CoValue,
|
||||
CoValueClass,
|
||||
DeeplyLoaded,
|
||||
DepthsIn,
|
||||
ID,
|
||||
subscribeToCoValue,
|
||||
} from "jazz-tools";
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
Component,
|
||||
ComputedRef,
|
||||
MaybeRef,
|
||||
PropType,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
computed,
|
||||
defineComponent,
|
||||
inject,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
provide,
|
||||
ref,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
unref,
|
||||
watch,
|
||||
} from "vue";
|
||||
|
||||
export const logoutHandler = ref<() => void>();
|
||||
|
||||
export interface JazzVueApp<Acc extends Account> {
|
||||
JazzProvider: Component;
|
||||
|
||||
useAccount(): { me: ComputedRef<Acc>; logOut: () => void };
|
||||
useAccount<D extends DepthsIn<Acc>>(
|
||||
depth: D,
|
||||
): {
|
||||
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined>;
|
||||
logOut: () => void;
|
||||
};
|
||||
|
||||
useAccountOrGuest(): { me: ComputedRef<Acc | AnonymousJazzAgent> };
|
||||
useAccountOrGuest<D extends DepthsIn<Acc>>(
|
||||
depth: D,
|
||||
): {
|
||||
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent>;
|
||||
};
|
||||
|
||||
useCoState<V extends CoValue, D>(
|
||||
Schema: CoValueClass<V>,
|
||||
id: MaybeRef<ID<V> | undefined>,
|
||||
depth?: D & DepthsIn<V>,
|
||||
): Ref<DeeplyLoaded<V, D> | undefined>;
|
||||
|
||||
useAcceptInvite<V extends CoValue>(args: {
|
||||
invitedObjectSchema: CoValueClass<V>;
|
||||
onAccept: (projectID: ID<V>) => void;
|
||||
forValueHint?: string;
|
||||
}): void;
|
||||
}
|
||||
|
||||
const JazzContextSymbol = Symbol("JazzContext");
|
||||
|
||||
export function createJazzVueApp<Acc extends Account>({
|
||||
AccountSchema = Account as any,
|
||||
} = {}): JazzVueApp<Acc> {
|
||||
const JazzProvider = defineComponent({
|
||||
name: "JazzProvider",
|
||||
props: {
|
||||
auth: {
|
||||
type: [String, Object] as PropType<AuthMethod | "guest">,
|
||||
required: true,
|
||||
},
|
||||
peer: {
|
||||
type: String as PropType<`wss://${string}` | `ws://${string}`>,
|
||||
required: true,
|
||||
},
|
||||
storage: {
|
||||
type: String as PropType<"indexedDB" | "singleTabOPFS">,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const ctx = ref<BrowserContext<Acc> | BrowserGuestContext | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const key = ref(0);
|
||||
|
||||
provide(JazzContextSymbol, ctx);
|
||||
|
||||
const initializeContext = async () => {
|
||||
if (ctx.value) {
|
||||
ctx.value.done?.();
|
||||
ctx.value = undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const context = await createJazzBrowserContext<Acc>(
|
||||
props.auth === "guest"
|
||||
? { peer: props.peer, storage: props.storage }
|
||||
: {
|
||||
AccountSchema,
|
||||
auth: props.auth,
|
||||
peer: props.peer,
|
||||
storage: props.storage,
|
||||
},
|
||||
);
|
||||
|
||||
ctx.value = {
|
||||
...context,
|
||||
logOut: () => {
|
||||
logoutHandler.value?.();
|
||||
// context.logOut();
|
||||
key.value += 1;
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error creating Jazz browser context:", e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
void initializeContext();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => key.value,
|
||||
async () => {
|
||||
await initializeContext();
|
||||
},
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
if (ctx.value) ctx.value.done?.();
|
||||
});
|
||||
|
||||
return () => (ctx.value ? slots.default?.() : null);
|
||||
},
|
||||
});
|
||||
|
||||
function useJazzContext() {
|
||||
const context =
|
||||
inject<Ref<BrowserContext<Acc> | BrowserGuestContext>>(JazzContextSymbol);
|
||||
if (!context) {
|
||||
throw new Error("useJazzContext must be used within a JazzProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
function useAccount(): { me: ComputedRef<Acc>; logOut: () => void };
|
||||
function useAccount<D extends DepthsIn<Acc>>(
|
||||
depth: D,
|
||||
): {
|
||||
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined>;
|
||||
logOut: () => void;
|
||||
};
|
||||
function useAccount<D extends DepthsIn<Acc>>(
|
||||
depth?: D,
|
||||
): {
|
||||
me: ComputedRef<Acc | DeeplyLoaded<Acc, D> | undefined>;
|
||||
logOut: () => void;
|
||||
} {
|
||||
const context = useJazzContext();
|
||||
|
||||
if (!context.value) {
|
||||
throw new Error("useAccount must be used within a JazzProvider");
|
||||
}
|
||||
|
||||
if (!("me" in context.value)) {
|
||||
throw new Error(
|
||||
"useAccount can't be used in a JazzProvider with auth === 'guest' - consider using useAccountOrGuest()",
|
||||
);
|
||||
}
|
||||
|
||||
const contextMe = computed(() =>
|
||||
"me" in context.value ? context.value.me : undefined,
|
||||
);
|
||||
|
||||
const me = useCoState<Acc, D>(
|
||||
contextMe.value?.constructor as CoValueClass<Acc>,
|
||||
contextMe.value?.id,
|
||||
depth,
|
||||
);
|
||||
|
||||
return {
|
||||
me: computed(() => {
|
||||
const value =
|
||||
depth === undefined
|
||||
? me.value || toRaw((context.value as BrowserContext<Acc>).me)
|
||||
: me.value;
|
||||
|
||||
return value ? toRaw(value) : value;
|
||||
}),
|
||||
logOut: context.value.logOut,
|
||||
};
|
||||
}
|
||||
|
||||
function useAccountOrGuest(): { me: ComputedRef<Acc | AnonymousJazzAgent> };
|
||||
function useAccountOrGuest<D extends DepthsIn<Acc>>(
|
||||
depth: D,
|
||||
): {
|
||||
me: ComputedRef<DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent>;
|
||||
};
|
||||
function useAccountOrGuest<D extends DepthsIn<Acc>>(
|
||||
depth?: D,
|
||||
): {
|
||||
me: ComputedRef<
|
||||
Acc | DeeplyLoaded<Acc, D> | undefined | AnonymousJazzAgent
|
||||
>;
|
||||
} {
|
||||
const context = useJazzContext();
|
||||
|
||||
if (!context.value) {
|
||||
throw new Error("useAccountOrGuest must be used within a JazzProvider");
|
||||
}
|
||||
|
||||
const contextMe = computed(() =>
|
||||
"me" in context.value ? context.value.me : undefined,
|
||||
);
|
||||
|
||||
const me = useCoState<Acc, D>(
|
||||
contextMe.value?.constructor as CoValueClass<Acc>,
|
||||
contextMe.value?.id,
|
||||
depth,
|
||||
);
|
||||
|
||||
if ("me" in context.value) {
|
||||
return {
|
||||
me: computed(() =>
|
||||
depth === undefined
|
||||
? me.value || toRaw((context.value as BrowserContext<Acc>).me)
|
||||
: me.value,
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
me: computed(() => toRaw((context.value as BrowserGuestContext).guest)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function useCoState<V extends CoValue, D>(
|
||||
Schema: CoValueClass<V>,
|
||||
id: MaybeRef<ID<V> | undefined>,
|
||||
depth: D & DepthsIn<V> = [] as D & DepthsIn<V>,
|
||||
): Ref<DeeplyLoaded<V, D> | undefined> {
|
||||
const state: ShallowRef<DeeplyLoaded<V, D> | undefined> =
|
||||
shallowRef(undefined);
|
||||
const context = useJazzContext();
|
||||
|
||||
if (!context.value) {
|
||||
throw new Error("useCoState must be used within a JazzProvider");
|
||||
}
|
||||
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
|
||||
watch(
|
||||
[() => unref(id), () => context, () => Schema, () => depth],
|
||||
() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
|
||||
const idValue = unref(id);
|
||||
if (!idValue) return;
|
||||
|
||||
unsubscribe = subscribeToCoValue(
|
||||
Schema,
|
||||
idValue,
|
||||
"me" in context.value
|
||||
? toRaw(context.value.me)
|
||||
: toRaw(context.value.guest),
|
||||
depth,
|
||||
(value) => {
|
||||
state.value = value;
|
||||
},
|
||||
);
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
});
|
||||
|
||||
const computedState = computed(() => state.value);
|
||||
|
||||
return computedState;
|
||||
}
|
||||
|
||||
function useAcceptInvite<V extends CoValue>({
|
||||
invitedObjectSchema,
|
||||
onAccept,
|
||||
forValueHint,
|
||||
}: {
|
||||
invitedObjectSchema: CoValueClass<V>;
|
||||
onAccept: (projectID: ID<V>) => void;
|
||||
forValueHint?: string;
|
||||
}): void {
|
||||
const context = useJazzContext();
|
||||
|
||||
if (!context.value) {
|
||||
throw new Error("useAcceptInvite must be used within a JazzProvider");
|
||||
}
|
||||
|
||||
if (!("me" in context.value)) {
|
||||
throw new Error(
|
||||
"useAcceptInvite can't be used in a JazzProvider with auth === 'guest'.",
|
||||
);
|
||||
}
|
||||
|
||||
const runInviteAcceptance = () => {
|
||||
const result = consumeInviteLinkFromWindowLocation({
|
||||
as: toRaw((context.value as BrowserContext<Acc>).me),
|
||||
invitedObjectSchema,
|
||||
forValueHint,
|
||||
});
|
||||
|
||||
result
|
||||
.then((res) => res && onAccept(res.valueID))
|
||||
.catch((e) => {
|
||||
console.error("Failed to accept invite", e);
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
runInviteAcceptance();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => onAccept,
|
||||
(newOnAccept, oldOnAccept) => {
|
||||
if (newOnAccept !== oldOnAccept) {
|
||||
runInviteAcceptance();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
JazzProvider,
|
||||
useAccount,
|
||||
useAccountOrGuest,
|
||||
useCoState,
|
||||
useAcceptInvite,
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
export * from "./createJazzVueApp.js";
|
||||
export * from "./composables.js";
|
||||
export { JazzProvider } from "./provider.js";
|
||||
export * from "./auth/useDemoAuth.js";
|
||||
export { default as DemoAuthBasicUI } from "./auth/DemoAuthBasicUI.vue";
|
||||
export { default as ProgressiveImg } from "./ProgressiveImg.vue";
|
||||
|
||||
export { createInviteLink, parseInviteLink } from "jazz-browser";
|
||||
|
||||
106
packages/jazz-vue/src/provider.ts
Normal file
106
packages/jazz-vue/src/provider.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
BrowserContext,
|
||||
BrowserGuestContext,
|
||||
createJazzBrowserContext,
|
||||
} from "jazz-browser";
|
||||
import { Account, AccountClass, AuthMethod } from "jazz-tools";
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
PropType,
|
||||
defineComponent,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
provide,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
|
||||
export const logoutHandler = ref<() => void>();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface Register {}
|
||||
|
||||
export type RegisteredAccount = Register extends { Account: infer Acc }
|
||||
? Acc
|
||||
: Account;
|
||||
|
||||
export const JazzContextSymbol = Symbol("JazzContext");
|
||||
|
||||
export const JazzProvider = defineComponent({
|
||||
name: "JazzProvider",
|
||||
props: {
|
||||
auth: {
|
||||
type: [String, Object] as PropType<AuthMethod | "guest">,
|
||||
required: true,
|
||||
},
|
||||
AccountSchema: {
|
||||
type: Object as PropType<AccountClass<RegisteredAccount>>,
|
||||
required: false,
|
||||
},
|
||||
peer: {
|
||||
type: String as PropType<`wss://${string}` | `ws://${string}`>,
|
||||
required: true,
|
||||
},
|
||||
storage: {
|
||||
type: String as PropType<"indexedDB" | "singleTabOPFS">,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const ctx = ref<
|
||||
BrowserContext<RegisteredAccount> | BrowserGuestContext | undefined
|
||||
>(undefined);
|
||||
|
||||
const key = ref(0);
|
||||
|
||||
provide(JazzContextSymbol, ctx);
|
||||
|
||||
const initializeContext = async () => {
|
||||
if (ctx.value) {
|
||||
ctx.value.done?.();
|
||||
ctx.value = undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const context = await createJazzBrowserContext<RegisteredAccount>(
|
||||
props.auth === "guest"
|
||||
? { peer: props.peer, storage: props.storage }
|
||||
: {
|
||||
AccountSchema: props.AccountSchema,
|
||||
auth: props.auth,
|
||||
peer: props.peer,
|
||||
storage: props.storage,
|
||||
},
|
||||
);
|
||||
|
||||
ctx.value = {
|
||||
...context,
|
||||
logOut: () => {
|
||||
logoutHandler.value?.();
|
||||
// context.logOut();
|
||||
key.value += 1;
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
console.error("Error creating Jazz browser context:", e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
void initializeContext();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => key.value,
|
||||
async () => {
|
||||
await initializeContext();
|
||||
},
|
||||
);
|
||||
|
||||
onUnmounted(() => {
|
||||
if (ctx.value) ctx.value.done?.();
|
||||
});
|
||||
|
||||
return () => (ctx.value ? slots.default?.() : null);
|
||||
},
|
||||
});
|
||||
27
packages/jazz-vue/src/testing.ts
Normal file
27
packages/jazz-vue/src/testing.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Account, AnonymousJazzAgent } from "jazz-tools";
|
||||
import { getJazzContextShape } from "jazz-tools/testing";
|
||||
import { provide } from "vue";
|
||||
import { PropType, defineComponent, ref } from "vue";
|
||||
import { JazzContextSymbol } from "./provider.js";
|
||||
|
||||
export const JazzTestProvider = defineComponent({
|
||||
name: "JazzTestProvider",
|
||||
props: {
|
||||
account: {
|
||||
type: Object as PropType<Account | { guest: AnonymousJazzAgent }>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const ctx = ref(getJazzContextShape(props.account));
|
||||
provide(JazzContextSymbol, ctx);
|
||||
|
||||
return () => slots.default?.();
|
||||
},
|
||||
});
|
||||
|
||||
export {
|
||||
createJazzTestAccount,
|
||||
createJazzTestGuest,
|
||||
linkAccounts,
|
||||
} from "jazz-tools/testing";
|
||||
66
packages/jazz-vue/src/tests/testUtils.ts
Normal file
66
packages/jazz-vue/src/tests/testUtils.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Account, AnonymousJazzAgent } from "jazz-tools";
|
||||
import { createApp, defineComponent, h } from "vue";
|
||||
import { JazzTestProvider } from "../testing";
|
||||
|
||||
export const withJazzTestSetup = <C extends (...args: any[]) => any>(
|
||||
composable: C,
|
||||
{ account }: { account: Account | { guest: AnonymousJazzAgent } },
|
||||
) => {
|
||||
let result;
|
||||
|
||||
const wrapper = defineComponent({
|
||||
setup() {
|
||||
result = composable();
|
||||
// suppress missing template warning
|
||||
return () => {};
|
||||
},
|
||||
});
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
return () =>
|
||||
h(
|
||||
JazzTestProvider,
|
||||
{
|
||||
account,
|
||||
},
|
||||
{
|
||||
default: () => h(wrapper),
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
app.mount(document.createElement("div"));
|
||||
// return the result and the app instance
|
||||
// for testing provide/unmount
|
||||
return [result, app] as [ReturnType<C>, ReturnType<typeof createApp>];
|
||||
};
|
||||
|
||||
export function waitFor(callback: () => boolean | void) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const checkPassed = () => {
|
||||
try {
|
||||
return { ok: callback(), error: null };
|
||||
} catch (error) {
|
||||
return { ok: false, error };
|
||||
}
|
||||
};
|
||||
|
||||
let retries = 0;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const { ok, error } = checkPassed();
|
||||
|
||||
if (ok !== false) {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (++retries > 10) {
|
||||
clearInterval(interval);
|
||||
reject(error);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
53
packages/jazz-vue/src/tests/useAcceptInvite.test.ts
Normal file
53
packages/jazz-vue/src/tests/useAcceptInvite.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { CoMap, Group, ID, co } from "jazz-tools";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createInviteLink, useAcceptInvite } from "../index.js";
|
||||
import { createJazzTestAccount, linkAccounts } from "../testing.js";
|
||||
import { waitFor, withJazzTestSetup } from "./testUtils.js";
|
||||
|
||||
describe("useAcceptInvite", () => {
|
||||
it("should accept the invite", async () => {
|
||||
class TestMap extends CoMap {
|
||||
value = co.string;
|
||||
}
|
||||
|
||||
const account = await createJazzTestAccount();
|
||||
const inviteSender = await createJazzTestAccount();
|
||||
|
||||
linkAccounts(account, inviteSender);
|
||||
|
||||
let acceptedId: ID<TestMap> | undefined;
|
||||
|
||||
const invitelink = createInviteLink(
|
||||
TestMap.create(
|
||||
{ value: "hello" },
|
||||
{ owner: Group.create({ owner: inviteSender }) },
|
||||
),
|
||||
"reader",
|
||||
);
|
||||
|
||||
location.href = invitelink;
|
||||
|
||||
withJazzTestSetup(
|
||||
() =>
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: TestMap,
|
||||
onAccept: (id) => {
|
||||
acceptedId = id;
|
||||
},
|
||||
}),
|
||||
{
|
||||
account,
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(acceptedId).toBeDefined();
|
||||
});
|
||||
|
||||
const accepted = await TestMap.load(acceptedId!, account, {});
|
||||
|
||||
expect(accepted?.value).toEqual("hello");
|
||||
});
|
||||
});
|
||||
51
packages/jazz-vue/src/tests/useAccount.test.ts
Normal file
51
packages/jazz-vue/src/tests/useAccount.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { Account, CoMap, co } from "jazz-tools";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createUseAccountComposables, useAccount } from "../composables.js";
|
||||
import { createJazzTestAccount } from "../testing.js";
|
||||
import { withJazzTestSetup } from "./testUtils.js";
|
||||
|
||||
describe("useAccount", () => {
|
||||
it("should return the correct value", async () => {
|
||||
const account = await createJazzTestAccount();
|
||||
|
||||
const [result] = withJazzTestSetup(() => useAccount(), {
|
||||
account,
|
||||
});
|
||||
|
||||
expect(result.me.value).toEqual(account);
|
||||
});
|
||||
|
||||
it("should load nested values if requested", async () => {
|
||||
class AccountRoot extends CoMap {
|
||||
value = co.string;
|
||||
}
|
||||
|
||||
class AccountSchema extends Account {
|
||||
root = co.ref(AccountRoot);
|
||||
|
||||
migrate() {
|
||||
if (!this._refs.root) {
|
||||
this.root = AccountRoot.create({ value: "123" }, { owner: this });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { useAccount } = createUseAccountComposables<AccountSchema>();
|
||||
|
||||
const account = await createJazzTestAccount({ AccountSchema });
|
||||
|
||||
const [result] = withJazzTestSetup(
|
||||
() =>
|
||||
useAccount({
|
||||
root: {},
|
||||
}),
|
||||
{
|
||||
account,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.me.value?.root?.value).toBe("123");
|
||||
});
|
||||
});
|
||||
80
packages/jazz-vue/src/tests/useAccountOrGuest.test.ts
Normal file
80
packages/jazz-vue/src/tests/useAccountOrGuest.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { Account, CoMap, co } from "jazz-tools";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createUseAccountComposables,
|
||||
useAccountOrGuest,
|
||||
} from "../composables.js";
|
||||
import { createJazzTestAccount, createJazzTestGuest } from "../testing.js";
|
||||
import { withJazzTestSetup } from "./testUtils.js";
|
||||
|
||||
describe("useAccountOrGuest", () => {
|
||||
it("should return the correct me value", async () => {
|
||||
const account = await createJazzTestAccount();
|
||||
|
||||
const [result] = withJazzTestSetup(() => useAccountOrGuest(), {
|
||||
account,
|
||||
});
|
||||
|
||||
expect(result.me.value).toEqual(account);
|
||||
});
|
||||
|
||||
it("should return the guest agent if the account is a guest", async () => {
|
||||
const account = await createJazzTestGuest();
|
||||
|
||||
const [result] = withJazzTestSetup(() => useAccountOrGuest(), {
|
||||
account,
|
||||
});
|
||||
|
||||
expect(result.me.value).toBe(account.guest);
|
||||
});
|
||||
|
||||
it("should load nested values if requested", async () => {
|
||||
class AccountRoot extends CoMap {
|
||||
value = co.string;
|
||||
}
|
||||
|
||||
class AccountSchema extends Account {
|
||||
root = co.ref(AccountRoot);
|
||||
|
||||
migrate() {
|
||||
if (!this._refs.root) {
|
||||
this.root = AccountRoot.create({ value: "123" }, { owner: this });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const account = await createJazzTestAccount({ AccountSchema });
|
||||
const { useAccountOrGuest } = createUseAccountComposables<AccountSchema>();
|
||||
|
||||
const [result] = withJazzTestSetup(
|
||||
() =>
|
||||
useAccountOrGuest({
|
||||
root: {},
|
||||
}),
|
||||
{
|
||||
account,
|
||||
},
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(result.me.value?.root?.value).toBe("123");
|
||||
});
|
||||
|
||||
it("should not load nested values if the account is a guest", async () => {
|
||||
const account = await createJazzTestGuest();
|
||||
|
||||
const [result] = withJazzTestSetup(
|
||||
() =>
|
||||
useAccountOrGuest({
|
||||
root: {},
|
||||
}),
|
||||
{
|
||||
account,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.me.value).toBe(account.guest);
|
||||
});
|
||||
});
|
||||
127
packages/jazz-vue/src/tests/useCoState.test.ts
Normal file
127
packages/jazz-vue/src/tests/useCoState.test.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { CoMap, co } from "jazz-tools";
|
||||
import { createJazzTestAccount } from "jazz-tools/testing";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { useCoState } from "../index.js";
|
||||
import { withJazzTestSetup } from "./testUtils.js";
|
||||
|
||||
describe("useCoState", () => {
|
||||
it("should return the correct value", async () => {
|
||||
class TestMap extends CoMap {
|
||||
content = co.string;
|
||||
}
|
||||
|
||||
const account = await createJazzTestAccount();
|
||||
|
||||
const map = TestMap.create(
|
||||
{
|
||||
content: "123",
|
||||
},
|
||||
{ owner: account },
|
||||
);
|
||||
|
||||
const [result] = withJazzTestSetup(() => useCoState(TestMap, map.id, {}), {
|
||||
account,
|
||||
});
|
||||
|
||||
expect(result.value?.content).toBe("123");
|
||||
});
|
||||
|
||||
it("should update the value when the coValue changes", async () => {
|
||||
class TestMap extends CoMap {
|
||||
content = co.string;
|
||||
}
|
||||
|
||||
const account = await createJazzTestAccount();
|
||||
|
||||
const map = TestMap.create(
|
||||
{
|
||||
content: "123",
|
||||
},
|
||||
{ owner: account },
|
||||
);
|
||||
|
||||
const [result] = withJazzTestSetup(() => useCoState(TestMap, map.id, {}), {
|
||||
account,
|
||||
});
|
||||
|
||||
expect(result.value?.content).toBe("123");
|
||||
|
||||
map.content = "456";
|
||||
|
||||
expect(result.value?.content).toBe("456");
|
||||
});
|
||||
|
||||
it("should load nested values if requested", async () => {
|
||||
class TestNestedMap extends CoMap {
|
||||
content = co.string;
|
||||
}
|
||||
|
||||
class TestMap extends CoMap {
|
||||
content = co.string;
|
||||
nested = co.ref(TestNestedMap);
|
||||
}
|
||||
|
||||
const account = await createJazzTestAccount();
|
||||
|
||||
const map = TestMap.create(
|
||||
{
|
||||
content: "123",
|
||||
nested: TestNestedMap.create(
|
||||
{
|
||||
content: "456",
|
||||
},
|
||||
{ owner: account },
|
||||
),
|
||||
},
|
||||
{ owner: account },
|
||||
);
|
||||
|
||||
const [result] = withJazzTestSetup(
|
||||
() =>
|
||||
useCoState(TestMap, map.id, {
|
||||
nested: {},
|
||||
}),
|
||||
{
|
||||
account,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.value?.content).toBe("123");
|
||||
expect(result.value?.nested?.content).toBe("456");
|
||||
});
|
||||
|
||||
it("should load nested values on access even if not requested", async () => {
|
||||
class TestNestedMap extends CoMap {
|
||||
content = co.string;
|
||||
}
|
||||
|
||||
class TestMap extends CoMap {
|
||||
content = co.string;
|
||||
nested = co.ref(TestNestedMap);
|
||||
}
|
||||
|
||||
const account = await createJazzTestAccount();
|
||||
|
||||
const map = TestMap.create(
|
||||
{
|
||||
content: "123",
|
||||
nested: TestNestedMap.create(
|
||||
{
|
||||
content: "456",
|
||||
},
|
||||
{ owner: account },
|
||||
),
|
||||
},
|
||||
{ owner: account },
|
||||
);
|
||||
|
||||
const [result] = withJazzTestSetup(() => useCoState(TestMap, map.id, {}), {
|
||||
account,
|
||||
});
|
||||
|
||||
expect(result.value?.content).toBe("123");
|
||||
expect(result.value?.nested?.content).toBe("456");
|
||||
});
|
||||
});
|
||||
@@ -1,27 +1,27 @@
|
||||
import path from "path";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import depsExternal from "rollup-plugin-node-externals";
|
||||
import { defineConfig } from "vite";
|
||||
import dts from "vite-plugin-dts";
|
||||
|
||||
export default defineConfig({
|
||||
// @ts-expect-error types
|
||||
plugins: [vue(), dts({ include: ["src/**/*.ts"], outDir: "dist" })],
|
||||
plugins: [
|
||||
vue(),
|
||||
dts({ include: ["src/**/*.ts"], outDir: "dist" }),
|
||||
depsExternal(),
|
||||
],
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, "src/index.ts"),
|
||||
entry: {
|
||||
index: path.resolve(__dirname, "src/index.ts"),
|
||||
testing: path.resolve(__dirname, "src/testing.ts"),
|
||||
},
|
||||
name: "JazzVue",
|
||||
formats: ["es"],
|
||||
fileName: (format) => `index.js`,
|
||||
fileName: (_, entryName) => `${entryName}.js`,
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ["vue", "jazz-browser", "jazz-tools"],
|
||||
output: {
|
||||
globals: {
|
||||
vue: "Vue",
|
||||
"jazz-browser": "JazzBrowser",
|
||||
"jazz-tools": "JazzTools",
|
||||
},
|
||||
},
|
||||
external: ["vue"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -1984,9 +1984,6 @@ importers:
|
||||
|
||||
packages/jazz-vue:
|
||||
dependencies:
|
||||
'@scure/bip39':
|
||||
specifier: ^1.3.0
|
||||
version: 1.5.0
|
||||
cojson:
|
||||
specifier: workspace:*
|
||||
version: link:../cojson
|
||||
@@ -2000,6 +1997,9 @@ importers:
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.1.4
|
||||
version: 5.2.1(vite@5.4.11(@types/node@22.10.2)(lightningcss@1.28.2)(terser@5.37.0))(vue@3.5.13(typescript@5.6.3))
|
||||
rollup-plugin-node-externals:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0(rollup@4.28.1)
|
||||
typescript:
|
||||
specifier: ~5.6.2
|
||||
version: 5.6.3
|
||||
@@ -9880,6 +9880,12 @@ packages:
|
||||
resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==}
|
||||
hasBin: true
|
||||
|
||||
rollup-plugin-node-externals@8.0.0:
|
||||
resolution: {integrity: sha512-2HIOpWsWn5DqBoYl6iCAmB4kd5GoGbF68PR4xKR1YBPvywiqjtYvDEjHFodyqRL51iAMDITP074Zxs0OKs6F+g==}
|
||||
engines: {node: '>= 21 || ^20.6.0 || ^18.19.0'}
|
||||
peerDependencies:
|
||||
rollup: ^4.0.0
|
||||
|
||||
rollup@4.28.1:
|
||||
resolution: {integrity: sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
@@ -20297,6 +20303,10 @@ snapshots:
|
||||
dependencies:
|
||||
glob: 10.4.5
|
||||
|
||||
rollup-plugin-node-externals@8.0.0(rollup@4.28.1):
|
||||
dependencies:
|
||||
rollup: 4.28.1
|
||||
|
||||
rollup@4.28.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
Reference in New Issue
Block a user