Compare commits

..

10 Commits

Author SHA1 Message Date
pax
caa6c147c8 Merge pull request #1490 from garden-co/changeset-release/main
Version Packages
2025-02-24 18:40:15 +02:00
github-actions[bot]
4da51e8f9c Version Packages 2025-02-24 16:37:25 +00:00
pax
a2076b179b Merge pull request #1488 from garden-co/cursor-docs
Cursor docs
2025-02-24 18:35:06 +02:00
pax-k
3fa276c18d chore: changeset 2025-02-24 18:18:33 +02:00
pax-k
1928519d39 feat: added cursor docs 2025-02-24 18:16:39 +02:00
Anselm Eickhoff
f4fa80b782 Merge pull request #1469 from garden-co/changeset-release/main 2025-02-23 10:13:07 +00:00
github-actions[bot]
782df5d4b8 Version Packages 2025-02-23 10:12:42 +00:00
Anselm Eickhoff
9db20ad630 Merge pull request #1468 from Schniz/schniz/fix-rn-kv-persistence 2025-02-23 10:11:09 +00:00
Gal Schlezinger
3405d8f275 Add changeset 2025-02-23 12:09:48 +02:00
Gal Schlezinger
0a64dca0cd [jazz-react-native] override Context#getKvStore to actually use the declared kvStore in the provider 2025-02-23 11:42:46 +02:00
25 changed files with 2654 additions and 4 deletions

View File

@@ -1,5 +1,13 @@
# chat-rn-clerk
## 1.0.75
### Patch Changes
- Updated dependencies [3405d8f]
- jazz-react-native@0.10.10
- jazz-react-native-auth-clerk@0.10.10
## 1.0.74
### Patch Changes

View File

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

View File

@@ -1,5 +1,12 @@
# chat-rn
## 1.0.71
### Patch Changes
- Updated dependencies [3405d8f]
- jazz-react-native@0.10.10
## 1.0.70
### Patch Changes

View File

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

View File

@@ -0,0 +1,28 @@
---
description: Creating Jazz Schema Rule
globs: *.ts, *.tsx
---
# Creating Jazz Schema Rule
You are a helpful AI assistant specialized in software engineering, TypeScript, Jazz.
Jazz is a TypeScript framework for building local-first apps.
Users can ask your help with anything related to Jazz, including creating Jazz schemas.
If the user needs help with creating or refining a Jazz Schema, follow this agentic protocol:
1) read the Jazz Docs [1_jazz_docs.md](mdc:packages/cursor-docs/docs/1_jazz_docs.md)
2) read each example one by one:
- [4_1_example_without_specs.md](mdc:docs/4_1_example_without_specs.md) for example app 1: A secure and organized password manager app that allows users to store, manage, and categorize their credentials in folders
- [4_2_example_without_specs.md](mdc:docs/4_2_example_without_specs.md) for example app 2: A feature-rich music player app that allows users to manage playlists, store tracks, and visualize audio waveforms
- @4_3_example_without_specs.md for example app 3: A social pet app where users can share pet photos, react with fun emojis, and organize posts in a collaborative feed
- [4_4_example_without_specs.md](mdc:docs/4_4_example_without_specs.md) for example app 4: A bubble tea ordering app that lets users customize drinks with different tea bases, add-ons, and delivery preferences
- [4_5_example_without_specs.md](mdc:docs/4_5_example_without_specs.md) for example app 5: An employee onboarding app that streamlines the hiring process through structured steps, including initial data collection, document uploads, and final approvals
- [4_6_example_without_specs.md](mdc:docs/4_6_example_without_specs.md) for example app 6: A task management app that helps users organize their to-dos with categories, tags, due dates, and priority levels
3) read the Jazz Schema template you have to follow [2_jazz_schema_template.md](mdc:docs/2_jazz_schema_template.md)
4) read the rules for creating Jazz Schema [3_jazz_rules.md](mdc:docs/3_jazz_rules.md)
When processing files:
1. MUST validate each file was read
2. MUST process files in sequence
3. MUST confirm completion before continuing
After you followed all four steps, continue with correctly creating the schema based on what you learned.

171
packages/cursor-docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,171 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
.DS_Store

View File

@@ -0,0 +1,2 @@
coverage
node_modules

View File

@@ -0,0 +1,7 @@
# cursor-docs
## 0.0.2
### Patch Changes
- 3fa276c: Added Cursor docs

View File

