Compare commits

...

147 Commits

Author SHA1 Message Date
Guido D'Orsi
a8af6efe1a Merge pull request #2162 from garden-co/changeset-release/main
Version Packages
2025-05-08 16:06:25 +02:00
github-actions[bot]
ee11b30d3a Version Packages 2025-05-08 13:38:09 +00:00
Guido D'Orsi
ef78d58729 Merge pull request #2155 from garden-co/feat/missing-profile
fix(auth): removed throw error when the profile is unavailable after a login
2025-05-08 15:36:09 +02:00
Guido D'Orsi
40e2dd0ece Merge pull request #2159 from garden-co/feat/storage-sync
fix: wait for storage sync before resolving the new account creation
2025-05-08 15:35:41 +02:00
Benjamin S. Leveritt
b60c7c1ce0 Merge pull request #2156 from garden-co/play-selection-co-literal-jazz-paper-scissors
Changes to co.literal for playSelection
2025-05-08 14:22:01 +01:00
Benjamin S. Leveritt
69cd362114 Merge pull request #2160 from garden-co/coplaintext-shorthands
CoPlainText shorthands
2025-05-08 14:19:31 +01:00
Benjamin S. Leveritt
526bf0a3cf Adds better output for Number(CoPlainText) 2025-05-08 13:57:59 +01:00
Benjamin S. Leveritt
439f0fe57e Add changeset 2025-05-08 13:30:07 +01:00
Benjamin S. Leveritt
686433f42e Adds reading as string shorthand
Dropping need for `.toString()` in situations where a string is expected
2025-05-08 13:29:54 +01:00
Benjamin S. Leveritt
3ba258e181 Adds owner shorthand to create and constructor 2025-05-08 13:27:55 +01:00
Guido D'Orsi
d6e143e4d5 fix: wait for storage sync before resolving the new account creation 2025-05-08 14:24:11 +02:00
Guido D'Orsi
3e6229da4d feat(waitForSync): skip closed peers and not subscribed client peers 2025-05-08 14:22:55 +02:00
Guido D'Orsi
adfc9a6032 feat: make waitForSync work on storage peers by handling optimistic/known state 2025-05-08 14:19:07 +02:00
Benjamin S. Leveritt
1a6879b1c2 Fix selection in worker 2025-05-08 10:10:45 +01:00
Benjamin S. Leveritt
172fec56f6 Changes to co.literal for playSelection 2025-05-08 10:06:32 +01:00
Guido D'Orsi
13892071f5 fix(auth): removed throw error when the profile is unavailable after a login 2025-05-08 10:45:23 +02:00
Guido D'Orsi
b62d75b847 Merge pull request #2149 from garden-co/changeset-release/main
Version Packages
2025-05-07 18:31:36 +02:00
github-actions[bot]
beafbd3088 Version Packages 2025-05-07 16:29:39 +00:00
Guido D'Orsi
653d8ba69f Merge pull request #2140 from garden-co/guido/rn-e2e-test
test: cover join chat in the rn e2e tests
2025-05-07 18:27:56 +02:00
Guido D'Orsi
6c23dab790 Merge pull request #2148 from garden-co/fix/autoload
fix(subscribe): fix error management and autoload on $each
2025-05-07 18:27:42 +02:00
Guido D'Orsi
80530a4065 fix(subscribe): fix error management and autoload on 2025-05-07 18:24:01 +02:00
Guido D'Orsi
2ba5ea684e Merge pull request #2089 from garden-co/changeset-release/main
Version Packages
2025-05-07 14:37:04 +02:00
Guido D'Orsi
a8466946d3 test: cover join chat in the rn e2e tests 2025-05-07 14:25:56 +02:00
github-actions[bot]
105b240076 Version Packages 2025-05-07 11:36:04 +00:00
Guido D'Orsi
097dd8a646 Update large-rings-help.md 2025-05-07 13:34:23 +02:00
Guido D'Orsi
138dd7ee8b Merge pull request #2139 from garden-co/feat/simplify-localnode-auth
feat: simplify LocalNode account creation flow
2025-05-07 13:30:25 +02:00
Guido D'Orsi
5b17085b30 test(e2e-rn): add an initial cleanup step 2025-05-07 13:19:32 +02:00
Guido D'Orsi
cb7bdffabe test(cloudflare): wait for the account to be uploaded before starting the worker 2025-05-07 12:54:48 +02:00
Guido D'Orsi
90892523da feat: simplify LocalNode account creation flow 2025-05-07 11:04:52 +02:00
Guido D'Orsi
693a058890 chore: fix type error in sqlite test 2025-05-06 23:19:48 +02:00
Guido D'Orsi
4d20f1bcfc Merge pull request #2114 from garden-co/perf/storage
Perf: optimize dependency load from storage and skip self sync on storage peers
2025-05-06 22:57:51 +02:00
Guido D'Orsi
36bf3924ff Merge pull request #2015 from garden-co/feat/new-subscription
A new subscription engine for jazz-tools
2025-05-06 22:57:37 +02:00
Guido D'Orsi
6b6fc8b31a chore: remove a console.log 2025-05-06 22:56:22 +02:00
Anselm
1649f06667 Allow immediately unsubscribing from CoValueCore changes (and use that) 2025-05-06 17:37:38 +01:00
Anselm
e9bd290dec Merge branch 'main' into feat/new-subscription 2025-05-06 17:35:17 +01:00
Guido D'Orsi
9d76292db5 Merge pull request #2125 from garden-co/fix/expect-covalue-loaded
fix(sync): avoid triggering fatal errors when syncing without all the deps loaded
2025-05-06 17:47:21 +02:00
Guido D'Orsi
66373bade3 fix(sync): avoid triggering fatal errors when syncing without all the deps loaded 2025-05-06 17:42:34 +02:00
Guido D'Orsi
40118a0b2c chore: remove console.log 2025-05-06 17:32:28 +02:00
Anselm Eickhoff
86b57415b8 Merge pull request #2074 from garden-co/refactor/combine-covalue-core-and-state
Combine CoValueCore and CoValueState Part 1 & 2
2025-05-06 15:55:02 +01:00
Guido D'Orsi
cdbc37a144 fix: add dist/crypto to the exports 2025-05-06 16:16:44 +02:00
Benjamin S. Leveritt
e996c71e83 Merge pull request #2121 from garden-co/2120-fix-covalues-crud-links
2120-fix-covalues-crud-links
2025-05-06 15:01:16 +01:00
Anselm Eickhoff
0f704a2330 Merge pull request #2108 from garden-co/refactor/simplify-localnode-bootstrap-and-more
Simplify LocalNode bootstrap and more
2025-05-06 14:38:52 +01:00
Guido D'Orsi
2f206e7613 chore: clean up changes from cojson 2025-05-06 15:18:37 +02:00
Guido D'Orsi
761759c893 chore: changeset 2025-05-06 15:02:40 +02:00
Guido D'Orsi
15c57e24ad test: cover subscribe updates on permissions change 2025-05-06 14:59:39 +02:00
Benjamin S. Leveritt
580e2577c9 Fix SchemaUnions 2025-05-06 13:45:55 +01:00
Guido D'Orsi
3804198c02 test: cover CoMap.Record and unsubscribe on refs change 2025-05-06 14:42:23 +02:00
Guido D'Orsi
537d56a30e chore: code cleanup 2025-05-06 14:41:33 +02:00
Benjamin S. Leveritt
5524d3e0cb Unneeded error squash 2025-05-06 13:18:03 +01:00
Anselm Eickhoff
3d2682e3a7 Merge pull request #2109 from garden-co/refactor/combine-covalue-core-and-state-2
Combine CoValueCore and CoValueState Part Part 2
2025-05-06 13:15:38 +01:00
Benjamin S. Leveritt
0c131f3af4 Last bits 2025-05-06 13:07:11 +01:00
Benjamin S. Leveritt
fada6b0eb5 Fix FileStreams section 2025-05-06 13:00:22 +01:00
Benjamin S. Leveritt
c8b94475cd Fix CoFeeds section 2025-05-06 12:58:13 +01:00
Benjamin S. Leveritt
162a024345 Fix CoLists section 2025-05-06 12:54:53 +01:00
Benjamin S. Leveritt
7cd195b3a4 Fix CoMaps section 2025-05-06 12:52:38 +01:00
Benjamin S. Leveritt
ccad5a3c70 Merge pull request #2042 from garden-co/2041-react-installation-docs
Adds React installation doc
2025-05-06 12:28:10 +01:00
Benjamin S. Leveritt
4268d189cb Adds link to useCoState 2025-05-06 12:27:37 +01:00
Anselm
d48712986f Merge branch 'refactor/simplify-localnode-bootstrap-and-more' into refactor/combine-covalue-core-and-state-2 2025-05-06 12:08:42 +01:00
Anselm
ba2fda5de1 Use ephemeral nodes again instead of switching control 2025-05-06 12:00:45 +01:00
Guido D'Orsi
b1d26f23d6 test: cover push on deeply loaded lists 2025-05-06 12:56:57 +02:00
Sammii
e7766746d9 Merge pull request #2118 from garden-co/fix/og-image-2-font-api-route
switching out local font for google fonts font
2025-05-06 11:40:48 +01:00
Anselm
7c64d104a6 Merge branch 'refactor/combine-covalue-core-and-state' into refactor/simplify-localnode-bootstrap-and-more 2025-05-06 11:39:42 +01:00
Anselm
1870a1268a Explain CoValueCore.verified 2025-05-06 11:31:50 +01:00
Anselm
a8222368d0 Faster implementation of VerifiedState.clone() 2025-05-06 11:20:07 +01:00
Sammii
72d11ce003 switching out local font for google fonts font 2025-05-06 11:18:35 +01:00
Guido D'Orsi
5fbf9770d2 test: cover more logic on coList and coMap 2025-05-06 11:42:10 +02:00
Trisha Lim
707dedb33e Merge pull request #2115 from garden-co/fix/toc-headings
fix: hidden headings are showing in TOC
2025-05-06 10:33:29 +01:00
Guido D'Orsi
86d42d9e49 chore: add comments 2025-05-06 10:31:56 +02:00
Guido D'Orsi
64f01915f4 Revert "fix(useCoState): defer updates to avoid getting the invalid mutation error when doing autoload"
This reverts commit c75a3042ce.
2025-05-06 10:26:00 +02:00
Guido D'Orsi
9a8fd2ce47 perf: do not trigger updates when autoloading available refs 2025-05-06 10:25:24 +02:00
Guido D'Orsi
4dc7cdb4e6 fix: coFeed account autoload 2025-05-06 10:11:15 +02:00
Guido D'Orsi
84b993944d chore: clanup 2025-05-05 18:46:11 +02:00
Guido D'Orsi
fa064443a0 chore: clanup 2025-05-05 18:45:39 +02:00
Trisha Lim
32e9678394 fix: hidden headings are showing in TOC 2025-05-05 17:10:08 +01:00
Guido D'Orsi
b1850efd7f chore: rename CoValueResolutionNode into SubscriptionScope 2025-05-05 18:05:10 +02:00
Guido D'Orsi
922e3c8244 chore: revert processedChangesId 2025-05-05 18:00:54 +02:00
Guido D'Orsi
1c063455d1 chore: revert timeout on click 2025-05-05 17:58:41 +02:00
Guido D'Orsi
35abeba323 Merge branch 'main' into feat/new-subscription 2025-05-05 17:58:12 +02:00
Guido D'Orsi
84f623097f chore: add hidden project generation page 2025-05-05 17:56:43 +02:00
Guido D'Orsi
73015a3438 fix: remove singleTabOPFS from the storage types 2025-05-05 17:55:20 +02:00
Guido D'Orsi
7c3bf78fef chore: split subscribe module 2025-05-05 17:33:53 +02:00
Guido D'Orsi
68cb6064a5 chore: simplify autoload code 2025-05-05 17:15:44 +02:00
Guido D'Orsi
992d5e572e feat: polish code and fix the typescript errors 2025-05-05 16:41:55 +02:00
Guido D'Orsi
c75a3042ce fix(useCoState): defer updates to avoid getting the invalid mutation error when doing autoload 2025-05-05 16:41:11 +02:00
Guido D'Orsi
fa8b20899d test(upload): increase the timeout on the share file button 2025-05-05 16:15:58 +02:00
Guido D'Orsi
f24cad1909 perf(sync): skip self-sync when getting content from storage peers 2025-05-05 16:06:34 +02:00
Guido D'Orsi
8b2df0e5e2 perf(storage): optimized the dependency push from storage to send a given dependency only once 2025-05-05 16:05:05 +02:00
Guido D'Orsi
f93222079f fix(coList): correctly copy state when calling rebuildFromCore 2025-05-05 15:44:50 +02:00
Guido D'Orsi
514f4c9a72 test(permissions): skip tests on invite roles group key revelation 2025-05-05 15:26:42 +02:00
Anselm
d5daf060c9 Merge CoValueStore into LocalNode and more cleanup 2025-05-04 15:13:01 +01:00
Anselm
604cd4e3a9 Merge CoValueState into CoValueCore 2025-05-04 14:33:27 +01:00
Anselm
3446b38f69 Provide core instead of content to CoValueCore listeners 2025-05-04 11:54:31 +01:00
Anselm
de12b03d3f use nodeWithRandomAgentAndSessionID in more tests 2025-05-04 11:49:41 +01:00
Anselm
c3db5cf0b5 Get rid of permission/key tracing and debug logs 2025-05-04 11:44:30 +01:00
Anselm
e7f2521b41 Fix recursion issues and all tests but one 2025-05-04 11:37:57 +01:00
Anselm
0c6cd571c9 Fix types in packages depending on cojson 2025-05-04 09:44:15 +01:00
Anselm
e1d56a45f7 Simplify LocalNode bootstrap and more 2025-05-03 16:41:50 +01:00
Anselm
e40e01fce0 Make core.verified nullable instead of core itself 2025-05-03 13:50:30 +01:00
Guido D'Orsi
d70343d864 chore(subscribe): code cleanup 2025-05-02 23:32:25 +02:00
Guido D'Orsi
160ab768e9 feat(storage): deduplicate dependency load 2025-05-02 23:31:48 +02:00
Guido D'Orsi
4ff03f67d9 feat: skip self-sync on new content for storage peers 2025-05-02 23:31:14 +02:00
Guido D'Orsi
5a81adffec feat: make the unavailable state transitive 2025-05-02 22:39:39 +02:00
Guido D'Orsi
f4fdb3c14e Merge pull request #2073 from garden-co/fix/inspector-docs
fix(docs): hide inspector widget docs for non-react
2025-05-02 18:59:37 +02:00
Guido D'Orsi
d001144a87 fix: update value when raw changes 2025-05-02 15:58:52 +02:00
Trisha Lim
ded473b75e Merge pull request #2087 from garden-co/fix/code-snippet-overflow
prevent horizontal scroll on code sample in landing page
2025-05-02 14:58:51 +01:00
Trisha Lim
602c34b0f2 Merge pull request #2080 from garden-co/fix/docs-content-overflow
fix(docs): body wider than page container
2025-05-02 14:58:19 +01:00
Guido D'Orsi
77cf06945a Merge remote-tracking branch 'origin/main' into feat/new-subscription 2025-05-02 14:25:56 +02:00
Guido D'Orsi
61f39bb56f Merge pull request #2061 from garden-co/fix/redundant-messages
fix(sync): remove redundant content messages when loading a coValue from multiple peers
2025-05-02 14:25:01 +02:00
Trisha Lim
7cc51b77f3 prevent horizontal scroll on code sample in landing page 2025-05-02 12:18:30 +01:00
Trisha Lim
bf303d58e3 fix(docs): body wider than page container 2025-05-02 11:57:22 +01:00
Sammii
def5c474e6 Merge pull request #2054 from garden-co/add-docs-og-image 2025-05-02 11:37:59 +01:00
Sammii
c81dca23ad catching empty toc 2025-05-02 10:55:35 +01:00
Guido D'Orsi
b470f63f86 chore: changeset 2025-05-02 11:54:47 +02:00
Anselm
5c7072bf6e Merge remote-tracking branch 'origin/fix/redundant-messages' into refactor/combine-covalue-core-and-state 2025-05-02 10:39:47 +01:00
Guido D'Orsi
bd62b1342a feat: add loadRequestSent to more surgically avoid to send a load request twice on the same peer in the sync protocol 2025-05-02 11:38:57 +02:00
Trisha Lim
af314e8584 hide inspector widget docs for non-react 2025-05-02 10:28:16 +01:00
Anselm
8ad1878f86 Fix biomes trailing comma 2025-05-02 10:26:47 +01:00
Anselm
2fe5cd1326 Split verified state logic out of CoValueCore 2025-05-02 10:23:29 +01:00
Anselm
b3605c0c22 Create folder for coValueCore before breaking it into pieces 2025-05-01 12:41:45 +01:00
Guido D'Orsi
e272849026 chore: add comment 2025-04-30 23:37:27 +02:00
Guido D'Orsi
c4fdfeaa48 fix(peer): disable crashOnClose while doing the gracefulShutdown 2025-04-30 23:33:21 +02:00
Guido D'Orsi
3f0859c3f2 fix(sync): remove redundant content messages when loading a coValue from multiple peers 2025-04-30 23:16:30 +02:00
Sammii
6e286bac7e creating styled open graph images for docs topics and subtopics 2025-04-30 18:00:02 +01:00
Benjamin S. Leveritt
235aab15b6 Adds a bit more context 2025-04-30 15:41:12 +01:00
Benjamin S. Leveritt
a440121ac9 Tweaks copy 2025-04-30 15:31:42 +01:00
Benjamin S. Leveritt
8ad9fc57cc Simplifies sections 2025-04-30 15:14:50 +01:00
Benjamin S. Leveritt
a763b947b8 Remove the Introduction header 2025-04-30 15:14:50 +01:00
Benjamin S. Leveritt
026a26da3c Adds React installation doc 2025-04-30 15:14:50 +01:00
Guido D'Orsi
c6142a1f64 chore: cleanup 2025-04-30 16:06:36 +02:00
Guido D'Orsi
f3fb2dee52 Merge branch 'feat/resolve-load-earlier' into feat/new-subscription 2025-04-30 16:00:02 +02:00
Sammii
3c97e8e7f2 Merge branch 'main' into add-docs-og-image 2025-04-30 13:33:07 +01:00
Sammii
7996a2aa9c og image docs 2025-04-30 13:30:30 +01:00
Guido D'Orsi
bf399d72c1 feat(subscribe): improve error logging, spec compliance 2025-04-29 18:31:14 +02:00
Guido D'Orsi
60fefe8158 Merge remote-tracking branch 'origin/main' into feat/new-subscription 2025-04-29 16:24:53 +02:00
Guido D'Orsi
9be66e196c feat(subscribe): improve error logging, spec compliance 2025-04-29 16:24:43 +02:00
Guido D'Orsi
04d96e52e0 fix: handle null values on msg.id 2025-04-29 16:24:43 +02:00
Guido D'Orsi
5b483dac6f feat: optimize the resolution of the loading states 2025-04-29 16:24:43 +02:00
Guido D'Orsi
fadb4bf76e feat(RawCoList): incremental processing 2025-04-29 16:17:37 +02:00
Guido D'Orsi
98a25b1fd6 feat(todo-example): add a page to generate random projects with many tasks 2025-04-29 16:04:10 +02:00
Guido D'Orsi
6df5d72dfd fix: Account deep loading 2025-04-28 18:23:49 +02:00
Guido D'Orsi
b91b33e9be feat: pass the subscribe.test.ts 2025-04-28 18:06:00 +02:00
Guido D'Orsi
fd7226585b Merge remote-tracking branch 'origin/main' into feat/new-subscription 2025-04-28 17:17:08 +02:00
Guido D'Orsi
712b67b782 fix: improve permission checks 2025-04-28 14:29:25 +02:00
Guido D'Orsi
56c7a2dda2 feat: initial commit of the new subscribe 2025-04-26 11:10:04 +02:00
Guido D'Orsi
e050f17945 Merge remote-tracking branch 'origin/main' into feat/new-subscription 2025-04-25 16:09:19 +02:00
Guido D'Orsi
8a60897086 chore: remove ACCESS_FROM debug code 2025-04-24 16:09:49 +02:00
Anselm
06db1dd423 Add opengraph images for docs and move where H1s get filtered out of toc 2025-04-11 10:30:59 +01:00
228 changed files with 8580 additions and 4545 deletions

