Compare commits
20 Commits
cojson@0.1
...
gio/queue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1193c3c63 | ||
|
|
f30130b92f | ||
|
|
1a77233ecb | ||
|
|
09a95b8542 | ||
|
|
f46329ac68 | ||
|
|
c551839179 | ||
|
|
a0683f9d21 | ||
|
|
a56958c69e | ||
|
|
9f336103e8 | ||
|
|
1174942a4e | ||
|
|
267920b7dc | ||
|
|
63a7aa0f54 | ||
|
|
2b7534d30c | ||
|
|
be23a81b1b | ||
|
|
68416784fd | ||
|
|
84d3c09cb1 | ||
|
|
53e8c39e8d | ||
|
|
87b41fefad | ||
|
|
265a4e8cc5 | ||
|
|
623467503f |
5
.changeset/silver-chicken-dance.md
Normal file
5
.changeset/silver-chicken-dance.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Add jazz.messagequeue.pushed/pulled counters, remove jazz.messagequeue.size gauge
|
||||
@@ -1,5 +1,12 @@
|
||||
# chat-rn-expo-clerk
|
||||
|
||||
## 1.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63a7aa0]
|
||||
- jazz-expo@0.13.1
|
||||
|
||||
## 1.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-expo-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.93",
|
||||
"version": "1.0.94",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useClerk } from "@clerk/clerk-expo";
|
||||
// FIXME: why isn't the export working? IDE is fine, Metro doesn't like the non 'dist' import
|
||||
import { JazzProviderWithClerk } from "jazz-expo/dist/auth/clerk";
|
||||
import { JazzProviderWithClerk } from "jazz-expo/auth/clerk";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { apiKey } from "./apiKey";
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# chat-rn-expo
|
||||
|
||||
## 1.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63a7aa0]
|
||||
- jazz-expo@0.13.1
|
||||
|
||||
## 1.0.80
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn-expo",
|
||||
"version": "1.0.80",
|
||||
"version": "1.0.81",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63a7aa0]
|
||||
- jazz-react-native@0.13.1
|
||||
|
||||
## 1.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.89",
|
||||
"version": "1.0.90",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,3 +1,64 @@
|
||||
# Multi-cursor example
|
||||
# Jazz Multi-Cursors Example
|
||||
|
||||
An example app of using Jazz for showing multiple-cursors on a simple canvas.
|
||||
Track user presence on a canvas with multiple cursors and out of bounds indicators.
|
||||
|
||||
## Getting started
|
||||
|
||||
You can either
|
||||
1. Clone the jazz repository, and run the app within the monorepo.
|
||||
2. Or create a new Jazz project using this example as a template.
|
||||
|
||||
|
||||
### Using the example as a template
|
||||
|
||||
Create a new Jazz project, and use this example as a template.
|
||||
```bash
|
||||
npx create-jazz-app@latest multi-cursors-app --example multi-cursors
|
||||
```
|
||||
|
||||
Go to the new project directory.
|
||||
```bash
|
||||
cd multi-cursors-app
|
||||
```
|
||||
|
||||
Run the dev server.
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Using the monorepo
|
||||
|
||||
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
|
||||
|
||||
Clone the jazz repository.
|
||||
```bash
|
||||
git clone https://github.com/garden-co/jazz.git
|
||||
```
|
||||
|
||||
Install and build dependencies.
|
||||
```bash
|
||||
pnpm i && npx turbo build
|
||||
```
|
||||
|
||||
Go to the example directory.
|
||||
```bash
|
||||
cd jazz/examples/multi-cursors/
|
||||
```
|
||||
|
||||
Start the dev server.
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
|
||||
|
||||
## Questions / problems / feedback
|
||||
|
||||
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
|
||||
|
||||
|
||||
## Configuration: sync server
|
||||
|
||||
By default, the example app uses [Jazz Cloud](https://jazz.tools/cloud) (`wss://cloud.jazz.tools`) - so cross-device use, invites and collaboration should just work.
|
||||
|
||||
You can also run a local sync server by running `npx jazz-run sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?peer=ws://localhost:4200`), or by setting the `sync` parameter of `JazzProvider` component in [./src/main.tsx](./src/main.tsx).
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
AlertCircleIcon,
|
||||
AlertTriangleIcon,
|
||||
ArrowDownIcon,
|
||||
ArrowRightIcon,
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
MessageCircleQuestionIcon,
|
||||
MonitorSmartphoneIcon,
|
||||
MoonIcon,
|
||||
MousePointer2Icon,
|
||||
MousePointerSquareDashedIcon,
|
||||
ScanFace,
|
||||
ScrollIcon,
|
||||
@@ -50,6 +50,7 @@ const icons = {
|
||||
close: XIcon,
|
||||
code: CodeIcon,
|
||||
copy: ClipboardIcon,
|
||||
cursor: MousePointer2Icon,
|
||||
darkTheme: MoonIcon,
|
||||
delete: TrashIcon,
|
||||
devices: MonitorSmartphoneIcon,
|
||||
|
||||
@@ -209,13 +209,14 @@ However, once authenticated, your users won't need to interact with Clerk anymor
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
The clerk provider is not built into `jazz-react` and needs the `jazz-react-auth-clerk` package to be installed.
|
||||
|
||||
After installing the package you can use the `JazzProviderWithClerk` component to wrap your app:
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
The clerk provider is not built into `jazz-expo` and needs the `jazz-expo/auth/clerk` import to be installed. Note the `__experimental_resourceCache` option. This helps render Clerk components when offline.
|
||||
You can use the `JazzProviderWithClerk` component to wrap your app. Note the `__experimental_resourceCache` option. This helps render Clerk components when offline.
|
||||
</ContentByFramework>
|
||||
|
||||
After installing the package you can use the `JazzProviderWithClerk` component to wrap your app:
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
|
||||
@@ -79,10 +79,10 @@ If you're using Clerk auth in your Expo application, you'll need to:
|
||||
```tsx twoslash
|
||||
// @noErrors: 2300 2307
|
||||
// Before
|
||||
import { ClerkAuthProvider, useClerkAuth } from "jazz-react-native-clerk"; // [!code --]
|
||||
import { JazzProviderWithClerk } from "jazz-react-native-clerk"; // [!code --]
|
||||
|
||||
// After
|
||||
import { ClerkAuthProvider, useClerkAuth } from "jazz-expo/auth/clerk"; // [!code ++]
|
||||
import { JazzProviderWithClerk } from "jazz-expo/auth/clerk"; // [!code ++]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -134,31 +134,6 @@ declare module "jazz-expo" { // [!code ++:5]
|
||||
|
||||
The `jazz-expo` implementation supports the Expo New Architecture.
|
||||
|
||||
## Styling with NativeWind
|
||||
|
||||
For Expo projects, remember to use NativeWind CSS for styling, which is the recommended approach for Jazz applications built with Expo:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
// @noErrors: 2305 2686
|
||||
import { View, Text } from "react-native";
|
||||
import { styled } from "nativewind";
|
||||
|
||||
const StyledView = styled(View);
|
||||
const StyledText = styled(Text);
|
||||
|
||||
export function MyComponent() {
|
||||
return (
|
||||
<StyledView className="p-4 bg-white dark:bg-slate-800">
|
||||
<StyledText className="text-black dark:text-white text-lg font-bold">
|
||||
Hello, Jazz with Expo!
|
||||
</StyledText>
|
||||
</StyledView>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## For More Information
|
||||
|
||||
For detailed setup instructions, refer to the [React Native Expo Setup Guide](/docs/react-native-expo/project-setup)
|
||||
|
||||
@@ -8,7 +8,9 @@ CoFeeds are append-only data structures that track entries from different user s
|
||||
|
||||
Each account can have multiple sessions (different browser tabs, devices, or app instances), making CoFeeds ideal for building features like activity logs, presence indicators, and notification systems.
|
||||
|
||||
The [Reactions example](https://github.com/garden-co/jazz/tree/main/examples/reactions) demonstrates a practical use of CoFeeds.
|
||||
The following examples demonstrate a practical use of CoFeeds:
|
||||
- [Multi-cursors](https://github.com/garden-co/jazz/tree/main/examples/multi-cursors) - track user presence on a canvas with multiple cursors and out of bounds indicators
|
||||
- [Reactions](https://github.com/garden-co/jazz/tree/main/examples/reactions) - store per-user emoji reaction using a CoFeed
|
||||
|
||||
## Creating CoFeeds
|
||||
|
||||
|
||||
@@ -253,6 +253,38 @@ const ReactionsIllustration = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const MultiCursorIllustration = () => (
|
||||
<div className="flex bg-stone-100 h-full flex-col items-center justify-center dark:bg-transparent p-4">
|
||||
<div className=" bg-white md:aspect-[3/2] flex flex-col rounded-md shadow-xl shadow-stone-400/20 dark:shadow-none">
|
||||
<div className="w-full py-2 flex items-center gap-1.5 px-2 border-b dark:border-b-stone-200">
|
||||
<span className="rounded-full size-2 bg-stone-200"></span>
|
||||
<span className="rounded-full size-2 bg-stone-200"></span>
|
||||
<span className="rounded-full size-2 bg-stone-200"></span>
|
||||
</div>
|
||||
|
||||
<div className="h-full mx-auto flex flex-col justify-center p-12 sm:p-16">
|
||||
<div className="inline-block relative px-1 ring-1 ring-blue-400">
|
||||
<div className="absolute size-2 bg-white border border-blue-400 -left-1 -top-1"></div>
|
||||
<div className="absolute size-2 bg-white border border-blue-400 -right-1 -top-1"></div>
|
||||
<div className="absolute size-2 bg-white border border-blue-400 -left-1 -bottom-1"></div>
|
||||
<div className="absolute size-2 bg-white border border-blue-400 -right-1 -bottom-1"></div>
|
||||
|
||||
<span className="text-lg font-semibold md:text-2xl md:font-bold text-stone-800 ">
|
||||
Hello, world!
|
||||
</span>
|
||||
<div className="absolute -top-10 right-4 text-rose-600 flex items-end gap-1">
|
||||
<Icon name="cursor"></Icon> <span className="text-xs">Mia</span>
|
||||
</div>
|
||||
<div className="absolute -bottom-10 left-4 text-green-600 flex items-end gap-1">
|
||||
<Icon name="cursor"></Icon>{" "}
|
||||
<span className="text-xs">Sebastian</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const PetIllustration = () => (
|
||||
<div className="h-full p-4 bg-[url('/dog.jpg')] bg-cover bg-center p-4 flex items-end">
|
||||
<div className="inline-flex justify-center gap-1 mx-auto">
|
||||
@@ -395,6 +427,16 @@ const reactExamples: Example[] = [
|
||||
demoUrl: "https://reactions-demo.jazz.tools",
|
||||
illustration: <ReactionsIllustration />,
|
||||
},
|
||||
{
|
||||
name: "Cursor presence",
|
||||
slug: "multi-cursors",
|
||||
description:
|
||||
"Track user presence on a canvas with multiple cursors and out of bounds indicators.",
|
||||
tech: [tech.react],
|
||||
features: [features.coFeed],
|
||||
demoUrl: "https://jazz-multi-cursors.vercel.app",
|
||||
illustration: <MultiCursorIllustration />,
|
||||
},
|
||||
{
|
||||
name: "Rate my pet",
|
||||
slug: "pets",
|
||||
@@ -484,7 +526,8 @@ const rnExamples: Example[] = [
|
||||
{
|
||||
name: "Chat",
|
||||
slug: "chat-rn",
|
||||
description: "A simple React Native app that creates a chat room with a shareable link.",
|
||||
description:
|
||||
"A simple React Native app that creates a chat room with a shareable link.",
|
||||
tech: [tech.reactNative],
|
||||
illustration: <ChatIllustration />,
|
||||
},
|
||||
@@ -492,7 +535,8 @@ const rnExamples: Example[] = [
|
||||
{
|
||||
name: "Chat",
|
||||
slug: "chat-rn-expo",
|
||||
description: "A simple Expo app that creates a chat room with a shareable link.",
|
||||
description:
|
||||
"A simple Expo app that creates a chat room with a shareable link.",
|
||||
tech: [tech.reactNative, tech.expo],
|
||||
illustration: <ChatIllustration />,
|
||||
},
|
||||
@@ -500,7 +544,8 @@ const rnExamples: Example[] = [
|
||||
{
|
||||
name: "Chat",
|
||||
slug: "chat-rn-expo-clerk",
|
||||
description: "Exactly like the React Native Expo chat app, with Clerk for auth.",
|
||||
description:
|
||||
"Exactly like the React Native Expo chat app, with Clerk for auth.",
|
||||
tech: [tech.reactNative, tech.expo],
|
||||
features: [features.clerk],
|
||||
illustration: <ClerkIllustration />,
|
||||
|
||||
@@ -56,8 +56,8 @@ export const docNavigationItems = [
|
||||
"react-native": 100,
|
||||
"react-native-expo": 100,
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Tools",
|
||||
@@ -82,7 +82,7 @@ export const docNavigationItems = [
|
||||
{
|
||||
name: "0.13.0 - React Native Split",
|
||||
href: "/docs/upgrade/0-13-0",
|
||||
done: 100
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
// upgrade guides
|
||||
|
||||
@@ -26,13 +26,11 @@ export function InterpolateInCode(replace: { [key: string]: string }) {
|
||||
}: { highlightedCode: string }) => {
|
||||
const newHighlightedCode = Object.entries(replace).reduce(
|
||||
(acc, [key, value]) => {
|
||||
return acc.replaceAll(
|
||||
key.replaceAll("$", "$").replaceAll("_", "_"),
|
||||
value,
|
||||
);
|
||||
return acc.replaceAll(key, value);
|
||||
},
|
||||
highlightedCode,
|
||||
);
|
||||
|
||||
return <div dangerouslySetInnerHTML={{ __html: newHighlightedCode }} />;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -126,7 +126,7 @@ describe("createWebSocketPeer", () => {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
new: {},
|
||||
priority: 1,
|
||||
priority: 6,
|
||||
};
|
||||
|
||||
void peer.outgoing.push(message1);
|
||||
@@ -214,7 +214,7 @@ describe("createWebSocketPeer", () => {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
new: {},
|
||||
priority: 1,
|
||||
priority: 6,
|
||||
};
|
||||
|
||||
void peer.outgoing.push(message1);
|
||||
@@ -243,7 +243,7 @@ describe("createWebSocketPeer", () => {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
new: {},
|
||||
priority: 1,
|
||||
priority: 6,
|
||||
};
|
||||
|
||||
void peer.outgoing.push(message1);
|
||||
@@ -269,7 +269,7 @@ describe("createWebSocketPeer", () => {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
new: {},
|
||||
priority: 1,
|
||||
priority: 6,
|
||||
};
|
||||
|
||||
const stream: SyncMessage[] = [];
|
||||
@@ -316,7 +316,7 @@ describe("createWebSocketPeer", () => {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
new: {},
|
||||
priority: 1,
|
||||
priority: 6,
|
||||
};
|
||||
|
||||
const stream: SyncMessage[] = [];
|
||||
@@ -365,7 +365,7 @@ describe("createWebSocketPeer", () => {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
new: {},
|
||||
priority: 1,
|
||||
priority: 6,
|
||||
};
|
||||
|
||||
void peer.outgoing.push(message1);
|
||||
@@ -411,7 +411,7 @@ describe("createWebSocketPeer", () => {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
new: {},
|
||||
priority: 1,
|
||||
priority: 6,
|
||||
};
|
||||
|
||||
void peer.outgoing.push(message1);
|
||||
@@ -450,7 +450,7 @@ describe("createWebSocketPeer", () => {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
new: {},
|
||||
priority: 1,
|
||||
priority: 6,
|
||||
};
|
||||
|
||||
void peer.outgoing.push(message1);
|
||||
|
||||
@@ -10,10 +10,21 @@ import { CO_VALUE_PRIORITY } from "./priority.js";
|
||||
import { Peer, SyncMessage } from "./sync.js";
|
||||
|
||||
export class PeerState {
|
||||
private queue: PriorityBasedMessageQueue;
|
||||
|
||||
constructor(
|
||||
private peer: Peer,
|
||||
knownStates: PeerKnownStates | undefined,
|
||||
) {
|
||||
/**
|
||||
* We set as default priority HIGH to handle all the messages without a
|
||||
* priority property as HIGH priority.
|
||||
*
|
||||
* This way we consider all the non-content messsages as HIGH priority.
|
||||
*/
|
||||
this.queue = new PriorityBasedMessageQueue(CO_VALUE_PRIORITY.HIGH, {
|
||||
peerRole: peer.role,
|
||||
});
|
||||
this.optimisticKnownStates = knownStates?.clone() ?? new PeerKnownStates();
|
||||
|
||||
// We assume that exchanges with storage peers are always successful
|
||||
@@ -76,13 +87,6 @@ export class PeerState {
|
||||
return this.peer.role === "server" || this.peer.role === "storage";
|
||||
}
|
||||
|
||||
/**
|
||||
* We set as default priority HIGH to handle all the messages without a
|
||||
* priority property as HIGH priority.
|
||||
*
|
||||
* This way we consider all the non-content messsages as HIGH priority.
|
||||
*/
|
||||
private queue = new PriorityBasedMessageQueue(CO_VALUE_PRIORITY.HIGH);
|
||||
private processing = false;
|
||||
public closed = false;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ValueType, metrics } from "@opentelemetry/api";
|
||||
import type { CoValuePriority } from "./priority.js";
|
||||
import { Counter, ValueType, metrics } from "@opentelemetry/api";
|
||||
import { CO_VALUE_PRIORITY, type CoValuePriority } from "./priority.js";
|
||||
import type { SyncMessage } from "./sync.js";
|
||||
|
||||
function promiseWithResolvers<R>() {
|
||||
@@ -34,7 +34,7 @@ type Tuple<T, N extends number, A extends unknown[] = []> = A extends {
|
||||
? A
|
||||
: Tuple<T, N, [...A, T]>;
|
||||
|
||||
type QueueTuple = Tuple<LinkedList<QueueEntry>, 8>;
|
||||
type QueueTuple = Tuple<LinkedList<QueueEntry>, 3>;
|
||||
|
||||
type LinkedListNode<T> = {
|
||||
value: T;
|
||||
@@ -46,6 +46,8 @@ type LinkedListNode<T> = {
|
||||
* as our queues can grow very large when the system is under pressure.
|
||||
*/
|
||||
export class LinkedList<T> {
|
||||
constructor(private meter?: QueueMeter) {}
|
||||
|
||||
head: LinkedListNode<T> | undefined = undefined;
|
||||
tail: LinkedListNode<T> | undefined = undefined;
|
||||
length = 0;
|
||||
@@ -64,6 +66,7 @@ export class LinkedList<T> {
|
||||
}
|
||||
|
||||
this.length++;
|
||||
this.meter?.push();
|
||||
}
|
||||
|
||||
shift() {
|
||||
@@ -82,34 +85,83 @@ export class LinkedList<T> {
|
||||
|
||||
this.length--;
|
||||
|
||||
this.meter?.pull();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export class PriorityBasedMessageQueue {
|
||||
private queues: QueueTuple = [
|
||||
new LinkedList<QueueEntry>(),
|
||||
new LinkedList<QueueEntry>(),
|
||||
new LinkedList<QueueEntry>(),
|
||||
new LinkedList<QueueEntry>(),
|
||||
new LinkedList<QueueEntry>(),
|
||||
new LinkedList<QueueEntry>(),
|
||||
new LinkedList<QueueEntry>(),
|
||||
new LinkedList<QueueEntry>(),
|
||||
];
|
||||
queueSizeCounter = metrics
|
||||
.getMeter("cojson")
|
||||
.createUpDownCounter("jazz.messagequeue.size", {
|
||||
description: "Size of the message queue",
|
||||
valueType: ValueType.INT,
|
||||
unit: "entry",
|
||||
});
|
||||
class QueueMeter {
|
||||
private pullCounter: Counter;
|
||||
private pushCounter: Counter;
|
||||
|
||||
private getQueue(priority: CoValuePriority) {
|
||||
return this.queues[priority];
|
||||
constructor(
|
||||
prefix: string,
|
||||
private attrs?: Record<string, string | number>,
|
||||
) {
|
||||
this.pullCounter = metrics
|
||||
.getMeter("cojosn")
|
||||
.createCounter(`${prefix}.pulled`, {
|
||||
description: "Number of messages pulled from the queue",
|
||||
valueType: ValueType.INT,
|
||||
unit: "1",
|
||||
});
|
||||
this.pushCounter = metrics
|
||||
.getMeter("cojosn")
|
||||
.createCounter(`${prefix}.pushed`, {
|
||||
description: "Number of messages pushed to the queue",
|
||||
valueType: ValueType.INT,
|
||||
unit: "1",
|
||||
});
|
||||
|
||||
/**
|
||||
* This makes sure that those metrics are generated (and emitted) as soon as the queue is created.
|
||||
* This is to avoid edge cases where one series reset is delayed, which would cause spikes or dips
|
||||
* when queried - and it also more correctly represents the actual state of the queue after a restart.
|
||||
*/
|
||||
this.pullCounter.add(0, this.attrs);
|
||||
this.pushCounter.add(0, this.attrs);
|
||||
}
|
||||
|
||||
constructor(private defaultPriority: CoValuePriority) {}
|
||||
public pull() {
|
||||
this.pullCounter.add(1, this.attrs);
|
||||
}
|
||||
|
||||
public push() {
|
||||
this.pushCounter.add(1, this.attrs);
|
||||
}
|
||||
}
|
||||
|
||||
function meteredList<T>(attrs?: Record<string, string | number>) {
|
||||
return new LinkedList<T>(new QueueMeter("jazz.messagequeue", attrs));
|
||||
}
|
||||
|
||||
const PRIORITY_TO_QUEUE_INDEX = {
|
||||
[CO_VALUE_PRIORITY.HIGH]: 0,
|
||||
[CO_VALUE_PRIORITY.MEDIUM]: 1,
|
||||
[CO_VALUE_PRIORITY.LOW]: 2,
|
||||
} as const;
|
||||
|
||||
export class PriorityBasedMessageQueue {
|
||||
private queues: QueueTuple;
|
||||
|
||||
constructor(
|
||||
private defaultPriority: CoValuePriority,
|
||||
/**
|
||||
* Optional attributes to be added to the generated metrics.
|
||||
* By default the metrics will have the priority as an attribute.
|
||||
*/
|
||||
attrs?: Record<string, string | number>,
|
||||
) {
|
||||
this.queues = [
|
||||
meteredList({ priority: CO_VALUE_PRIORITY.HIGH, ...attrs }),
|
||||
meteredList({ priority: CO_VALUE_PRIORITY.MEDIUM, ...attrs }),
|
||||
meteredList({ priority: CO_VALUE_PRIORITY.LOW, ...attrs }),
|
||||
];
|
||||
}
|
||||
|
||||
private getQueue(priority: CoValuePriority) {
|
||||
return this.queues[PRIORITY_TO_QUEUE_INDEX[priority]];
|
||||
}
|
||||
|
||||
public push(msg: SyncMessage) {
|
||||
const { promise, resolve, reject } = promiseWithResolvers<void>();
|
||||
@@ -119,24 +171,12 @@ export class PriorityBasedMessageQueue {
|
||||
|
||||
this.getQueue(priority).push(entry);
|
||||
|
||||
this.queueSizeCounter.add(1, {
|
||||
priority,
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public pull() {
|
||||
const priority = this.queues.findIndex((queue) => queue.length > 0);
|
||||
|
||||
if (priority === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.queueSizeCounter.add(-1, {
|
||||
priority,
|
||||
});
|
||||
|
||||
return this.queues[priority]?.shift();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { type CoValueHeader } from "./coValueCore.js";
|
||||
* The priority value is handled as weight in the weighed round robin algorithm
|
||||
* used to determine the order in which messages are sent.
|
||||
*
|
||||
* Follows the HTTP urgency range and order:
|
||||
* Loosely follows the HTTP urgency range and order, but limited to 3 values:
|
||||
* - https://www.rfc-editor.org/rfc/rfc9218.html#name-urgency
|
||||
*/
|
||||
export const CO_VALUE_PRIORITY = {
|
||||
@@ -16,7 +16,7 @@ export const CO_VALUE_PRIORITY = {
|
||||
LOW: 6,
|
||||
} as const;
|
||||
|
||||
export type CoValuePriority = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
||||
export type CoValuePriority = 0 | 3 | 6;
|
||||
|
||||
export function getPriorityFromHeader(
|
||||
header: CoValueHeader | undefined | boolean,
|
||||
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
tearDownTestMetricReader,
|
||||
} from "./testUtils.js";
|
||||
|
||||
function setup() {
|
||||
function setup(attrs?: Record<string, string | number>) {
|
||||
const metricReader = createTestMetricReader();
|
||||
const queue = new PriorityBasedMessageQueue(CO_VALUE_PRIORITY.MEDIUM);
|
||||
const queue = new PriorityBasedMessageQueue(CO_VALUE_PRIORITY.MEDIUM, attrs);
|
||||
return { queue, metricReader };
|
||||
}
|
||||
|
||||
@@ -18,10 +18,133 @@ describe("PriorityBasedMessageQueue", () => {
|
||||
tearDownTestMetricReader();
|
||||
});
|
||||
|
||||
describe("meteredQueue", () => {
|
||||
test("should corretly count pushes", async () => {
|
||||
const { queue, metricReader } = setup();
|
||||
const message: SyncMessage = {
|
||||
action: "load",
|
||||
id: "co_ztest-id",
|
||||
header: false,
|
||||
sessions: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pushed", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
}),
|
||||
).toBe(0);
|
||||
|
||||
void queue.push(message);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pushed", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
}),
|
||||
).toBe(1);
|
||||
|
||||
void queue.push(message);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pushed", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
}),
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
test("should corretly count pulls", async () => {
|
||||
const { queue, metricReader } = setup();
|
||||
const message: SyncMessage = {
|
||||
action: "load",
|
||||
id: "co_ztest-id",
|
||||
header: false,
|
||||
sessions: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pulled", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
}),
|
||||
).toBe(0);
|
||||
|
||||
void queue.push(message);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pulled", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
}),
|
||||
).toBe(0);
|
||||
|
||||
void queue.pull();
|
||||
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pulled", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
}),
|
||||
).toBe(1);
|
||||
|
||||
// We only have one item in the queue, so this should not change the metric value
|
||||
void queue.pull();
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pulled", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
}),
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test("should corretly set custom attributes to the metrics", async () => {
|
||||
const { queue, metricReader } = setup({ role: "server" });
|
||||
const message: SyncMessage = {
|
||||
action: "load",
|
||||
id: "co_ztest-id",
|
||||
header: false,
|
||||
sessions: {},
|
||||
};
|
||||
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pushed", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
role: "server",
|
||||
}),
|
||||
).toBe(0);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pushed", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
role: "client",
|
||||
}),
|
||||
).toBeUndefined();
|
||||
|
||||
void queue.push(message);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pushed", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
role: "server",
|
||||
}),
|
||||
).toBe(1);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pulled", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
role: "server",
|
||||
}),
|
||||
).toBe(0);
|
||||
|
||||
void queue.pull();
|
||||
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pushed", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
role: "server",
|
||||
}),
|
||||
).toBe(1);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.pulled", {
|
||||
priority: CO_VALUE_PRIORITY.MEDIUM,
|
||||
role: "server",
|
||||
}),
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
test("should initialize with correct properties", () => {
|
||||
const { queue } = setup();
|
||||
expect(queue["defaultPriority"]).toBe(CO_VALUE_PRIORITY.MEDIUM);
|
||||
expect(queue["queues"].length).toBe(8);
|
||||
expect(queue["queues"].length).toBe(3);
|
||||
expect(queue["queues"].every((q) => !q.length)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -52,7 +175,7 @@ describe("PriorityBasedMessageQueue", () => {
|
||||
});
|
||||
|
||||
test("should pull messages in priority order", async () => {
|
||||
const { queue, metricReader } = setup();
|
||||
const { queue } = setup();
|
||||
const lowPriorityMsg: SyncMessage = {
|
||||
action: "content",
|
||||
id: "co_zlow",
|
||||
@@ -73,42 +196,12 @@ describe("PriorityBasedMessageQueue", () => {
|
||||
};
|
||||
|
||||
void queue.push(lowPriorityMsg);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
||||
priority: lowPriorityMsg.priority,
|
||||
}),
|
||||
).toBe(1);
|
||||
void queue.push(mediumPriorityMsg);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
||||
priority: mediumPriorityMsg.priority,
|
||||
}),
|
||||
).toBe(1);
|
||||
void queue.push(highPriorityMsg);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
||||
priority: highPriorityMsg.priority,
|
||||
}),
|
||||
).toBe(1);
|
||||
|
||||
expect(queue.pull()?.msg).toEqual(highPriorityMsg);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
||||
priority: highPriorityMsg.priority,
|
||||
}),
|
||||
).toBe(0);
|
||||
expect(queue.pull()?.msg).toEqual(mediumPriorityMsg);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
||||
priority: mediumPriorityMsg.priority,
|
||||
}),
|
||||
).toBe(0);
|
||||
expect(queue.pull()?.msg).toEqual(lowPriorityMsg);
|
||||
expect(
|
||||
await metricReader.getMetricValue("jazz.messagequeue.size", {
|
||||
priority: lowPriorityMsg.priority,
|
||||
}),
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test("should return undefined when pulling from empty queue", () => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-browser
|
||||
|
||||
## 0.13.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 63a7aa0: Add exports for clerk and crypto to make import shortcuts work everywhere
|
||||
|
||||
## 0.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
2
packages/jazz-expo/auth/clerk.d.ts
vendored
Normal file
2
packages/jazz-expo/auth/clerk.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-export the Clerk provider from the dist folder to make the `jazz-expo/auth/clerk` import work everywhere
|
||||
export * from "../dist/auth/clerk";
|
||||
2
packages/jazz-expo/auth/clerk.js
Normal file
2
packages/jazz-expo/auth/clerk.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-export the Clerk provider from the dist folder to make the `jazz-expo/auth/clerk` import work everywhere
|
||||
export * from "../dist/auth/clerk";
|
||||
2
packages/jazz-expo/crypto/index.d.ts
vendored
Normal file
2
packages/jazz-expo/crypto/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-export the crypto module from the dist folder to make the `jazz-expo/crypto` import work everywhere
|
||||
export * from "jazz-react-native-core/dist/crypto";
|
||||
2
packages/jazz-expo/crypto/index.js
Normal file
2
packages/jazz-expo/crypto/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-export the crypto module from the dist folder to make the `jazz-expo/crypto` import work everywhere
|
||||
export * from "jazz-react-native-core/dist/crypto";
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-expo",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -22,6 +22,11 @@
|
||||
"types": "./dist/auth/clerk/index.d.ts",
|
||||
"default": "./dist/auth/clerk/index.js"
|
||||
},
|
||||
"./crypto": {
|
||||
"react-native": "./crypto/index.js",
|
||||
"types": "./crypto/index.d.ts",
|
||||
"default": "./crypto/index.js"
|
||||
},
|
||||
"./testing": {
|
||||
"react-native": "./dist/testing.js",
|
||||
"types": "./dist/testing.d.ts",
|
||||
|
||||
@@ -22,7 +22,6 @@ function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
storage="sqlite"
|
||||
sync={{
|
||||
peer: "wss://cloud.jazz.tools/?key=chat-rn-expo-clerk-example-jazz@garden.co",
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# jazz-browser
|
||||
|
||||
## 0.13.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 63a7aa0: Add exports for clerk and crypto to make import shortcuts work everywhere
|
||||
|
||||
## 0.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
2
packages/jazz-react-native/crypto/index.d.ts
vendored
Normal file
2
packages/jazz-react-native/crypto/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-export the crypto module from the dist folder to make the `jazz-react-native/crypto` import work everywhere
|
||||
export * from "jazz-react-native-core/dist/crypto";
|
||||
2
packages/jazz-react-native/crypto/index.js
Normal file
2
packages/jazz-react-native/crypto/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// Re-export the crypto module from the dist folder to make the `jazz-react-native/crypto` import work everywhere
|
||||
export * from "jazz-react-native-core/dist/crypto";
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-react-native",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -12,6 +12,11 @@
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./crypto": {
|
||||
"react-native": "./crypto/index.js",
|
||||
"types": "./crypto/index.d.ts",
|
||||
"default": "./crypto/index.js"
|
||||
},
|
||||
"./testing": {
|
||||
"react-native": "./dist/testing.js",
|
||||
"types": "./dist/testing.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user