@@ -0,0 +1,19 @@
Copyright 2024, Garden Computing, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,944 @@
---
## **CoMap Overview**
**CoMap** is a collaborative object mapping system from `jazz-tools`, mapping string keys to values.
### **1. Basic Definition**
```typescript
import { CoMap, co } from "jazz-tools";
class Person extends CoMap {
name = co.string;
age = co.number;
isActive = co.boolean;
}
```
### **2. Field Types**
- **Basic:** `co.string`, `co.number`, `co.boolean`
- **Optional:** `co.optional.string`, `co.optional.number`
- **Literals (Enums):** `co.literal("draft", "published", "archived")`
- **Dates:** `co.Date`
- **Custom Encoded:**
```typescript
customField = co.encoded({
encode: (v: string) => v.toUpperCase(),
decode: (v: unknown) => String(v).toLowerCase()
});
```
### **3. References to Other CoMaps**
```typescript
class Comment extends CoMap {
text = co.string;
createdAt = co.Date;
}
class Post extends CoMap {
title = co.string;
content = co.string;
mainComment = co.ref(Comment);
pinnedComment = co.optional.ref(Comment);
}
```
### **4. Lists with CoList**
```typescript
import { CoList, CoMap, co } from "jazz-tools";
class Task extends CoMap {
title = co.string;
completed = co.boolean;
}
class TaskList extends CoList.Of(co.ref(Task)) {}
class Project extends CoMap {
name = co.string;
tasks = co.ref(TaskList);
}
```
### **5. Validation & Custom Methods**
```typescript
class DraftPost extends CoMap {
title = co.optional.string;
content = co.optional.string;
validate() {
const errors: string[] = [];
if (!this.title) errors.push("Title is required");
if (!this.content) errors.push("Content is required");
return { errors };
}
get summary() { return this.content?.slice(0, 100) + "..."; }
}
```
### **Real-World Examples**
- **Chat Schema**
```typescript
class Message extends CoMap { text = co.string; image = co.optional.ref(ImageDefinition); }
class Chat extends CoList.Of(co.ref(Message)) {}
```
- **Organization Schema**
```typescript
class Project extends CoMap { name = co.string; }
class ListOfProjects extends CoList.Of(co.ref(Project)) {}
class Organization extends CoMap {
name = co.string;
projects = co.ref(ListOfProjects);
}
```
- **Issue Tracking**
```typescript
class Issue extends CoMap {
title = co.string;
description = co.string;
estimate = co.number;
status? = co.literal("backlog", "in progress", "done");
}
class ListOfIssues extends CoList.Of(co.ref(Issue)) {}
class Project extends CoMap {
name = co.string;
issues = co.ref(ListOfIssues);
}
```
### **Testing Example**
```typescript
class TestMap extends CoMap {
color = co.string;
_height = co.number;
birthday = co.Date;
name? = co.string;
nullable = co.optional.encoded<string | undefined>({
encode: (v: string | undefined) => v || null,
decode: (v: unknown) => (v as string) || undefined,
});
optionalDate = co.optional.encoded(Encoders.Date);
get roughColor() { return this.color + "ish"; }
}
```
### **Key Takeaways**
- Extend **CoMap** for schemas.
- Use `co.ref()` for references, `co.optional` for optional fields.
- Use `CoList.Of()` for collections.
- Fields auto-sync across clients.
- Add computed properties & validation methods.
---
## **CoList Overview**
**CoList** is a collaborative array in `jazz-tools`.
### **1. Basic Definition**
```typescript
import { CoList, co } from "jazz-tools";
class ColorList extends CoList.Of(co.string) {}
class NumberList extends CoList.Of(co.number) {}
class BooleanList extends CoList.Of(co.boolean) {}
```
### **2. Lists of CoMaps**
```typescript
import { CoList, CoMap, co } from "jazz-tools";
class Task extends CoMap { title = co.string; completed = co.boolean; }
class ListOfTasks extends CoList.Of(co.ref(Task)) {}
```
### **3. CoList Operations**
```typescript
const taskList = ListOfTasks.create([], { owner: me });
taskList.push(Task.create({ title: "New task", completed: false }, { owner: me }));
const firstTask = taskList[0];
taskList.filter(task => !task.completed);
taskList.splice(1, 1);
```
### **4. Nested Lists**
```typescript
class Comment extends CoMap { text = co.string; createdAt = co.Date; }
class ListOfComments extends CoList.Of(co.ref(Comment)) {}
class Post extends CoMap {
title = co.string;
content = co.string;
comments = co.ref(ListOfComments);
}
class ListOfPosts extends CoList.Of(co.ref(Post)) {}
```
### **Real-World Examples**
- **Chat Schema**
```typescript
class Message extends CoMap { text = co.string; image = co.optional.ref(ImageDefinition); }
class Chat extends CoList.Of(co.ref(Message)) {}
```
- **Todo App Schema**
```typescript
class Task extends CoMap { done = co.boolean; text = co.string; }
class ListOfTasks extends CoList.Of(co.ref(Task)) {}
```
- **Organization Schema**
```typescript
class Project extends CoMap { name = co.string; }
class ListOfProjects extends CoList.Of(co.ref(Project)) {}
class Organization extends CoMap {
name = co.string;
projects = co.ref(ListOfProjects);
}
```
### **5. Advanced Features**
```typescript
class TaskList extends CoList.Of(co.ref(Task)) {
getCompletedTasks() { return this.filter(task => task.completed); }
getPendingTasks() { return this.filter(task => !task.completed); }
}
```
### **Key Takeaways**
- `CoList.Of()` for list definitions.
- `co.ref()` for CoMap references.
- Acts like arrays with real-time sync.
- Supports custom methods & nested lists.
---
## **CoFeed Overview**
**CoFeed** is an append-only event stream, ideal for time-ordered data.
### **1. Basic Definition**
```typescript
import { CoFeed, co } from "jazz-tools";
class ActivityFeed extends CoFeed.Of(co.string) {}
class MetricsFeed extends CoFeed.Of(co.number) {}
```
### **2. Feeds with Complex Types**
```typescript
interface LogEvent {
timestamp: number;
level: "info" | "warn" | "error";
message: string;
}
class LogFeed extends CoFeed.Of(co.json<LogEvent>()) {}
```
### **3. Pet Reactions Example**
```typescript
export const ReactionTypes = ["aww","love","haha","wow","tiny","chonkers"] as const;
export class PetReactions extends CoFeed.Of(co.json<ReactionType>()) {}
```
### **4. Working with CoFeeds**
```typescript
const reactions = PetReactions.create({ owner: me });
reactions.post("love");
reactions.subscribe(feedId, me, {}, (feed) => console.log(feed.latest()));
```
### **5. Common Use Cases**
- **Activity Streams**
```typescript
class ActivityStream extends CoFeed.Of(co.json<{ type:"comment"|"like"|"share";userId:string;timestamp:number;}>) {}
```
- **Chat Messages**
```typescript
class ChatFeed extends CoFeed.Of(co.json<{ type:"message"|"join"|"leave";userId:string; content?:string;timestamp:number;}>) {}
```
- **Audit Logs**
```typescript
class AuditLog extends CoFeed.Of(co.json<{ action:string; user:string; details:Record<string,unknown>;timestamp:number;}>) {}
```
### **6. Differences: CoFeed vs. CoList**
| Feature | CoFeed (append-only) | CoList (mutable) |
|----------|----------------------|------------------|
| Order | Time-ordered | Arbitrary |
| Use Case | Logs, streams | Collections |
### **Key Takeaways**
- **CoFeed**: event logs, activity streams, append-only.
- **CoList**: modifiable lists.
- Real-time updates, easy to subscribe.
---
## **SchemaUnion Overview**
**SchemaUnion** handles runtime-discriminated union types of `CoMap` instances.
### **1. Basic Definition**
```typescript
import { SchemaUnion, CoMap, co } from "jazz-tools";
class BaseShape extends CoMap { type = co.string; }
class Circle extends BaseShape { type = co.literal("circle"); radius = co.number; }
class Rectangle extends BaseShape { type = co.literal("rectangle"); width = co.number; height = co.number; }
const Shape = SchemaUnion.Of<BaseShape>((raw) => {
switch (raw.get("type")) {
case "circle": return Circle;
case "rectangle": return Rectangle;
default: throw new Error("Unknown shape");
}
});
```
### **2. Nested Discriminators**
```typescript
class BaseButton extends CoMap { type = co.literal("button"); variant = co.string; }
class PrimaryButton extends BaseButton { variant = co.literal("primary"); label = co.string; size = co.literal("small","medium","large"); }
class SecondaryButton extends BaseButton { variant = co.literal("secondary"); label = co.string; outline = co.boolean; }
const Button = SchemaUnion.Of<BaseButton>((raw) => {
switch (raw.get("variant")) {
case "primary": return PrimaryButton;
case "secondary": return SecondaryButton;
default: throw new Error("Unknown variant");
}
});
```
### **3. Using SchemaUnion with CoLists**
```typescript
class BaseWidget extends CoMap { type = co.string; }
class ButtonWidget extends BaseWidget { type = co.literal("button"); label = co.string; }
class SliderWidget extends BaseWidget { type = co.literal("slider"); min = co.number; max = co.number; }
const Widget = SchemaUnion.Of<BaseWidget>((raw) => {
switch (raw.get("type")) {
case "button": return ButtonWidget;
case "slider": return SliderWidget;
default: throw new Error("Unknown widget");
}
});
class WidgetList extends CoList.Of(co.ref(Widget)) {}
```
### **4. Working with SchemaUnion Instances**
```typescript
const button = ButtonWidget.create({ type:"button", label:"Click me" }, { owner: me });
const widget = await loadCoValue(Widget, widgetId, me, {});
if (widget instanceof ButtonWidget) console.log(widget.label);
if (widget instanceof SliderWidget) console.log(widget.min, widget.max);
```
### **5. Validation Example**
```typescript
class BaseFormField extends CoMap {
type = co.string;
label = co.string;
required = co.boolean;
}
class TextField extends BaseFormField {
type = co.literal("text");
minLength = co.optional.number;
maxLength = co.optional.number;
validate(value: string){/* ... */}
}
class NumberField extends BaseFormField {
type = co.literal("number");
min = co.optional.number;
max = co.optional.number;
validate(value: number){/* ... */}
}
const FormField = SchemaUnion.Of<BaseFormField>((raw) => {
switch (raw.get("type")) {
case "text": return TextField;
case "number": return NumberField;
default: throw new Error("Unknown type");
}
});
```
### **Key Takeaways**
- **SchemaUnion** = polymorphic CoMaps.
- Use `co.literal()` for discriminators.
- `instanceof` for type-narrowing.
- Great for complex forms & dynamic components.
---
## **Groups, Accounts, Owners, Roles & Permissions in Jazz**
### **1. Ownership & Groups**
Every `CoValue` has an owner (an `Account` or `Group`):
```typescript
import { Account, Group, CoMap, co } from "jazz-tools";
const privateDoc = Document.create({ title:"Private" }, { owner: me });
const group = Group.create({ owner: me });
const sharedDoc = Document.create({ title:"Shared" }, { owner: group });
```
### **2. Roles & Permissions**
Built-in roles: `"admin"`, `"writer"`, `"reader"`, `"readerInvite"`, `"writerInvite"`.
```typescript
group.addMember(bob, "writer");
group.addMember(alice, "reader");
group.addMember("everyone","reader");
```
### **3. Organizations & Memberships**
```typescript
import { Account, CoMap, CoList, Group, co } from "jazz-tools";
class Project extends CoMap { name = co.string; description = co.string; }
class Organization extends CoMap {
name = co.string;
projects = co.ref(CoList.Of(co.ref(Project)));
static create(name: string, owner: Account) {
const group = Group.create({ owner });
return super.create({ name, projects: CoList.Of(co.ref(Project)).create([], { owner: group }) }, { owner: group });
}
addMember(account: Account, role: "admin"|"writer"|"reader") {
this._owner.castAs(Group).addMember(account, role);
}
}
```
### **4. Account Root & Migration Pattern**
```typescript
class TodoAccountRoot extends CoMap {
projects = co.ref(ListOfProjects);
}
export class UserProfile extends Profile { someProperty = co.string; }
class TodoAccount extends Account {
root = co.ref(TodoAccountRoot);
profile = co.ref(UserProfile);
migrate() {
if (!this._refs.root) {
this.root = TodoAccountRoot.create({ projects: ListOfProjects.create([], { owner: this }) }, { owner: this });
}
}
}
```
### **5. Public Sharing Example**
```typescript
class SharedFile extends CoMap {
name = co.string;
file = co.ref(FileStream);
createdAt = co.Date;
size = co.number;
}
class FileShareAccountRoot extends CoMap {
type = co.string;
sharedFiles = co.ref(ListOfSharedFiles);
publicGroup = co.ref(Group);
}
export class UserProfile extends Profile { someProperty = co.string; }
class FileShareAccount extends Account {
root = co.ref(FileShareAccountRoot);
profile = co.ref(UserProfile);
async migrate() {
await this._refs.root?.load();
if (!this.root || this.root.type !== "file-share-account") {
const publicGroup = Group.create({ owner: this });
publicGroup.addMember("everyone","reader");
this.root = FileShareAccountRoot.create({
type:"file-share-account",
sharedFiles: ListOfSharedFiles.create([], { owner: publicGroup }),
publicGroup
}, { owner: this });
}
}
}
```
### **6. Group Extensions**
```typescript
const parentGroup = Group.create({ owner: me });
parentGroup.addMember(bob, "reader");
const childGroup = Group.create({ owner: me });
childGroup.extend(parentGroup);
const doc = Document.create({ title:"Inherited Access" }, { owner: childGroup });
```
### **7. Checking Permissions**
```typescript
const group = document._owner.castAs(Group);
const myRole = group.myRole();
const hasWriteAccess = myRole === "admin" || myRole === "writer";
```
### **8. Invitation Pattern**
```typescript
class TeamInvite extends CoMap {
email = co.string;
role = co.literal("admin","writer","reader");
accepted = co.boolean;
}
class Team extends CoMap {
invites = co.ref(CoList.Of(co.ref(TeamInvite)));
async inviteMember(email: string, role: "admin"|"writer"|"reader") {
const group = this._owner.castAs(Group);
const invite = TeamInvite.create({ email, role, accepted:false }, { owner: group });
this.invites.push(invite);
group.addMember(email, (role+"Invite") as const);
}
acceptInvite(account: Account) {
const group = this._owner.castAs(Group);
const invite = this.invites.find(i => i.email === account.email);
if (invite) {
invite.accepted = true;
group.addMember(account, invite.role);
}
}
}
```
### **Key Takeaways**
- Every `CoValue` has an owner (Account or Group).
- Groups enable sharing/role-based access (`"admin"`, `"writer"`, `"reader"`, etc.).
- Groups can inherit permissions.
- Use account roots for private per-user data.
- Public sharing via `"everyone"` role.
- Invites allow controlled membership.
---
## **Inbox Pattern in Jazz**
Enables message exchange between accounts using `CoMap`, `CoList`, `Group`.
### **1. Basic Inbox Setup**
```typescript
import { CoMap, co, Group } from "jazz-tools";
class Message extends CoMap {
text = co.string;
createdAt = co.Date;
read = co.boolean;
}
class ChatInbox extends CoMap {
messages = co.ref(CoList.Of(co.ref(Message)));
lastReadAt = co.Date;
}
```
### **2. Sending Messages**
```typescript
async function sendMessage(sender: Account, receiverId: ID<Account>, text: string) {
const message = Message.create(
{ text, createdAt:new Date(), read:false },
{ owner: Group.create({ owner: sender }) }
);
const inboxSender = await InboxSender.load(receiverId, sender);
inboxSender.sendMessage(message);
}
```
### **3. Receiving Messages**
```typescript
async function setupInbox(receiver: Account) {
const inbox = await Inbox.load(receiver);
return inbox.subscribe(Message,(message,senderId)=>console.log("New:",message.text));
}
```
### **4. Chat Application Example**
```typescript
class ChatMessage extends CoMap { text = co.string; createdAt=co.Date; read=co.boolean; }
class ChatThread extends CoMap {
participants = co.json<string[]>();
messages = co.ref(CoList.Of(co.ref(ChatMessage)));
lastReadAt = co.optional.Date;
}
class ChatRoot extends CoMap {
threads = co.ref(CoList.Of(co.ref(ChatThread)));
inbox = co.ref(Inbox);
}
export class UserProfile extends Profile { someProperty=co.string; }
class ChatAccount extends Account {
root = co.ref(ChatRoot);
profile = co.ref(UserProfile);
async migrate() {
if(!this._refs.root) {
const group = Group.create({ owner:this });
this.root = ChatRoot.create({
threads: CoList.Of(co.ref(ChatThread)).create([], { owner: group }),
inbox: await Inbox.create(this)
},{ owner:this });
}
}
async sendMessage(to: ID<Account>, text:string) {
const message = ChatMessage.create({ text, createdAt:new Date(), read:false },
{ owner: Group.create({ owner:this }) });
const inboxSender=await InboxSender.load(to,this);
inboxSender.sendMessage(message);
}
async setupInboxListener() {
const inbox = await Inbox.load(this);
return inbox.subscribe(ChatMessage, async (message, senderId) => {
const thread = await this.findOrCreateThread(senderId);
thread.messages.push(message);
});
}
}
```
### **5. Testing the Inbox Pattern**
```typescript
describe("Inbox", () => {
it("should allow message exchange", async () => {
const { clientAccount:sender, serverAccount:receiver } = await setupTwoNodes();
const receiverInbox = await Inbox.load(receiver);
const message = Message.create({ text:"Hello" },{ owner:Group.create({ owner:sender }) });
const inboxSender = await InboxSender.load(receiver.id, sender);
inboxSender.sendMessage(message);
const receivedMessages: Message[] = [];
let senderAccountID: unknown;
const unsubscribe = receiverInbox.subscribe(Message, (msg, id) => {
senderAccountID=id; receivedMessages.push(msg);
});
await waitFor(() => receivedMessages.length===1);
expect(receivedMessages[0]?.text).toBe("Hello");
expect(senderAccountID).toBe(sender.id);
unsubscribe();
});
});
```
### **6. Message Status Tracking**
```typescript
class MessageStatus extends CoMap {
messageId = co.string;
delivered = co.boolean;
read = co.boolean;
readAt = co.optional.Date;
}
class EnhancedMessage extends CoMap {
text = co.string;
createdAt = co.Date;
status = co.ref(MessageStatus);
}
async function sendMessageWithStatus(sender: Account, receiverId: ID<Account>, text:string) {
const group = Group.create({ owner:sender });
const status = MessageStatus.create({ messageId:crypto.randomUUID(), delivered:false, read:false }, { owner:group });
const message = EnhancedMessage.create({ text, createdAt:new Date(), status }, { owner:group });
const inboxSender = await InboxSender.load(receiverId,sender);
inboxSender.sendMessage(message);
return message;
}
```
### **Key Takeaways**
- Messages owned by a `Group` from the sender.
- Use `InboxSender.load()` to send, `Inbox.load()` to receive.
- Subscribe for real-time updates.
- Append status tracking as needed.
---
## **Invite Pattern in Jazz**
Sharing access to `CoValues` with other users via invites.
### **1. Creating & Handling Invites**
```typescript
import { CoMap, Group, co, createInviteLink } from "jazz-tools";
class Project extends CoMap { name = co.string; members = co.ref(CoList.Of(co.ref(Member))); }
const group = Group.create({ owner: me });
const project = Project.create({ name:"New Project", members:CoList.Of(co.ref(Member)).create([],{owner:group}) }, { owner:group });
const readerInvite = createInviteLink(project,"reader");
const writerInvite = createInviteLink(project,"writer");
const adminInvite = createInviteLink(project,"admin");
```
### **2. Accepting Invites in UI**
```typescript
import { useAcceptInvite } from "jazz-react";
useAcceptInvite({
invitedObjectSchema:Project,
onAccept:(projectId)=>navigate(`/projects/${projectId}`)
});
```
### **3. Organization Example**
```typescript
class Organization extends CoMap {
name = co.string;
projects = co.ref(ListOfProjects);
createInvite(role:"reader"|"writer"|"admin"){ return createInviteLink(this,role); }
}
useAcceptInvite({
invitedObjectSchema:Organization,
onAccept:async(orgId)=>{/* ... */}
});
```
### **4. Value Hints in Invites**
```typescript
class Team extends CoMap {
name = co.string;
generateInvite(role:"reader"|"writer"|"admin"){ return createInviteLink(this, role, window.location.origin, "team"); }
}
useAcceptInvite({
invitedObjectSchema:Team,
forValueHint:"team",
onAccept:(teamId)=>navigate(`/teams/${teamId}`)
});
```
### **5. Testing Invites**
```typescript
describe("Invite Links", () => {
test("generate and parse invites", async () => {
const inviteLink = createInviteLink(group, "writer","https://example.com","myGroup");
const parsed = parseInviteLink(inviteLink);
expect(parsed?.valueID).toBe(group.id);
expect(parsed?.valueHint).toBe("myGroup");
});
test("accept invite", async () => {
const newAccount = await createJazzTestAccount();
const inviteLink = createInviteLink(group, "writer");
const result = await consumeInviteLink({ inviteURL: inviteLink, as:newAccount, invitedObjectSchema:Group });
expect(result?.valueID).toBe(group.id);
});
});
```
### **6. File Sharing with Invites**
```typescript
class SharedFile extends CoMap {
name = co.string;
sharedWith = co.ref(CoList.Of(co.ref(SharedWith)));
}
class SharedWith extends CoMap {
email = co.string;
role = co.literal("reader","writer");
acceptedAt = co.optional.Date;
}
class FileShareAccount extends Account {
async shareFile(file:SharedFile, email:string, role:"reader"|"writer") {
const inviteLink = createInviteLink(file,role);
file.sharedWith.push(SharedWith.create({ email, role, acceptedAt:null },{ owner:file._owner }));
await sendInviteEmail(email, inviteLink);
}
}
useAcceptInvite({
invitedObjectSchema:SharedFile,
onAccept:async(fileId)=>{
const file = await SharedFile.load(fileId,{});
const shareRecord = file.sharedWith.find(s=>s.email===currentUser.email);
if(shareRecord) shareRecord.acceptedAt=new Date();
navigate(`/files/${fileId}`);
}
});
```
### **Key Takeaways**
- Use Groups for shared ownership.
- `createInviteLink()` generates invites.
- `useAcceptInvite()` handles acceptance.
- Value hints (`forValueHint`) differentiate invite types.
---
## **CoValue Types & Patterns in Jazz**
### **1. CoMap**
- Use for structured data with named fields.
- Example:
```typescript
class UserProfile extends CoMap {
name = co.string;
email = co.string;
avatar = co.ref(FileStream);
preferences = co.json<{ theme:string; notifications:boolean }>();
}
class TagColors extends CoMap.Record(co.string) {}
```
### **2. CoList**
- Use for ordered, real-time collaborative arrays.
```typescript
class TodoList extends CoList.Of(co.ref(TodoItem)) {}
class StringList extends CoList.Of(co.string) {}
```
### **3. CoFeed**
- Use for append-only event/log data.
```typescript
class UserActivity extends CoFeed.Of(co.json<{ type:string; timestamp:number; text?:string }>) {}
```
### **4. SchemaUnion**
- Use for polymorphic objects with a runtime discriminator.
```typescript
class BaseWidget extends CoMap { type=co.string; }
class ButtonWidget extends BaseWidget { type=co.literal("button"); label=co.string; }
const Widget=SchemaUnion.Of<BaseWidget>(raw=>raw.get("type")==="button"?ButtonWidget:null);
```
### **5. Groups & Permissions**
- Owner can be an Account or Group.
- Roles: `"admin"|"writer"|"reader"|"readerInvite"|"writerInvite"`.
```typescript
const group=Group.create({owner:me});
group.addMember("everyone","reader");
```
### **6. Accounts**
- Per-user data storage with migrations.
```typescript
class JazzAccount extends Account {
root=co.ref(JazzAccountRoot);
profile=co.ref(UserProfile);
async migrate(){/*...*/}
}
```
### **7. Migrations**
- Update/initialize user data on account creation/login.
### **8. Invites**
- Role-based sharing through invite links.
### **Common Patterns**
- **Account Root Pattern**: store users top-level data.
- **Shared Document Pattern**: CoMap for doc, CoList for collaborators, CoFeed for history.
- **Draft Pattern**: CoMap with partial fields, validation.
- **Public Sharing**: set `Group.addMember("everyone","reader")`.
---
## **Examples**
1. **User Profile Storage (CoMap)**
**JSON**:
```json
{
"name":"John Doe","email":"john@example.com",
"avatar":{"url":"...","size":"..."},
"preferences":{"theme":"dark","notifications":true}
}
```
**Jazz**:
```typescript
class UserProfile extends CoMap {
name = co.string;
email = co.string;
avatar = co.ref(FileStream);
preferences = co.json<{theme:string;notifications:boolean}>();
}
```
2. **To-Do List (CoList)**
**JSON**:
```json
{"tasks":[{"id":1,"title":"Buy groceries","completed":false},{"id":2,"title":"Call mom","completed":true}]}
```
**Jazz**:
```typescript
class TodoItem extends CoMap { title=co.string; completed=co.boolean; }
class TodoList extends CoList.Of(co.ref(TodoItem)) {}
```
3. **Activity Feed (CoFeed)**
**JSON**:
```json
{"activities":[{"type":"login","timestamp":1700000000},{"type":"logout","timestamp":1700000500},{"type":"comment","timestamp":1700001000,"text":"Great post!"}]}
```
**Jazz**:
```typescript
class UserActivity extends CoFeed.Of(co.json<{type:string;timestamp:number;text?:string}>()) {}
```
4. **Polymorphic Widgets (SchemaUnion)**
**JSON**:
```json
{"widgets":[{"type":"button","label":"Click Me"},{"type":"slider","min":0,"max":100}]}
```
**Jazz**:
```typescript
class BaseWidget extends CoMap { type=co.string; }
class ButtonWidget extends BaseWidget { type=co.literal("button"); label=co.string; }
class SliderWidget extends BaseWidget { type=co.literal("slider"); min=co.number; max=co.number; }
const Widget=SchemaUnion.Of<BaseWidget>((raw)=>{...});
```
5. **Access Control via Groups**
**JSON**:
```json
{"group":{"owner":"user123","members":[{"id":"user456","role":"admin"},{"id":"user789","role":"writer"}]}}
```
**Jazz**:
```typescript
const group=Group.create({owner:user123});
group.addMember(user456,"admin");
group.addMember(user789,"writer");
```
6. **User Account with Root Data (Accounts)**
**JSON**:
```json
{"user":{"profile":{"name":"Jane Doe"},"documents":[{"title":"My Notes","content":"This is a note."}],"activities":[{"type":"login"}]}}
```
**Jazz**:
```typescript
class AppAccountRoot extends CoMap {
profile=co.ref(UserProfile);
documents=co.ref(CoList.Of(co.ref(Document)));
activities=co.ref(UserActivity);
}
class AppAccount extends Account {
root=co.ref(AppAccountRoot);
profile=co.ref(UserProfile);
}
```
7. **Document Collaboration**
**JSON**:
```json
{
"document":{
"title":"Project Plan","content":"Detailed...","collaborators":[{"id":"user1","role":"editor"},{"id":"user2","role":"viewer"}],
"history":[{"user":"user1","timestamp":1700000000,"change":"Edited content"}]
}
}
```
**Jazz**:
```typescript
class Document extends CoMap {
title=co.string;content=co.string;
collaborators=co.ref(CoList.Of(co.ref(UserProfile)));
history=co.ref(CoFeed.Of(co.json<{user:string;timestamp:number;change:string}>()));
}
```
8. **Draft System**
**JSON**:
```json
{"draft":{"name":"New Project","tasks":[],"valid":false,"errors":["Project name required"]}}
```
**Jazz**:
```typescript
class DraftProject extends CoMap {
name=co.optional.string;
tasks=co.ref(CoList.Of(co.ref(TodoItem)));
validate(){/*...*/}
}
```
9. **Public File Sharing**
**JSON**:
```json
{"file":{"name":"Presentation.pdf","size":2048,"uploadedAt":1700000000,"sharedWith":["everyone"]}}
```
**Jazz**:
```typescript
class SharedFile extends CoMap {
name=co.string;
file=co.ref(FileStream);
uploadedAt=co.Date;
}
const publicGroup=Group.create({owner:me});
publicGroup.addMember("everyone","reader");
```
10. **Invite System**
**JSON**:
```json
{"invites":[{"email":"user@example.com","role":"writer","status":"pending"}]}
```
**Jazz**:
```typescript
class Invite extends CoMap {
email=co.string;
role=co.literal("reader","writer","admin");
status=co.literal("pending","accepted");
}
const inviteLink=createInviteLink(project,"writer");
useAcceptInvite({ invitedObjectSchema:Project, onAccept:(id)=>navigate(`/projects/${id}`) });
```
---

