Skip to content

Commit

Permalink
Merge pull request #351 from evoluhq/opfs-sahpool
Browse files Browse the repository at this point in the history
Opfs sahpool
  • Loading branch information
steida authored Mar 4, 2024
2 parents 2886af9 + bb60c82 commit 7d15f88
Show file tree
Hide file tree
Showing 37 changed files with 1,543 additions and 1,304 deletions.
7 changes: 7 additions & 0 deletions .changeset/afraid-turkeys-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@evolu/common": major
---

Add Config name property and remove LocalStorage support.

It's a breaking change only because PlatformName was restricted. There is no change in sync protocol so that all data can be safely restored.
5 changes: 5 additions & 0 deletions .changeset/green-brooms-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@evolu/common-react": patch
---

Update peer dependencies
7 changes: 7 additions & 0 deletions .changeset/hungry-stingrays-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@evolu/react-native": major
---

The new SQLite database filename

It's configurable by the Config name property now.
15 changes: 15 additions & 0 deletions .changeset/lazy-sloths-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@evolu/common-web": major
---

6x faster update, 2x faster sync, and no COOP/COEP headers

Sync is 2x faster because Evolu doesn't create sync messages for createdAt and updatedAt columns anymore. They are inferred from the sync message timestamp instead. This change also made updates 2x faster.

@evolu/common-web is roughly 3x faster because we switched from OPFS via sqlite3_vfs to OPFS SyncAccessHandle Pool VFS.

The "opfs-sahpool" also does not require COOP/COEP HTTP headers (and associated restrictions), and it works on all major browsers released since March 2023.

This change was challenging because, by default, the "opfs-sahpool" does not support multiple simultaneous connections and can be instantiated only within a web worker and only within one tab of the same origin. Evolu uses Web Locks and BroadcastChannel to re-enable multiple tabs functionality.

It's a breaking change because we had to remove support for LocalStore. Fortunately, we don't need it anymore, and without LocalStorare, Evolu can support separated SQLite instances.
5 changes: 5 additions & 0 deletions .changeset/real-laws-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@evolu/server": patch
---

Update peer dependencies
10 changes: 10 additions & 0 deletions .changeset/short-moons-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@evolu/common": patch
"@evolu/common-react": patch
"@evolu/common-web": patch
"@evolu/react": patch
"@evolu/react-native": patch
"@evolu/server": patch
---

