Compare commits
4 Commits
mcp
...
jazz-react
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9007dd1b5 | ||
|
|
ee1e5b06e4 | ||
|
|
fae290c4cf | ||
|
|
381d68019f |
78
.github/workflows/build-and-deploy.yaml
vendored
Normal file
78
.github/workflows/build-and-deploy.yaml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
name: Build and Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
cache: 'yarn'
|
||||||
|
cache-dependency-path: yarn.lock
|
||||||
|
- name: Yarn Build
|
||||||
|
run: |
|
||||||
|
yarn install --frozen-lockfile;
|
||||||
|
yarn build;
|
||||||
|
working-directory: ./examples/todo
|
||||||
|
|
||||||
|
- uses: satackey/action-docker-layer-caching@v0.0.11
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
key: docker-layer-caching-${{ github.workflow }}-{hash}
|
||||||
|
restore-keys: |
|
||||||
|
docker-layer-caching-${{ github.workflow }}-
|
||||||
|
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: gardencmp
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Docker Build & Push
|
||||||
|
run: |
|
||||||
|
export DOCKER_TAG=ghcr.io/gardencmp/jazz-example-todo:${{github.head_ref || github.ref_name}}-${{github.sha}}-$(date +%s) ;
|
||||||
|
docker build . --file Dockerfile --tag $DOCKER_TAG;
|
||||||
|
docker push $DOCKER_TAG;
|
||||||
|
echo "DOCKER_TAG=$DOCKER_TAG" >> $GITHUB_ENV
|
||||||
|
working-directory: ./examples/todo
|
||||||
|
|
||||||
|
- uses: gacts/install-nomad@v1
|
||||||
|
- name: Tailscale
|
||||||
|
uses: tailscale/github-action@v1
|
||||||
|
with:
|
||||||
|
authkey: ${{ secrets.TAILSCALE_AUTHKEY }}
|
||||||
|
|
||||||
|
- name: Deploy on Nomad
|
||||||
|
run: |
|
||||||
|
if [ "${{github.ref_name}}" == "main" ]; then
|
||||||
|
export BRANCH_SUFFIX="";
|
||||||
|
export BRANCH_SUBDOMAIN="";
|
||||||
|
else
|
||||||
|
export BRANCH_SUFFIX=-${{github.head_ref || github.ref_name}};
|
||||||
|
export BRANCH_SUBDOMAIN=${{github.head_ref || github.ref_name}}.;
|
||||||
|
fi
|
||||||
|
|
||||||
|
export DOCKER_USER=gardencmp;
|
||||||
|
export DOCKER_PASSWORD=${{ secrets.DOCKER_PULL_PAT }};
|
||||||
|
export DOCKER_TAG=${{ env.DOCKER_TAG }};
|
||||||
|
|
||||||
|
for region in ${{ vars.DEPLOY_REGIONS }}
|
||||||
|
do
|
||||||
|
export REGION=$region;
|
||||||
|
envsubst '${DOCKER_USER} ${DOCKER_PASSWORD} ${DOCKER_TAG} ${BRANCH_SUFFIX} ${BRANCH_SUBDOMAIN} ${REGION}' < job-template.nomad > job-instance.nomad;
|
||||||
|
cat job-instance.nomad;
|
||||||
|
NOMAD_ADDR='${{ secrets.NOMAD_ADDR }}' nomad job run job-instance.nomad;
|
||||||
|
done
|
||||||
|
working-directory: ./examples/todo
|
||||||
6
examples/todo/Dockerfile
Normal file
6
examples/todo/Dockerfile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
FROM caddy:2.7.3-alpine
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
|
||||||
|
|
||||||
|
COPY ./dist /usr/share/caddy/
|
||||||
|
|
||||||
|
RUN caddy
|
||||||
54
examples/todo/job-template.nomad
Normal file
54
examples/todo/job-template.nomad
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
job "example-todo$BRANCH_SUFFIX" {
|
||||||
|
region = "$REGION"
|
||||||
|
datacenters = ["$REGION"]
|
||||||
|
|
||||||
|
group "static" {
|
||||||
|
// count = 3
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "http" {
|
||||||
|
to = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint {
|
||||||
|
attribute = "${node.class}"
|
||||||
|
operator = "="
|
||||||
|
value = "edge"
|
||||||
|
}
|
||||||
|
|
||||||
|
// spread {
|
||||||
|
// attribute = "${node.datacenter}"
|
||||||
|
// weight = 100
|
||||||
|
// }
|
||||||
|
|
||||||
|
task "server" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "$DOCKER_TAG"
|
||||||
|
ports = ["http"]
|
||||||
|
|
||||||
|
auth = {
|
||||||
|
username = "$DOCKER_USER"
|
||||||
|
password = "$DOCKER_PASSWORD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
tags = ["public"]
|
||||||
|
meta {
|
||||||
|
public_name = "${BRANCH_SUBDOMAIN}example-todo"
|
||||||
|
}
|
||||||
|
port = "http"
|
||||||
|
provider = "consul"
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 50 # MHz
|
||||||
|
memory = 50 # MB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# deploy bump 4
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-example-todo",
|
"name": "jazz-example-todo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"jazz-react": "^0.0.9",
|
"jazz-react": "^0.0.10",
|
||||||
"jazz-react-auth-local": "^0.0.6",
|
"jazz-react-auth-local": "^0.0.7",
|
||||||
"lucide-react": "^0.265.0",
|
"lucide-react": "^0.265.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ function App() {
|
|||||||
|
|
||||||
const createList = () => {
|
const createList = () => {
|
||||||
const listTeam = localNode.createTeam();
|
const listTeam = localNode.createTeam();
|
||||||
const list = listTeam.createMap<TodoListContent, null>();
|
const list = listTeam.createMap<TodoListContent>();
|
||||||
|
|
||||||
list.edit((list) => {
|
list.edit((list) => {
|
||||||
list.set("title", "My Todo List");
|
list.set("title", "My Todo List");
|
||||||
@@ -47,7 +47,7 @@ function App() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full items-center justify-center gap-10">
|
<div className="flex flex-col h-full items-center justify-start gap-10 pt-10 md:pt-[30vh] pb-10">
|
||||||
{listId && <TodoList listId={listId} />}
|
{listId && <TodoList listId={listId} />}
|
||||||
<Button onClick={createList}>Create New List</Button>
|
<Button onClick={createList}>Create New List</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,7 +59,7 @@ export function TodoList({ listId }: { listId: CoID<TodoList> }) {
|
|||||||
|
|
||||||
const createTodo = (text: string) => {
|
const createTodo = (text: string) => {
|
||||||
if (!list) return;
|
if (!list) return;
|
||||||
let task = list.coValue.getTeam().createMap<TaskContent, {}>();
|
let task = list.coValue.getTeam().createMap<TaskContent>();
|
||||||
|
|
||||||
task = task.edit((task) => {
|
task = task.edit((task) => {
|
||||||
task.set("text", text);
|
task.set("text", text);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import { Input } from "./components/ui/input.tsx";
|
import { Input } from "./components/ui/input.tsx";
|
||||||
import { Button } from "./components/ui/button.tsx";
|
import { Button } from "./components/ui/button.tsx";
|
||||||
import { AuthComponent } from "jazz-react";
|
import { AuthComponent } from "jazz-react";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { LocalAuth } from "./LocalAuth.tsx";
|
|||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<WithJazz auth={LocalAuth}>
|
<WithJazz auth={LocalAuth} syncAddress={new URLSearchParams(window.location.search).get("sync") || undefined}>
|
||||||
<App />
|
<App />
|
||||||
</WithJazz>
|
</WithJazz>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "0.0.17",
|
"version": "0.0.18",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { CoValue, newRandomSessionID } from "./coValue.js";
|
import { CoValue, newRandomSessionID } from "./coValue.js";
|
||||||
import { LocalNode } from "./node.js";
|
import { LocalNode } from "./node.js";
|
||||||
import { CoMap } from "./contentTypes/coMap.js";
|
import { CoMap } from "./contentTypes/coMap.js";
|
||||||
import { agentSecretFromBytes, agentSecretToBytes } from "./crypto.js";
|
import {
|
||||||
|
agentSecretFromBytes,
|
||||||
|
agentSecretToBytes,
|
||||||
|
getAgentID,
|
||||||
|
newRandomAgentSecret,
|
||||||
|
} from "./crypto.js";
|
||||||
import { connectedPeers } from "./streamUtils.js";
|
import { connectedPeers } from "./streamUtils.js";
|
||||||
|
import { AnonymousControlledAccount, ControlledAccount } from "./account.js";
|
||||||
|
|
||||||
import type { SessionID } from "./ids.js";
|
import type { SessionID, AgentID } from "./ids.js";
|
||||||
import type { CoID, ContentType } from "./contentType.js";
|
import type { CoID, ContentType } from "./contentType.js";
|
||||||
import type { JsonValue } from "./jsonValue.js";
|
import type { JsonValue } from "./jsonValue.js";
|
||||||
import type { SyncMessage } from "./sync.js";
|
import type { SyncMessage } from "./sync.js";
|
||||||
@@ -12,14 +18,23 @@ import type { AgentSecret } from "./crypto.js";
|
|||||||
|
|
||||||
type Value = JsonValue | ContentType;
|
type Value = JsonValue | ContentType;
|
||||||
|
|
||||||
const internals = {
|
export const cojsonInternals = {
|
||||||
agentSecretFromBytes,
|
agentSecretFromBytes,
|
||||||
agentSecretToBytes,
|
agentSecretToBytes,
|
||||||
newRandomSessionID,
|
newRandomSessionID,
|
||||||
connectedPeers
|
newRandomAgentSecret,
|
||||||
|
connectedPeers,
|
||||||
|
getAgentID,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { LocalNode, CoValue, CoMap, internals };
|
export {
|
||||||
|
LocalNode,
|
||||||
|
CoValue,
|
||||||
|
CoMap,
|
||||||
|
cojsonInternals as internals,
|
||||||
|
AnonymousControlledAccount,
|
||||||
|
ControlledAccount,
|
||||||
|
};
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
Value,
|
Value,
|
||||||
@@ -29,4 +44,17 @@ export type {
|
|||||||
AgentSecret,
|
AgentSecret,
|
||||||
SessionID,
|
SessionID,
|
||||||
SyncMessage,
|
SyncMessage,
|
||||||
|
AgentID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export namespace CojsonInternalTypes {
|
||||||
|
export type CoValueKnownState = import("./sync.js").CoValueKnownState;
|
||||||
|
export type DoneMessage = import("./sync.js").DoneMessage;
|
||||||
|
export type KnownStateMessage = import("./sync.js").KnownStateMessage;
|
||||||
|
export type LoadMessage = import("./sync.js").LoadMessage;
|
||||||
|
export type NewContentMessage = import("./sync.js").NewContentMessage;
|
||||||
|
export type CoValueHeader = import("./coValue.js").CoValueHeader;
|
||||||
|
export type Transaction = import("./coValue.js").Transaction;
|
||||||
|
export type Signature = import("./crypto.js").Signature;
|
||||||
|
export type RawCoID = import("./ids.js").RawCoID;
|
||||||
|
}
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ export class Team {
|
|||||||
this.rotateReadKey();
|
this.rotateReadKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
createMap<M extends { [key: string]: JsonValue }, Meta extends JsonObject | null>(
|
createMap<M extends { [key: string]: JsonValue }, Meta extends JsonObject | null = null>(
|
||||||
meta?: Meta
|
meta?: Meta
|
||||||
): CoMap<M, Meta> {
|
): CoMap<M, Meta> {
|
||||||
return this.node
|
return this.node
|
||||||
|
|||||||
@@ -100,14 +100,14 @@ export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel(reason) {
|
cancel(_reason) {
|
||||||
console.log("Manually closing reader");
|
console.log("Manually closing reader");
|
||||||
readerClosed = true;
|
readerClosed = true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const writable = new WritableStream<T>({
|
const writable = new WritableStream<T>({
|
||||||
write(chunk, controller) {
|
write(chunk) {
|
||||||
if (readerClosed) {
|
if (readerClosed) {
|
||||||
console.log("Reader closed, not writing chunk", chunk);
|
console.log("Reader closed, not writing chunk", chunk);
|
||||||
throw new Error("Reader closed, not writing chunk");
|
throw new Error("Reader closed, not writing chunk");
|
||||||
@@ -118,7 +118,7 @@ export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
|
|||||||
setTimeout(() => resolveNextItemReady());
|
setTimeout(() => resolveNextItemReady());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
abort(reason) {
|
abort(_reason) {
|
||||||
console.log("Manually closing writer");
|
console.log("Manually closing writer");
|
||||||
writerClosed = true;
|
writerClosed = true;
|
||||||
resolveNextItemReady();
|
resolveNextItemReady();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Hash, Signature } from "./crypto.js";
|
import { Signature } from "./crypto.js";
|
||||||
import { CoValueHeader, Transaction } from "./coValue.js";
|
import { CoValueHeader, Transaction } from "./coValue.js";
|
||||||
import { CoValue } from "./coValue.js";
|
import { CoValue } from "./coValue.js";
|
||||||
import { LocalNode } from "./node.js";
|
import { LocalNode } from "./node.js";
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
"eslint:recommended",
|
||||||
'plugin:@typescript-eslint/recommended',
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:require-extensions/recommended",
|
||||||
],
|
],
|
||||||
parser: '@typescript-eslint/parser',
|
parser: "@typescript-eslint/parser",
|
||||||
plugins: ['@typescript-eslint'],
|
plugins: ["@typescript-eslint", "require-extensions"],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: './tsconfig.json',
|
project: "./tsconfig.json",
|
||||||
},
|
},
|
||||||
root: true,
|
root: true,
|
||||||
rules: {
|
rules: {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
||||||
|
],
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
},
|
},
|
||||||
|
};
|
||||||
};
|
|
||||||
|
|||||||
2
packages/jazz-react-auth-local/.npmignore
Normal file
2
packages/jazz-react-auth-local/.npmignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
coverage
|
||||||
|
node_modules
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-react-auth-local",
|
"name": "jazz-react-auth-local",
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jazz-react": "^0.0.9",
|
"jazz-react": "^0.0.10",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -13,5 +13,10 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^17.0.2"
|
"react": "^17.0.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"build": "npm run lint && rm -rf ./dist && tsc --declaration --sourceMap --outDir dist",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { newRandomAgentSecret, AgentSecret, agentSecretToBytes, agentSecretFromBytes} from "cojson/src/crypto";
|
import { cojsonInternals, AgentSecret } from "cojson";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
export function useLocalAuth(onCredential: (credentials: AgentSecret) => void) {
|
export function useLocalAuth(onCredential: (credentials: AgentSecret) => void) {
|
||||||
const [displayName, setDisplayName] = useState<string>("");
|
const [displayName, setDisplayName] = useState<string>("");
|
||||||
@@ -12,8 +12,8 @@ export function useLocalAuth(onCredential: (credentials: AgentSecret) => void) {
|
|||||||
}, [onCredential]);
|
}, [onCredential]);
|
||||||
|
|
||||||
const signUp = useCallback(() => {
|
const signUp = useCallback(() => {
|
||||||
(async function () {
|
void (async function () {
|
||||||
const credential = newRandomAgentSecret();
|
const credential = cojsonInternals.newRandomAgentSecret();
|
||||||
|
|
||||||
console.log(credential);
|
console.log(credential);
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ export function useLocalAuth(onCredential: (credentials: AgentSecret) => void) {
|
|||||||
id: window.location.hostname,
|
id: window.location.hostname,
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
id: agentSecretToBytes(credential),
|
id: cojsonInternals.agentSecretToBytes(credential),
|
||||||
name: displayName,
|
name: displayName,
|
||||||
displayName: displayName,
|
displayName: displayName,
|
||||||
},
|
},
|
||||||
@@ -42,7 +42,7 @@ export function useLocalAuth(onCredential: (credentials: AgentSecret) => void) {
|
|||||||
console.log(
|
console.log(
|
||||||
webAuthNCredential,
|
webAuthNCredential,
|
||||||
credential,
|
credential,
|
||||||
agentSecretToBytes(credential)
|
cojsonInternals.agentSecretToBytes(credential)
|
||||||
);
|
);
|
||||||
|
|
||||||
sessionStorage.credential = JSON.stringify(credential);
|
sessionStorage.credential = JSON.stringify(credential);
|
||||||
@@ -51,7 +51,7 @@ export function useLocalAuth(onCredential: (credentials: AgentSecret) => void) {
|
|||||||
}, [displayName]);
|
}, [displayName]);
|
||||||
|
|
||||||
const signIn = useCallback(() => {
|
const signIn = useCallback(() => {
|
||||||
(async function () {
|
void (async function () {
|
||||||
const webAuthNCredential = await navigator.credentials.get({
|
const webAuthNCredential = await navigator.credentials.get({
|
||||||
publicKey: {
|
publicKey: {
|
||||||
challenge: Uint8Array.from([0, 1, 2]),
|
challenge: Uint8Array.from([0, 1, 2]),
|
||||||
@@ -62,11 +62,19 @@ export function useLocalAuth(onCredential: (credentials: AgentSecret) => void) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!webAuthNCredential) {
|
||||||
|
throw new Error("Couldn't log in");
|
||||||
|
}
|
||||||
|
|
||||||
const userIdBytes = new Uint8Array(
|
const userIdBytes = new Uint8Array(
|
||||||
(webAuthNCredential as any).response.userHandle
|
(
|
||||||
|
webAuthNCredential as unknown as {
|
||||||
|
response: { userHandle: ArrayBuffer };
|
||||||
|
}
|
||||||
|
).response.userHandle
|
||||||
);
|
);
|
||||||
const credential =
|
const credential =
|
||||||
agentSecretFromBytes(userIdBytes);
|
cojsonInternals.agentSecretFromBytes(userIdBytes);
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
throw new Error("Invalid credential");
|
throw new Error("Invalid credential");
|
||||||
|
|||||||
@@ -2,18 +2,12 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"],
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "esnext",
|
"target": "ES2020",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"jsx": "preserve",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowJs": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module.exports = {
|
|||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
plugins: ['@typescript-eslint'],
|
plugins: ['@typescript-eslint'],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: './tsconfig.json',
|
project: "./tsconfig.json",
|
||||||
},
|
},
|
||||||
root: true,
|
root: true,
|
||||||
rules: {
|
rules: {
|
||||||
@@ -14,5 +14,4 @@ module.exports = {
|
|||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
},
|
},
|
||||||
|
};
|
||||||
};
|
|
||||||
2
packages/jazz-react/.npmignore
Normal file
2
packages/jazz-react/.npmignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
coverage
|
||||||
|
node_modules
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-react",
|
"name": "jazz-react",
|
||||||
"version": "0.0.9",
|
"version": "0.0.10",
|
||||||
"main": "src/index.tsx",
|
"main": "src/index.tsx",
|
||||||
"types": "src/index.tsx",
|
"types": "src/index.tsx",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cojson": "^0.0.17",
|
"cojson": "^0.0.18",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -13,5 +13,10 @@
|
|||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^17.0.2"
|
"react": "^17.0.2"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint src/**/*.tsx",
|
||||||
|
"build": "npm run lint && rm -rf ./dist && tsc --declaration --sourceMap --outDir dist",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
LocalNode,
|
LocalNode,
|
||||||
internals as cojsonInternals,
|
cojsonInternals,
|
||||||
SessionID,
|
SessionID,
|
||||||
ContentType,
|
ContentType,
|
||||||
SyncMessage,
|
SyncMessage,
|
||||||
AgentSecret,
|
AgentSecret,
|
||||||
|
CoID,
|
||||||
|
AnonymousControlledAccount,
|
||||||
|
AgentID
|
||||||
} from "cojson";
|
} from "cojson";
|
||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { ReadableStream, WritableStream } from "isomorphic-streams";
|
import { ReadableStream, WritableStream } from "isomorphic-streams";
|
||||||
import { CoID } from "cojson";
|
|
||||||
import { AgentID } from "cojson/src/ids";
|
|
||||||
import { getAgentID } from "cojson/src/crypto";
|
|
||||||
import { AnonymousControlledAccount } from "cojson/src/account";
|
|
||||||
import { IDBStorage } from "jazz-storage-indexeddb";
|
import { IDBStorage } from "jazz-storage-indexeddb";
|
||||||
|
|
||||||
type JazzContext = {
|
type JazzContext = {
|
||||||
@@ -37,10 +36,10 @@ export function WithJazz({
|
|||||||
const sessionDone = useRef<() => void>();
|
const sessionDone = useRef<() => void>();
|
||||||
|
|
||||||
const onCredential = useCallback((credential: AgentSecret) => {
|
const onCredential = useCallback((credential: AgentSecret) => {
|
||||||
const agentID = getAgentID(credential);
|
const agentID = cojsonInternals.getAgentID(credential);
|
||||||
const sessionHandle = getSessionFor(agentID);
|
const sessionHandle = getSessionFor(agentID);
|
||||||
|
|
||||||
sessionHandle.session.then((sessionID) =>
|
void sessionHandle.session.then((sessionID) =>
|
||||||
setNode(
|
setNode(
|
||||||
new LocalNode(
|
new LocalNode(
|
||||||
new AnonymousControlledAccount(credential),
|
new AnonymousControlledAccount(credential),
|
||||||
@@ -60,12 +59,12 @@ export function WithJazz({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (node) {
|
if (node) {
|
||||||
IDBStorage.connectTo(node, { trace: true });
|
void IDBStorage.connectTo(node, { trace: true });
|
||||||
|
|
||||||
let shouldTryToReconnect = true;
|
let shouldTryToReconnect = true;
|
||||||
let ws: WebSocket | undefined;
|
let ws: WebSocket | undefined;
|
||||||
|
|
||||||
(async function websocketReconnectLoop() {
|
void (async function websocketReconnectLoop() {
|
||||||
while (shouldTryToReconnect) {
|
while (shouldTryToReconnect) {
|
||||||
ws = new WebSocket(syncAddress);
|
ws = new WebSocket(syncAddress);
|
||||||
|
|
||||||
@@ -134,7 +133,7 @@ function getSessionFor(agentID: AgentID): SessionHandle {
|
|||||||
resolveSession = resolve;
|
resolveSession = resolve;
|
||||||
});
|
});
|
||||||
|
|
||||||
(async function () {
|
void (async function () {
|
||||||
for (let idx = 0; idx < 100; idx++) {
|
for (let idx = 0; idx < 100; idx++) {
|
||||||
// To work better around StrictMode
|
// To work better around StrictMode
|
||||||
for (let retry = 0; retry < 2; retry++) {
|
for (let retry = 0; retry < 2; retry++) {
|
||||||
@@ -209,6 +208,8 @@ export function useTelepathicState<T extends ContentType>(id: CoID<T>) {
|
|||||||
);
|
);
|
||||||
setState(newState as T);
|
setState(newState as T);
|
||||||
});
|
});
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log("Failed to load", id, e);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -2,18 +2,13 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"],
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "esnext",
|
"target": "ES2020",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"jsx": "preserve",
|
"jsx": "react",
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowJs": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ module.exports = {
|
|||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
plugins: ['@typescript-eslint'],
|
plugins: ['@typescript-eslint'],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: './tsconfig.json',
|
project: "./tsconfig.json",
|
||||||
},
|
},
|
||||||
root: true,
|
root: true,
|
||||||
rules: {
|
rules: {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
// "@typescript-eslint/no-floating-promises": "error",
|
||||||
},
|
},
|
||||||
|
};
|
||||||
};
|
|
||||||
2
packages/jazz-storage-indexeddb/.npmignore
Normal file
2
packages/jazz-storage-indexeddb/.npmignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
coverage
|
||||||
|
node_modules
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "jazz-storage-indexeddb",
|
"name": "jazz-storage-indexeddb",
|
||||||
"version": "0.0.4",
|
"version": "0.0.5",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"types": "src/index.ts",
|
"types": "src/index.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cojson": "^0.0.17",
|
"cojson": "^0.0.18",
|
||||||
"typescript": "^5.1.6"
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
"webdriverio": "^8.15.0"
|
"webdriverio": "^8.15.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest --browser chrome"
|
"test": "vitest --browser chrome",
|
||||||
|
"lint": "eslint src/**/*.ts",
|
||||||
|
"build": "npm run lint && rm -rf ./dist && tsc --declaration --sourceMap --outDir dist",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ test.skip("Should be able to initialize and load from empty DB", async () => {
|
|||||||
|
|
||||||
console.log("yay!");
|
console.log("yay!");
|
||||||
|
|
||||||
const team = node.createTeam();
|
const _team = node.createTeam();
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
import {
|
import { LocalNode, cojsonInternals, SessionID, SyncMessage } from "cojson";
|
||||||
LocalNode,
|
import { CojsonInternalTypes } from "cojson";
|
||||||
internals as cojsonInternals,
|
|
||||||
SessionID,
|
|
||||||
ContentType,
|
|
||||||
SyncMessage,
|
|
||||||
JsonValue,
|
|
||||||
} from "cojson";
|
|
||||||
import { CoValueHeader, Transaction } from "cojson/src/coValue";
|
|
||||||
import { Signature } from "cojson/src/crypto";
|
|
||||||
import { RawCoID } from "cojson/src/ids";
|
|
||||||
import {
|
|
||||||
CoValueKnownState,
|
|
||||||
DoneMessage,
|
|
||||||
KnownStateMessage,
|
|
||||||
LoadMessage,
|
|
||||||
NewContentMessage,
|
|
||||||
} from "cojson/src/sync";
|
|
||||||
import {
|
import {
|
||||||
ReadableStream,
|
ReadableStream,
|
||||||
WritableStream,
|
WritableStream,
|
||||||
@@ -24,8 +8,8 @@ import {
|
|||||||
} from "isomorphic-streams";
|
} from "isomorphic-streams";
|
||||||
|
|
||||||
type CoValueRow = {
|
type CoValueRow = {
|
||||||
id: RawCoID;
|
id: CojsonInternalTypes.RawCoID;
|
||||||
header: CoValueHeader;
|
header: CojsonInternalTypes.CoValueHeader;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StoredCoValueRow = CoValueRow & { rowID: number };
|
type StoredCoValueRow = CoValueRow & { rowID: number };
|
||||||
@@ -34,7 +18,7 @@ type SessionRow = {
|
|||||||
coValue: number;
|
coValue: number;
|
||||||
sessionID: SessionID;
|
sessionID: SessionID;
|
||||||
lastIdx: number;
|
lastIdx: number;
|
||||||
lastSignature: Signature;
|
lastSignature: CojsonInternalTypes.Signature;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StoredSessionRow = SessionRow & { rowID: number };
|
type StoredSessionRow = SessionRow & { rowID: number };
|
||||||
@@ -42,7 +26,7 @@ type StoredSessionRow = SessionRow & { rowID: number };
|
|||||||
type TransactionRow = {
|
type TransactionRow = {
|
||||||
ses: number;
|
ses: number;
|
||||||
idx: number;
|
idx: number;
|
||||||
tx: Transaction;
|
tx: CojsonInternalTypes.Transaction;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class IDBStorage {
|
export class IDBStorage {
|
||||||
@@ -60,13 +44,14 @@ export class IDBStorage {
|
|||||||
this.toLocalNode = toLocalNode.getWriter();
|
this.toLocalNode = toLocalNode.getWriter();
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
while (true) {
|
let done = false;
|
||||||
const { done, value } = await this.fromLocalNode.read();
|
while (!done) {
|
||||||
if (done) {
|
const result = await this.fromLocalNode.read();
|
||||||
break;
|
done = result.done;
|
||||||
}
|
|
||||||
|
|
||||||
this.handleSyncMessage(value);
|
if (result.value) {
|
||||||
|
this.handleSyncMessage(result.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@@ -159,7 +144,7 @@ export class IDBStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendNewContentAfter(
|
async sendNewContentAfter(
|
||||||
theirKnown: CoValueKnownState,
|
theirKnown: CojsonInternalTypes.CoValueKnownState,
|
||||||
{
|
{
|
||||||
coValues,
|
coValues,
|
||||||
sessions,
|
sessions,
|
||||||
@@ -169,7 +154,7 @@ export class IDBStorage {
|
|||||||
sessions: IDBObjectStore;
|
sessions: IDBObjectStore;
|
||||||
transactions: IDBObjectStore;
|
transactions: IDBObjectStore;
|
||||||
},
|
},
|
||||||
asDependencyOf?: RawCoID
|
asDependencyOf?: CojsonInternalTypes.RawCoID
|
||||||
) {
|
) {
|
||||||
const coValueRow = await promised<StoredCoValueRow | undefined>(
|
const coValueRow = await promised<StoredCoValueRow | undefined>(
|
||||||
coValues.index("coValuesById").get(theirKnown.id)
|
coValues.index("coValuesById").get(theirKnown.id)
|
||||||
@@ -181,13 +166,13 @@ export class IDBStorage {
|
|||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const ourKnown: CoValueKnownState = {
|
const ourKnown: CojsonInternalTypes.CoValueKnownState = {
|
||||||
id: theirKnown.id,
|
id: theirKnown.id,
|
||||||
header: !!coValueRow,
|
header: !!coValueRow,
|
||||||
sessions: {},
|
sessions: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const newContent: NewContentMessage = {
|
const newContent: CojsonInternalTypes.NewContentMessage = {
|
||||||
action: "content",
|
action: "content",
|
||||||
id: theirKnown.id,
|
id: theirKnown.id,
|
||||||
header: theirKnown.header ? undefined : coValueRow?.header,
|
header: theirKnown.header ? undefined : coValueRow?.header,
|
||||||
@@ -237,7 +222,7 @@ export class IDBStorage {
|
|||||||
change.key
|
change.key
|
||||||
)
|
)
|
||||||
.filter(
|
.filter(
|
||||||
(key): key is RawCoID =>
|
(key): key is CojsonInternalTypes.RawCoID =>
|
||||||
typeof key === "string" &&
|
typeof key === "string" &&
|
||||||
key.startsWith("co_")
|
key.startsWith("co_")
|
||||||
);
|
);
|
||||||
@@ -258,7 +243,7 @@ export class IDBStorage {
|
|||||||
await this.toLocalNode.write({
|
await this.toLocalNode.write({
|
||||||
action: "known",
|
action: "known",
|
||||||
...ourKnown,
|
...ourKnown,
|
||||||
asDependencyOf
|
asDependencyOf,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (newContent.header || Object.keys(newContent.new).length > 0) {
|
if (newContent.header || Object.keys(newContent.new).length > 0) {
|
||||||
@@ -266,11 +251,11 @@ export class IDBStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoad(msg: LoadMessage) {
|
handleLoad(msg: CojsonInternalTypes.LoadMessage) {
|
||||||
return this.sendNewContentAfter(msg, this.inTransaction("readonly"));
|
return this.sendNewContentAfter(msg, this.inTransaction("readonly"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleContent(msg: NewContentMessage) {
|
async handleContent(msg: CojsonInternalTypes.NewContentMessage) {
|
||||||
const { coValues, sessions, transactions } =
|
const { coValues, sessions, transactions } =
|
||||||
this.inTransaction("readwrite");
|
this.inTransaction("readwrite");
|
||||||
|
|
||||||
@@ -320,7 +305,7 @@ export class IDBStorage {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const ourKnown: CoValueKnownState = {
|
const ourKnown: CojsonInternalTypes.CoValueKnownState = {
|
||||||
id: msg.id,
|
id: msg.id,
|
||||||
header: true,
|
header: true,
|
||||||
sessions: {},
|
sessions: {},
|
||||||
@@ -389,11 +374,11 @@ export class IDBStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKnown(msg: KnownStateMessage) {
|
handleKnown(msg: CojsonInternalTypes.KnownStateMessage) {
|
||||||
return this.sendNewContentAfter(msg, this.inTransaction("readonly"));
|
return this.sendNewContentAfter(msg, this.inTransaction("readonly"));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDone(msg: DoneMessage) {}
|
handleDone(_msg: CojsonInternalTypes.DoneMessage) {}
|
||||||
|
|
||||||
inTransaction(mode: "readwrite" | "readonly"): {
|
inTransaction(mode: "readwrite" | "readonly"): {
|
||||||
coValues: IDBObjectStore;
|
coValues: IDBObjectStore;
|
||||||
@@ -406,11 +391,13 @@ export class IDBStorage {
|
|||||||
);
|
);
|
||||||
|
|
||||||
tx.onerror = (event) => {
|
tx.onerror = (event) => {
|
||||||
|
const target = event.target as {
|
||||||
|
error: DOMException;
|
||||||
|
source?: { name: string };
|
||||||
|
} | null;
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error in transaction (${
|
`Error in transaction (${target?.source?.name}): ${target?.error}`,
|
||||||
(event.target as any).source?.name
|
{ cause: target?.error }
|
||||||
}): ${(event.target as any).error}`,
|
|
||||||
{ cause: (event.target as any).error }
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const coValues = tx.objectStore("coValues");
|
const coValues = tx.objectStore("coValues");
|
||||||
|
|||||||
@@ -2,18 +2,12 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"],
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "esnext",
|
"target": "ES2020",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"jsx": "preserve",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowJs": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user