View File

@@ -1,5 +1,32 @@
# chat-rn-expo-clerk
## 1.0.112
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-expo@0.13.20
- jazz-react-native-media-images@0.13.20
## 1.0.111
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-expo@0.13.19
- jazz-react-native-media-images@0.13.19
## 1.0.110
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-expo@0.13.18
- jazz-react-native-media-images@0.13.18
## 1.0.109
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-expo-clerk",
"main": "index.js",
"version": "1.0.109",
"version": "1.0.112",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",

View File

@@ -1,5 +1,29 @@
# chat-rn-expo
## 1.0.99
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-expo@0.13.20
## 1.0.98
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-expo@0.13.19
## 1.0.97
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-expo@0.13.18
## 1.0.96
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn-expo",
"version": "1.0.96",
"version": "1.0.99",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",

View File

@@ -20,6 +20,7 @@ import { Chat, Message } from "./schema";
export default function ChatScreen({ navigation }: { navigation: any }) {
const { me, logOut } = useAccount();
const [chatId, setChatId] = useState<ID<Chat>>();
const [chatIdInput, setChatIdInput] = useState<string>();
const loadedChat = useCoState(Chat, chatId, { resolve: { $each: true } });
const [message, setMessage] = useState("");
const profile = useCoState(Profile, me._refs.profile?.id, {});
@@ -57,27 +58,11 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
};
const joinChat = () => {
Alert.prompt(
"Join Chat",
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
[
{
text: "Cancel",
style: "cancel",
},
{
text: "Join",
onPress: (chatId) => {
if (chatId) {
setChatId(chatId as ID<Chat>);
} else {
Alert.alert("Error", "Chat ID cannot be empty.");
}
},
},
],
"plain-text",
);
if (chatIdInput) {
setChatId(chatIdInput as ID<Chat>);
} else {
Alert.alert("Error", "Chat ID cannot be empty.");
}
};
const sendMessage = () => {
@@ -160,9 +145,25 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
>
<Text className="text-white font-semibold">Start new chat</Text>
</TouchableOpacity>
<Text className="text-m font-bold mt-6">Join existing chat</Text>
<TextInput
className="rounded h-12 p-2 m-2 mt-4 w-80 border border-gray-200 block"
placeholder="Chat ID"
value={chatIdInput ?? ""}
onChangeText={(value) => {
setChatIdInput(value);
}}
textAlignVertical="center"
onSubmitEditing={() => {
if (chatIdInput) {
setChatId(chatIdInput as ID<Chat>);
}
}}
testID="chat-id-input"
/>
<TouchableOpacity
onPress={joinChat}
className="bg-green-500 p-4 rounded-md mt-4"
className="bg-green-500 p-4 rounded-md"
>
<Text className="text-white font-semibold">Join chat</Text>
</TouchableOpacity>
@@ -172,7 +173,6 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
<FlatList
contentContainerStyle={{
flexGrow: 1,
flex: 1,
gap: 6,
padding: 8,
}}

View File

@@ -9,6 +9,8 @@ appId: com.jazz.chatrn
# - tapOn: "Reload"
# login
- assertVisible: "Logout"
- tapOn: "Logout"
- assertVisible: "Anonymous user"
- runFlow:
label: "Erase existing username"
@@ -42,9 +44,11 @@ appId: com.jazz.chatrn
# logout
- tapOn: "Logout"
- assertVisible: "Anonymous user"
# This doesn't work on CI, maybe because Android has a different alert dialog
# - tapOn: "Join chat"
# - inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
# - pressKey: "enter"
# - assertVisible: "boorad"
# - assertVisible: "bro, low key, it do be like that tho"
# join chat
- tapOn:
id: "chat-id-input"
- inputText: "co_zFs6KFyhxPw4xtw83tcEMzeHUNv" # Use a static id because maestro doesn't have access to the system clipboard
- tapOn: "Join chat"
- assertVisible: "boorad"
- assertVisible: "bro, low key, it do be like that tho"

View File

@@ -1,5 +1,41 @@
# chat-rn
## 1.0.107
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [439f0fe]
- Updated dependencies [3e6229d]
- cojson@0.13.20
- jazz-tools@0.13.20
- jazz-react-native@0.13.20
- cojson-transport-ws@0.13.20
## 1.0.106
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react-native@0.13.19
## 1.0.105
### Patch Changes
- Updated dependencies [9089252]
- Updated dependencies [b470f63]
- Updated dependencies [761759c]
- Updated dependencies [66373ba]
- Updated dependencies [f24cad1]
- cojson@0.13.18
- jazz-tools@0.13.18
- cojson-transport-ws@0.13.18
- jazz-react-native@0.13.18
## 1.0.104
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.104",
"version": "1.0.107",
"main": "index.js",
"scripts": {
"android": "react-native run-android",

View File

@@ -1,5 +1,32 @@
# chat-vue
## 0.0.91
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-browser@0.13.20
- jazz-vue@0.13.20
## 0.0.90
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-browser@0.13.19
- jazz-vue@0.13.19
## 0.0.89
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-browser@0.13.18
- jazz-vue@0.13.18
## 0.0.88
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-vue",
"version": "0.0.88",
"version": "0.0.91",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,32 @@
# jazz-example-chat
## 0.0.189
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-inspector@0.13.20
- jazz-react@0.13.20
## 0.0.188
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-inspector@0.13.19
- jazz-react@0.13.19
## 0.0.187
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-inspector@0.13.18
- jazz-react@0.13.18
## 0.0.186
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.186",
"version": "0.0.189",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,32 @@
# minimal-auth-clerk
## 0.0.88
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
- jazz-react-auth-clerk@0.13.20
## 0.0.87
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
- jazz-react-auth-clerk@0.13.19
## 0.0.86
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
- jazz-react-auth-clerk@0.13.18
## 0.0.85
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "clerk",
"private": true,
"version": "0.0.85",
"version": "0.0.88",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# file-share-svelte
## 0.0.71
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-svelte@0.13.20
## 0.0.70
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-svelte@0.13.19
## 0.0.69
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-svelte@0.13.18
## 0.0.68
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "file-share-svelte",
"version": "0.0.68",
"version": "0.0.71",
"private": true,
"type": "module",
"scripts": {

View File

@@ -30,7 +30,7 @@
const fileId = file._refs.file.id;
// Load the file as a blob, can take a while
const blob = await FileStream.loadAsBlob(fileId, me, {});
const blob = await FileStream.loadAsBlob(fileId);
if (!blob) {
toast.error('Failed to download file');
return;

View File

@@ -1,48 +1,26 @@
<script lang="ts">
import { useAccount, useCoState } from 'jazz-svelte';
import { SharedFile, ListOfSharedFiles } from '$lib/schema';
import { createInviteLink } from 'jazz-svelte';
import { FileStream } from 'jazz-tools';
import FileItem from '$lib/components/FileItem.svelte';
import { SvelteMap } from 'svelte/reactivity';
import { generateTempFileId } from '$lib/utils';
import { CloudUpload } from 'lucide-svelte';
const { me, logOut } = useAccount();
const mySharedFilesId = me?.root?._refs.sharedFiles.id;
const sharedFiles = $derived(useCoState(ListOfSharedFiles, mySharedFilesId, [{}]));
const sharedFiles = $derived(useCoState(ListOfSharedFiles, mySharedFilesId));
let fileInput: HTMLInputElement;
type PendingSharedFile = {
name: string;
id: string;
createdAt: Date;
};
// Track files that are currently uploading
const uploadingFiles = new SvelteMap<string, PendingSharedFile>();
async function handleFileUpload(event: Event) {
const input = event.target as HTMLInputElement;
const files = input.files;
if (!files || !files.length || !me.root?.sharedFiles || !me.root.publicGroup) return;
if (!files || !files.length || !me?.root?.sharedFiles || !me?.root?.publicGroup) return;
const file = files[0];
const fileName = file.name;
const createdAt = new Date();
const fileId = generateTempFileId(fileName, createdAt);
const tempFile: PendingSharedFile = {
name: fileName,
id: fileId,
createdAt
};
// Add to uploading files
uploadingFiles.set(fileId, tempFile);
try {
const ownership = { owner: me.root.publicGroup };
@@ -65,17 +43,10 @@
// Add the file to the user's files list
me.root.sharedFiles.push(sharedFile);
} finally {
uploadingFiles.delete(fileId);
fileInput.value = ''; // reset input
}
}
async function shareFile(file: SharedFile) {
const inviteLink = createInviteLink(file, 'reader');
await navigator.clipboard.writeText(inviteLink);
alert('Share link copied to clipboard!');
}
async function deleteFile(file: SharedFile) {
if (!me?.root?.sharedFiles || !sharedFiles.current) return;
@@ -127,13 +98,11 @@
<!-- Files List -->
<div class="space-y-4">
{#if sharedFiles.current}
{#if !(sharedFiles.current.length === 0 && uploadingFiles.size === 0)}
{#each [...sharedFiles.current, ...uploadingFiles.values()] as file (generateTempFileId(file?.name, file?.createdAt))}
{#if !(sharedFiles.current.length === 0)}
{#each sharedFiles.current as file}
{#if file}
<FileItem
{file}
loading={uploadingFiles.has(generateTempFileId(file?.name, file?.createdAt))}
onShare={shareFile}
onDelete={deleteFile}
/>
{/if}

View File

@@ -1,5 +1,32 @@
# jazz-tailwind-demo-auth-starter
## 0.0.28
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-inspector@0.13.20
- jazz-react@0.13.20
## 0.0.27
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-inspector@0.13.19
- jazz-react@0.13.19
## 0.0.26
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-inspector@0.13.18
- jazz-react@0.13.18
## 0.0.25
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "filestream",
"private": true,
"version": "0.0.25",
"version": "0.0.28",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# form
## 0.1.29
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.1.28
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.1.27
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.1.26
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "form",
"private": true,
"version": "0.1.26",
"version": "0.1.29",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# image-upload
## 0.0.85
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.84
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.83
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.82
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "image-upload",
"private": true,
"version": "0.0.82",
"version": "0.0.85",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,35 @@
# jazz-example-inspector
## 0.0.139
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson@0.13.20
- cojson-transport-ws@0.13.20
- jazz-inspector@0.13.20
## 0.0.138
### Patch Changes
- jazz-inspector@0.13.19
## 0.0.137
### Patch Changes
- Updated dependencies [9089252]
- Updated dependencies [b470f63]
- Updated dependencies [66373ba]
- Updated dependencies [f24cad1]
- cojson@0.13.18
- cojson-transport-ws@0.13.18
- jazz-inspector@0.13.18
## 0.0.136
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector-app",
"private": true,
"version": "0.0.136",
"version": "0.0.139",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -8,7 +8,10 @@ export function cn(...inputs: ClassValue[]) {
/**
* Given a player selections, returns the winner of the current game.
*/
export function determineWinner(player1Choice: string, player2Choice: string) {
export function determineWinner(
player1Choice: "rock" | "paper" | "scissors",
player2Choice: "rock" | "paper" | "scissors",
) {
if (player1Choice === player2Choice) {
return "draw";
} else if (

View File

@@ -10,139 +10,139 @@
// Import Routes
import { Route as rootRoute } from "./routes/__root";
import { Route as AuthenticatedImport } from "./routes/_authenticated";
import { Route as AuthenticatedGameGameIdImport } from "./routes/_authenticated/game.$gameId";
import { Route as AuthenticatedWaitingRoomWaitingRoomIdImport } from "./routes/_authenticated/waiting-room.$waitingRoomId";
import { Route as IndexImport } from "./routes/index";
import { Route as rootRoute } from './routes/__root'
import { Route as AuthenticatedImport } from './routes/_authenticated'
import { Route as IndexImport } from './routes/index'
import { Route as AuthenticatedWaitingRoomWaitingRoomIdImport } from './routes/_authenticated/waiting-room.$waitingRoomId'
import { Route as AuthenticatedGameGameIdImport } from './routes/_authenticated/game.$gameId'
// Create/Update Routes
const AuthenticatedRoute = AuthenticatedImport.update({
id: "/_authenticated",
id: '/_authenticated',
getParentRoute: () => rootRoute,
} as any);
} as any)
const IndexRoute = IndexImport.update({
id: "/",
path: "/",
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any);
} as any)
const AuthenticatedWaitingRoomWaitingRoomIdRoute =
AuthenticatedWaitingRoomWaitingRoomIdImport.update({
id: "/waiting-room/$waitingRoomId",
path: "/waiting-room/$waitingRoomId",
id: '/waiting-room/$waitingRoomId',
path: '/waiting-room/$waitingRoomId',
getParentRoute: () => AuthenticatedRoute,
} as any);
} as any)
const AuthenticatedGameGameIdRoute = AuthenticatedGameGameIdImport.update({
id: "/game/$gameId",
path: "/game/$gameId",
id: '/game/$gameId',
path: '/game/$gameId',
getParentRoute: () => AuthenticatedRoute,
} as any);
} as any)
// Populate the FileRoutesByPath interface
declare module "@tanstack/react-router" {
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
"/": {
id: "/";
path: "/";
fullPath: "/";
preLoaderRoute: typeof IndexImport;
parentRoute: typeof rootRoute;
};
"/_authenticated": {
id: "/_authenticated";
path: "";
fullPath: "";
preLoaderRoute: typeof AuthenticatedImport;
parentRoute: typeof rootRoute;
};
"/_authenticated/game/$gameId": {
id: "/_authenticated/game/$gameId";
path: "/game/$gameId";
fullPath: "/game/$gameId";
preLoaderRoute: typeof AuthenticatedGameGameIdImport;
parentRoute: typeof AuthenticatedImport;
};
"/_authenticated/waiting-room/$waitingRoomId": {
id: "/_authenticated/waiting-room/$waitingRoomId";
path: "/waiting-room/$waitingRoomId";
fullPath: "/waiting-room/$waitingRoomId";
preLoaderRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdImport;
parentRoute: typeof AuthenticatedImport;
};
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/_authenticated': {
id: '/_authenticated'
path: ''
fullPath: ''
preLoaderRoute: typeof AuthenticatedImport
parentRoute: typeof rootRoute
}
'/_authenticated/game/$gameId': {
id: '/_authenticated/game/$gameId'
path: '/game/$gameId'
fullPath: '/game/$gameId'
preLoaderRoute: typeof AuthenticatedGameGameIdImport
parentRoute: typeof AuthenticatedImport
}
'/_authenticated/waiting-room/$waitingRoomId': {
id: '/_authenticated/waiting-room/$waitingRoomId'
path: '/waiting-room/$waitingRoomId'
fullPath: '/waiting-room/$waitingRoomId'
preLoaderRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdImport
parentRoute: typeof AuthenticatedImport
}
}
}
// Create and export the route tree
interface AuthenticatedRouteChildren {
AuthenticatedGameGameIdRoute: typeof AuthenticatedGameGameIdRoute;
AuthenticatedWaitingRoomWaitingRoomIdRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
AuthenticatedGameGameIdRoute: typeof AuthenticatedGameGameIdRoute
AuthenticatedWaitingRoomWaitingRoomIdRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdRoute
}
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedGameGameIdRoute: AuthenticatedGameGameIdRoute,
AuthenticatedWaitingRoomWaitingRoomIdRoute:
AuthenticatedWaitingRoomWaitingRoomIdRoute,
};
}
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(
AuthenticatedRouteChildren,
);
)
export interface FileRoutesByFullPath {
"/": typeof IndexRoute;
"": typeof AuthenticatedRouteWithChildren;
"/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
'/': typeof IndexRoute
'': typeof AuthenticatedRouteWithChildren
'/game/$gameId': typeof AuthenticatedGameGameIdRoute
'/waiting-room/$waitingRoomId': typeof AuthenticatedWaitingRoomWaitingRoomIdRoute
}
export interface FileRoutesByTo {
"/": typeof IndexRoute;
"": typeof AuthenticatedRouteWithChildren;
"/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
'/': typeof IndexRoute
'': typeof AuthenticatedRouteWithChildren
'/game/$gameId': typeof AuthenticatedGameGameIdRoute
'/waiting-room/$waitingRoomId': typeof AuthenticatedWaitingRoomWaitingRoomIdRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute;
"/": typeof IndexRoute;
"/_authenticated": typeof AuthenticatedRouteWithChildren;
"/_authenticated/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/_authenticated/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
__root__: typeof rootRoute
'/': typeof IndexRoute
'/_authenticated': typeof AuthenticatedRouteWithChildren
'/_authenticated/game/$gameId': typeof AuthenticatedGameGameIdRoute
'/_authenticated/waiting-room/$waitingRoomId': typeof AuthenticatedWaitingRoomWaitingRoomIdRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths: "/" | "" | "/game/$gameId" | "/waiting-room/$waitingRoomId";
fileRoutesByTo: FileRoutesByTo;
to: "/" | "" | "/game/$gameId" | "/waiting-room/$waitingRoomId";
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '' | '/game/$gameId' | '/waiting-room/$waitingRoomId'
fileRoutesByTo: FileRoutesByTo
to: '/' | '' | '/game/$gameId' | '/waiting-room/$waitingRoomId'
id:
| "__root__"
| "/"
| "/_authenticated"
| "/_authenticated/game/$gameId"
| "/_authenticated/waiting-room/$waitingRoomId";
fileRoutesById: FileRoutesById;
| '__root__'
| '/'
| '/_authenticated'
| '/_authenticated/game/$gameId'
| '/_authenticated/waiting-room/$waitingRoomId'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute;
AuthenticatedRoute: typeof AuthenticatedRouteWithChildren;
IndexRoute: typeof IndexRoute
AuthenticatedRoute: typeof AuthenticatedRouteWithChildren
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AuthenticatedRoute: AuthenticatedRouteWithChildren,
};
}
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>();
._addFileTypes<FileRouteTypes>()
/* ROUTE_MANIFEST_START
{

View File

@@ -14,7 +14,7 @@ import { type ID } from "jazz-tools";
import { Badge, CircleHelp, Scissors, ScrollText } from "lucide-react";
import { useEffect, useState } from "react";
const playIcon = (selection: string | undefined) => {
const playIcon = (selection: "rock" | "paper" | "scissors" | undefined) => {
switch (selection) {
case "rock":
return <Badge className="w-5 h-5" />;
@@ -50,9 +50,9 @@ function RouteComponent() {
const isPlayer1 = loaderGame.player1?.account?.isMe;
const player = isPlayer1 ? "player1" : "player2";
const [playSelection, setPlaySelection] = useState(
loaderGame[player]?.playSelection ?? "",
);
const [playSelection, setPlaySelection] = useState<
"rock" | "paper" | "scissors" | undefined
>(loaderGame[player]?.playSelection);
const sendInboxMessage = experimental_useInboxSender(WORKER_ID);
const game = useCoState(Game, gameId as ID<Game>);
@@ -62,7 +62,7 @@ function RouteComponent() {
return loaderGame.subscribe((game) => {
if (gameCompleted && !game.outcome) {
setPlaySelection(""); // Reset play selection when one player clicks on "Start a new game"
setPlaySelection(undefined); // Reset play selection when one player clicks on "Start a new game"
}
gameCompleted = Boolean(game.outcome);
@@ -82,7 +82,10 @@ function RouteComponent() {
const opponentSelection = opponentPlayer?.playSelection;
const onSubmit = async (playSelection: string) => {
const onSubmit = async (
playSelection: "rock" | "paper" | "scissors" | undefined,
) => {
if (!playSelection) return;
sendInboxMessage(
PlayIntent.create({ type: "play", gameId, player, playSelection }),
);
@@ -117,7 +120,9 @@ function RouteComponent() {
) : null}
<CardContent>
<div>
{playSelection === "" ? "Make Your Selection" : "Your Selection: "}
{playSelection === undefined
? "Make Your Selection"
: "Your Selection: "}
</div>
<CardSmall>{playIcon(playSelection)}</CardSmall>
{gameComplete ? null : (
@@ -148,7 +153,7 @@ function RouteComponent() {
<div className="m-4">
<Button
disabled={
playSelection === "" ||
playSelection === undefined ||
Boolean(currentPlayer?.playSelection)
}
onClick={() => onSubmit(playSelection)}

View File

@@ -30,7 +30,7 @@ export class Game extends CoMap {
export class Player extends CoMap {
account = co.ref(Account);
playSelection? = co.string;
playSelection? = co.literal("rock", "paper", "scissors");
}
export class WaitingRoom extends CoMap {
@@ -47,7 +47,7 @@ export class PlayIntent extends InboxMessage {
type = co.literal("play");
gameId = co.string;
player = co.literal("player1", "player2");
playSelection = co.string;
playSelection = co.literal("rock", "paper", "scissors");
}
export class NewGameIntent extends InboxMessage {

View File

@@ -186,9 +186,9 @@ async function handlePlayIntent(_: ID<Account>, message: PlayIntent) {
// once both players have a selection, determine the winner
if (
!!player1Selection &&
player1Selection !== "" &&
player1Selection !== undefined &&
!!player2Selection &&
player2Selection !== ""
player2Selection !== undefined
) {
const outcome = determineWinner(player1Selection, player2Selection);
game.outcome = outcome;

View File

@@ -1,5 +1,29 @@
# multi-cursors
## 0.0.81
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.80
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.79
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.78
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "multi-cursors",
"private": true,
"version": "0.0.78",
"version": "0.0.81",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,32 @@
# multiauth
## 0.0.29
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
- jazz-react-auth-clerk@0.13.20
## 0.0.28
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
- jazz-react-auth-clerk@0.13.19
## 0.0.27
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
- jazz-react-auth-clerk@0.13.18
## 0.0.26
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "multiauth",
"private": true,
"version": "0.0.26",
"version": "0.0.29",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,32 @@
# jazz-example-musicplayer
## 0.0.110
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-inspector@0.13.20
- jazz-react@0.13.20
## 0.0.109
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-inspector@0.13.19
- jazz-react@0.13.19
## 0.0.108
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-inspector@0.13.18
- jazz-react@0.13.18
## 0.0.107
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.107",
"version": "0.0.110",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# organization
## 0.0.81
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.80
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.79
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.78
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "organization",
"private": true,
"version": "0.0.78",
"version": "0.0.81",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# passkey-svelte
## 0.0.75
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-svelte@0.13.20
## 0.0.74
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-svelte@0.13.19
## 0.0.73
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-svelte@0.13.18
## 0.0.72
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "passkey-svelte",
"version": "0.0.72",
"version": "0.0.75",
"type": "module",
"private": true,
"scripts": {

View File

@@ -1,5 +1,29 @@
# minimal-auth-passkey
## 0.0.86
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.85
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.84
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.83
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passkey",
"private": true,
"version": "0.0.83",
"version": "0.0.86",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# passphrase
## 0.0.83
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.82
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.81
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.80
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "passphrase",
"private": true,
"version": "0.0.80",
"version": "0.0.83",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# jazz-password-manager
## 0.0.107
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.106
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.105
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.104
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.104",
"version": "0.0.107",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# jazz-example-pets
## 0.0.205
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.204
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.203
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.202
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.202",
"version": "0.0.205",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,29 @@
# reactions
## 0.0.85
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.84
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.83
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.82
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "reactions",
"private": true,
"version": "0.0.82",
"version": "0.0.85",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,33 @@
# richtext
## 0.0.75
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
- jazz-richtext-prosemirror@0.1.9
## 0.0.74
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
- jazz-richtext-prosemirror@0.1.8
## 0.0.73
### Patch Changes
- Updated dependencies [761759c]
- Updated dependencies [133b8ab]
- jazz-tools@0.13.18
- jazz-richtext-prosemirror@0.1.7
- jazz-react@0.13.18
## 0.0.72
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "richtext",
"private": true,
"version": "0.0.72",
"version": "0.0.75",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,32 @@
# todo-vue
## 0.0.89
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-browser@0.13.20
- jazz-vue@0.13.20
## 0.0.88
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-browser@0.13.19
- jazz-vue@0.13.19
## 0.0.87
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-browser@0.13.18
- jazz-vue@0.13.18
## 0.0.86
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "todo-vue",
"version": "0.0.86",
"version": "0.0.89",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,29 @@
# jazz-example-todo
## 0.0.204
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-react@0.13.20
## 0.0.203
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-react@0.13.19
## 0.0.202
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-react@0.13.18
## 0.0.201
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.201",
"version": "0.0.204",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,6 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
"@faker-js/faker": "^9.7.0",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-toast": "^1.1.4",

View File

@@ -41,12 +41,9 @@ export class TodoAccount extends Account {
*/
migrate() {
if (!this._refs.root) {
this.root = TodoAccountRoot.create(
{
projects: ListOfProjects.create([], { owner: this }),
},
{ owner: this },
);
this.root = TodoAccountRoot.create({
projects: ListOfProjects.create([]),
});
}
}
}

View File

@@ -23,6 +23,7 @@ import {
ThemeProvider,
TitleAndLogo,
} from "./basicComponents/index.ts";
import { TaskGenerator } from "./components/TaskGenerator.tsx";
import { wordlist } from "./wordlist.ts";
/**
@@ -95,6 +96,10 @@ export default function App() {
path: "/invite/*",
element: <p>Accepting invite...</p>,
},
{
path: "/generate",
element: <TaskGenerator />,
},
]);
// `useAcceptInvite()` is a hook that accepts an invite link from the URL hash,

View File

@@ -35,7 +35,11 @@ export function ProjectTodoTable() {
// content - whether we create edits locally, load persisted data, or receive
// sync updates from other devices or participants!
// It also recursively resolves and subsribes to all referenced CoValues.
const project = useCoState(TodoProject, projectId);
const project = useCoState(TodoProject, projectId, {
resolve: {
tasks: true,
},
});
// `createTask` is similar to `createProject` we saw earlier, creating a new CoMap
// for a new task (in the same group as the project), and then

View File

@@ -0,0 +1,61 @@
import { TodoAccount } from "@/1_schema";
import { FormEvent, useState } from "react";
import { useNavigate } from "react-router-dom";
import { generateRandomProject } from "../generate";
export function TaskGenerator() {
const [isGenerating, setIsGenerating] = useState(false);
const navigate = useNavigate();
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const numTasks = Math.max(
1,
parseInt(formData.get("numTasks") as string) || 1,
);
setIsGenerating(true);
const project = generateRandomProject(numTasks);
const { root } = await TodoAccount.getMe().ensureLoaded({
resolve: {
root: {
projects: true,
},
},
});
root.projects.push(project);
navigate(`/project/${project.id}`);
};
return (
<div className="p-4 border rounded-lg shadow-sm bg-white">
<h2 className="text-lg font-semibold mb-4">Generate Random Tasks</h2>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<label htmlFor="numTasks" className="text-sm font-medium">
Number of tasks:
</label>
<input
id="numTasks"
name="numTasks"
type="number"
min="1"
defaultValue={5}
className="w-20 px-2 py-1 border rounded"
/>
</div>
<button
type="submit"
disabled={isGenerating}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-blue-300"
>
{isGenerating ? "Generating..." : "Generate Tasks"}
</button>
</form>
</div>
);
}

View File

@@ -0,0 +1,25 @@
import { faker } from "@faker-js/faker";
import { ListOfTasks, Task, TodoProject } from "./1_schema";
export function generateRandomProject(numTasks: number): TodoProject {
// Generate a random project title
const projectTitle = faker.company.catchPhrase();
// Create a list of tasks
const tasks = ListOfTasks.create([]);
// Generate random tasks
for (let i = 0; i < numTasks; i++) {
const task = Task.create({
done: faker.datatype.boolean(),
text: faker.lorem.sentence({ min: 3, max: 8 }),
});
tasks.push(task);
}
// Create and return the project
return TodoProject.create({
title: projectTitle,
tasks: tasks,
});
}

View File

@@ -1,5 +1,32 @@
# version-history
## 0.0.83
### Patch Changes
- Updated dependencies [439f0fe]
- jazz-tools@0.13.20
- jazz-inspector@0.13.20
- jazz-react@0.13.20
## 0.0.82
### Patch Changes
- Updated dependencies [80530a4]
- jazz-tools@0.13.19
- jazz-inspector@0.13.19
- jazz-react@0.13.19
## 0.0.81
### Patch Changes
- Updated dependencies [761759c]
- jazz-tools@0.13.18
- jazz-inspector@0.13.18
- jazz-react@0.13.18
## 0.0.80
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "version-history",
"private": true,
"version": "0.0.80",
"version": "0.0.83",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -22,7 +22,7 @@ export function JazzLogo({
fillRule="evenodd"
clipRule="evenodd"
d="M136.179 44.8277C136.179 44.8277 136.179 44.8277 136.179 44.8276V21.168C117.931 28.5527 97.9854 32.6192 77.0897 32.6192C65.1466 32.6192 53.5138 31.2908 42.331 28.7737V51.4076C42.331 51.4076 42.331 51.4076 42.331 51.4076V81.1508C41.2955 80.4385 40.1568 79.8458 38.9405 79.3915C36.1732 78.358 33.128 78.0876 30.1902 78.6145C27.2524 79.1414 24.5539 80.4419 22.4358 82.3516C20.3178 84.2613 18.8754 86.6944 18.291 89.3433C17.7066 91.9921 18.0066 94.7377 19.1528 97.2329C20.2991 99.728 22.2403 101.861 24.7308 103.361C27.2214 104.862 30.1495 105.662 33.1448 105.662H33.1455C33.6061 105.662 33.8365 105.662 34.0314 105.659C44.5583 105.449 53.042 96.9656 53.2513 86.4386C53.2534 86.3306 53.2544 86.2116 53.2548 86.0486H53.2552V85.7149L53.2552 85.5521V82.0762L53.2552 53.1993C61.0533 54.2324 69.0092 54.7656 77.0897 54.7656C77.6696 54.7656 78.2489 54.7629 78.8276 54.7574V110.696C77.792 109.983 76.6533 109.391 75.437 108.936C72.6697 107.903 69.6246 107.632 66.6867 108.159C63.7489 108.686 61.0504 109.987 58.9323 111.896C56.8143 113.806 55.3719 116.239 54.7875 118.888C54.2032 121.537 54.5031 124.283 55.6494 126.778C56.7956 129.273 58.7368 131.405 61.2273 132.906C63.7179 134.406 66.646 135.207 69.6414 135.207C70.1024 135.207 70.3329 135.207 70.5279 135.203C81.0548 134.994 89.5385 126.51 89.7478 115.983C89.7517 115.788 89.7517 115.558 89.7517 115.097V111.621L89.7517 54.3266C101.962 53.4768 113.837 51.4075 125.255 48.2397V80.9017C124.219 80.1894 123.081 79.5966 121.864 79.1424C119.097 78.1089 116.052 77.8384 113.114 78.3653C110.176 78.8922 107.478 80.1927 105.36 82.1025C103.242 84.0122 101.799 86.4453 101.215 89.0941C100.631 91.743 100.931 94.4886 102.077 96.9837C103.223 99.4789 105.164 101.612 107.655 103.112C110.145 104.612 113.073 105.413 116.069 105.413C116.53 105.413 116.76 105.413 116.955 105.409C127.482 105.2 135.966 96.7164 136.175 86.1895C136.179 85.9945 136.179 85.764 136.179 85.3029V81.8271L136.179 44.8277Z"
className="fill-primary"
fill="#146AFF"
/>
</svg>
);

View File

@@ -1,5 +1,3 @@
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { ImageResponse } from "next/og";
import { JazzLogo } from "../atoms/logos/JazzLogo";
@@ -10,28 +8,112 @@ export const imageSize = {
export const imageContentType = "image/png";
export default async function OpenGraphImage({ title }: { title: string }) {
const manropeSemiBold = await readFile(
join(process.cwd(), "public/fonts/Manrope-SemiBold.ttf"),
async function loadManropeGoogleFont() {
const url = `https://fonts.googleapis.com/css2?family=Manrope:wght@600`;
const css = await (await fetch(url)).text();
const resource = css.match(
/src: url\((.+)\) format\('(opentype|truetype)'\)/,
);
if (resource) {
const response = await fetch(resource[1]);
if (response.status == 200) {
return await response.arrayBuffer();
}
}
throw new Error("failed to load font data");
}
export async function OpenGraphImage({
title,
framework,
contents,
topic,
subtopic,
}: {
title: string;
framework?: string;
contents?: string[];
topic?: string;
subtopic?: string;
}) {
if (!title) {
throw new Error(
`No title from tocItems in opengraph-image.tsx ${framework} ${topic} ${subtopic}`,
);
}
return new ImageResponse(
<div
style={{
fontSize: "7em",
background: "white",
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "center",
padding: "77px",
letterSpacing: "-0.05em",
}}
>
{title}
<div
style={{ display: "flex", position: "absolute", bottom: 35, right: 45 }}
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "1rem",
fontSize: "4rem",
}}
>
{title}
</div>
<div
style={{
position: "absolute",
right: 15,
top: 10,
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
fontSize: "3rem",
color: "#888888",
letterSpacing: "-0.03em",
}}
>
<div style={{ display: "flex", color: "#BBB", paddingRight: "0.5rem" }}>
{framework}
</div>
{topic && (
<span style={{ color: "#CCC", paddingRight: "0.5rem" }}>
/ {topic}
</span>
)}
{subtopic && <span style={{ color: "#DDD" }}>/ {subtopic}</span>}
</div>
<div
style={{
display: "flex",
flexDirection: "column",
marginTop: "1rem",
gap: "0.2rem",
fontSize: "2rem",
color: "#888888",
letterSpacing: "-0.03em",
}}
>
{contents?.map((content) => (
<div key={content}>{content}</div>
))}
</div>
<div
style={{
display: "flex",
position: "absolute",
bottom: 35,
right: 45,
}}
>
<JazzLogo width={193} height={73} />
</div>
@@ -41,7 +123,9 @@ export default async function OpenGraphImage({ title }: { title: string }) {
fonts: [
{
name: "Manrope",
data: manropeSemiBold,
data: await loadManropeGoogleFont(),
style: "normal",
weight: 600,
},
],
},

View File

@@ -0,0 +1,25 @@
import { OpenGraphImage, imageSize, imageContentType } from '@garden-co/design-system/src/components/organisms/OpenGraphImage';
import { getMdxWithToc } from '@/lib/docMdxContent';
export const title = "Quickstart";
export const size = imageSize;
export const contentType = imageContentType;
export const alt = "Quickstart";
export default async function Image({ params }: { params: Promise<{ framework: string, topic: string, subtopic: string }> }) {
const { framework, topic, subtopic } = await params;
const { tocItems } = await getMdxWithToc(framework, [topic, subtopic]);
const title = tocItems[0]?.value;
if (!title) {
throw new Error(`No title from tocItems in opengraph-image.tsx ${framework} ${topic} ${subtopic}`);
}
return OpenGraphImage({
title: title,
framework,
contents: tocItems[0]?.children?.map((child) => child.value) ?? [],
topic,
subtopic,
});
}

View File

@@ -0,0 +1,26 @@
import { OpenGraphImage, imageSize, imageContentType } from '@garden-co/design-system/src/components/organisms/OpenGraphImage';
import { getMdxWithToc } from '@/lib/docMdxContent';
export const title = "Quickstart";
export const size = imageSize;
export const contentType = imageContentType;
export const alt = "Quickstart";
export default async function Image({ params }: { params: Promise<{ framework: string, topic: string }> }) {
const { framework, topic } = await params;
const { tocItems } = await getMdxWithToc(framework, [topic]);
// console.log('tocItems', tocItems);
const title = tocItems[0]?.value;
if (!title) {
throw new Error(`No title from tocItems in opengraph-image.tsx ${framework} ${topic}`);
}
return OpenGraphImage({
title: title,
framework,
contents: tocItems[0]?.children?.map((child) => child.value) ?? [],
topic,
});
}

View File

@@ -0,0 +1,32 @@
import { getMdxWithToc } from "@/lib/docMdxContent";
import {
OpenGraphImage,
imageSize,
imageContentType,
} from "@garden-co/design-system/src/components/organisms/OpenGraphImage";
export const title = "Quickstart";
export const size = imageSize;
export const contentType = imageContentType;
export const alt = "Jazz Docs | Quickstart";
export default async function Image({
params,
}: {
params: Promise<{ framework: string }>;
}) {
const { framework } = await params;
const { tocItems } = await getMdxWithToc(framework, []);
const title = tocItems[0]?.value;
if (!title) {
throw new Error(`No title from tocItems in opengraph-image.tsx ${framework}`);
}
return OpenGraphImage({
title: title,
framework,
contents: tocItems[0]?.children?.map((child) => child.value) ?? [],
});
}

View File

@@ -516,7 +516,7 @@ const reactExamples: Example[] = [
tech: [tech.react],
features: [features.serverWorker, features.inbox],
illustration: <JazzPaperScissorsIllustration />,
demoUrl: "https://jazz-paper-scissors.vercel.app"
demoUrl: "https://jazz-paper-scissors.vercel.app",
},
{
name: "Clerk",
@@ -552,7 +552,7 @@ const reactExamples: Example[] = [
tech: [tech.react],
features: [features.inviteLink],
illustration: <OrganizationIllustration />,
demoUrl: "https://jazz-organization.vercel.app"
demoUrl: "https://jazz-organization.vercel.app",
},
{
name: "Version history",
@@ -561,7 +561,7 @@ const reactExamples: Example[] = [
"Track and restore previous versions of your data, and see who made the changes.",
tech: [tech.react],
illustration: <VersionHistoryIllustration />,
demoUrl: "https://jazz-version-history.vercel.app"
demoUrl: "https://jazz-version-history.vercel.app",
},
];

View File

@@ -53,6 +53,7 @@ pre.shiki .line {
.twoslash-popup-code pre.shiki .line {
display: inline;
padding-left: 0;
white-space: break-spaces;
}
html.dark .shiki {

View File

@@ -1,5 +1,6 @@
import { marketingCopy } from "@/content/marketingCopy";
import OpenGraphImage, {
import {
OpenGraphImage,
imageSize,
imageContentType,
} from "@garden-co/design-system/src/components/organisms/OpenGraphImage";

View File

@@ -19,8 +19,7 @@ export default function DocsLayout({
navIcon?: IconName;
tocItems?: TocEntry[];
}) {
const tableOfContentsItems =
tocItems?.length && tocItems[0].children ? tocItems[0].children : [];
const tableOfContentsItems = tocItems ?? [];
const navSections: NavSection[] = [
{

View File

@@ -3,13 +3,14 @@
import type { Toc, TocEntry } from "@stefanprobst/rehype-extract-toc";
import { clsx } from "clsx";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
const TocList = ({
items,
level,
currentId,
}: { items: Toc; level: number; currentId: string }) => {
const isActive = (item: TocEntry) => {
if (!item.id) return false;
if (item.id === currentId) return true;
@@ -20,7 +21,7 @@ const TocList = ({
};
return (
<ul className="space-y-2" style={{ paddingLeft: level ? "1rem" : "0" }}>
<ul className="space-y-2" style={{ paddingLeft: (level > 0) ? "1rem" : "0" }}>
{items.map((item) => (
<li key={item.id} className="space-y-2">
{item.id && (
@@ -57,8 +58,10 @@ export function TableOfContents({
}) {
const [currentId, setCurrentId] = useState<string>("");
const itemsUnderH1 = useMemo(() => items[0]?.children || [], [items]);
const getHeadings = useCallback(() => {
return items
return itemsUnderH1
.flatMap((node) => {
const headings = [node];
if (node.children) {
@@ -78,17 +81,17 @@ export function TableOfContents({
return { id: item.id, top };
})
.filter((x): x is { id: string; top: number } => x !== null);
}, [items]);
}, [itemsUnderH1]);
useEffect(() => {
if (items.length === 0) return;
if (itemsUnderH1.length === 0) return;
const onScroll = () => {
const headings = getHeadings();
if (headings.length === 0) return;
const top = window.scrollY;
let current = headings[0].id;
let current = headings[0]?.id;
for (const heading of headings) {
if (top >= heading.top - 500) {
@@ -98,7 +101,7 @@ export function TableOfContents({
}
}
setCurrentId(current);
current && setCurrentId(current);
};
window.addEventListener("scroll", onScroll, { passive: true });
@@ -107,14 +110,14 @@ export function TableOfContents({
return () => {
window.removeEventListener("scroll", onScroll);
};
}, [getHeadings, items]);
}, [getHeadings, itemsUnderH1]);
if (!items.length) return null;
if (!itemsUnderH1.length) return null;
return (
<div className={className}>
<p className="font-medium text-highlight mb-3">On this page</p>
<TocList items={items} level={0} currentId={currentId} />
<TocList items={itemsUnderH1} level={0} currentId={currentId} />
</div>
);
}

View File

@@ -1,6 +1,8 @@
```tsx
<JazzProvider
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
sync={{
peer: "wss://cloud.jazz.tools/?key=you@example.com"
}}
>
{children}
</JazzProvider>

View File

@@ -21,7 +21,7 @@ function Code({
<div
className={clsx(
className,
"w-full h-full relative -right-2 -bottom-1 max-w-full lg:max-w-[480px] overflow-x-auto ml-auto overflow-hidden",
"w-full h-full relative -right-2 -bottom-1 max-w-full lg:max-w-[500px] ml-auto",
"shadow-xl shadow-blue/20",
"rounded-tl-lg border",
"flex-1 bg-white ring ring-4 ring-stone-400/20",
@@ -33,7 +33,9 @@ function Code({
{fileName}
</span>
</div>
<pre className="text-xs lg:text-sm p-1 pb-2">{children}</pre>
<pre className="text-xs lg:text-sm p-1 pb-2 overflow-x-auto">
{children}
</pre>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { CodeGroup } from '@/components/forMdx'
import { CodeGroup, ContentByFramework } from '@/components/forMdx'
import { JazzIcon } from "@garden-co/design-system/src/components/atoms/logos/JazzIcon";
# Jazz Inspector
@@ -9,11 +9,14 @@ For now, you can get your account credentials from the `jazz-logged-in-secret` l
[https://inspector.jazz.tools](https://inspector.jazz.tools)
## Exporting current account to Inspector from your app
<ContentByFramework framework={["react", "svelte", "vue", "vanilla"]}>
## Exporting current account to Inspector from your app [!framework=react,svelte,vue,vanilla]
In development mode, you can launch the Inspector from your Jazz app to inspect your account by pressing `Cmd+J`.
</ContentByFramework>
## Embedding the Inspector widget into your app
<ContentByFramework framework="react">
## Embedding the Inspector widget into your app [!framework=react]
Alternatively, you can embed the Inspector directly into your app, so you don't need to open a separate window.
@@ -40,7 +43,7 @@ import { JazzInspector } from "jazz-inspector";
This will show the Inspector launch button on the right of your page.
### Positioning the Inspector button
### Positioning the Inspector button [!framework=react]
You can also customize the button position with the following options:
@@ -67,3 +70,4 @@ For example:
</div>
Check out the [music player app](https://github.com/garden-co/jazz/blob/main/examples/music-player/src/2_main.tsx) for a full example.
</ContentByFramework>

View File

@@ -4,8 +4,6 @@ import { CodeGroup } from "@/components/forMdx";
# Providers
## Introduction
`<JazzProvider />` is the core component that connects your Expo application to Jazz. It handles:
- **Data Synchronization**: Manages connections to peers and the Jazz cloud

View File

@@ -4,8 +4,6 @@ import { CodeGroup } from "@/components/forMdx";
# Providers
## Introduction
`<JazzProvider />` is the core component that connects your React Native application to Jazz. It handles:
- **Data Synchronization**: Manages connections to peers and the Jazz cloud

View File

@@ -4,8 +4,6 @@ import { CodeGroup } from "@/components/forMdx";
# Providers
## Introduction
`<JazzProvider />` is the core component that connects your React application to Jazz. It handles:
- **Data Synchronization**: Manages connections to peers and the Jazz cloud

View File

@@ -1,99 +1,380 @@
export const metadata = { title: "React" };
export const metadata = { title: "Installation" };
import { CodeGroup } from "@/components/forMdx";
# <span id="react">React</span>
# Installation and Setup
Wrap your application with `<JazzProvider />`, this is where you specify the sync & storage server to connect to (see [Sync and storage](/docs/react/sync-and-storage)).
Add Jazz to your React application in minutes. This setup covers standard React apps, Next.js, and gives an overview of experimental SSR approaches.
Integrating Jazz with React is straightforward. You'll define data schemas that describe your application's structure, then wrap your app with a provider that handles sync and storage. The whole process takes just three steps:
1. [Install dependencies](#install-dependencies)
2. [Write your schema](#write-your-schema)
3. [Wrap your app in `<JazzProvider />`](#standard-react-setup)
Looking for complete examples? Check out our [example applications](/examples) for chat apps, collaborative editors, and more.
## Install dependencies
First, install the required packages:
<CodeGroup>
{/* prettier-ignore */}
```tsx
```bash
pnpm install jazz-react jazz-tools
```
</CodeGroup>
## Write your schema
Define your data schema using [CoValues](/docs/schemas/covalues) from `jazz-tools`.
<CodeGroup>
```tsx twoslash
// schema.ts
import { Account, co } from "jazz-tools";
export class MyAppAccount extends Account {
name = co.string;
}
```
</CodeGroup>
See [CoValues](/docs/schemas/covalues) for more information on how to define your schema.
## Standard React Setup
Wrap your application with `<JazzProvider />` to connect to the Jazz network and define your data schema:
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { Account } from "jazz-tools";
export class MyAppAccount extends Account {}
// @filename: app.tsx
import * as React from "react";
import { createRoot } from 'react-dom/client';
function App() {
return <div>Hello, world!</div>;
}
// ---cut---
// app.tsx
import { JazzProvider } from "jazz-react";
import { MyAppAccount } from "./schema";
ReactDOM.createRoot(document.getElementById("root")!).render(
<JazzProvider // [!code ++:6]
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema={MyAppAccount}
>
<App />
</JazzProvider>
createRoot(document.getElementById("root")!).render(
<JazzProvider
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema={MyAppAccount}
>
<App />
</JazzProvider>
);
// [!code ++:6]
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
// Register your Account schema to enhance TypeScript support
declare module "jazz-react" {
interface Register {
Account: MyAppAccount;
}
interface Register {
Account: MyAppAccount;
}
}
```
</CodeGroup>
This setup handles:
- Connection to the Jazz sync server
- Schema registration for type-safe data handling
- Local storage configuration
With this in place, you're ready to start using Jazz hooks in your components. [Learn how to access and update your data](/docs/using-covalues/subscription-and-loading#subscription-hooks).
## Next.js
## Next.js Integration
### Client-side only
### Client-side Only (Easiest)
The easiest way to use Jazz with Next.JS is to only use it on the client side. You can ensure this by:
- marking the Jazz provider file as `"use client"`
The simplest approach for Next.js is client-side only integration:
<CodeGroup>
{/* prettier-ignore */}
```tsx
"use client" // [!code ++]
```tsx twoslash
// @filename: schema.ts
import { Account } from "jazz-tools";
export class MyAppAccount extends Account {}
// @filename: app.tsx
import * as React from "react";
// ---cut---
// app.tsx
"use client" // Mark as client component
import { JazzProvider } from "jazz-react";
import { MyAppAccount } from "./schema";
export function MyJazzProvider(props: { children: React.ReactNode }) {
return (
<JazzProvider
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema={MyAppAccount}
>
{props.children}
</JazzProvider>
);
export function JazzWrapper({ children }: { children: React.ReactNode }) {
return (
<JazzProvider
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
AccountSchema={MyAppAccount}
>
{children}
</JazzProvider>
);
}
```
</CodeGroup>
- marking any file with components where you use Jazz hooks (such as `useAccount` or `useCoState`) as `"use client"`
Remember to mark any component that uses Jazz hooks with `"use client"`:
### SSR use (experimental)
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { Account, co } from "jazz-tools";
Pure SSR use of Jazz is basically just using jazz-nodejs (see [Node.JS / Server Workers](/docs/react/project-setup/server-side)) inside Server Components.
export class MyAppAccount extends Account {
name = co.string;
}
// @filename: Profile.tsx
import * as React from "react";
import { MyAppAccount } from "./schema";
Instead of using hooks as you would on the client, you await promises returned by `CoValue.load(...)` inside your Server Components.
declare module "jazz-react" {
interface Register {
Account: MyAppAccount;
}
}
// ---cut---
// Profile.tsx
"use client"; // [!code ++]
TODO: code example
import { useAccount } from "jazz-react";
This should work well for cases like rendering publicly-readable information, since the worker account will be able to load them.
export function Profile() {
const { me } = useAccount();
return <div>Hello, {me?.name}</div>;
}
```
</CodeGroup>
In the future, it will be possible to use trusted auth methods (such as Clerk, Auth0, etc.) that let you act as the same Jazz user both on the client and on the server, letting you use SSR even for data private to that user.
### SSR Support (Experimental)
### SSR + client-side (experimental)
For server-side rendering, Jazz offers experimental approaches:
You can combine the two approaches by creating
- Pure SSR
- Hybrid SSR + Client Hydration
1. A pure "rendering" component that renders an already-loaded CoValue (in JSON-ified form)
#### Pure SSR
TODO: code example
Use Jazz in server components by directly loading data with `CoValue.load()`.
2. A "hydrating" component (with `"use client"`) that
{/*
<CodeGroup>
```tsx twoslash
// @errors: 18047
// @filename: schema.ts
import { co, CoList, CoMap } from "jazz-tools";
- expects a pre-loaded CoValue as a prop (in JSON-ified form)
- uses one of the client-side Jazz hooks (such as `useAccount` or `useCoState`) to subscribe to that same CoValue
- passing the client-side subscribed state to the "rendering" component, with the pre-loaded CoValue as a fallback until the client receives the first subscribed state
export class MyItem extends CoMap {
title = co.string;
}
TODO: code example
export class MyCollection extends CoList.Of(co.ref(MyItem)) {}
// @filename: PublicData.tsx
import * as React from "react";
import { ID } from "jazz-tools";
const collectionID = "co_z123" as ID<MyCollection>;
// ---cut---
// Server Component (no "use client" directive)
import { MyCollection, MyItem } from "./schema";
3. A "pre-loading" Server Component that
export default async function PublicData() {
// Load data directly in the server component
const items = await MyCollection.load(collectionID);
if (!items) {
return <div>Loading...</div>;
}
return (
<ul>
{items.map(item => (
item ? <li key={item.id}>{item.title}</li> : null
))}
</ul>
);
}
```
</CodeGroup>
*/}
- pre-loads the CoValue by awaiting it's `load(...)` method (as described above)
- renders the "hydrating" component, passing the pre-loaded CoValue as a prop
This works well for public data accessible to the server account.
TODO: code example
#### Hybrid SSR + Client Hydration
For more complex cases, you can pre-render on the server and hydrate on the client:
1. Create a shared rendering component.
{/*
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { co, CoList, CoMap } from "jazz-tools";
export class MyItem extends CoMap {
title = co.string;
}
// @filename: ItemList.tsx
import * as React from "react";
import { MyItem } from "./schema";
// ---cut---
// ItemList.tsx - works in both server and client contexts
export function ItemList({ items }: { items: MyItem[] }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}
```
</CodeGroup>
*/}
2. Create a client hydration component.
{/*
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { co, CoList, CoMap } from "jazz-tools";
export class MyItem extends CoMap {
title = co.string;
}
export class MyCollection extends CoList.Of(co.ref(MyItem)) {}
// @filename: ItemList.tsx
import * as React from "react";
import { MyItem } from "./schema";
export function ItemList({ items }: { items: MyItem[] }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}
// @filename: ItemListHydrator.tsx
// ItemListHydrator.tsx
import * as React from "react";
import { useCoState } from "jazz-react";
import { ID } from "jazz-tools";
const myCollectionID = "co_z123" as ID<MyCollection>;
// ---cut---
"use client"
import { MyCollection, MyItem } from "./schema";
import { ItemList } from "./ItemList";
export function ItemListHydrator({ initialItems }: { initialItems: MyItem[] }) {
// Hydrate with real-time data once client loads
const myCollection = useCoState(MyCollection, myCollectionID);
// Filter out nulls for type safety
const items = Array.from(myCollection?.values() || []).filter(
(item): item is MyItem => !!item
);
// Use server data until client data is available
const displayItems = items || initialItems;
return <ItemList items={displayItems} />;
}
```
</CodeGroup>
*/}
3. Create a server component that pre-loads data.
{/*
<CodeGroup>
```tsx twoslash
// @filename: schema.ts
import { co, CoList, CoMap } from "jazz-tools";
export class MyItem extends CoMap {
title = co.string;
}
export class MyCollection extends CoList.Of(co.ref(MyItem)) {}
// @filename: ItemList.tsx
import * as React from "react";
import { MyItem } from "./schema";
export function ItemList({ items }: { items: MyItem[] }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
);
}
// @filename: ItemListHydrator.tsx
// ItemListHydrator.tsx
import * as React from "react";
import { useCoState } from "jazz-react";
import { ID } from "jazz-tools";
const myCollectionID = "co_z123" as ID<MyCollection>;
// ---cut---
"use client"
import { MyCollection, MyItem } from "./schema";
import { ItemList } from "./ItemList";
export function ItemListHydrator({ initialItems }: { initialItems: MyItem[] }) {
// Hydrate with real-time data once client loads
const myCollection = useCoState(MyCollection, myCollectionID);
// Filter out nulls for type safety
const items = Array.from(myCollection?.values() || []).filter(
(item): item is MyItem => !!item
);
// Use server data until client data is available
const displayItems = items || initialItems;
return <ItemList items={displayItems} />;
}
// @filename: ServerItemPage.tsx
import * as React from 'react';
import { ID } from "jazz-tools";
import { MyCollection, MyItem } from "./schema";
import { ItemListHydrator } from "./ItemListHydrator";
const myCollectionID = "co_z123" as ID<MyCollection>;
// ---cut---
// ServerItemPage.tsx
export default async function ServerItemPage() {
// Pre-load data on the server
const initialItems = await MyCollection.load(myCollectionID);
// Filter out nulls for type safety
const items = Array.from(initialItems?.values() || []).filter(
(item): item is MyItem => !!item
);
// Pass to client hydrator
return <ItemListHydrator initialItems={items} />;
}
```
</CodeGroup>
*/}
This approach gives you the best of both worlds: fast initial loading with server rendering, plus real-time updates on the client.
## Further Reading
- [Schemas](/docs/schemas/covalues) - Learn about defining your data model
- [Provider Configuration](/docs/project-setup/providers) - Learn about other configuration options for Providers
- [Authentication](/docs/authentication/overview) - Set up user authentication
- [Sync and Storage](/docs/sync-and-storage) - Learn about data persistence

View File

@@ -12,7 +12,7 @@ As their name suggests, CoValues are inherently collaborative, meaning **multipl
- CoValues keep their full edit histories, from which they derive their "current state".
- The fact that this happens in an eventually-consistent way makes them [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type).
- Having the full history also means that you often don't need explicit timestamps and author info - you get this for free as part of a CoValue's [edit metadata](/docs/using-covalues/metadata).
- Having the full history also means that you often don't need explicit timestamps and author info - you get this for free as part of a CoValue's [edit metadata](/docs/using-covalues/history).
CoValues model JSON with CoMaps and CoLists, but also offer CoFeeds for simple per-user value feeds, and let you represent binary data with FileStreams.
@@ -31,11 +31,14 @@ Even before you know the details of how your app will work, you'll probably know
Jazz makes it quick to declare schemas, since they are simple TypeScript classes:
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { co, CoMap, CoList } from "jazz-tools";
class ListOfTasks extends CoList.Of(co.string) {}
// ---cut---
// schema.ts
export class TodoProject extends CoMap {
title = co.string;
tasks = co.ref(ListOfTasks);
title = co.string;
tasks = co.ref(ListOfTasks);
}
```
</CodeGroup>
@@ -45,16 +48,28 @@ Here you can see how we extend a CoValue type and use `co` for declaring (collab
Classes might look old-fashioned, but Jazz makes use of them being both types and values in TypeScript, letting you refer to either with a single definition and import.
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
// @filename: schema.ts
import { co, CoMap, CoList } from "jazz-tools";
export class ListOfTasks extends CoList.Of(co.string) {}
export class TodoProject extends CoMap {
title = co.string;
tasks = co.ref(ListOfTasks);
}
// @filename: app.ts
import { Group } from "jazz-tools";
// ---cut---
// app.ts
import { TodoProject, ListOfTasks } from "./schema";
const project: TodoProject = TodoProject.create(
{
title: "New Project",
tasks: ListOfTasks.create([], Group.create()),
},
Group.create()
{
title: "New Project",
tasks: ListOfTasks.create([], Group.create()),
},
Group.create()
);
```
</CodeGroup>
@@ -63,17 +78,18 @@ const project: TodoProject = TodoProject.create(
### `CoMap` (declaration)
CoMaps are the most commonly used type of CoValue. They are the equivalent of JSON objects. (Collaborative editing follows a last-write-wins strategy per-key.)
CoMaps are the most commonly used type of CoValue. They are the equivalent of JSON objects (Collaborative editing follows a last-write-wins strategy per-key).
You can either declare struct-like CoMaps:
<CodeGroup>
{/* prettier-ignore */}
```ts
class Person extends CoMap {
name = co.string;
age = co.number;
pet = co.optional.ref(Pet);
```ts twoslash
// schema.ts
import { co, CoMap } from "jazz-tools";
// ---cut---
export class Task extends CoMap {
title = co.string;
completed = co.boolean;
}
```
</CodeGroup>
@@ -81,8 +97,13 @@ class Person extends CoMap {
Or record-like CoMaps (key-value pairs, where keys are always `string`):
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { co, CoMap } from "jazz-tools";
class Fruit extends CoMap {
name = co.string;
color = co.string;
}
// ---cut---
class ColorToHex extends CoMap.Record(co.string) {}
class ColorToFruit extends CoMap.Record(co.ref(Fruit)) {}
@@ -90,10 +111,10 @@ class ColorToFruit extends CoMap.Record(co.ref(Fruit)) {}
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/creation#comap-creation),
See the corresponding sections for [creating](/docs/using-covalues/comaps#creating-comaps),
[subscribing/loading](/docs/using-covalues/subscription-and-loading),
[reading from](/docs/using-covalues/reading#comap-reading) and
[writing to](/docs/using-covalues/writing#comap-writing) CoMaps.
[reading from](/docs/using-covalues/comaps#reading-from-comaps) and
[updating](/docs/using-covalues/comaps#updating-comaps) CoMaps.
### `CoList` (declaration)
@@ -102,38 +123,48 @@ CoLists are ordered lists and are the equivalent of JSON arrays. (They support c
You define them by specifying the type of the items they contain:
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { co, CoList, CoMap } from "jazz-tools";
class Task extends CoMap {
title = co.string;
completed = co.boolean;
}
// ---cut---
class ListOfColors extends CoList.Of(co.string) {}
class ListOfTasks extends CoList.Of(co.ref(Task)) {}
```
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/creation#colist-creation),
See the corresponding sections for [creating](/docs/using-covalues/colists#creating-colists),
[subscribing/loading](/docs/using-covalues/subscription-and-loading),
[reading from](/docs/using-covalues/reading#colist-reading) and
[writing to](/docs/using-covalues/writing#colist-writing) CoLists.
[reading from](/docs/using-covalues/colists#reading-from-colists) and
[updating](/docs/using-covalues/colists#updating-colists) CoLists.
### `CoFeed` (declaration)
CoFeeds are a special CoValue type that represent a feed of values for a set of users / sessions. (Each session of a user gets its own append-only feed.)
CoFeeds are a special CoValue type that represent a feed of values for a set of users/sessions (Each session of a user gets its own append-only feed).
They allow easy access of the latest or all items belonging to a user or their sessions. This makes them particularly useful for user presence, reactions, notifications, etc.
You define them by specifying the type of feed item:
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { co, CoFeed, CoMap } from "jazz-tools";
class Task extends CoMap {
title = co.string;
completed = co.boolean;
}
// ---cut---
class FeedOfTasks extends CoFeed.Of(co.ref(Task)) {}
```
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/creation#cofeed-creation),
See the corresponding sections for [creating](/docs/using-covalues/cofeeds#creating-cofeeds),
[subscribing/loading](/docs/using-covalues/subscription-and-loading),
[reading from](/docs/using-covalues/reading#cofeed-reading) and
[writing to](/docs/using-covalues/writing#cofeed-writing) CoFeeds.
[reading from](/docs/using-covalues/cofeeds#reading-from-cofeeds) and
[writing to](/docs/using-covalues/cofeeds#writing-to-cofeeds) CoFeeds.
### `FileStream` (declaration)
@@ -144,21 +175,22 @@ They allow you to upload and reference files, images, etc.
You typically don't need to declare or extend them yourself, you simply refer to the built-in `FileStream` from another CoValue:
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { co, CoMap } from "jazz-tools";
// ---cut---
import { FileStream } from "jazz-tools";
class UserProfile extends CoMap {
name = co.string;
avatar = co.ref(FileStream);
class Document extends CoMap {
title = co.string;
file = co.ref(FileStream);
}
```
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/creation#filestream-creation),
See the corresponding sections for [creating](/docs/using-covalues/filestreams#creating-filestreams),
[subscribing/loading](/docs/using-covalues/subscription-and-loading),
[reading from](/docs/using-covalues/reading#filestream-reading) and
[writing to](/docs/using-covalues/writing#filestream-writing) FileStreams.
[reading from](/docs/using-covalues/filestreams#reading-from-filestreams) and
[writing to](/docs/using-covalues/filestreams#writing-to-filestreams) FileStreams.
### `SchemaUnion` (declaration)
@@ -167,8 +199,9 @@ SchemaUnion is a helper type that allows you to load and refer to multiple subcl
You declare them with a base class type and discriminating lambda, in which you have access to the `RawCoMap`, on which you can call `get` with the field name to get the discriminating value.
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { co } from "jazz-tools";
// ---cut---
import { SchemaUnion, CoMap } from "jazz-tools";
class BaseWidget extends CoMap {
@@ -196,9 +229,9 @@ const WidgetUnion = SchemaUnion.Of<BaseWidget>((raw) => {
```
</CodeGroup>
See the corresponding sections for [creating](/docs/using-covalues/creation#schemaunion-creation),
See the corresponding sections for [creating](/docs/using-covalues/schemaunions#creating-schemaunions),
[subscribing/loading](/docs/using-covalues/subscription-and-loading) and
[narrowing](/docs/using-covalues/reading#schemaunion-narrowing) SchemaUnions.
[narrowing](/docs/using-covalues/schemaunions#narrowing) SchemaUnions.
## CoValue field/item types
@@ -209,12 +242,11 @@ Now that we've seen the different types of CoValues, let's see more precisely ho
You can declare primitive field types using the `co` declarer:
<CodeGroup>
{/* prettier-ignore */}
```ts
import { co } from "jazz-tools";
```ts twoslash
import { co, CoMap, CoList } from "jazz-tools";
export class Person extends CoMap {
title = co.string;
title = co.string;
}
export class ListOfColors extends CoList.Of(co.string) {}
@@ -224,23 +256,25 @@ export class ListOfColors extends CoList.Of(co.string) {}
Here's a quick overview of the primitive types you can use:
<CodeGroup>
{/* prettier-ignore */}
```ts
co.string;
co.number;
co.boolean;
co.null;
co.Date;
co.literal("waiting", "ready");
```ts twoslash
import { co } from "jazz-tools";
// ---cut---
co.string; // For simple strings
co.number; // For numbers
co.boolean; // For booleans
co.null; // For null
co.Date; // For dates
co.literal("waiting", "ready"); // For enums
```
</CodeGroup>
Finally, for more complex JSON data, that you *don't want to be collaborative internally* (but only ever update as a whole), you can use `co.json<T>()`:
<CodeGroup>
{/* prettier-ignore */}
```ts
co.json<{ name: string }>();
```ts twoslash
import { co } from "jazz-tools";
// ---cut---
co.json<{ name: string }>(); // For JSON objects
```
</CodeGroup>
@@ -257,13 +291,18 @@ The important caveat here is that **a referenced CoValue might or might not be l
In Schemas, you declare Refs using the `co.ref<T>()` declarer:
<CodeGroup>
{/* prettier-ignore */}
```ts
class Company extends CoMap {
members = co.ref(ListOfPeople);
```ts twoslash
import { co, CoMap, CoList } from "jazz-tools";
class Person extends CoMap {
name = co.string;
}
// ---cut---
// schema.ts
class ListOfPeople extends CoList.Of(co.ref(Person)) {}
class Company extends CoMap {
members = co.ref(ListOfPeople);
}
```
</CodeGroup>
@@ -272,10 +311,14 @@ class ListOfPeople extends CoList.Of(co.ref(Person)) {}
⚠️ If you want to make a referenced CoValue field optional, you *have to* use `co.optional.ref<T>()`: ⚠️
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { co, CoMap } from "jazz-tools";
class Pet extends CoMap {
name = co.string;
}
// ---cut---
class Person extends CoMap {
pet = co.optional.ref(Pet);
pet = co.optional.ref(Pet);
}
```
</CodeGroup>
@@ -285,20 +328,25 @@ class Person extends CoMap {
Since CoValue schemas are based on classes, you can easily add computed fields and methods:
<CodeGroup>
{/* prettier-ignore */}
```ts
```ts twoslash
import { co, CoMap } from "jazz-tools";
function differenceInYears(date1: Date, date2: Date) {
const diffTime = Math.abs(date1.getTime() - date2.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24 * 365.25));
}
// ---cut---
class Person extends CoMap {
firstName = co.string;
lastName = co.string;
dateOfBirth = co.Date;
firstName = co.string;
lastName = co.string;
dateOfBirth = co.Date;
get name() {
return `${this.firstName} ${this.lastName}`;
}
get name() {
return `${this.firstName} ${this.lastName}`;
}
ageAsOf(date: Date) {
return differenceInYears(date, this.dateOfBirth);
}
ageAsOf(date: Date) {
return differenceInYears(date, this.dateOfBirth);
}
}
```
</CodeGroup>

View File

@@ -39,7 +39,6 @@ For files from input elements or drag-and-drop interfaces, use `createFromBlob`:
<CodeGroup>
```ts twoslash
// @errors: 18047
import { FileStream, Group } from "jazz-tools";
const myGroup = Group.create();
const progressBar: HTMLElement = document.querySelector('.progress-bar')!;

View File

@@ -42,7 +42,7 @@ export async function getDocMetadata(framework: string, slug?: string[]) {
function DocProse({ children }: { children: React.ReactNode }) {
return (
<Prose className="overflow-x-hidden lg:overflow-x-visible lg:flex-1 pb-8 pt-[calc(61px+2rem)] md:pt-8 md:max-w-3xl mx-auto">
<Prose className="overflow-hidden pb-8 pt-[calc(61px+2rem)] md:pt-8 md:max-w-3xl mx-auto">
{children}
</Prose>
);
@@ -55,23 +55,8 @@ export async function DocPage({
framework: string;
slug?: string[];
}) {
const slugPath = slug?.join("/");
try {
const mdxSource = await getMdxSource(framework, slugPath);
const {
default: Content,
tableOfContents = [],
headingsFrameworkVisibility = {},
} = mdxSource;
// Remove items that should not be shown for the current framework
const tocItems = (tableOfContents as Toc).filter(({ id }) =>
id && id in headingsFrameworkVisibility
? headingsFrameworkVisibility[id]?.includes(framework)
: true,
);
const { Content, tocItems } = await getMdxWithToc(framework, slug);
return (
<DocsLayout nav={<DocNav />} tocItems={tocItems}>
@@ -93,3 +78,50 @@ export async function DocPage({
);
}
}
export async function getMdxWithToc(framework: string, slug?: string[]) {
const slugPath = slug?.join("/");
const mdxSource = await getMdxSource(framework, slugPath);
const {
default: Content,
tableOfContents,
headingsFrameworkVisibility,
} = mdxSource;
// Remove items that should not be shown for the current framework
const tocItems = filterTocItemsForFramework(
tableOfContents as Toc,
framework,
headingsFrameworkVisibility
);
return {
Content,
tocItems,
};
}
function filterTocItemsForFramework(
tocItems: Toc,
framework: string,
headingsFrameworkVisibility: Record<string, string[]>
): Toc {
return tocItems
.map(item => {
const isVisible =
!item.id || !(item.id in headingsFrameworkVisibility) ||
headingsFrameworkVisibility[item.id]?.includes(framework);
if (!isVisible) return null;
const filteredChildren = item.children
? filterTocItemsForFramework(item.children, framework, headingsFrameworkVisibility)
: [];
return {
...item,
children: filteredChildren,
};
})
.filter(Boolean) as Toc;
}

View File

@@ -1,5 +1,28 @@
# cojson-storage-indexeddb
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson-storage@0.13.20
- cojson@0.13.20
## 0.13.18
### Patch Changes
- Updated dependencies [9089252]
- Updated dependencies [b470f63]
- Updated dependencies [8b2df0e]
- Updated dependencies [66373ba]
- Updated dependencies [f24cad1]
- cojson@0.13.18
- cojson-storage@0.13.18
## 0.13.17
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage-indexeddb",
"version": "0.13.17",
"version": "0.13.20",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",

View File

@@ -44,10 +44,7 @@ export class IDBNode {
}
static async asPeer(
{
trace,
localNodeName = "local",
}: { trace?: boolean; localNodeName?: string } | undefined = {
{ localNodeName = "local" }: { localNodeName?: string } | undefined = {
localNodeName: "local",
},
): Promise<Peer> {
@@ -57,7 +54,6 @@ export class IDBNode {
{
peer1role: "client",
peer2role: "storage",
trace,
crashOnClose: true,
},
);

View File

@@ -1,4 +1,4 @@
import { ControlledAgent, LocalNode } from "cojson";
import { LocalNode } from "cojson";
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
import { expect, test } from "vitest";
import { IDBStorage } from "../index.js";
@@ -9,7 +9,7 @@ test("Should be able to initialize and load from empty DB", async () => {
const agentSecret = Crypto.newRandomAgentSecret();
const node = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
@@ -25,7 +25,7 @@ test("Should be able to sync data to database and then load that from a new node
const agentSecret = Crypto.newRandomAgentSecret();
const node1 = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
@@ -43,7 +43,7 @@ test("Should be able to sync data to database and then load that from a new node
await new Promise((resolve) => setTimeout(resolve, 200));
const node2 = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);

View File

@@ -1,5 +1,28 @@
# cojson-storage-sqlite
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson-storage@0.13.20
- cojson@0.13.20
## 0.13.18
### Patch Changes
- Updated dependencies [9089252]
- Updated dependencies [b470f63]
- Updated dependencies [8b2df0e]
- Updated dependencies [66373ba]
- Updated dependencies [f24cad1]
- cojson@0.13.18
- cojson-storage@0.13.18
## 0.13.17
### Patch Changes

View File

@@ -1,13 +1,13 @@
{
"name": "cojson-storage-sqlite",
"type": "module",
"version": "0.13.17",
"version": "0.13.20",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"dependencies": {
"better-sqlite3": "^11.7.0",
"cojson": "workspace:0.13.17",
"cojson": "workspace:0.13.20",
"cojson-storage": "workspace:*"
},
"devDependencies": {

View File

@@ -56,17 +56,15 @@ export class SQLiteNode {
static async asPeer({
filename,
trace,
localNodeName = "local",
}: {
filename: string;
trace?: boolean;
localNodeName?: string;
}): Promise<Peer> {
const [localNodeAsPeer, storageAsPeer] = cojsonInternals.connectedPeers(
localNodeName,
"storage",
{ peer1role: "client", peer2role: "storage", trace, crashOnClose: true },
{ peer1role: "client", peer2role: "storage", crashOnClose: true },
);
await SQLiteNode.open(

View File

@@ -1,9 +1,8 @@
import { assert } from "node:console";
import { randomUUID } from "node:crypto";
import { unlinkSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { ControlledAgent, LocalNode } from "cojson";
import { LocalNode } from "cojson";
import { SyncManager } from "cojson-storage";
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
import { expect, onTestFinished, test, vi } from "vitest";
@@ -34,7 +33,7 @@ test("Should be able to initialize and load from empty DB", async () => {
const agentSecret = Crypto.newRandomAgentSecret();
const node = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
@@ -50,7 +49,7 @@ test("should sync and load data from storage", async () => {
const agentSecret = Crypto.newRandomAgentSecret();
const node1 = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
@@ -80,14 +79,16 @@ test("should sync and load data from storage", async () => {
).toMatchInlineSnapshot(`
[
"client -> CONTENT Group header: true new: After: 0 New: 3",
"storage -> KNOWN Group sessions: header/3",
"client -> CONTENT Map header: true new: After: 0 New: 1",
"storage -> KNOWN Map sessions: header/1",
]
`);
node1Sync.restore();
const node2 = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
@@ -131,7 +132,7 @@ test("should load dependencies correctly (group inheritance)", async () => {
const agentSecret = Crypto.newRandomAgentSecret();
const node1 = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
@@ -165,15 +166,18 @@ test("should load dependencies correctly (group inheritance)", async () => {
).toMatchInlineSnapshot(`
[
"client -> CONTENT ParentGroup header: true new: After: 0 New: 4",
"storage -> KNOWN ParentGroup sessions: header/4",
"client -> CONTENT Group header: true new: After: 0 New: 5",
"storage -> KNOWN Group sessions: header/5",
"client -> CONTENT Map header: true new: After: 0 New: 1",
"storage -> KNOWN Map sessions: header/1",
]
`);
node1Sync.restore();
const node2 = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
@@ -214,11 +218,91 @@ test("should load dependencies correctly (group inheritance)", async () => {
`);
});
test("should not send the same dependency value twice", async () => {
const agentSecret = Crypto.newRandomAgentSecret();
const node1 = new LocalNode(
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
const node1Sync = trackMessages(node1);
const { peer, dbPath } = await createSQLiteStorage();
node1.syncManager.addPeer(peer);
const group = node1.createGroup();
const parentGroup = node1.createGroup();
group.extend(parentGroup);
const mapFromParent = parentGroup.createMap();
const map = group.createMap();
map.set("hello", "world");
mapFromParent.set("hello", "world");
await new Promise((resolve) => setTimeout(resolve, 200));
node1Sync.restore();
const node2 = new LocalNode(
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
const node2Sync = trackMessages(node2);
const { peer: peer2 } = await createSQLiteStorage(dbPath);
node2.syncManager.addPeer(peer2);
await node2.load(map.id);
await node2.load(mapFromParent.id);
expect(node2.expectCoValueLoaded(map.id)).toBeTruthy();
expect(node2.expectCoValueLoaded(mapFromParent.id)).toBeTruthy();
expect(node2.expectCoValueLoaded(group.id)).toBeTruthy();
expect(node2.expectCoValueLoaded(parentGroup.id)).toBeTruthy();
expect(
toSimplifiedMessages(
{
Map: map.core,
Group: group.core,
ParentGroup: parentGroup.core,
MapFromParent: mapFromParent.core,
},
node2Sync.messages,
),
).toMatchInlineSnapshot(`
[
"client -> LOAD Map sessions: empty",
"storage -> KNOWN ParentGroup sessions: header/4",
"storage -> CONTENT ParentGroup header: true new: After: 0 New: 4",
"storage -> KNOWN Group sessions: header/5",
"storage -> CONTENT Group header: true new: After: 0 New: 5",
"client -> KNOWN ParentGroup sessions: header/4",
"storage -> KNOWN Map sessions: header/1",
"storage -> CONTENT Map header: true new: After: 0 New: 1",
"client -> KNOWN Group sessions: header/5",
"client -> KNOWN Map sessions: header/1",
"client -> LOAD MapFromParent sessions: empty",
"storage -> KNOWN MapFromParent sessions: header/1",
"storage -> CONTENT MapFromParent header: true new: After: 0 New: 1",
"client -> KNOWN MapFromParent sessions: header/1",
]
`);
});
test("should recover from data loss", async () => {
const agentSecret = Crypto.newRandomAgentSecret();
const node1 = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);
@@ -263,17 +347,20 @@ test("should recover from data loss", async () => {
).toMatchInlineSnapshot(`
[
"client -> CONTENT Group header: true new: After: 0 New: 3",
"storage -> KNOWN Group sessions: header/3",
"client -> CONTENT Map header: true new: After: 0 New: 1",
"storage -> KNOWN Map sessions: header/1",
"client -> CONTENT Map header: false new: After: 3 New: 1",
"storage -> KNOWN CORRECTION Map sessions: header/1",
"client -> CONTENT Map header: false new: After: 1 New: 3",
"storage -> KNOWN Map sessions: header/4",
]
`);
node1Sync.restore();
const node2 = new LocalNode(
new ControlledAgent(agentSecret, Crypto),
agentSecret,
Crypto.newRandomSessionID(Crypto.getAgentID(agentSecret)),
Crypto,
);

View File

@@ -1,5 +1,27 @@
# cojson-storage
## 0.13.20
### Patch Changes
- adfc9a6: Make waitForSync work on storage peers by handling optimistic/known states
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson@0.13.20
## 0.13.18
### Patch Changes
- 8b2df0e: Optimized the dependency push from storage to send a given dependency only once
- Updated dependencies [9089252]
- Updated dependencies [b470f63]
- Updated dependencies [66373ba]
- Updated dependencies [f24cad1]
- cojson@0.13.18
## 0.13.17
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "cojson-storage",
"version": "0.13.17",
"version": "0.13.20",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",

View File

@@ -23,6 +23,8 @@ export class SyncManager {
private readonly toLocalNode: OutgoingSyncQueue;
private readonly dbClient: DBClientInterface;
private loadedCoValues = new Set<RawCoID>();
constructor(dbClient: DBClientInterface, toLocalNode: OutgoingSyncQueue) {
this.toLocalNode = toLocalNode;
this.dbClient = dbClient;
@@ -149,6 +151,8 @@ export class SyncManager {
}),
);
this.loadedCoValues.add(coValueRow.id);
const dependedOnCoValuesList = getDependedOnCoValues({
coValueRow,
newContentMessages,
@@ -167,8 +171,12 @@ export class SyncManager {
};
await Promise.all(
dependedOnCoValuesList.map((dependedOnCoValue) =>
this.collectCoValueData(
dependedOnCoValuesList.map((dependedOnCoValue) => {
if (this.loadedCoValues.has(dependedOnCoValue)) {
return;
}
return this.collectCoValueData(
{
id: dependedOnCoValue,
header: false,
@@ -176,8 +184,8 @@ export class SyncManager {
},
messageMap,
asDependencyOf || coValueRow.id,
),
),
);
}),
);
return messageMap;
@@ -229,7 +237,13 @@ export class SyncManager {
if ((sessionRow?.lastIdx || 0) < (msg.new[sessionID]?.after || 0)) {
invalidAssumptions = true;
} else {
return this.putNewTxs(msg, sessionID, sessionRow, storedCoValueRowID);
const newLastIdx = await this.putNewTxs(
msg,
sessionID,
sessionRow,
storedCoValueRowID,
);
ourKnown.sessions[sessionID] = newLastIdx;
}
});
}
@@ -240,6 +254,11 @@ export class SyncManager {
...ourKnown,
isCorrection: invalidAssumptions,
});
} else {
this.sendStateMessage({
action: "known",
...ourKnown,
});
}
}
@@ -302,11 +321,13 @@ export class SyncManager {
});
}
return Promise.all(
await Promise.all(
actuallyNewTransactions.map((newTransaction, i) =>
this.dbClient.addTransaction(sessionRowID, nextIdx + i, newTransaction),
),
);
return newLastIdx;
}
handleKnown(_msg: CojsonInternalTypes.KnownStateMessage) {

View File

@@ -291,7 +291,7 @@ describe("DB sync manager", () => {
});
});
test("Saves new transaction without sending message when IDB has fewer transactions", async () => {
test("Saves new transaction and sends an ack message as response", async () => {
DBClient.prototype.getCoValue.mockResolvedValueOnce({
id: coValueIdToLoad,
header: coValueHeader,
@@ -314,7 +314,12 @@ describe("DB sync manager", () => {
incomingTxCount,
);
expect(syncManager.sendStateMessage).not.toBeCalled();
expect(syncManager.sendStateMessage).toBeCalledWith({
action: "known",
header: true,
id: coValueIdToLoad,
sessions: expect.any(Object),
});
});
test("Sends correction message when peer sends a message far ahead of our state due to invalid assumption", async () => {

View File

@@ -1,5 +1,25 @@
# cojson-transport-nodejs-ws
## 0.13.20
### Patch Changes
- Updated dependencies [adfc9a6]
- Updated dependencies [1389207]
- Updated dependencies [d6e143e]
- Updated dependencies [3e6229d]
- cojson@0.13.20
## 0.13.18
### Patch Changes
- Updated dependencies [9089252]
- Updated dependencies [b470f63]
- Updated dependencies [66373ba]
- Updated dependencies [f24cad1]
- cojson@0.13.18
## 0.13.17
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "cojson-transport-ws",
"type": "module",
"version": "0.13.17",
"version": "0.13.20",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "MIT",

View File

@@ -26,7 +26,7 @@ describe("WebSocket Peer Integration", () => {
// Create client node
const clientAgent = crypto.newRandomAgentSecret();
const clientNode = new LocalNode(
new ControlledAgent(clientAgent, crypto),
clientAgent,
crypto.newRandomSessionID(crypto.getAgentID(clientAgent)),
crypto,
);
@@ -66,7 +66,7 @@ describe("WebSocket Peer Integration", () => {
test("should sync data between nodes through WebSocket connection", async () => {
const clientAgent = crypto.newRandomAgentSecret();
const clientNode = new LocalNode(
new ControlledAgent(clientAgent, crypto),
clientAgent,
crypto.newRandomSessionID(crypto.getAgentID(clientAgent)),
crypto,
);
@@ -99,7 +99,7 @@ describe("WebSocket Peer Integration", () => {
test("should handle disconnection and cleanup", async () => {
const clientAgent = crypto.newRandomAgentSecret();
const clientNode = new LocalNode(
new ControlledAgent(clientAgent, crypto),
clientAgent,
crypto.newRandomSessionID(crypto.getAgentID(clientAgent)),
crypto,
);
@@ -134,7 +134,7 @@ describe("WebSocket Peer Integration", () => {
test("should trigger a timeout if the server does not respond", async () => {
const clientAgent = crypto.newRandomAgentSecret();
const clientNode = new LocalNode(
new ControlledAgent(clientAgent, crypto),
clientAgent,
crypto.newRandomSessionID(crypto.getAgentID(clientAgent)),
crypto,
);

Some files were not shown because too many files have changed in this diff Show More