Update peer dependencies
8 changes: 4 additions & 4 deletions apps/native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
"clean": "rm -rf .turbo .expo node_modules dist"
},
"dependencies": {
"@effect/schema": "^0.63.0",
"@evolu/react-native": "workspace:*",
"@effect/schema": "^0.63.2",
"@evolu/common": "workspace:*",
"@evolu/common-react": "workspace:*",
"@evolu/react-native": "workspace:*",
"@react-native-community/netinfo": "11.1.0",
"babel-plugin-module-resolver": "^5.0.0",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.0",
"effect": "2.4.0",
"effect": "2.4.1",
"events": "^3.3.0",
"expo": "^50.0.7",
"expo-sqlite": "~13.2.2",
Expand All @@ -35,7 +35,7 @@
"@babel/core": "^7.23.3",
"@babel/plugin-proposal-dynamic-import": "^7.18.6",
"@babel/plugin-transform-private-methods": "^7.23.3",
"@types/react": "^18.2.58",
"@types/react": "^18.2.62",
"eslint": "^8.57.0",
"eslint-config-evolu": "workspace:*",
"prettier": "^3.2.5",
Expand Down
17 changes: 0 additions & 17 deletions apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,6 @@ const nextConfig = {
experimental: {
optimizePackageImports: ["effect", "@effect/schema", "kysely"],
},
async headers() {
return [
{
source: "/(.*?)",
headers: [
{
key: "cross-origin-embedder-policy",
value: "require-corp",
},
{
key: "cross-origin-opener-policy",
value: "same-origin",
},
],
},
];
},
};

module.exports = withNextra(nextConfig);
14 changes: 7 additions & 7 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@
"clean": "rm -rf .turbo .next node_modules"
},
"dependencies": {
"@effect/schema": "^0.63.0",
"@effect/schema": "^0.63.2",
"@evolu/common": "workspace:*",
"@evolu/react": "workspace:*",
"clsx": "^2.1.0",
"effect": "2.4.0",
"next": "14.1.0",
"nextra": "^2.13.3",
"nextra-theme-docs": "^2.13.3",
"effect": "2.4.1",
"next": "14.1.2",
"nextra": "^2.13.4",
"nextra-theme-docs": "^2.13.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@evolu/tsconfig": "workspace:*",
"@types/node": "^20.11.20",
"@types/react": "^18.2.58",
"@types/react": "^18.2.62",
"@types/react-dom": "^18.2.19",
"autoprefixer": "^10.4.17",
"autoprefixer": "^10.4.18",
"eslint": "^8.57.0",
"eslint-config-evolu": "workspace:*",
"postcss": "^8.4.35",
Expand Down
8 changes: 4 additions & 4 deletions packages/eslint-config-evolu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
"clean": "rm -rf node_modules"
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"eslint-config-next": "14.1.0",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"eslint-config-next": "14.1.2",
"eslint-config-prettier": "^9.1.0",
"eslint-config-turbo": "^1.12.4",
"eslint-plugin-jsdoc": "^48.2.0",
"eslint-plugin-node": "^11.1.0",
"next": "14.1.0",
"next": "14.1.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/evolu-common-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@evolu/common-react",
"version": "5.0.7",
"version": "5.0.6",
"description": "Common code for Evolu React libraries",
"keywords": [
"evolu",
Expand Down Expand Up @@ -41,7 +41,7 @@
"devDependencies": {
"@evolu/common": "workspace:*",
"@evolu/tsconfig": "workspace:*",
"@types/react": "^18.2.58",
"@types/react": "^18.2.62",
"eslint": "^8.57.0",
"eslint-config-evolu": "workspace:*",
"react": "^18.2.0",
Expand All @@ -50,7 +50,7 @@
},
"peerDependencies": {
"@evolu/common": "^3.1.8",
"@types/react": "^18.2.58",
"@types/react": "^18.2.62",
"react": "^18.2.0"
},
"publishConfig": {
Expand Down
42 changes: 37 additions & 5 deletions packages/evolu-common-web/src/DbWorker.worker.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,42 @@
import { DbWorkerInput } from "@evolu/common";
import { dbWorker } from "./DbWorker.js";
import {
ConfigLive,
DbWorker,
DbWorkerCommonLive,
DbWorkerInput,
NanoIdGeneratorLive,
} from "@evolu/common";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import { Bip39Live, DbWorkerLockLive } from "./PlatformLive.js";
import { SqliteLive } from "./SqliteLive.js";
import { SyncWorkerLive } from "./SyncWorkerLive.js";

dbWorker.onMessage = (output): void => {
postMessage(output);
};
let dbWorker: DbWorker | null = null;

onmessage = (e: MessageEvent<DbWorkerInput>): void => {
if (dbWorker == null) {
if (e.data._tag !== "init") throw new Error("init must be called first");
dbWorker = Effect.provide(
DbWorker,
DbWorkerCommonLive.pipe(
Layer.provide(
Layer.mergeAll(
SqliteLive,
Bip39Live,
SyncWorkerLive,
DbWorkerLockLive,
),
),
Layer.provide(
Layer.merge(NanoIdGeneratorLive, ConfigLive(e.data.config)),
),
),
).pipe(Effect.runSync);

dbWorker.onMessage = (output): void => {
postMessage(output);
};
}

dbWorker.postMessage(e.data);
};
73 changes: 20 additions & 53 deletions packages/evolu-common-web/src/DbWorkerLive.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
DbWorker,
DbWorkerOutput,
PlatformName,
makeUnexpectedError,
} from "@evolu/common";
import { DbWorker, DbWorkerOutput, PlatformName } from "@evolu/common";
import * as Effect from "effect/Effect";
import * as Function from "effect/Function";
import * as Layer from "effect/Layer";
Expand All @@ -13,56 +8,28 @@ export const DbWorkerLive = Layer.effect(
Effect.gen(function* (_) {
const platformName = yield* _(PlatformName);

if (platformName === "web-with-opfs") {
const worker = new Worker(
new URL("DbWorker.worker.js", import.meta.url),
{ type: "module" },
);
worker.onmessage = (e: MessageEvent<DbWorkerOutput>): void => {
dbWorker.onMessage(e.data);
};
const dbWorker: DbWorker = {
postMessage: (input) => {
worker.postMessage(input);
},
// no-op for SSR
if (platformName === "server")
return DbWorker.of({
postMessage: Function.constVoid,
onMessage: Function.constVoid,
};
return dbWorker;
}
});

if (platformName === "web-without-opfs") {
const promise = Effect.promise(() => import("./DbWorker.js")).pipe(
Effect.map(({ dbWorker: importedDbWorker }) => {
importedDbWorker.onMessage = dbWorker.onMessage;
return importedDbWorker.postMessage;
}),
Effect.runPromise,
);
const dbWorker: DbWorker = {
postMessage: (input) => {
promise.then(
(postMessage) => {
postMessage(input);
},
(reason: unknown) => {
dbWorker.onMessage({
_tag: "onError",
error: {
_tag: "UnexpectedError",
error: makeUnexpectedError(reason).pipe(Effect.runSync),
},
});
},
);
},
onMessage: Function.constVoid,
};
return dbWorker;
}
const worker = new Worker(new URL("DbWorker.worker.js", import.meta.url), {
type: "module",
});

return DbWorker.of({
postMessage: Function.constVoid,
worker.onmessage = (e: MessageEvent<DbWorkerOutput>): void => {
dbWorker.onMessage(e.data);
};

const dbWorker: DbWorker = {
postMessage: (input) => {
worker.postMessage(input);
},
onMessage: Function.constVoid,
});
};

return dbWorker;
}),
);
35 changes: 6 additions & 29 deletions packages/evolu-common-web/src/PlatformLive.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DbWorkerLock } from "@evolu/common";
import {
AppState,
Bip39,
Expand All @@ -12,37 +13,9 @@ import * as Effect from "effect/Effect";
import * as Function from "effect/Function";
import * as Layer from "effect/Layer";

const isChromeWithOpfs = (): boolean =>
navigator.userAgentData != null &&
navigator.userAgentData.brands.find(
({ brand, version }) =>
// Chrome or Chromium
brand.includes("Chrom") && Number(version) >= 109,
) != null;

const isFirefoxWithOpfs = (): boolean => {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf("firefox") === -1) return false;
const matches = userAgent.match(/firefox\/([0-9]+\.*[0-9]*)/);
if (matches == null) return false;
return Number(matches[1]) >= 111;
};

const isSafariWithOpfs = (): boolean => {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf("safari") === -1) return false;
const matches = userAgent.match(/version\/([0-9]+)/);
if (matches == null) return false;
return Number(matches[1]) >= 17;
};

export const PlatformNameLive = Layer.succeed(
PlatformName,
canUseDom
? isChromeWithOpfs() || isFirefoxWithOpfs() || isSafariWithOpfs()
? "web-with-opfs"
: "web-without-opfs"
: "server",
canUseDom ? "web" : "server",
);

export const SyncLockLive = Layer.effect(
Expand Down Expand Up @@ -80,6 +53,10 @@ export const SyncLockLive = Layer.effect(
}),
);

export const DbWorkerLockLive = Layer.succeed(DbWorkerLock, (callback) => {
navigator.locks.request("evolu:DbWorker", callback);
});

export const AppStateLive = Layer.effect(
AppState,
Effect.gen(function* (_) {
Expand Down
Loading

0 comments on commit 7d15f88

Please sign in to comment.