View File

@@ -0,0 +1,214 @@
---
import { Account, CoList, CoMap, Group, Profile, co } from "jazz-tools";
/**
* Represents a main data item in the apps domain.
*
* Properties:
* - name: Required field identifying the item.
* - metadata_field: Optional metadata (string).
* - container: Reference to a parent Container.
* - deleted: Soft delete flag for archiving/removing without permanent deletion.
*/
export class MainItem extends CoMap {
/** A required, identifying name. */
name = co.string;
/** An optional string field for metadata. */
metadata_field = co.optional.string;
/** Reference to the parent container. */
container = co.ref(Container);
/** Soft-delete flag: if true, treat this item as removed. */
deleted = co.boolean;
}
/**
* A list/array of MainItem references.
* Provides real-time collaboration features (insertion, removal, ordering).
*/
export class MainItemList extends CoList.Of(co.ref(MainItem)) {}
/**
* A container/organizational structure for grouping MainItem objects.
*
* Properties:
* - name: A human-friendly name for the container.
* - items: A CoList of MainItem references.
*/
export class Container extends CoMap {
/** Human-friendly name for this container. */
name = co.string;
/** A list of MainItems held by this container. */
items = co.ref(MainItemList);
}
/**
* The top-level structure in the users account, representing all stored data.
*
* Properties:
* - container: The default or root container for MainItems.
* - version: An optional version number for supporting migrations.
*/
export class AccountRoot extends CoMap {
/** A single container to hold or organize items. */
container = co.ref(Container);
/** Tracks schema version for migrations. */
version = co.optional.number;
}
/**
* Represents a users profile data.
*
* Properties:
* - email: Required email field for identification/contact.
*
* Static method:
* - validate: Enforces that both name and email are provided.
*/
export class UserProfile extends Profile {
/** Required user email. */
email = co.string;
/**
* Validate user profile data, ensuring both "name" and "email" exist and are non-empty.
*/
static validate(data: { name?: string; email?: string }) {
const errors: string[] = [];
if (!data.name?.trim()) {
errors.push("Please enter a name.");
}
if (!data.email?.trim()) {
errors.push("Please enter an email.");
}
return { errors };
}
}
/**
* The main Account class that holds the users data (AccountRoot) and profile.
* Handles initial migrations (setting up default Container, etc.) and can be extended
* to run future schema migrations.
*/
export class JazzAccount extends Account {
/** Reference to the users profile. */
profile = co.ref(UserProfile);
/** Reference to the account root data (container, version, etc.). */
root = co.ref(AccountRoot);
/**
* Migrate is run on creation and each login. If there is no root, creates initial data.
* Otherwise, you can add version-based migrations (below).
*/
async migrate(creationProps?: { name: string; other?: Record<string, unknown> }) {
if (!this._refs.root && creationProps) {
await this.initialMigration(creationProps);
return;
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// uncomment this to add migrations
// Check the current version and run subsequent migrations
// const currentVersion = this.root?.version || 0;
// if (currentVersion < 1) {
// await this.migrationV1();
// }
// Add more version checks and migrations as needed
// if (currentVersion < 2) {
// await this.migrationV2();
// }
}
/**
* Executes the initial migration logic when the account is first created:
* - Validates the users profile data (name, email).
* - Sets up a public group (readable by "everyone") for the users profile.
* - Sets up a private group to own private resources.
* - Creates a default Container with a single MainItem.
*/
private async initialMigration(
creationProps: { name: string; other?: Record<string, unknown> }
) {
const { name, other } = creationProps;
// Validate profile data
const profileErrors = UserProfile.validate({ name, ...other });
if (profileErrors.errors.length > 0) {
throw new Error(
"Invalid profile data: " + profileErrors.errors.join(", "),
);
}
// Create a public group for the profile
const publicGroup = Group.create({ owner: this });
publicGroup.addMember("everyone", "reader");
// Create the user profile with validated data
this.profile = UserProfile.create(
{
name,
...other,
},
{ owner: publicGroup },
);
// Create a private group for data that should not be publicly readable
const privateGroup = Group.create({ owner: this });
// Create a default container with one default item
const defaultContainer = Container.create(
{
name: this.profile?.name
? \`\${this.profile.name}'s items\`
: "Your items",
items: MainItemList.create(
[
MainItem.create({ name: "Default item" }, privateGroup),
],
privateGroup,
),
},
privateGroup,
);
// Initialize the account root with version tracking
this.root = AccountRoot.create(
{
container: defaultContainer,
version: 0, // Start at version 0
},
{ owner: this },
);
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// uncomment this to add migrations
// private async migrationV1() {
// Example migration logic:
// if (this.root) {
// // e.g., add a new field to all items
// // for (const container of this.root.containers || []) {
// // for (const item of container.items || []) {
// // item.newField = "default value";
// // }
// // }
// this.root.version = 1;
// }
// }
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// uncomment this to add migrations
// private async migrationV2() {
// if (this.root) {
// // Future migration logic goes here
// this.root.version = 2;
// }
// }
}
---

View File

@@ -0,0 +1,53 @@
---
**Jazz Schema Rules:**
1. **User Profile and Account**
1.1. Define `export class UserProfile extends Profile` with exactly one property:
```ts
name = co.string;
```
1.2. Add a static `validate` method in `UserProfile` that checks `name` is present and non-empty.
1.3. Define `export class JazzAccount extends Account` with exactly two properties:
```ts
profile = co.ref(UserProfile);
root = co.ref(AccountRoot);
```
1.4. The `JazzAccount` class must have a `migrate(creationProps?: { name: string; other?: Record<string, unknown> })` method.
- Within `migrate`, if `this._refs.root` is undefined and `creationProps` is provided, run `initialMigration`.
- The `creationProps` **must** include a `name` property; `other` is optional but do not define more fields.
2. **Container, Root & Ownership**
2.1. The `AccountRoot` class (extending `CoMap`) **must** have a `container` property referencing a `Container`.
2.2. The `Container` class (extending `CoMap`) should contain the main domain entities of the app.
2.3. **Never** define a `name` field in the `Container` class. The template shows an example `name` property for a Container, but these rules override that.
2.4. Whenever the root structure is initialized, it is always owned by the current `JazzAccount`:
```ts
this.root = AccountRoot.create({ container: defaultContainer, version: 0 }, { owner: this });
```
3. **Groups & Ownership**
3.1. If the `UserProfile` is intended to be public, set its owner to a `publicGroup` that has `"everyone"` as `"reader"`. Otherwise, use a private group.
3.2. When creating a group, no need to explicitly pass `owner: this`. That is implicit if it's the same account.
3.3. **Do not** use properties like `user`, `users`, `group`, or `groups` in CoMaps or CoLists. Ownership is implicit.
4. **No Direct CoList Fields**
4.1. **Never** do:
```ts
co.ref(CoList.Of(co.ref(SomeClass)));
```
4.2. Instead, define a CoList class (e.g. `export class SomeClassList extends CoList.Of(co.ref(SomeClass)) {}`) and reference it.
5. **Schema Structure & Fields**
5.1. Follow the provided template patterns. **Do not** add extra entities or fields outside the users requirements or the template.
5.2. Do **not** use properties like `createdAt` or `updatedAt`; theyre implicit in CoValue.
5.3. If a property is optional, denote it with a question mark (`?`) in the field definition, or use `co.optional.*`.
5.4. Keep comments from the template, especially around migration blocks, intact.
5.5. Never set a property to "co.ref(UserProfile)".
6. **Output & Formatting**
6.1. Generate the final schema in TypeScript with no extra markdown or triple backticks.
6.2. Do **not** expand or alter the templates classes beyond what is required.
6.3. Avoid redundant or conflicting rules from the template; these revised rules take priority.
---

View File

@@ -0,0 +1,225 @@
# Example app 1: A secure and organized password manager app that allows users to store, manage, and categorize their credentials in folders
```typescript
import { Account, CoList, CoMap, Group, Profile, co } from "jazz-tools";
/**
* Represents a password item in the Password Manager.
*
* Properties:
* - name: The required name identifying the password item.
* - username: Optional username.
* - username_input_selector: Optional selector for the username input field.
* - password: The required password.
* - password_input_selector: Optional selector for the password input.
* - uri: Optional URI associated with the item.
* - folder: Reference to the parent Folder.
* - deleted: Soft delete flag.
*/
export class PasswordItem extends CoMap {
name = co.string;
username = co.optional.string;
username_input_selector = co.optional.string;
password = co.string;
password_input_selector = co.optional.string;
uri = co.optional.string;
folder = co.ref(Folder);
deleted = co.boolean;
}
/**
* A list of PasswordItem references.
*/
export class PasswordList extends CoList.Of(co.ref(PasswordItem)) {}
/**
* Represents a folder that groups password items.
*
* Properties:
* - name: The folder's name.
* - items: A list of PasswordItems contained in the folder.
*/
export class Folder extends CoMap {
name = co.string;
items = co.ref(PasswordList);
}
/**
* A list of Folder references.
*/
export class FolderList extends CoList.Of(co.ref(Folder)) {}
/**
* Top-level container for the Password Manager.
* This container holds the main entities of the app.
*
* Properties:
* - folders: A list of Folder entities.
*/
export class Container extends CoMap {
folders = co.ref(FolderList);
}
/**
* The account root holds all user data.
*
* Properties:
* - container: The main container that organizes the apps data.
* - version: An optional version number used for migrations.
*/
export class PasswordManagerAccountRoot extends CoMap {
container = co.ref(Container);
version = co.optional.number;
}
/**
* Represents the user's profile.
*
* Properties:
* - name: The required user name.
*
* Static method:
* - validate: Ensures that a non-empty name is provided.
*/
export class UserProfile extends Profile {
name = co.string;
static validate(data: { name?: string; email?: string }) {
const errors: string[] = [];
if (!data.name?.trim()) {
errors.push("Please enter a name.");
}
// Note: In this schema, only 'name' is required.
return { errors };
}
}
/**
* Main account class for the Password Manager.
* Contains only the profile and root properties.
* Handles data initialization and migrations.
*/
export class PasswordManagerAccount extends Account {
profile = co.ref(UserProfile);
root = co.ref(PasswordManagerAccountRoot);
/**
* The migrate method is called on account creation and login.
* If the root is not initialized, it runs the initial migration.
* Otherwise, you can add version-based migrations as needed.
*/
async migrate(creationProps?: { name: string; other?: Record<string, unknown> }) {
if (!this._refs.root && creationProps) {
await this.initialMigration(creationProps);
return;
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment the following lines to add migrations:
// const currentVersion = this.root?.version || 0;
// if (currentVersion < 1) {
// await this.migrationV1();
// }
// if (currentVersion < 2) {
// await this.migrationV2();
// }
}
/**
* Executes the initial migration logic when the account is first created.
* - Validates the user's profile data.
* - Sets up a public group for the profile (accessible by "everyone").
* - Sets up a private group for private resources.
* - Creates a default Container with a default Folder and a default PasswordItem.
*/
private async initialMigration(creationProps: { name: string; other?: Record<string, unknown> }) {
const { name, other } = creationProps;
const profileErrors = UserProfile.validate({ name, ...other });
if (profileErrors.errors.length > 0) {
throw new Error("Invalid profile data: " + profileErrors.errors.join(", "));
}
// Create a public group for the user profile.
const publicGroup = Group.create({ owner: this });
publicGroup.addMember("everyone", "reader");
// Create the user profile with validated data.
this.profile = UserProfile.create(
{
name,
...other,
},
{ owner: publicGroup }
);
// Create a private group for private data.
const privateGroup = Group.create({ owner: this });
// Create a default Folder with one default PasswordItem.
const defaultFolder = Folder.create(
{
name: "Default",
items: PasswordList.create(
[
PasswordItem.create(
{
name: "Gmail",
username: "user@gmail.com",
password: "password123",
uri: "https://gmail.com",
// The folder reference will be set after defaultFolder creation.
folder: null as any,
deleted: false,
},
privateGroup
),
],
privateGroup
),
},
privateGroup
);
// Set the folder reference for the default PasswordItem.
defaultFolder.items[0].folder = defaultFolder;
// Create a default container that holds the FolderList.
const defaultContainer = Container.create(
{
folders: FolderList.create([defaultFolder], privateGroup),
},
privateGroup
);
// Initialize the account root with version tracking.
this.root = PasswordManagerAccountRoot.create(
{
container: defaultContainer,
version: 0, // Set initial version
},
{ owner: this }
);
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment the following methods to add migrations:
// private async migrationV1() {
// if (this.root) {
// // Example migration logic: add a new field to all password items.
// // for (const folder of this.root.container.folders || []) {
// // for (const item of folder.items || []) {
// // item.newField = "default value";
// // }
// // }
// this.root.version = 1;
// }
// }
// private async migrationV2() {
// if (this.root) {
// // Future migration logic goes here.
// this.root.version = 2;
// }
// }
}
```

View File

@@ -0,0 +1,173 @@
# Example app 2: A feature-rich music player app that allows users to manage playlists, store tracks, and visualize audio waveforms
```typescript
export class MusicTrack extends CoMap {
title = co.string;
duration = co.number;
sourceTrack = co.optional.ref(MusicTrack);
file = co.ref(FileStream);
waveform = co.ref(MusicTrackWaveform);
container = co.ref(Playlist);
deleted = co.boolean;
}
/**
* Represents waveform data for a music track.
*
* Properties:
* - data: A JSON array of numbers representing the waveform.
*/
export class MusicTrackWaveform extends CoMap {
data = co.json<number[]>();
}
/**
* A collaborative list of MusicTrack references.
*/
export class MusicTrackList extends CoList.Of(co.ref(MusicTrack)) {}
/**
* Acts as a container for music tracks.
*
* Properties:
* - name: The name of the playlist.
* - items: A list of MusicTracks in this playlist.
*/
export class Playlist extends CoMap {
name = co.string;
items = co.ref(MusicTrackList);
}
/**
* The top-level account root for the music app.
*
* Properties:
* - container: The main playlist (acting as the container for music tracks).
* - version: Optional version number for migrations.
*/
export class MusicAccountRoot extends CoMap {
container = co.ref(Playlist);
version = co.optional.number;
}
/**
* Represents a user's profile.
*
* Properties:
* - name: The required user name.
*
* Static method:
* - validate: Ensures that a non-empty name and email are provided.
*/
export class UserProfile extends Profile {
name = co.string;
static validate(data: { name?: string; email?: string }) {
const errors: string[] = [];
if (!data.name?.trim()) {
errors.push("Please enter a name.");
}
if (!data.email?.trim()) {
errors.push("Please enter an email.");
}
return { errors };
}
}
/**
* The main Account class for the music app.
* Contains only the profile and root properties.
* Handles data initialization and migrations.
*/
export class MusicAccount extends Account {
profile = co.ref(UserProfile);
root = co.ref(MusicAccountRoot);
/**
* Migrate is run on account creation and each login.
* If the root is not initialized, run initial migration.
* Otherwise, version-based migrations can be added.
*/
async migrate(creationProps?: { name: string; other?: Record<string, unknown> }) {
if (!this._refs.root && creationProps) {
await this.initialMigration(creationProps);
return;
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment to add migrations:
// const currentVersion = this.root?.version || 0;
// if (currentVersion < 1) {
// await this.migrationV1();
// }
// if (currentVersion < 2) {
// await this.migrationV2();
// }
}
/**
* Executes initial migration when the account is first created:
* - Validates the user's profile data (name, email).
* - Sets up a public group (with "everyone" as reader) for the profile.
* - Creates a default Playlist with an empty MusicTrackList.
* - Initializes the account root with version 0.
*/
private async initialMigration(creationProps: { name: string; other?: Record<string, unknown> }) {
const { name, other } = creationProps;
const profileErrors = UserProfile.validate({ name, ...other });
if (profileErrors.errors.length > 0) {
throw new Error("Invalid profile data: " + profileErrors.errors.join(", "));
}
// Create a public group for the profile.
const publicGroup = Group.create({ owner: this });
publicGroup.addMember("everyone", "reader");
// Create the user profile with validated data.
this.profile = UserProfile.create(
{ name, ...other },
{ owner: publicGroup }
);
// Create a private group for the user's music data.
const privateGroup = Group.create({ owner: this });
// Create a default Playlist as the main container.
const defaultPlaylist = Playlist.create(
{
name: this.profile.name + "'s playlist",
items: MusicTrackList.create([], privateGroup),
},
privateGroup
);
// Initialize the account root with version tracking.
this.root = MusicAccountRoot.create(
{
container: defaultPlaylist,
version: 0, // Set initial version
},
{ owner: this }
);
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment to add migrations:
// private async migrationV1() {
// if (this.root) {
// // Example migration logic: add a new field to all music tracks.
// // for (const track of this.root.container.items || []) {
// // track.newField = "default value";
// // }
// this.root.version = 1;
// }
// }
// private async migrationV2() {
// if (this.root) {
// // Future migration logic goes here.
// this.root.version = 2;
// }
// }
}
```

View File

@@ -0,0 +1,178 @@
# Example app 3: A social pet app where users can share pet photos, react with fun emojis, and organize posts in a collaborative feed
```typescript
import { Account, CoFeed, CoList, CoMap, Group, ImageDefinition, Profile, co } from "jazz-tools";
export const ReactionTypes = [
"aww",
"love",
"haha",
"wow",
"tiny",
"chonkers",
] as const;
export type ReactionType = (typeof ReactionTypes)[number];
/**
* Represents an append-only feed of reactions for a pet post.
*/
export class PetReactions extends CoFeed.Of(co.json<ReactionType>()) {}
/**
* Represents a pet post.
*
* Properties:
* - name: The title or caption for the pet post.
* - image: A reference to an ImageDefinition containing the pet's image.
* - reactions: A feed of reactions (of type ReactionType) for the post.
*/
export class PetPost extends CoMap {
name = co.string;
image = co.ref(ImageDefinition);
reactions = co.ref(PetReactions);
}
/**
* A collaborative list of PetPost references.
*/
export class ListOfPosts extends CoList.Of(co.ref(PetPost)) {}
/**
* Container for the pet posts.
*
* This container acts as the main organizational structure holding the posts.
*
* Properties:
* - posts: A list of pet posts.
*/
export class PetContainer extends CoMap {
posts = co.ref(ListOfPosts);
}
/**
* The top-level account root for the pet app.
*
* Properties:
* - container: The main container that organizes pet posts.
* - version: An optional version number for supporting migrations.
*/
export class PetAccountRoot extends CoMap {
container = co.ref(PetContainer);
version = co.optional.number;
}
/**
* Represents a users profile.
*
* Properties:
* - name: The required user name.
*
* Static method:
* - validate: Ensures that both "name" and "email" (if provided) are non-empty.
*/
export class UserProfile extends Profile {
name = co.string;
static validate(data: { name?: string; email?: string }) {
const errors: string[] = [];
if (!data.name?.trim()) {
errors.push("Please enter a name.");
}
if (data.email !== undefined && !data.email?.trim()) {
errors.push("Please enter an email.");
}
return { errors };
}
}
/**
* Main account class for the pet app.
*
* Contains only the profile and root properties, and handles account initialization
* and migrations.
*/
export class PetAccount extends Account {
profile = co.ref(UserProfile);
root = co.ref(PetAccountRoot);
/**
* Migrate is run on account creation and on every log-in.
* If the root is not initialized, it runs the initial migration.
* Otherwise, version-based migrations can be added.
*/
async migrate(creationProps?: { name: string; other?: Record<string, unknown> }) {
if (!this._refs.root && creationProps) {
await this.initialMigration(creationProps);
return;
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment the following lines to add migrations:
// const currentVersion = this.root?.version || 0;
// if (currentVersion < 1) {
// await this.migrationV1();
// }
// if (currentVersion < 2) {
// await this.migrationV2();
// }
}
/**
* Executes the initial migration logic when the account is first created:
* - Validates the user's profile data (name, email).
* - Sets up a public group (accessible by "everyone") for the users profile.
* - Sets up a private group for the user's pet posts.
* - Creates a default container with an empty list of posts.
* - Initializes the account root with version 0.
*/
private async initialMigration(creationProps: { name: string; other?: Record<string, unknown> }) {
const { name, other } = creationProps;
const profileErrors = UserProfile.validate({ name, ...other });
if (profileErrors.errors.length > 0) {
throw new Error("Invalid profile data: " + profileErrors.errors.join(", "));
}
// Create a public group for the user profile.
const publicGroup = Group.create({ owner: this });
publicGroup.addMember("everyone", "reader");
// Create the user profile with validated data.
this.profile = UserProfile.create(
{ name, ...other },
{ owner: publicGroup }
);
// Create a private group for pet data.
const privateGroup = Group.create({ owner: this });
// Create a default container holding an empty list of posts.
const defaultContainer = PetContainer.create(
{ posts: ListOfPosts.create([], privateGroup) },
privateGroup
);
// Initialize the account root with version tracking.
this.root = PetAccountRoot.create(
{ container: defaultContainer, version: 0 },
{ owner: this }
);
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment to add migrations:
// private async migrationV1() {
// if (this.root) {
// // Example migration logic: update pet posts if needed.
// this.root.version = 1;
// }
// }
// private async migrationV2() {
// if (this.root) {
// // Future migration logic goes here.
// this.root.version = 2;
// }
// }
}
```

View File

@@ -0,0 +1,236 @@
# Example app 4: A bubble tea ordering app that lets users customize drinks with different tea bases, add-ons, and delivery preferences
```typescript
import { Account, CoList, CoMap, Group, Profile, co } from "jazz-tools";
export const BubbleTeaAddOnTypes = [
"Pearl",
"Lychee jelly",
"Red bean",
"Brown sugar",
"Taro",
] as const;
export const BubbleTeaBaseTeaTypes = [
"Black",
"Oolong",
"Jasmine",
"Thai",
] as const;
/**
* A list of Bubble Tea add-ons.
* Provides a computed property to check for insertions.
*/
export class ListOfBubbleTeaAddOns extends CoList.Of(co.literal(...BubbleTeaAddOnTypes)) {
get hasChanges() {
return Object.entries(this._raw.insertions).length > 0;
}
}
/**
* Represents a finalized Bubble Tea order.
*
* Properties:
* - baseTea: Selected base tea type.
* - addOns: Selected add-ons.
* - deliveryDate: Delivery date for the order.
* - withMilk: Indicates if the order includes milk.
* - instructions: Optional additional instructions.
*/
export class BubbleTeaOrder extends CoMap {
baseTea = co.literal(...BubbleTeaBaseTeaTypes);
addOns = co.ref(ListOfBubbleTeaAddOns);
deliveryDate = co.Date;
withMilk = co.boolean;
instructions = co.optional.string;
}
/**
* Represents a draft (in-progress) Bubble Tea order.
*
* Properties:
* - baseTea: Optional base tea type.
* - addOns: Optional reference to selected add-ons.
* - deliveryDate: Optional delivery date.
* - withMilk: Optional milk preference.
* - instructions: Optional instructions.
*
* Methods:
* - validate: Checks that required fields are present.
* Computed:
* - hasChanges: Indicates if there have been modifications.
*/
export class DraftBubbleTeaOrder extends CoMap {
baseTea = co.optional.literal(...BubbleTeaBaseTeaTypes);
addOns = co.optional.ref(ListOfBubbleTeaAddOns);
deliveryDate = co.optional.Date;
withMilk = co.optional.boolean;
instructions = co.optional.string;
get hasChanges() {
return Object.keys(this._edits).length > 1 || this.addOns?.hasChanges;
}
validate() {
const errors: string[] = [];
if (!this.baseTea) {
errors.push("Please select your preferred base tea.");
}
if (!this.deliveryDate) {
errors.push("Please select a delivery date.");
}
return { errors };
}
}
/**
* A collaborative list of finalized Bubble Tea orders.
*/
export class ListOfBubbleTeaOrders extends CoList.Of(co.ref(BubbleTeaOrder)) {}
/**
* Container for Bubble Tea orders.
* Holds the draft order and the list of finalized orders.
*/
export class BubbleTeaContainer extends CoMap {
draft = co.ref(DraftBubbleTeaOrder);
orders = co.ref(ListOfBubbleTeaOrders);
}
/**
* The top-level account root for the Bubble Tea app.
*
* Properties:
* - container: The main container that organizes the Bubble Tea orders.
* - version: Optional version number for migration tracking.
*/
export class BubbleTeaAccountRoot extends CoMap {
container = co.ref(BubbleTeaContainer);
version = co.optional.number;
}
/**
* Represents a user's profile.
*
* Properties:
* - name: Required user name.
*
* Static method:
* - validate: Ensures that a non-empty name and email (if provided) are present.
*/
export class UserProfile extends Profile {
name = co.string;
static validate(data: { name?: string; email?: string }) {
const errors: string[] = [];
if (!data.name?.trim()) {
errors.push("Please enter a name.");
}
if (data.email !== undefined && !data.email.trim()) {
errors.push("Please enter an email.");
}
return { errors };
}
}
/**
* Main account class for the Bubble Tea app.
* Contains only the profile and root properties.
* Handles account initialization and migrations.
*/
export class BubbleTeaAccount extends Account {
profile = co.ref(UserProfile);
root = co.ref(BubbleTeaAccountRoot);
/**
* The migrate method is run on account creation and login.
* If the root is not initialized, it runs the initial migration.
* Otherwise, version-based migrations can be added.
*/
async migrate(creationProps?: { name: string; other?: Record<string, unknown> }) {
if (!this._refs.root && creationProps) {
await this.initialMigration(creationProps);
return;
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment the following lines to add migrations:
// const currentVersion = this.root?.version || 0;
// if (currentVersion < 1) {
// await this.migrationV1();
// }
// if (currentVersion < 2) {
// await this.migrationV2();
// }
}
/**
* Executes the initial migration logic when the account is first created:
* - Validates the user's profile data (name, email).
* - Sets up a public group (accessible by "everyone") for the user's profile.
* - Sets up a private group for the Bubble Tea data.
* - Creates a default BubbleTeaContainer with an empty draft and order list.
* - Initializes the account root with version 0.
*/
private async initialMigration(creationProps: { name: string; other?: Record<string, unknown> }) {
const { name, other } = creationProps;
const profileErrors = UserProfile.validate({ name, ...other });
if (profileErrors.errors.length > 0) {
throw new Error("Invalid profile data: " + profileErrors.errors.join(", "));
}
// Create a public group for the user profile.
const publicGroup = Group.create({ owner: this });
publicGroup.addMember("everyone", "reader");
// Create the user profile with validated data.
this.profile = UserProfile.create(
{ name, ...other },
{ owner: publicGroup }
);
// Create a private group for Bubble Tea data.
const privateGroup = Group.create({ owner: this });
// Create a default container with an empty draft order and empty list of finalized orders.
const defaultContainer = BubbleTeaContainer.create(
{
draft: DraftBubbleTeaOrder.create(
{
addOns: ListOfBubbleTeaAddOns.create([], privateGroup),
},
privateGroup
),
orders: ListOfBubbleTeaOrders.create([], privateGroup),
},
privateGroup
);
// Initialize the account root with version tracking.
this.root = BubbleTeaAccountRoot.create(
{
container: defaultContainer,
version: 0, // Set initial version
},
{ owner: this }
);
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment to add migrations:
// private async migrationV1() {
// if (this.root) {
// // Example migration logic: update orders if needed.
// this.root.version = 1;
// }
// }
// private async migrationV2() {
// if (this.root) {
// // Future migration logic goes here.
// this.root.version = 2;
// }
// }
}
```

View File

@@ -0,0 +1,211 @@
# Example app 5: An employee onboarding app that streamlines the hiring process through structured steps, including initial data collection, document uploads, and final approvals
```typescript
import { Account, CoList, CoMap, Group, ImageDefinition, Profile, co } from "jazz-tools";
type Steps = "initial" | "upload" | "final";
interface Step {
type: Steps;
prevStep: ReturnType<typeof co.ref> | undefined;
done: boolean;
isCurrentStep(): boolean;
}
/**
* Represents the initial onboarding step.
*
* Properties:
* - type: Always "initial".
* - ssn: Optional Social Security Number.
* - address: Optional address.
* - done: Indicates if this step is completed.
* - prevStep: Not applicable for the initial step.
*/
export class CoInitialStep extends CoMap implements Step {
type = co.literal("initial");
ssn? = co.string;
address? = co.string;
done = co.boolean;
prevStep = co.null;
isCurrentStep() {
return !this.done;
}
}
/**
* Represents the document upload step.
*
* Properties:
* - type: Always "upload".
* - prevStep: Reference to the completed initial step.
* - photo: Optional reference to an image (e.g. document photo).
* - done: Indicates if this step is completed.
*/
export class CoDocUploadStep extends CoMap implements Step {
type = co.literal("upload");
prevStep = co.ref(CoInitialStep);
photo = co.ref(ImageDefinition, { optional: true });
done = co.boolean;
isCurrentStep() {
return !!(this.prevStep?.done && !this.done);
}
}
/**
* Represents the final onboarding step.
*
* Properties:
* - type: Always "final".
* - prevStep: Reference to the completed document upload step.
* - done: Indicates if this step is completed.
*/
export class CoFinalStep extends CoMap implements Step {
type = co.literal("final");
prevStep = co.ref(CoDocUploadStep);
done = co.boolean;
isCurrentStep() {
return !!(this.prevStep?.done && !this.done);
}
}
/**
* Represents an employee undergoing the onboarding process.
*
* Properties:
* - name: The employee's name.
* - deleted: Optional soft-delete flag.
* - initialStep: Reference to the initial step.
* - docUploadStep: Reference to the document upload step.
* - finalStep: Reference to the final step.
*/
export class CoEmployee extends CoMap {
name = co.string;
deleted? = co.boolean;
initialStep = co.ref(CoInitialStep);
docUploadStep = co.ref(CoDocUploadStep);
finalStep = co.ref(CoFinalStep);
}
/**
* A collaborative list of employee references.
*/
export class EmployeeList extends CoList.Of(co.ref(CoEmployee)) {}
/**
* The top-level account root for the HR app.
*
* Properties:
* - employees: A list of employees.
* - version: Optional version number for migrations.
*/
export class HRAccountRoot extends CoMap {
employees = co.ref(EmployeeList);
version = co.optional.number;
}
/**
* Represents a user's profile.
*
* Properties:
* - name: The required user name.
*
* Static method:
* - validate: Ensures that a non-empty name (and email, if provided) is present.
*/
export class UserProfile extends Profile {
name = co.string;
static validate(data: { name?: string; email?: string }) {
const errors: string[] = [];
if (!data.name?.trim()) {
errors.push("Please enter a name.");
}
if (data.email !== undefined && !data.email.trim()) {
errors.push("Please enter an email.");
}
return { errors };
}
}
/**
* The main account class for the HR app.
* Contains only the profile and root properties.
* Handles account initialization and migrations.
*/
export class HRAccount extends Account {
profile = co.ref(UserProfile);
root = co.ref(HRAccountRoot);
/**
* Migrate is run on account creation and on every log-in.
* If the account root is not initialized, it runs the initial migration.
*/
async migrate(creationProps?: { name: string; other?: Record<string, unknown> }) {
if (!this._refs.root && creationProps) {
await this.initialMigration(creationProps);
return;
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment the following lines to add migrations:
// const currentVersion = this.root?.version || 0;
// if (currentVersion < 1) {
// await this.migrationV1();
// }
// if (currentVersion < 2) {
// await this.migrationV2();
// }
}
/**
* Executes the initial migration logic when the account is first created:
* - Validates the user's profile data.
* - Sets up a public group (accessible by "everyone") for the user profile.
* - Creates a default HRAccountRoot with an empty employee list.
* - Initializes the account root with version 0.
*/
private async initialMigration(creationProps: { name: string; other?: Record<string, unknown> }) {
const { name, other } = creationProps;
const profileErrors = UserProfile.validate({ name, ...other });
if (profileErrors.errors.length > 0) {
throw new Error("Invalid profile data: " + profileErrors.errors.join(", "));
}
// Create a public group for the user profile.
const publicGroup = Group.create({ owner: this });
publicGroup.addMember("everyone", "reader");
// Create the user profile with validated data.
this.profile = UserProfile.create({ name, ...other }, { owner: publicGroup });
// Create a private group for HR data.
const privateGroup = Group.create({ owner: this });
// Create a default employee list (empty).
const employees = EmployeeList.create([], privateGroup);
// Initialize the account root with version tracking.
this.root = HRAccountRoot.create(
{ employees, version: 0 },
{ owner: this }
);
}
// KEEP THESE COMMENTS IN THE GENERATED SCHEMA CODE
// Uncomment to add migrations:
// private async migrationV1() {
// if (this.root) {
// // Example migration logic: update employee records if needed.
// this.root.version = 1;
// }
// }
// private async migrationV2() {
// if (this.root) {
// // Future migration logic goes here.
// this.root.version = 2;
// }
// }
}
```

View File

@@ -0,0 +1,148 @@
# Example app 6: A task management app that helps users organize their to-dos with categories, tags, due dates, and priority levels
```typescript
import { Account, CoList, CoMap, Group, Profile, co } from "jazz-tools";
// Task priority levels
export type PriorityLevel = "Low" | "Medium" | "High";
// Represents a tag that can be associated with tasks
export class Tag extends CoMap {
name = co.string;
deleted = co.boolean;
}
export class TagList extends CoList.Of(co.ref(Tag)) {}
// Represents a category that can group tasks
export class Category extends CoMap {
name = co.string;
deleted = co.boolean;
}
export class CategoryList extends CoList.Of(co.ref(Category)) {}
// Represents a single task in the todo app
export class Task extends CoMap {
title = co.string;
description = co.optional.string;
dueDate = co.optional.Date;
isCompleted = co.boolean;
priority = co.literal("Low", "Medium", "High");
tags = co.ref(TagList);
category = co.optional.ref(Category);
deleted = co.boolean;
}
export class TaskList extends CoList.Of(co.ref(Task)) {}
// Container for organizing tasks, categories and tags
export class Container extends CoMap {
tasks = co.ref(TaskList);
categories = co.ref(CategoryList);
tags = co.ref(TagList);
}
// Root structure holding all data
export class AccountRoot extends CoMap {
container = co.ref(Container);
version = co.optional.number;
}
export class UserProfile extends Profile {
name = co.string;
static validate(data: { name?: string; other?: Record<string, unknown> }) {
const errors: string[] = [];
if (!data.name?.trim()) {
errors.push("Please enter a name.");
}
return { errors };
}
}
// Main account class that handles data initialization
export class JazzAccount extends Account {
profile = co.ref(UserProfile);
root = co.ref(AccountRoot);
async migrate(creationProps?: {
name: string;
other?: Record<string, unknown>;
}) {
if (!this._refs.root && creationProps) {
await this.initialMigration(creationProps);
return;
}
// uncomment this to add migrations
// const currentVersion = this.root?.version || 0;
// if (currentVersion < 1) {
// await this.migrationV1();
// }
// if (currentVersion < 2) {
// await this.migrationV2();
// }
}
private async initialMigration(
creationProps: {
name: string;
other?: Record<string, unknown>;
}
) {
const { name, other } = creationProps;
const profileErrors = UserProfile.validate({ name, ...other });
if (profileErrors.errors.length > 0) {
throw new Error(
"Invalid profile data: " + profileErrors.errors.join(", "),
);
}
const publicGroup = Group.create({ owner: this });
publicGroup.addMember("everyone", "reader");
this.profile = UserProfile.create(
{
name,
...other,
},
{ owner: publicGroup },
);
const privateGroup = Group.create({ owner: this });
// Create default container with empty lists
const defaultContainer = Container.create(
{
tasks: TaskList.create([], privateGroup),
categories: CategoryList.create([], privateGroup),
tags: TagList.create([], privateGroup),
},
privateGroup,
);
// Initialize root structure with version
this.root = AccountRoot.create({
container: defaultContainer,
version: 0, // Set initial version
// here owner is always "this" Account
}, { owner: this });
}
// uncomment this to add migrations
// private async migrationV1() {
// if (this.root) {
// // Add migration logic here
// this.root.version = 1;
// }
// }
// private async migrationV2() {
// if (this.root) {
// // Future migration logic here
// this.root.version = 2;
// }
// }
}
```

View File

@@ -0,0 +1,6 @@
{
"name": "cursor-docs",
"license": "MIT",
"version": "0.0.2",
"scripts": {}
}

View File

@@ -1,5 +1,12 @@
# jazz-react-native-auth-clerk
## 0.10.10
### Patch Changes
- Updated dependencies [3405d8f]
- jazz-react-native@0.10.10
## 0.10.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native-auth-clerk",
"version": "0.10.9",
"version": "0.10.10",
"type": "module",
"main": "dist/index.js",
"types": "src/index.tsx",

View File

@@ -1,5 +1,11 @@
# jazz-browser
## 0.10.10
### Patch Changes
- 3405d8f: read provided kvStore instead of falling back to the default in-memory store
## 0.10.8
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-react-native",
"version": "0.10.8",
"version": "0.10.10",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -11,6 +11,7 @@ import {
createJazzReactNativeContext,
createJazzReactNativeGuestContext,
} from "./platform.js";
import { KvStoreContext } from "./storage/kv-store-context.js";
export type JazzContextManagerProps<Acc extends Account> = {
guestMode?: boolean;
@@ -59,6 +60,10 @@ export class ReactNativeContextManager<
await this.updateContext(props, currentContext, authProps);
}
getKvStore(): KvStore {
return KvStoreContext.getInstance().getStorage();
}
propsChanged(props: JazzContextManagerProps<Acc>) {
if (!this.props) {
return true;

2
pnpm-lock.yaml generated
View File

@@ -1544,6 +1544,8 @@ importers:
specifier: ~5.6.2
version: 5.6.3
packages/cursor-docs: {}
packages/hash-slash:
devDependencies:
'@types/react':