Compare commits

..

2 Commits

Author SHA1 Message Date
Trisha Lim
89f099b15c add local-first conf sticker 2025-05-20 18:28:18 +01:00
Trisha Lim
1e0b496555 docs: add twoslash type check to CoLists, CoFeeds (#2281)
* docs: add twoslash type check to CoLists

* revert to not using a list type

* twoslash type check for CoFeed
2025-05-20 15:04:24 +01:00
4 changed files with 299 additions and 38 deletions

View File

@@ -51,7 +51,14 @@ const features: Array<{
export function HeroSection() {
return (
<div className="container grid items-center gap-x-8 gap-y-12 my-12 md:my-16 lg:my-24 lg:gap-x-10 lg:grid-cols-3">
<div className="container relative grid items-center gap-x-8 gap-y-12 my-12 md:my-16 lg:my-24 lg:gap-x-10 lg:grid-cols-3">
<Link href="" className="absolute right-4 -top-12 sm:top-0 xl:top-6 xl:right-6">
<img
alt="Meet us at Local-first Conference 2025"
src="/local-first-conf-sticker.png"
className="w-[120px] sm:w-[150px] h-auto rotate-[10deg]"
/>
</Link>
<div className="flex flex-col justify-center gap-5 lg:col-span-2 lg:gap-8">
<Kicker>Toolkit for backendless apps</Kicker>
<H1>

View File

@@ -19,7 +19,9 @@ The following examples demonstrate a practical use of CoFeeds:
CoFeeds are defined by specifying the type of items they'll contain, similar to how you define CoLists:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
// ---cut---
// Define a schema for feed items
const Activity = co.map({
timestamp: z.date(),
@@ -73,24 +75,54 @@ Since CoFeeds are made of entries from users over multiple sessions, you can acc
To retrieve entries from a session:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const sessionId = `${me.id}_session_z1` as SessionID;
// ---cut---
// Get the feed for a specific session
const sessionFeed = activityFeed.perSession[sessionId];
// Latest entry from a session
console.log(sessionFeed.value.action); // "watering"
console.log(sessionFeed?.value?.action); // "watering"
```
</CodeGroup>
For convenience, you can also access the latest entry from the current session with `inCurrentSession`:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const sessionId = `${me.id}_session_z1` as SessionID;
// ---cut---
// Get the feed for the current session
const currentSessionFeed = activityFeed.inCurrentSession;
// Latest entry from the current session
console.log(currentSessionFeed.value.action); // "harvesting"
console.log(currentSessionFeed?.value?.action); // "harvesting"
```
</CodeGroup>
@@ -99,24 +131,54 @@ console.log(currentSessionFeed.value.action); // "harvesting"
To retrieve entries from a specific account (with entries from all sessions combined) use `perAccount`:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
// ---cut---
// Get the feed for a specific account
const accountFeed = activityFeed.perAccount[accountId];
// Latest entry from the account
console.log(accountFeed.value.action); // "watering"
console.log(accountFeed.value?.action); // "watering"
```
</CodeGroup>
For convenience, you can also access the latest entry from the current account with `byMe`:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
// ---cut---
// Get the feed for the current account
const myLatestEntry = activityFeed.byMe;
// Latest entry from the current account
console.log(myLatestEntry.value.action); // "harvesting"
console.log(myLatestEntry?.value?.action); // "harvesting"
```
</CodeGroup>
@@ -127,7 +189,23 @@ console.log(myLatestEntry.value.action); // "harvesting"
To retrieve all entries from a CoFeed:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
const sessionId = `${me.id}_session_z1` as SessionID;
// ---cut---
// Get the feeds for a specific account and session
const accountFeed = activityFeed.perAccount[accountId];
const sessionFeed = activityFeed.perSession[sessionId];
@@ -149,11 +227,25 @@ for (const entry of sessionFeed.all) {
To retrieve the latest entry from a CoFeed, ie. the last update:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
// ---cut---
// Get the latest entry from the current account
const latestEntry = activityFeed.byMe;
console.log(`My last action was ${latestEntry.value.action}`);
console.log(`My last action was ${latestEntry?.value?.action}`);
// "My last action was harvesting"
// Get the latest entry from each account
@@ -171,7 +263,21 @@ CoFeeds are append-only; you can add new items, but not modify existing ones. Th
### Adding Items
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
// ---cut---
// Log a new activity
activityFeed.push(Activity.create({
timestamp: new Date(),
@@ -188,23 +294,37 @@ Each item is automatically associated with the current user's session. You don't
Each entry is automatically added to the current session's feed. When a user has multiple open sessions (like both a mobile app and web browser), each session creates its own separate entries:
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const fromMobileFeed = ActivityFeed.create([]);
const fromBrowserFeed = ActivityFeed.create([]);
// ---cut---
// On mobile device:
fromMobileFeed.push(Activity.create({
timestamp: new Date(),
action: "harvesting",
location: "Vegetable patch"
notes: "Vegetable patch"
}));
// On web browser (same user):
fromBrowserFeed.push(Activity.create({
timestamp: new Date(),
action: "planting",
location: "Flower bed"
notes: "Flower bed"
}));
// These are separate entries in the same feed, from the same account
```
</CodeGroup>
@@ -217,7 +337,22 @@ CoFeeds support metadata, which is useful for tracking information about the fee
The `by` property is the account that made the entry.
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
// ---cut---
const accountFeed = activityFeed.perAccount[accountId];
// Get the account that made the last entry
@@ -230,7 +365,22 @@ console.log(accountFeed?.by);
The `madeAt` property is a timestamp of when the entry was added to the feed.
<CodeGroup>
```ts
```ts twoslash
import { Group, co, z, ID, Account, SessionID } from "jazz-tools";
import { createJazzTestAccount } from 'jazz-tools/testing';
const me = await createJazzTestAccount();
const Activity = co.map({
timestamp: z.date(),
action: z.literal(["watering", "planting", "harvesting", "maintenance"]),
notes: z.optional(z.string()),
});
const ActivityFeed = co.feed(Activity);
const activityFeed = ActivityFeed.create([]);
const accountId = me.id;
// ---cut---
const accountFeed = activityFeed.perAccount[accountId];
// Get the timestamp of the last update

View File

@@ -13,7 +13,13 @@ CoLists are ordered collections that work like JavaScript arrays. They provide i
CoLists are defined by specifying the type of items they contain:
<CodeGroup>
```ts
```ts twoslash
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
// ---cut---
import { co, z } from "jazz-tools";
const ListOfResources = co.list(z.string());
@@ -25,7 +31,14 @@ const ListOfTasks = co.list(Task);
To create a `CoList`:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
// ---cut---
// Create an empty list
const resources = co.list(z.string()).create([]);
@@ -68,7 +81,22 @@ See [Groups as permission scopes](/docs/groups/intro) for more information on ho
CoLists support standard array access patterns:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
const ListOfTasks = co.list(Task);
const tasks = ListOfTasks.create([
Task.create({ title: "Prepare soil beds", status: "todo" }),
Task.create({ title: "Order compost", status: "todo" }),
]);
// ---cut---
// Access by index
const firstTask = tasks[0];
console.log(firstTask.title); // "Prepare soil beds"
@@ -94,7 +122,22 @@ console.log(todoTasks.length); // 1
Update `CoList`s just like you would JavaScript arrays:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
const ListOfTasks = co.list(Task);
const ListOfResources = co.list(z.string());
const resources = ListOfResources.create([]);
const tasks = ListOfTasks.create([]);
// ---cut---
// Add items
resources.push("Tomatoes"); // Add to end
resources.unshift("Lettuce"); // Add to beginning
@@ -116,7 +159,18 @@ tasks[0].status = "complete"; // Update properties of references
Remove specific items by index with `splice`, or remove the first or last item with `pop` or `shift`:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const ListOfResources = co.list(z.string());
const resources = ListOfResources.create([
"Tomatoes",
"Cucumber",
"Peppers",
]);
// ---cut---
// Remove 2 items starting at index 1
resources.splice(1, 2);
console.log(resources); // ["Cucumber", "Peppers"]
@@ -136,7 +190,14 @@ resources.shift(); // Remove first item
`CoList`s support the standard JavaScript array methods you already know:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const ListOfResources = co.list(z.string());
const resources = ListOfResources.create([]);
// ---cut---
// Add multiple items at once
resources.push("Tomatoes", "Basil", "Peppers");
@@ -158,9 +219,23 @@ console.log(resources); // ["Basil", "Peppers", "Tomatoes"]
CoLists maintain type safety for their items:
<CodeGroup>
```ts
```ts twoslash
import { co, z } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
const ListOfTasks = co.list(Task);
const ListOfResources = co.list(z.string());
const resources = ListOfResources.create([]);
const tasks = ListOfTasks.create([]);
// ---cut---
// TypeScript catches type errors
resources.push("Carrots"); // ✓ Valid string
// @errors: 2345
resources.push(42); // ✗ Type error: expected string
// For lists of references
@@ -178,20 +253,30 @@ tasks.forEach(task => {
CoLists work well with UI rendering libraries:
<CodeGroup>
```tsx
```tsx twoslash
import * as React from "react";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
});
// ---cut---
import { co, z, Loaded } from "jazz-tools";
const ListOfTasks = co.list(Task);
// React example
function TaskList({ tasks: Loaded<typeof ListOfTasks> }) {
return (
<ul>
{tasks.map(task => (
function TaskList({ tasks }: { tasks: Loaded<typeof ListOfTasks> }) {
return (
<ul>
{tasks.map(task => (
task ? (
<li key={task.id}>
{task.title} - {task.status}
</li>
))}
</ul>
): null
))}
</ul>
);
}
```
@@ -202,15 +287,34 @@ function TaskList({ tasks: Loaded<typeof ListOfTasks> }) {
CoLists can be used to create one-to-many relationships:
<CodeGroup>
```ts
import { co, z } from "jazz-tools";
```ts twoslash
import { co, z, CoListSchema } from "jazz-tools";
const Task = co.map({
title: z.string(),
status: z.literal(["todo", "in-progress", "complete"]),
get project(): z.ZodOptional<typeof Project> {
return z.optional(Project);
}
});
const ListOfTasks = co.list(Task);
const Project = co.map({
name: z.string(),
tasks: co.list(Task),
get tasks(): CoListSchema<typeof Task> {
return ListOfTasks;
}
});
// ...
const project = Project.create(
{
name: "Garden Project",
tasks: ListOfTasks.create([]),
},
);
const task = Task.create({
title: "Plant seedlings",

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB