├── CLAUDE.md ├── tsbuild-all ├── src │ └── index.ts ├── package.json └── tsconfig.json ├── .env ├── packages ├── ws-litefs │ ├── test_fs │ │ └── .gitkeep │ ├── test_fs2 │ │ └── .gitkeep │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ ├── config.ts │ │ ├── __tests__ │ │ │ └── testServerConfig.ts │ │ ├── internal │ │ │ ├── __tests__ │ │ │ │ ├── LiteFSDB.test.ts │ │ │ │ └── PrimarySocket.test.ts │ │ │ └── util.ts │ │ └── logger.ts │ ├── vitest.config.ts │ ├── tsconfig.json │ ├── REAMDE.md │ └── package.json ├── ws-server │ ├── testDbs │ │ └── .gitkeep │ ├── src │ │ ├── error │ │ │ └── SchemaMismatchError.ts │ │ ├── fs │ │ │ ├── touchHack.ts │ │ │ ├── collapser.ts │ │ │ └── util.ts │ │ ├── __tests__ │ │ │ ├── testConfig.ts │ │ │ ├── index.test.ts │ │ │ ├── DBFactory.test.ts │ │ │ └── DB.test.ts │ │ ├── config.ts │ │ ├── DBFactory.ts │ │ └── Trasnport.ts │ ├── testSchemas │ │ └── test.sql │ ├── tsconfig.json │ ├── notes.md │ └── package.json ├── ws-demo │ ├── .gitignore │ ├── src │ │ ├── vite-env.d.ts │ │ ├── global.d.ts │ │ ├── schemas │ │ │ └── main.sql │ │ ├── syncConfig.ts │ │ ├── worker.ts │ │ ├── App.css │ │ └── main.tsx │ ├── README.md │ ├── tsconfig.node.json │ ├── vite.config.ts │ ├── index.html │ ├── server.js │ ├── tsconfig.json │ └── package.json ├── direct-connect-nodejs │ ├── dbs-test │ │ └── .gitkeep │ ├── schemas-test │ │ ├── .gitkeep │ │ ├── test.sql │ │ └── test.v2.sql │ ├── .gitignore │ ├── src │ │ ├── private │ │ │ ├── __tests__ │ │ │ │ ├── ServiceDB.test.ts │ │ │ │ ├── OutboundStream.test.ts │ │ │ │ └── collapser.test.ts │ │ │ ├── touchHack.ts │ │ │ ├── collapser.ts │ │ │ └── InboundStream.ts │ │ ├── config │ │ │ ├── TestConfig.ts │ │ │ └── DefaultConfig.ts │ │ ├── Types.ts │ │ ├── index.ts │ │ └── logger.ts │ ├── tsconfig.json │ ├── notes.md │ ├── package.json │ └── README.md ├── crsqlite-wasm │ ├── .gitignore │ ├── src │ │ ├── ahp │ │ │ ├── DB.ts │ │ │ └── worker │ │ │ │ ├── AhpConnection.ts │ │ │ │ └── ahp-worker.ts │ │ ├── __tests__ │ │ │ └── wrapper.test.ts │ │ ├── log.ts │ │ ├── cache.ts │ │ └── DB2.ts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── direct-connect-browser │ ├── src │ │ ├── common │ │ │ └── __tests__ │ │ │ │ └── DB.test.ts │ │ ├── index.ts │ │ ├── shared.worker.ts │ │ ├── Types.ts │ │ └── dedicated.worker.ts │ ├── tsconfig.json │ └── package.json ├── ws-client │ ├── src │ │ ├── types.ts │ │ ├── index.ts │ │ ├── worker │ │ │ ├── workerMsgTypes.ts │ │ │ ├── worker.ts │ │ │ ├── WorkerInterface.ts │ │ │ └── SyncService.ts │ │ ├── config.ts │ │ ├── DB.ts │ │ └── transport │ │ │ └── Transport.ts │ ├── tsconfig.json │ └── package.json ├── browser-tests │ ├── cypress │ │ ├── .gitignore │ │ ├── fixtures │ │ │ └── example.json │ │ ├── support │ │ │ ├── component-index.html │ │ │ ├── component.ts │ │ │ └── commands.ts │ │ └── component │ │ │ ├── int.cy.ts │ │ │ ├── tblrx.cy.ts │ │ │ ├── WholeDbReplicator.cy.ts │ │ │ ├── automigrate.cy.ts │ │ │ └── wa-sqlite-wrapper.cy.ts │ ├── src │ │ ├── vite-env.d.ts │ │ ├── counter.ts │ │ ├── main.ts │ │ └── typescript.svg │ ├── README.md │ ├── cypress.config.ts │ ├── .gitignore │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ ├── vite.config.js │ └── public │ │ └── vite.svg ├── sandbox │ ├── README.md │ ├── .gitignore │ ├── src │ │ ├── global.d.ts │ │ ├── env.d.ts │ │ └── main.tsx │ ├── notes.md │ ├── index.html │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ └── slurp.mjs ├── rx-query │ ├── src │ │ ├── QueryToDataflow.ts │ │ ├── RelationCache.ts │ │ ├── RxDbTx.ts │ │ ├── __tests__ │ │ │ └── todo.test.ts │ │ ├── QueryAST.ts │ │ └── QueryRewriter.ts │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── ws-common │ ├── README.md │ ├── tsconfig.json │ └── package.json ├── rx-tbl │ ├── src │ │ ├── index.ts │ │ └── __tests__ │ │ │ └── tblrx.test.ts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── sandbox-node │ ├── tst.db-shm │ ├── tst.db-wal │ ├── package.json │ └── src │ │ ├── script2.js │ │ └── script1.js ├── react │ ├── src │ │ ├── __tests__ │ │ │ ├── stateHooks.test.ts │ │ │ └── queryHooks.test.ts │ │ ├── db │ │ │ ├── useDB.ts │ │ │ ├── DBContext.ts │ │ │ └── useSync.ts │ │ ├── rowid.ts │ │ ├── index.ts │ │ ├── context.ts │ │ └── stateHooks.ts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── xplat-api │ ├── src │ │ └── __tests__ │ │ │ └── xplat-api.test.ts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── p2p │ ├── src │ │ ├── __tests__ │ │ │ └── WholeDbReplicator.test.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── libsqlite3-darwin-arm64 │ ├── vendor │ │ └── libsqlite3.0.dylib │ ├── .gitignore │ ├── LICENSE.sqlite │ └── package.json ├── ws-browserdb │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.test.ts ├── node-tests │ ├── src │ │ ├── fill.ts │ │ ├── simple.ts │ │ └── __tests__ │ │ │ ├── automigrate.test.ts │ │ │ └── xplat.test.ts │ ├── tsconfig.json │ └── package.json ├── libsqlite3-darwin-x64 │ ├── .gitignore │ └── package.json ├── @react-native │ └── react-native │ │ ├── package.json │ │ ├── index.js │ │ └── README.md ├── id │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ └── index.test.ts │ └── CHANGELOG.md ├── @op-engineering │ └── op-sqlite │ │ ├── index.js │ │ └── package.json ├── node-allinone │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── xplat-tests │ ├── src │ │ ├── index.ts │ │ └── automigrate.test.ts │ ├── tsconfig.json │ └── package.json ├── logger-provider │ ├── tsconfig.json │ ├── package.json │ ├── CHANGELOG.md │ └── src │ │ └── index.ts ├── direct-connect-common │ ├── tsconfig.json │ ├── src │ │ ├── msg │ │ │ ├── BinarySerializer.ts │ │ │ ├── SerializerFactory.ts │ │ │ ├── __tests__ │ │ │ │ └── sandbox.test.ts │ │ │ └── JsonSerializer.ts │ │ ├── index.ts │ │ └── util.ts │ └── package.json ├── bun-sqlite-example │ └── package.json └── bun-sqlite-lib │ ├── tsconfig.json │ ├── src │ ├── index.test.ts │ └── bin │ │ └── sqlite-path.ts │ ├── package.json │ └── test.ts ├── demos └── expo-crsql-demo │ ├── CLAUDE.md │ ├── server │ ├── bunfig.toml │ ├── src │ │ ├── .gitignore │ │ ├── react.svg │ │ ├── index.html │ │ ├── App.tsx │ │ ├── frontend.tsx │ │ └── APITester.tsx │ ├── bun-env.d.ts │ └── tsconfig.json │ ├── hooks │ ├── useColorScheme.ts │ ├── useColorScheme.web.ts │ ├── useThemeColor.ts │ └── useDatabaseSuspense.ts │ ├── .kamal │ ├── hooks │ │ ├── docker-setup.sample │ │ ├── post-proxy-reboot.sample │ │ ├── pre-proxy-reboot.sample │ │ ├── post-app-boot.sample │ │ ├── pre-app-boot.sample │ │ ├── post-deploy.sample │ │ ├── pre-connect.sample │ │ └── pre-build.sample │ └── secrets │ ├── services │ └── db.ts │ ├── assets │ ├── images │ │ ├── icon.png │ │ ├── favicon.png │ │ ├── react-logo.png │ │ ├── splash-icon.png │ │ ├── adaptive-icon.png │ │ ├── react-logo@2x.png │ │ ├── react-logo@3x.png │ │ └── partial-react-logo.png │ └── fonts │ │ └── SpaceMono-Regular.ttf │ ├── ios │ ├── crsqldemo │ │ ├── Images.xcassets │ │ │ ├── Contents.json │ │ │ ├── SplashScreenLogo.imageset │ │ │ │ ├── image.png │ │ │ │ ├── image@2x.png │ │ │ │ ├── image@3x.png │ │ │ │ └── Contents.json │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── App-Icon-1024x1024@1x.png │ │ │ │ └── Contents.json │ │ │ └── SplashScreenBackground.colorset │ │ │ │ └── Contents.json │ │ ├── crsqldemo-Bridging-Header.h │ │ ├── crsqldemo.entitlements │ │ ├── Supporting │ │ │ └── Expo.plist │ │ └── PrivacyInfo.xcprivacy │ ├── Podfile.properties.json │ ├── crsqldemo.xcworkspace │ │ └── contents.xcworkspacedata │ ├── .gitignore │ └── .xcode.env │ ├── app │ ├── index.tsx │ ├── +not-found.tsx │ ├── _layout.tsx │ └── (tabs) │ │ └── _layout.tsx │ ├── components │ ├── ui │ │ ├── TabBarBackground.tsx │ │ ├── TabBarBackground.ios.tsx │ │ ├── IconSymbol.ios.tsx │ │ └── IconSymbol.tsx │ ├── ThemedView.tsx │ ├── HapticTab.tsx │ ├── ExternalLink.tsx │ ├── HelloWave.tsx │ ├── Collapsible.tsx │ └── ThemedText.tsx │ ├── babel.config.js │ ├── eslint.config.js │ ├── tsconfig.json │ ├── .gitignore │ ├── TODO.md │ ├── constants │ └── Colors.ts │ ├── app.json │ └── metro.config.js ├── .prettierignore ├── .prettierrc ├── .gitignore ├── scripts ├── latest-versions.sh └── update-pkgjson.sh ├── .gitmodules ├── .changeset ├── config.json └── README.md ├── deep-clean.sh ├── tsconfig-template.json ├── test.sh ├── package.json ├── .envrc ├── TODO.md ├── LICENSE ├── .dockerignore ├── flake.lock └── .github └── workflows └── js-tests.yaml /CLAUDE.md: -------------------------------------------------------------------------------- 1 | AGENTS.md -------------------------------------------------------------------------------- /tsbuild-all/src/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | CRSQLITE_NOPREBUILD=1 2 | -------------------------------------------------------------------------------- /packages/ws-litefs/test_fs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ws-litefs/test_fs2/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ws-server/testDbs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/CLAUDE.md: -------------------------------------------------------------------------------- 1 | AGENTS.md -------------------------------------------------------------------------------- /packages/ws-demo/.gitignore: -------------------------------------------------------------------------------- 1 | dbs/ 2 | 3 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/dbs-test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | src/crsqlite.mjs -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/schemas-test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | dbs-test/ 2 | -------------------------------------------------------------------------------- /packages/ws-litefs/.gitignore: -------------------------------------------------------------------------------- 1 | test_fs/ 2 | test_fs2/ 3 | -------------------------------------------------------------------------------- /packages/direct-connect-browser/src/common/__tests__/DB.test.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ws-client/src/types.ts: -------------------------------------------------------------------------------- 1 | export type DBID = string; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | LICENSE 2 | spec.md 3 | deps/ 4 | wa-sqlite/ 5 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/.gitignore: -------------------------------------------------------------------------------- 1 | videos/ 2 | screenshots/ -------------------------------------------------------------------------------- /packages/sandbox/README.md: -------------------------------------------------------------------------------- 1 | Just a package to use to create bug repros. 2 | -------------------------------------------------------------------------------- /packages/ws-demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/bunfig.toml: -------------------------------------------------------------------------------- 1 | [serve.static] 2 | env = "BUN_PUBLIC_*" -------------------------------------------------------------------------------- /packages/browser-tests/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/src/ahp/DB.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The client side db wrapper. 3 | */ 4 | -------------------------------------------------------------------------------- /packages/sandbox/.gitignore: -------------------------------------------------------------------------------- 1 | dbs/*.db 2 | dbs/*.db-shm 3 | dbs/*.db-wal 4 | dbs/*.touch 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "singleQuote": false 5 | } -------------------------------------------------------------------------------- /demos/expo-crsql-demo/hooks/useColorScheme.ts: -------------------------------------------------------------------------------- 1 | export { useColorScheme } from 'react-native'; 2 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/src/.gitignore: -------------------------------------------------------------------------------- 1 | .expo-web-build/* 2 | !.expo-web-build/index.html 3 | -------------------------------------------------------------------------------- /packages/ws-server/src/error/SchemaMismatchError.ts: -------------------------------------------------------------------------------- 1 | class SchemaMismatchError extends Error {} 2 | -------------------------------------------------------------------------------- /packages/ws-demo/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/ws-client 2 | 3 | Syncs database state over a partykit room. 4 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/hooks/docker-setup.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Docker set up on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /packages/rx-query/src/QueryToDataflow.ts: -------------------------------------------------------------------------------- 1 | export default function rewrittenQueryToDataflow(queryAst: string) {} 2 | -------------------------------------------------------------------------------- /packages/ws-common/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/ws-client 2 | 3 | Syncs database state over a partykit room. 4 | -------------------------------------------------------------------------------- /packages/rx-tbl/src/index.ts: -------------------------------------------------------------------------------- 1 | import tblrx from "./tblrx.js"; 2 | export * from "./tblrx.js"; 3 | export default tblrx; 4 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/hooks/post-proxy-reboot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Rebooted kamal-proxy on $KAMAL_HOSTS" 4 | -------------------------------------------------------------------------------- /packages/rx-query/src/RelationCache.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to hydrate joins 3 | */ 4 | export default class RelationCache {} 5 | -------------------------------------------------------------------------------- /packages/sandbox-node/tst.db-shm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/packages/sandbox-node/tst.db-shm -------------------------------------------------------------------------------- /packages/sandbox-node/tst.db-wal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/packages/sandbox-node/tst.db-wal -------------------------------------------------------------------------------- /packages/sandbox/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.wasm?url"; 2 | declare module "*.js?url"; 3 | declare module "*?raw"; 4 | -------------------------------------------------------------------------------- /packages/ws-demo/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.wasm?url"; 2 | declare module "*.js?url"; 3 | declare module "*?raw"; 4 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/hooks/pre-proxy-reboot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Rebooting kamal-proxy on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /packages/react/src/__tests__/stateHooks.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test("useThrottledState", () => {}); -------------------------------------------------------------------------------- /packages/ws-server/testSchemas/test.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS foo (a primary key not null, b); 2 | SELECT crsql_as_crr('foo'); -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/hooks/post-app-boot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/hooks/pre-app-boot.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..." 4 | -------------------------------------------------------------------------------- /packages/rx-tbl/src/__tests__/tblrx.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test("these tests exists in xplat-tests", () => {}); -------------------------------------------------------------------------------- /packages/xplat-api/src/__tests__/xplat-api.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test("only interface, no code", () => {}); -------------------------------------------------------------------------------- /demos/expo-crsql-demo/services/db.ts: -------------------------------------------------------------------------------- 1 | export type { DB } from "@op-engineering/op-sqlite"; 2 | export { open } from "@op-engineering/op-sqlite"; 3 | -------------------------------------------------------------------------------- /packages/rx-query/src/RxDbTx.ts: -------------------------------------------------------------------------------- 1 | // Collect all writes and process them post-commit of the transaction. 2 | export default class RxDbTx { 3 | 4 | } -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/images/icon.png -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "expo" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/p2p/src/__tests__/WholeDbReplicator.test.ts: -------------------------------------------------------------------------------- 1 | // You will find the tests in pkg/xplat-tests 2 | test("tests in js/xplat-tests", () => {}); 3 | -------------------------------------------------------------------------------- /packages/rx-query/src/__tests__/todo.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test("these tests exists in xplat-tests", () => {}); 4 | -------------------------------------------------------------------------------- /packages/ws-demo/src/schemas/main.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS test (id PRIMARY KEY not null, name TEXT); 2 | SELECT crsql_as_crr('test'); 3 | 4 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/images/favicon.png -------------------------------------------------------------------------------- /packages/direct-connect-browser/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as WorkerInterface } from "./WorkerInterface.js"; 2 | export { newDbid } from "./Types.js"; 3 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/private/__tests__/ServiceDB.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, beforeAll } from "vitest"; 2 | 3 | test("stuff", () => {}); 4 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/images/react-logo.png -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/images/splash-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/images/splash-icon.png -------------------------------------------------------------------------------- /packages/sandbox/notes.md: -------------------------------------------------------------------------------- 1 | So our only issue is that we are not processing messages we receive from the server? Or not updating the UI after we do process them? 2 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/app/index.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect } from 'expo-router'; 2 | 3 | export default function Index() { 4 | return ; 5 | } -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/images/react-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/images/react-logo@2x.png -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/images/react-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/images/react-logo@3x.png -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/schemas-test/test.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS foo ( 2 | a primary key NOT NULL, 3 | b 4 | ); 5 | 6 | SELECT crsql_as_crr('foo'); -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /demos/expo-crsql-demo/assets/images/partial-react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/assets/images/partial-react-logo.png -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/Podfile.properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo.jsEngine": "hermes", 3 | "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true", 4 | "newArchEnabled": "true" 5 | } 6 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/crsqldemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/schemas-test/test.v2.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS foo ( 2 | a primary key not null, 3 | b, 4 | c 5 | ); 6 | 7 | SELECT crsql_as_crr('foo'); -------------------------------------------------------------------------------- /packages/libsqlite3-darwin-arm64/vendor/libsqlite3.0.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/packages/libsqlite3-darwin-arm64/vendor/libsqlite3.0.dylib -------------------------------------------------------------------------------- /packages/ws-browserdb/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/ws-browserdb 2 | 3 | Implemented the DB interface, required to sync a DB over a WabSocket, for WASM builds of SQLite. 4 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/src/__tests__/wrapper.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test("see js/tests/browser-tests/cypress/component/wa-sqlite-wrapper.cy.ts", () => {}); 4 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/private/__tests__/OutboundStream.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, afterAll } from "vitest"; 2 | 3 | test("throws on schema version mismatch", () => {}); 4 | -------------------------------------------------------------------------------- /packages/node-tests/src/fill.ts: -------------------------------------------------------------------------------- 1 | if (!global.navigator) { 2 | Object.defineProperty(global, 'navigator', { 3 | value: {}, 4 | writable: true, 5 | configurable: true 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /packages/react/src/__tests__/queryHooks.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test("usePointQuery", () => {}); 4 | 5 | test("useRangeQuery", () => {}); 6 | 7 | test("useQuery", () => {}); -------------------------------------------------------------------------------- /packages/browser-tests/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/browser-tests 2 | 3 | Runs some test suites in the browser rather than in a node environment. Why? WASM has a slightly different memory subsystem than a native build. 4 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /packages/ws-litefs/src/index.ts: -------------------------------------------------------------------------------- 1 | export { createLiteFSWriteService } from "./LiteFSWriteService.js"; 2 | export { createLiteFSDBFactory } from "./LiteFSDBFactory.js"; 3 | export { default as FSNotify } from "./FSNotify.js"; 4 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/src/log.ts: -------------------------------------------------------------------------------- 1 | const isDebug = (globalThis as any).__vlcn_wa_crsqlite_dbg; 2 | export default function log(...data: any[]) { 3 | if (isDebug) { 4 | console.log("crsqlite-wasm: ", ...data); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/SplashScreenLogo.imageset/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/SplashScreenLogo.imageset/image.png -------------------------------------------------------------------------------- /packages/libsqlite3-darwin-arm64/.gitignore: -------------------------------------------------------------------------------- 1 | # SQLite source files 2 | sqlite3.c 3 | sqlite3.h 4 | sqlite3ext.h 5 | shell.c 6 | sqlite.zip 7 | 8 | # Built library 9 | vendor/ 10 | 11 | # But keep the LICENSE 12 | !LICENSE.sqlite -------------------------------------------------------------------------------- /packages/libsqlite3-darwin-x64/.gitignore: -------------------------------------------------------------------------------- 1 | # SQLite source files 2 | sqlite3.c 3 | sqlite3.h 4 | sqlite3ext.h 5 | shell.c 6 | sqlite.zip 7 | 8 | # Built library 9 | vendor/ 10 | 11 | # But keep the LICENSE 12 | !LICENSE.sqlite -------------------------------------------------------------------------------- /packages/react/src/db/useDB.ts: -------------------------------------------------------------------------------- 1 | import dbFactory from "./DBFactory.js"; 2 | import { CtxAsync } from "../context.js"; 3 | 4 | export default function useDB(dbname: string): CtxAsync { 5 | return dbFactory.getHook(dbname)!()!; 6 | } 7 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/components/ui/TabBarBackground.tsx: -------------------------------------------------------------------------------- 1 | // This is a shim for web and Android where the tab bar is generally opaque. 2 | export default undefined; 3 | 4 | export function useBottomTabOverflow() { 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/SplashScreenLogo.imageset/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/SplashScreenLogo.imageset/image@2x.png -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/SplashScreenLogo.imageset/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/SplashScreenLogo.imageset/image@3x.png -------------------------------------------------------------------------------- /packages/@react-native/react-native/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native", 3 | "version": "0.0.0-fake", 4 | "description": "Fake package to block react-native hoisting to root", 5 | "private": true, 6 | "main": "index.js" 7 | } -------------------------------------------------------------------------------- /packages/id/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [] 9 | } 10 | -------------------------------------------------------------------------------- /packages/@op-engineering/op-sqlite/index.js: -------------------------------------------------------------------------------- 1 | // Fake @op-engineering/op-sqlite package 2 | // This exists to prevent hoisting of the real package in the monorepo 3 | export default { 4 | warning: "This is a fake package to prevent hoisting" 5 | }; -------------------------------------------------------------------------------- /packages/xplat-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [] 9 | } 10 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subtleGradient/crsql-base/main/demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png -------------------------------------------------------------------------------- /demos/expo-crsql-demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | api.cache(true); 3 | return { 4 | presets: [["babel-preset-expo", { unstable_transformImportMeta: true }]], 5 | plugins: ["babel-plugin-syntax-hermes-parser"], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/crsqldemo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/ws-litefs/src/config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "@vlcn.io-community/ws-server"; 2 | import path from "path"; 3 | 4 | export function litefsPrimaryPath(config: Config) { 5 | return path.normalize(path.join(config.dbFolder!, ".primary")); 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.local.json 3 | .DS_Store 4 | .direnv 5 | .gems 6 | .ipynb_checkpoints/ 7 | .turbo/ 8 | .vscode 9 | __pycache__/ 10 | a.out 11 | bench/ 12 | build/ 13 | dist/ 14 | node_modules/ 15 | out.db 16 | target/ 17 | tsconfig.tsbuildinfo 18 | -------------------------------------------------------------------------------- /packages/p2p/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [{ "path": "../xplat-api" }] 9 | } 10 | -------------------------------------------------------------------------------- /packages/sandbox/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_APP_TITLE: string; 5 | // more env variables... 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv; 10 | } 11 | -------------------------------------------------------------------------------- /packages/xplat-api/README.md: -------------------------------------------------------------------------------- 1 | # CR-SQLite - @vlcn.io-community/xplat-api 2 | 3 | Cross-platform API for `sqlite` that works in both the browser and node/deno environments. 4 | 5 | This allows us to write our applications once and run them in either environment. 6 | -------------------------------------------------------------------------------- /packages/rx-query/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [{ "path": "../xplat-api" }] 9 | } 10 | -------------------------------------------------------------------------------- /packages/rx-tbl/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [{ "path": "../xplat-api" }] 9 | } 10 | -------------------------------------------------------------------------------- /packages/node-allinone/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [{"path": "../xplat-api"}] 9 | } 10 | -------------------------------------------------------------------------------- /packages/xplat-tests/src/index.ts: -------------------------------------------------------------------------------- 1 | export { tests as wdbTests } from "./WholeDbRepliator.test.js"; 2 | export { tests as tblrxTests } from "./tblrx.test.js"; 3 | export { tests as intTests } from "./int.test.js"; 4 | export { tests as automigrateTests } from "./automigrate.test.js"; 5 | -------------------------------------------------------------------------------- /packages/@op-engineering/op-sqlite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@op-engineering/op-sqlite", 3 | "version": "0.0.1-fake", 4 | "description": "Fake package to prevent hoisting of real @op-engineering/op-sqlite", 5 | "main": "index.js", 6 | "private": true, 7 | "type": "module" 8 | } -------------------------------------------------------------------------------- /scripts/latest-versions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" 4 | source "$SCRIPT_DIR/pkgs.inc.sh" 5 | 6 | printf '%s\n' "${pkgs[@]}" | xargs -I {} -P 8 bash -c 'echo "{}:$(npm show {} versions --json | jq -r '.[-1]')"' 7 | -------------------------------------------------------------------------------- /packages/ws-common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/logger-provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ws-demo/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ws-litefs/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | // litefs tests are run sequentially given they all make use of a shared directory 6 | maxThreads: 1, 7 | minThreads: 1, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /packages/direct-connect-common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [] 10 | } 11 | -------------------------------------------------------------------------------- /packages/@react-native/react-native/index.js: -------------------------------------------------------------------------------- 1 | // This is a fake package to prevent react-native from being hoisted to the root 2 | // This ensures that react-native dependencies stay within their respective packages 3 | throw new Error("This is a fake react-native package for blocking hoisting. Do not import."); -------------------------------------------------------------------------------- /packages/p2p/src/index.ts: -------------------------------------------------------------------------------- 1 | import api from "./WholeDbReplicator.js"; 2 | export { 3 | PokeProtocol, 4 | Changeset, 5 | SiteIDLocal, 6 | SiteIDWire, 7 | WholeDbReplicator, 8 | } from "./WholeDbReplicator.js"; 9 | export { default as wdbRtc } from "./WholeDbRtc.js"; 10 | export default api; 11 | -------------------------------------------------------------------------------- /packages/ws-litefs/src/__tests__/testServerConfig.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "@vlcn.io-community/ws-server"; 2 | 3 | export const config = Object.freeze({ 4 | dbFolder: "./test_fs/dbs", 5 | schemaFolder: "./test_fs/schemas", 6 | pathPattern: /\/vlcn-ws/, 7 | notifyLatencyMs: 50, 8 | }) as Config; 9 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | component: { 5 | devServer: { 6 | framework: "react", 7 | bundler: "vite", 8 | }, 9 | }, 10 | screenshotOnRunFailure: false, 11 | video: false, 12 | }); 13 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/eslint.config.js: -------------------------------------------------------------------------------- 1 | // https://docs.expo.dev/guides/using-eslint/ 2 | const { defineConfig } = require('eslint/config'); 3 | const expoConfig = require('eslint-config-expo/flat'); 4 | 5 | module.exports = defineConfig([ 6 | expoConfig, 7 | { 8 | ignores: ['dist/*'], 9 | }, 10 | ]); 11 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [{ "path": "../xplat-api" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/ws-demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import { defineConfig } from "vite"; 3 | 4 | export default defineConfig({ 5 | base: "./", 6 | build: { 7 | target: "esnext", 8 | }, 9 | 10 | optimizeDeps: { 11 | esbuildOptions: { 12 | target: "esnext", 13 | }, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/emsdk"] 2 | path = deps/emsdk 3 | url = https://github.com/emscripten-core/emsdk.git 4 | [submodule "deps/wa-sqlite"] 5 | path = deps/wa-sqlite 6 | url = git@github.com:vlcn-io/wa-sqlite.git 7 | [submodule "deps/cr-sqlite"] 8 | path = deps/cr-sqlite 9 | url = git@github.com:vlcn-io/cr-sqlite.git 10 | -------------------------------------------------------------------------------- /packages/sandbox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/ws-litefs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [{ "path": "../ws-common" }, { "path": "../ws-server" }] 10 | } 11 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /deep-clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Navigate to the packages directory 4 | cd ./packages 5 | 6 | # Iterate over each subdirectory 7 | for dir in */ ; do 8 | # Navigate to the current subdirectory 9 | cd "$dir" 10 | # Run bun deep-clean 11 | bun run deep-clean 12 | # Navigate back to the packages directory 13 | cd .. 14 | done 15 | -------------------------------------------------------------------------------- /packages/browser-tests/src/counter.ts: -------------------------------------------------------------------------------- 1 | export function setupCounter(element: HTMLButtonElement) { 2 | let counter = 0 3 | const setCounter = (count: number) => { 4 | counter = count 5 | element.innerHTML = `count is ${counter}` 6 | } 7 | element.addEventListener('click', () => setCounter(counter + 1)) 8 | setCounter(0) 9 | } 10 | -------------------------------------------------------------------------------- /packages/ws-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [ 10 | { 11 | "path": "../ws-common" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/xplat-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [ 9 | { "path": "../xplat-api" }, 10 | { "path": "../p2p" }, 11 | { "path": "../rx-tbl" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "@/*": [ 7 | "./*" 8 | ] 9 | } 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".expo/types/**/*.ts", 15 | "expo-env.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/ws-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as WorkerInterface } from "./worker/WorkerInterface.js"; 2 | export { createSyncedDB } from "./SyncedDB.js"; 3 | export type { Config } from "./config.js"; 4 | export { defaultConfig } from "./config.js"; 5 | export type { DB } from "./DB.js"; 6 | export type { Transport } from "./transport/Transport.js"; 7 | -------------------------------------------------------------------------------- /packages/ws-litefs/src/internal/__tests__/LiteFSDB.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import { LiteFSDB } from "../LiteFSDB"; 3 | 4 | test("Always delegates to proxy when primary", () => {}); 5 | 6 | test("Always delegates writes to primary connection when follower", () => {}); 7 | 8 | test("Reads go to proxy when not primary", () => {}); 9 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "App-Icon-1024x1024@1x.png", 5 | "idiom": "universal", 6 | "platform": "ios", 7 | "size": "1024x1024" 8 | } 9 | ], 10 | "info": { 11 | "version": 1, 12 | "author": "expo" 13 | } 14 | } -------------------------------------------------------------------------------- /packages/node-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [ 9 | { "path": "../node-allinone" }, 10 | { "path": "../xplat-tests" }, 11 | { "path": "../rx-tbl" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/p2p/README.md: -------------------------------------------------------------------------------- 1 | # Replicators / p2p 2 | 3 | Note -- this is a demonstration replicator that relies on a very simple protocol for exchanging data between peers. 4 | 5 | [todomvc-p2p](https://github.com/vlcn-io/live-examples/tree/main/src/todomvc-p2p) has example use of this replicator. Note that this layer requires a [peerjs](https://peerjs.com/) signaling server. 6 | -------------------------------------------------------------------------------- /packages/react/src/rowid.ts: -------------------------------------------------------------------------------- 1 | export type Opaque = BaseType & { 2 | readonly [Symbols.base]: BaseType; 3 | readonly [Symbols.brand]: BrandType; 4 | }; 5 | 6 | namespace Symbols { 7 | export declare const base: unique symbol; 8 | export declare const brand: unique symbol; 9 | } 10 | 11 | export type RowID = Opaque; 12 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [ 10 | { "path": "../direct-connect-common" }, 11 | { "path": "../xplat-api" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/ws-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [ 10 | { "path": "../ws-common" }, 11 | { 12 | "path": "../logger-provider" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/browser-tests/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/src/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/react/src/db/DBContext.ts: -------------------------------------------------------------------------------- 1 | import { CtxAsync } from "../context.js"; 2 | import React from "react"; 3 | 4 | export function createContext() { 5 | const DBContext = React.createContext(null); 6 | DBContext.displayName = "DBContext"; 7 | function useDb() { 8 | return React.useContext(DBContext); 9 | } 10 | return { DBContext, useDb }; 11 | } 12 | -------------------------------------------------------------------------------- /packages/ws-demo/src/syncConfig.ts: -------------------------------------------------------------------------------- 1 | import { Config, defaultConfig } from "@vlcn.io-community/ws-client"; 2 | import { createDbProvider } from "@vlcn.io-community/ws-browserdb"; 3 | import wasmUrl from "@vlcn.io-community/crsqlite-wasm/crsqlite.wasm?url"; 4 | 5 | export const config: Config = { 6 | dbProvider: createDbProvider(wasmUrl), 7 | transportProvider: defaultConfig.transportProvider, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/config/TestConfig.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { Config } from "../Types.js"; 3 | 4 | const TestConfig: Config = { 5 | serviceName: "test", 6 | dbsDir: path.join(".", "dbs-test"), 7 | cacheTtlInSeconds: 60, 8 | notifyLatencyInMs: 10, 9 | serviceDbPath: ":memory:", 10 | msgContentType: "application/json", 11 | }; 12 | 13 | export default TestConfig; 14 | -------------------------------------------------------------------------------- /packages/ws-demo/src/worker.ts: -------------------------------------------------------------------------------- 1 | import { Config, defaultConfig } from "@vlcn.io-community/ws-client"; 2 | import { start } from "@vlcn.io-community/ws-client/worker.js"; 3 | import { createDbProvider } from "@vlcn.io-community/ws-browserdb"; 4 | 5 | export const config: Config = { 6 | dbProvider: createDbProvider(), 7 | transportProvider: defaultConfig.transportProvider, 8 | }; 9 | 10 | start(config); 11 | -------------------------------------------------------------------------------- /packages/bun-sqlite-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/bun-sqlite-example", 3 | "private": true, 4 | "version": "0.1.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "bun run src/index.ts", 8 | "test": "bun test" 9 | }, 10 | "dependencies": { 11 | "@vlcn.io-community/bun-sqlite-lib": "workspace:*" 12 | }, 13 | "devDependencies": { 14 | "@types/bun": "^1.2.19" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true, 7 | "jsx": "react-jsx" 8 | }, 9 | "include": ["./src/"], 10 | "references": [ 11 | { "path": "../xplat-api" }, 12 | { "path": "../rx-tbl" }, 13 | { "path": "../ws-client" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/hooks/post-deploy.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A sample post-deploy hook 4 | # 5 | # These environment variables are available: 6 | # KAMAL_RECORDED_AT 7 | # KAMAL_PERFORMER 8 | # KAMAL_VERSION 9 | # KAMAL_HOSTS 10 | # KAMAL_ROLES (if set) 11 | # KAMAL_DESTINATION (if set) 12 | # KAMAL_RUNTIME 13 | 14 | echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" 15 | -------------------------------------------------------------------------------- /packages/react/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/react 2 | 3 | In progress React integration. While we've gotten it to allowing ~100 SQLite queries to resolve in a single frame @ 60fps, there's still work to do on making the reactivity smarter and fine-grained. 4 | 5 | The high level design idea: https://riffle.systems/essays/prelude/ 6 | 7 | The [live-examples](https://github.com/vlcn-io/live-examples) make use of the react hooks. 8 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/config/DefaultConfig.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { Config } from "../Types.js"; 3 | 4 | const DefaultConfig: Config = { 5 | serviceName: "vlcn-default", 6 | dbsDir: path.join(".", "dbs"), 7 | cacheTtlInSeconds: 60 * 5, 8 | notifyLatencyInMs: 10, 9 | serviceDbPath: "./dbs/service.db", 10 | msgContentType: "application/json", 11 | }; 12 | 13 | export default DefaultConfig; 14 | -------------------------------------------------------------------------------- /packages/ws-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vulcan + WebSocket 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/browser-tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/bun-env.d.ts: -------------------------------------------------------------------------------- 1 | // Generated by `bun init` 2 | 3 | declare module "*.svg" { 4 | /** 5 | * A path to the SVG file 6 | */ 7 | const path: `${string}.svg`; 8 | export = path; 9 | } 10 | 11 | declare module "*.module.css" { 12 | /** 13 | * A record of class names to their corresponding CSS module classes 14 | */ 15 | const classes: { readonly [key: string]: string }; 16 | export = classes; 17 | } 18 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./queryHooks.js"; 2 | export * from "./context.js"; 3 | export * from "./rowid.js"; 4 | export * from "./stateHooks.js"; 5 | export { default as DBProvider } from "./db/DBProvider.js"; 6 | export { default as useDB } from "./db/useDB.js"; 7 | export { default as useSync } from "./db/useSync.js"; 8 | export { default as dbFactory } from "./db/DBFactory.js"; 9 | export { Query } from "@vlcn.io/typed-sql"; 10 | -------------------------------------------------------------------------------- /tsbuild-all/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/tsbuild-all", 3 | "version": "0.14.0", 4 | "main": "lib/index.js", 5 | "type": "module", 6 | "devDependencies": { 7 | "typescript": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "clean": "tsc --build --clean", 11 | "build": "tsc --build", 12 | "watch": "tsc --build -w", 13 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strictNullChecks": true, 5 | "module": "esnext", 6 | "target": "esnext", 7 | "skipLibCheck": true, 8 | "moduleResolution": "Node", 9 | "baseUrl": "./src/", 10 | "declaration": true, 11 | "allowJs": true, 12 | "composite": true, 13 | "declarationMap": true, 14 | "incremental": true, 15 | "strict": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bun + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/ws-client/src/worker/workerMsgTypes.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "../config"; 2 | import { TransporOptions } from "../transport/Transport"; 3 | import { DBID } from "../types"; 4 | 5 | export type Msg = StartSyncMsg | StopSyncMsg; 6 | 7 | export type StartSyncMsg = { 8 | _tag: "StartSync"; 9 | dbid: DBID; 10 | transportOpts: TransporOptions; 11 | }; 12 | 13 | export type StopSyncMsg = { 14 | _tag: "StopSync"; 15 | dbid: DBID; 16 | }; 17 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Supporting/Expo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EXUpdatesCheckOnLaunch 6 | ALWAYS 7 | EXUpdatesEnabled 8 | 9 | EXUpdatesLaunchWaitMs 10 | 0 11 | 12 | -------------------------------------------------------------------------------- /packages/ws-server/src/fs/touchHack.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is only needed in simulated environments on OSX and Windows 3 | */ 4 | import { throttle } from "throttle-debounce"; 5 | import util from "./util.js"; 6 | 7 | const apply = throttle(25, (dbpath: string) => { 8 | util.touchFile(dbpath); 9 | }); 10 | 11 | export default function touchHack(dbpath: string) { 12 | if (!util.needsTouchHack()) { 13 | return; 14 | } 15 | 16 | apply(dbpath); 17 | } 18 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | for d in ./packages/*/ ; do 6 | if [ -f "$d/package.json" ]; then 7 | # Check if package.json has a test script 8 | if grep -q '"test":' "$d/package.json"; then 9 | echo "🧪 Testing in $d" 10 | (cd "$d" && bun run test) 11 | else 12 | echo "⏭️ Skipping $d (no test script)" 13 | fi 14 | fi 15 | done 16 | 17 | echo "✅ All tests completed!" 18 | -------------------------------------------------------------------------------- /packages/ws-server/src/__tests__/testConfig.ts: -------------------------------------------------------------------------------- 1 | import { cryb64 } from "@vlcn.io-community/ws-common"; 2 | import { Config } from "../config.js"; 3 | 4 | export const config: Config = { 5 | schemaFolder: "./testSchemas", 6 | dbFolder: null, 7 | pathPattern: /\/vlcn-ws/, 8 | }; 9 | 10 | import fs from "node:fs"; 11 | 12 | export const schemaContent = fs.readFileSync("./testSchemas/test.sql", "utf-8"); 13 | export const schemaVersion = cryb64(schemaContent); 14 | -------------------------------------------------------------------------------- /packages/sandbox-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandbox-node", 3 | "private": true, 4 | "type": "module", 5 | "version": "0.7.0", 6 | "scripts": { 7 | "test": "echo 'No tests yet'", 8 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 9 | }, 10 | "dependencies": { 11 | "better-sqlite3": "^12.2.0" 12 | }, 13 | "devDependencies": { 14 | "@vlcn.io/crsqlite": "^0.16.3", 15 | "vite": "^4.2.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/private/touchHack.ts: -------------------------------------------------------------------------------- 1 | import { Config } from "../Types.js"; 2 | import { throttle } from "throttle-debounce"; 3 | import util from "./util.js"; 4 | 5 | const apply = throttle(25, (config: Config, dbid: Uint8Array) => { 6 | util.touchFile(config, dbid); 7 | }); 8 | 9 | export default function touchHack(config: Config, dbid: Uint8Array) { 10 | if (!util.needsTouchHack()) { 11 | return; 12 | } 13 | 14 | apply(config, dbid); 15 | } 16 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | .xcode.env.local 25 | 26 | # Bundle artifacts 27 | *.jsbundle 28 | 29 | # CocoaPods 30 | /Pods/ 31 | -------------------------------------------------------------------------------- /packages/ws-server/src/config.ts: -------------------------------------------------------------------------------- 1 | export const defaultConfig: Config = Object.freeze({ 2 | dbFolder: "./dbs", 3 | schemaFolder: "./schemas", 4 | pathPattern: /\/vlcn-ws/, 5 | appName: "fix-me", 6 | notifyLatencyMs: 50, 7 | }); 8 | 9 | export type Config = Readonly<{ 10 | dbFolder: string | null; 11 | schemaFolder: string; 12 | pathPattern: RegExp; 13 | appName?: string; 14 | notifyLatencyMs?: number; 15 | notifyPat?: string; 16 | notifyPolling?: boolean; 17 | }>; 18 | -------------------------------------------------------------------------------- /packages/direct-connect-browser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true, 7 | "lib": ["esnext", "dom"] 8 | }, 9 | "include": ["./src/"], 10 | "references": [ 11 | { "path": "../rx-tbl" }, 12 | { "path": "../xplat-api" }, 13 | { "path": "../crsqlite-wasm" }, 14 | { "path": "../direct-connect-common" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/SplashScreenBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "components": { 6 | "alpha": "1.000", 7 | "blue": "1.00000000000000", 8 | "green": "1.00000000000000", 9 | "red": "1.00000000000000" 10 | }, 11 | "color-space": "srgb" 12 | }, 13 | "idiom": "universal" 14 | } 15 | ], 16 | "info": { 17 | "version": 1, 18 | "author": "expo" 19 | } 20 | } -------------------------------------------------------------------------------- /packages/crsqlite-wasm/src/ahp/worker/AhpConnection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a "connection" from a client tab/worker to the Ahp DB 3 | * 4 | * Hmm. we also have to solve prepared statement cleanup. 5 | * 6 | * Prepared statements will reside in the worker now. 7 | * 8 | * A tab could die before closing its prepared statements.. how might we know? 9 | * The message port we hold would be closed? In which case we can reclaim all statements? 10 | */ 11 | 12 | export default class AhpConnection { 13 | constructor() {} 14 | } 15 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/Images.xcassets/SplashScreenLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "image.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "image@2x.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "image@3x.png", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "version": 1, 21 | "author": "expo" 22 | } 23 | } -------------------------------------------------------------------------------- /packages/direct-connect-common/src/msg/BinarySerializer.ts: -------------------------------------------------------------------------------- 1 | import { ISerializer, Msg } from "../types.js"; 2 | import binEncode from "./binEncode.js"; 3 | import binDecode from "./binDecode.js"; 4 | 5 | export default class BinarySerializer implements ISerializer { 6 | get contentType(): "application/octet-stream" { 7 | return "application/octet-stream"; 8 | } 9 | 10 | encode(msg: Msg): Uint8Array { 11 | return binEncode(msg); 12 | } 13 | 14 | decode(msg: Uint8Array): Msg { 15 | return binDecode(msg); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/direct-connect-common/src/msg/SerializerFactory.ts: -------------------------------------------------------------------------------- 1 | import JsonSerializer from "./JsonSerializer.js"; 2 | import BinarySerializer from "./BinarySerializer.js"; 3 | 4 | export default { 5 | getSerializer( 6 | contentType: "application/json" | "application/octet-stream", 7 | args: any[] 8 | ) { 9 | switch (contentType) { 10 | case "application/json": 11 | return new JsonSerializer(...args); 12 | case "application/octet-stream": 13 | return new BinarySerializer(); 14 | } 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/private/__tests__/collapser.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, vi } from "vitest"; 2 | import { collect } from "../collapser"; 3 | 4 | test("many invocations are collapsed into a single invocation", () => { 5 | vi.useFakeTimers(); 6 | 7 | let count = 0; 8 | const fn = collect(100, (args) => { 9 | count++; 10 | expect(args).toEqual([1, 2, 3]); 11 | }); 12 | fn(1); 13 | fn(2); 14 | fn(3); 15 | expect(count).toBe(0); 16 | 17 | vi.runAllTimers(); 18 | expect(count).toBe(1); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/sandbox/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import { resolve } from "path"; 3 | import { defineConfig } from "vite"; 4 | 5 | export default defineConfig({ 6 | base: "./", 7 | build: { 8 | target: "esnext", 9 | rollupOptions: { 10 | input: { 11 | page1: resolve(__dirname, "direct-connect-browser.html"), 12 | page2: resolve(__dirname, "page2.html"), 13 | }, 14 | }, 15 | }, 16 | 17 | optimizeDeps: { 18 | esbuildOptions: { 19 | target: "esnext", 20 | }, 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/notes.md: -------------------------------------------------------------------------------- 1 | - DBs should auto-migrate to current schema version on open. 2 | - No external "migrate" command 3 | - A "set as current" command for a given schema version, however. 4 | 5 | - this'll migrate all currently open DBs and restart their connections 6 | - will migrate other dbs as they open 7 | 8 | - Get changes with a limit! 9 | 10 | - add artificial limit if one is not provided. 11 | - allow specifying upper version too so we can fetch gaps! 12 | 13 | - Live re-slurp on nextjs schema file changes! 14 | -------------------------------------------------------------------------------- /packages/react/src/context.ts: -------------------------------------------------------------------------------- 1 | import { TblRx } from "@vlcn.io-community/rx-tbl"; 2 | import { DBAsync, DB } from "@vlcn.io-community/xplat-api"; 3 | import { createContext, useContext } from "react"; 4 | 5 | export type CtxAsync = { 6 | readonly db: DBAsync; 7 | readonly rx: TblRx; 8 | }; 9 | 10 | export type Ctx = { 11 | readonly db: DB; 12 | readonly rx: TblRx; 13 | }; 14 | 15 | export function createReactContext() { 16 | return createContext(null); 17 | } 18 | 19 | export const VlcnAsyncCtx = createReactContext(); 20 | -------------------------------------------------------------------------------- /packages/ws-demo/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as http from "http"; 4 | import { attachWebsocketServer } from "@vlcn.io-community/ws-server"; 5 | import express from "express"; 6 | 7 | const port = process.env.PORT || 8080; 8 | 9 | const app = express(); 10 | const server = http.createServer(app); 11 | 12 | attachWebsocketServer(server, { 13 | dbFolder: "./dbs", 14 | schemaFolder: "./src/schemas", 15 | pathPattern: /\/sync/, 16 | }); 17 | 18 | server.listen(port, () => console.log("info", `listening on port ${port}!`)); 19 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /packages/ws-litefs/REAMDE.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/ws-litefs 2 | 3 | Alpha quality websocket support on LiteFS. 4 | 5 | If you're not do a streaming sync and syncing over `REST` you can simply use the LiteFS proxy: https://fly.io/docs/litefs/proxy/ 6 | 7 | WebSocket sync requires us to: 8 | 1. Do our own forwarding of writes 9 | 2. Handle getting change notifications to replicas 10 | 11 | This package accomplishes that although it currently does not handle the case where the primary fails over. This will be fixed up in `LiteFSWriteService` in the near future. -------------------------------------------------------------------------------- /packages/direct-connect-common/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./util.js"; 2 | export { default as jsonDecode } from "./msg/jsonDecode.js"; 3 | export { default as jsonEncode } from "./msg/jsonEncode.js"; 4 | export { default as JsonSerializer } from "./msg/JsonSerializer.js"; 5 | export { default as BinarySerializer } from "./msg/BinarySerializer.js"; 6 | export { default as SerializerFactory } from "./msg/SerializerFactory.js"; 7 | export * from "./types.js"; 8 | 9 | export const SCHEMA_NAME = "schema_name"; 10 | export const SCHEMA_VERSION = "schema_version"; 11 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/components/ThemedView.tsx: -------------------------------------------------------------------------------- 1 | import { View, type ViewProps } from 'react-native'; 2 | 3 | import { useThemeColor } from '@/hooks/useThemeColor'; 4 | 5 | export type ThemedViewProps = ViewProps & { 6 | lightColor?: string; 7 | darkColor?: string; 8 | }; 9 | 10 | export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) { 11 | const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background'); 12 | 13 | return ; 14 | } 15 | -------------------------------------------------------------------------------- /packages/ws-browserdb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["./src/"], 9 | "references": [ 10 | { 11 | "path": "../crsqlite-wasm" 12 | }, 13 | { 14 | "path": "../ws-client" 15 | }, 16 | { 17 | "path": "../ws-common" 18 | }, 19 | { 20 | "path": "../rx-tbl" 21 | }, 22 | { 23 | "path": "../xplat-api" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/sandbox-node/src/script2.js: -------------------------------------------------------------------------------- 1 | import SQLiteDB from "better-sqlite3"; 2 | 3 | // the crsqlite package exports the path to the extension 4 | import { extensionPath } from "@vlcn.io/crsqlite"; 5 | 6 | const db = new SQLiteDB("tst.db"); 7 | // suggested settings for best performance of sqlite 8 | db.pragma("journal_mode = WAL"); 9 | db.pragma("synchronous = NORMAL"); 10 | // load that extension with the `better-sqlite3` bindings 11 | db.loadExtension(extensionPath); 12 | 13 | console.log(db.prepare("SELECT * FROM items").all()); 14 | console.log("THIS IS NEVER REACHED!"); 15 | -------------------------------------------------------------------------------- /packages/bun-sqlite-lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ES2022", 5 | "moduleResolution": "node", 6 | "lib": ["ES2022"], 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true, 16 | "types": ["bun", "node"] 17 | }, 18 | "include": ["src/**/*"], 19 | "exclude": ["node_modules", "dist"] 20 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "0.0.0", 4 | "workspaces": [ 5 | "packages/*", 6 | "packages/@op-engineering/op-sqlite", 7 | "packages/@react-native/react-native", 8 | "tsbuild-all" 9 | ], 10 | "engines": { 11 | "node": ">=19" 12 | }, 13 | "dependencies": { 14 | "@changesets/cli": "^2.29.5", 15 | "@vlcn.io/crsqlite": "0.16.3" 16 | }, 17 | "name": "cr-sqlite-js", 18 | "module": "index.ts", 19 | "type": "module", 20 | "devDependencies": { 21 | "@types/bun": "^1.2.19" 22 | }, 23 | "peerDependencies": { 24 | "typescript": "^5.9.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/ws-client/src/worker/worker.ts: -------------------------------------------------------------------------------- 1 | import { Msg } from "./workerMsgTypes.js"; 2 | import SyncService from "./SyncService.js"; 3 | import { Config } from "../config.js"; 4 | 5 | export function start(config: Config) { 6 | const svc = new SyncService(config); 7 | self.onmessage = (e: MessageEvent) => { 8 | const msg = e.data; 9 | 10 | switch (msg._tag) { 11 | case "StartSync": { 12 | svc.startSync(msg); 13 | break; 14 | } 15 | case "StopSync": { 16 | svc.stopSync(msg); 17 | break; 18 | } 19 | } 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/ws-demo/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 2em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | 15 | .logo:hover { 16 | filter: drop-shadow(0 0 2em #646cffaa); 17 | } 18 | .logo.react:hover { 19 | filter: drop-shadow(0 0 2em #61dafbaa); 20 | } 21 | .logo.vlcn:hover { 22 | filter: drop-shadow(0 0 2em #e3eaef); 23 | } 24 | 25 | .card { 26 | padding: 2em; 27 | } 28 | 29 | .read-the-docs { 30 | color: #888; 31 | } 32 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/Types.ts: -------------------------------------------------------------------------------- 1 | export type Config = { 2 | /** 3 | * Service name is available in case you host many different sync services. 4 | * Maybe you have several where each get their own schema and db dirs. 5 | */ 6 | readonly serviceName: string; 7 | /** 8 | * Where SQLite databases should be created and persisted. 9 | */ 10 | readonly dbsDir: string; 11 | readonly cacheTtlInSeconds: number; 12 | readonly notifyLatencyInMs: number; 13 | readonly serviceDbPath: string; 14 | readonly msgContentType: "application/json" | "application/octet-stream"; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/browser-tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "skipLibCheck": true, 17 | "types": ["cypress", "node", "chai", "mocha"] 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/direct-connect-common/src/util.ts: -------------------------------------------------------------------------------- 1 | export function hexToBytes(hex: string) { 2 | const ret = new Uint8Array(hex.length / 2); 3 | for (let c = 0; c < hex.length; c += 2) { 4 | ret[c / 2] = parseInt(hex.substring(c, c + 2), 16); 5 | } 6 | return ret; 7 | } 8 | 9 | export function bytesToHex(bytes: Uint8Array) { 10 | let hex: string[] = []; 11 | for (let i = 0; i < bytes.length; i++) { 12 | let current = bytes[i] < 0 ? bytes[i] + 256 : bytes[i]; 13 | hex.push((current >>> 4).toString(16)); 14 | hex.push((current & 0xf).toString(16)); 15 | } 16 | return hex.join(""); 17 | } 18 | -------------------------------------------------------------------------------- /packages/node-tests/src/simple.ts: -------------------------------------------------------------------------------- 1 | // this script exists to allow us to run `lldb` against arbitrary code 2 | // loaded into node. 3 | // clobber below with whatever you need to debug today. 4 | 5 | import crsqlite from "@vlcn.io-community/crsqlite-allinone"; 6 | 7 | const db = crsqlite.open(); 8 | db.exec(`CREATE TABLE IF NOT EXISTS data (id NUMBER PRIMARY KEY not null)`); 9 | db.exec(`SELECT crsql_as_crr('data')`); 10 | db.exec(`INSERT INTO data VALUES (42) ON CONFLICT DO NOTHING`); 11 | console.log(db.prepare(`SELECT * FROM data`).all()); 12 | console.log(db.prepare(`SELECT * FROM crsql_changes`).all()); 13 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/hooks/useColorScheme.web.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useColorScheme as useRNColorScheme } from 'react-native'; 3 | 4 | /** 5 | * To support static rendering, this value needs to be re-calculated on the client side for web 6 | */ 7 | export function useColorScheme() { 8 | const [hasHydrated, setHasHydrated] = useState(false); 9 | 10 | useEffect(() => { 11 | setHasHydrated(true); 12 | }, []); 13 | 14 | const colorScheme = useRNColorScheme(); 15 | 16 | if (hasHydrated) { 17 | return colorScheme; 18 | } 19 | 20 | return 'light'; 21 | } 22 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | # Native 13 | .kotlin/ 14 | *.orig.* 15 | *.jks 16 | *.p8 17 | *.p12 18 | *.key 19 | *.mobileprovision 20 | 21 | # Metro 22 | .metro-health-check* 23 | 24 | # debug 25 | npm-debug.* 26 | yarn-debug.* 27 | yarn-error.* 28 | 29 | # macOS 30 | .DS_Store 31 | *.pem 32 | 33 | # local env files 34 | .env*.local 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | 39 | app-example 40 | -------------------------------------------------------------------------------- /packages/ws-litefs/src/internal/__tests__/PrimarySocket.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | 3 | test("send create db does not resolve until receiving a response", () => {}); 4 | 5 | test("we throw when receiving an unknown reqid", () => {}); 6 | 7 | test("repsonses are correctly paired with requests", () => {}); 8 | 9 | test("pending requests are removed when receiving a response", () => {}); 10 | 11 | test("pending requests are removed on error", () => {}); 12 | 13 | test("pending requests are removed on close", () => {}); 14 | 15 | test("socket closed after 5-6 seconds of missed heartbeats", () => {}); 16 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/component/int.cy.ts: -------------------------------------------------------------------------------- 1 | import { intTests } from "@vlcn.io-community/xplat-tests"; 2 | import sqliteWasm from "@vlcn.io-community/crsqlite-wasm"; 3 | // @ts-ignore 4 | import wasmUrl from "@vlcn.io-community/crsqlite-wasm/crsqlite.wasm?url"; 5 | 6 | const crsqlite = await sqliteWasm((file) => wasmUrl); 7 | 8 | describe("WholeDbReplicator.cy.ts", () => { 9 | Object.entries(intTests).map((x) => { 10 | it(x[0], () => { 11 | const tc = x[1]; 12 | return tc( 13 | () => crsqlite.open(), 14 | (p: boolean) => expect(p).to.equal(true) 15 | ); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/component/tblrx.cy.ts: -------------------------------------------------------------------------------- 1 | import { tblrxTests } from "@vlcn.io-community/xplat-tests"; 2 | import sqliteWasm from "@vlcn.io-community/crsqlite-wasm"; 3 | 4 | // @ts-ignore 5 | import wasmUrl from "@vlcn.io-community/crsqlite-wasm/crsqlite.wasm?url"; 6 | 7 | const crsqlite = await sqliteWasm(() => wasmUrl); 8 | 9 | describe("tblrx.cy.ts", () => { 10 | Object.entries(tblrxTests).forEach((x) => { 11 | it(x[0], () => { 12 | const tc = x[1]; 13 | return tc( 14 | () => crsqlite.open(), 15 | (p: boolean) => expect(p).to.equal(true) 16 | ); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/rx-query/src/QueryAST.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | export type QueryAST = {}; 5 | 6 | export function queryToAST(query: string): QueryAST { 7 | return {}; 8 | } 9 | 10 | export function astToQuery(ast: QueryAST): string { 11 | return ""; 12 | } 13 | 14 | /** 15 | * - Coalescing duplicate queries into 1 16 | * - Adding a query cache 17 | * - Collecting all read queries across many micro-tasks into the same 18 | * indexeddb read transaction 19 | * - Folding many calls to the same `useQuery` into a single query against the DB 20 | * 21 | * To realize implementing a fully reactive query system is actually 22 | * not that hard. 23 | */ 24 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/component/WholeDbReplicator.cy.ts: -------------------------------------------------------------------------------- 1 | import { wdbTests } from "@vlcn.io-community/xplat-tests"; 2 | import sqliteWasm from "@vlcn.io-community/crsqlite-wasm"; 3 | 4 | // @ts-ignore 5 | import wasmUrl from "@vlcn.io-community/crsqlite-wasm/crsqlite.wasm?url"; 6 | 7 | const crsqlite = await sqliteWasm((file) => wasmUrl); 8 | 9 | describe("WholeDbReplicator.cy.ts", () => { 10 | Object.entries(wdbTests).forEach((x) => { 11 | it(x[0], () => { 12 | const tc = x[1]; 13 | return tc( 14 | () => crsqlite.open(), 15 | (p: boolean) => expect(p).to.equal(true) 16 | ); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/hooks/useThemeColor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Learn more about light and dark modes: 3 | * https://docs.expo.dev/guides/color-schemes/ 4 | */ 5 | 6 | import { Colors } from '@/constants/Colors'; 7 | import { useColorScheme } from '@/hooks/useColorScheme'; 8 | 9 | export function useThemeColor( 10 | props: { light?: string; dark?: string }, 11 | colorName: keyof typeof Colors.light & keyof typeof Colors.dark 12 | ) { 13 | const theme = useColorScheme() ?? 'light'; 14 | const colorFromProps = props[theme]; 15 | 16 | if (colorFromProps) { 17 | return colorFromProps; 18 | } else { 19 | return Colors[theme][colorName]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/libsqlite3-darwin-arm64/LICENSE.sqlite: -------------------------------------------------------------------------------- 1 | SQLite is in the Public Domain 2 | 3 | All of the code and documentation in SQLite has been dedicated to the public domain 4 | by the authors. All code authors, and representatives of the companies they work for, 5 | have signed affidavits dedicating their contributions to the public domain and 6 | originals of those signed affidavits are stored in a firesafe at the main offices 7 | of Hwaci. Anyone is free to copy, modify, publish, use, compile, sell, or distribute 8 | the original SQLite code, either in source code form or as a compiled binary, for 9 | any purpose, commercial or non-commercial, and by any means. 10 | -------------------------------------------------------------------------------- /packages/ws-server/notes.md: -------------------------------------------------------------------------------- 1 | Additional prep for litefs: 2 | 3 | - Need a DB implementation for LiteFS 4 | - On write request, check if we're primary 5 | - If primary, check if write stmts are prepred 6 | - If not, prep them 7 | - If so, ok 8 | --- no need to check, we can prepare write statements on readonly db. Just can't run them. Woo. 9 | - If not primary, read `.primary` 10 | - forward write to endpoint on server that'll accept it 11 | 12 | https://github.com/vlcn-io/js/blob/322daec57acd2a75cf209718e4ac9f083d985370/js/packages 13 | https://github.com/vlcn-io/js/tree/4e737a078cb1f3226d35e163b77bd3ea56926be9/js/packages/client-websocket 14 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/components/HapticTab.tsx: -------------------------------------------------------------------------------- 1 | import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs'; 2 | import { PlatformPressable } from '@react-navigation/elements'; 3 | import * as Haptics from 'expo-haptics'; 4 | 5 | export function HapticTab(props: BottomTabBarButtonProps) { 6 | return ( 7 | { 10 | if (process.env.EXPO_OS === 'ios') { 11 | // Add a soft haptic feedback when pressing down on the tabs. 12 | Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); 13 | } 14 | props.onPressIn?.(ev); 15 | }} 16 | /> 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/components/ui/TabBarBackground.ios.tsx: -------------------------------------------------------------------------------- 1 | import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; 2 | import { BlurView } from 'expo-blur'; 3 | import { StyleSheet } from 'react-native'; 4 | 5 | export default function BlurTabBarBackground() { 6 | return ( 7 | 14 | ); 15 | } 16 | 17 | export function useBottomTabOverflow() { 18 | return useBottomTabBarHeight(); 19 | } 20 | -------------------------------------------------------------------------------- /packages/browser-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/browser-tests", 3 | "private": true, 4 | "version": "0.14.0", 5 | "type": "module", 6 | "scripts": { 7 | "test": "bun cypress run --component" 8 | }, 9 | "dependencies": { 10 | "@vlcn.io-community/crsqlite-wasm": "workspace:*", 11 | "@vlcn.io-community/xplat-tests": "workspace:*" 12 | }, 13 | "devDependencies": { 14 | "@types/chai": "^5.2.2", 15 | "@types/mocha": "^10.0.10", 16 | "@types/node": "^24.2.1", 17 | "cypress": "^13.15.2", 18 | "react": "^18.3.1", 19 | "react-dom": "^18.3.1", 20 | "typescript": "^5.9.2", 21 | "vite": "^5.4.11" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/src/ahp/worker/ahp-worker.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * - We have a single connection to the DB 3 | * - Many tabs may have "connections" to multiplex onto this single connection 4 | * - We need a connection abstraction to manage these multiplexed connections such that we can isolate them 5 | * 6 | * Do we need connection isolation or just tx isolation? 7 | * 8 | * We could dump the connection abstraction if we provide tx abstraction. 9 | * 10 | * tx is open then we need to acquire a tx lock. 11 | * But tab could get killed while holding the tx lock... so have a timeout? 12 | * or use navigator locks? What is the perf hit of navigator locks? 13 | * 14 | * 15 | */ 16 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/TODO.md: -------------------------------------------------------------------------------- 1 | # demos/expo-crsql-demo/TODO.md 2 | 3 | - [x] create expo app 4 | 5 | - [ ] xplat db thing 6 | 7 | - [ ] db demo screen 8 | - [ ] cr-sqlite expo client web 9 | - [ ] cr-sqlite expo client iOS 10 | - [ ] cr-sqlite expo client Android 11 | - [ ] cr-sqlite expo server bun 12 | 13 | - [ ] add expo-updates 14 | - [ ] fix the @op-engineering/op-sqlite expo-updates compat issue https://op-engineering.github.io/op-sqlite/docs/installation#expo-updates https://chatgpt.com/c/6897a42a-c0cc-832d-9bd0-649bdc092b2f 15 | 16 | - [x] expo web build 17 | - [x] server build 18 | - [x] Dockerfile 19 | - [ ] Kamal config 20 | - [ ] deploy expo web app to production 21 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { APITester } from "./APITester"; 2 | import "./index.css"; 3 | 4 | import logo from "./logo.svg"; 5 | import reactLogo from "./react.svg"; 6 | 7 | export function App() { 8 | return ( 9 |
10 |
11 | Bun Logo 12 | React Logo 13 |
14 | 15 |

Bun + React

16 |

17 | Edit src/App.tsx and save to test HMR 18 |

19 | 20 |
21 | ); 22 | } 23 | 24 | export default App; 25 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | 3 | # Project-specific environment variables 4 | export PROJECT_ROOT="$PWD" 5 | 6 | # Node.js optimizations for monorepo builds 7 | export NODE_OPTIONS="--max-old-space-size=4096" 8 | 9 | # Add local node_modules/.bin to PATH 10 | PATH_add "$PWD/node_modules/.bin" 11 | PATH_add "$PWD/packages/*/node_modules/.bin" 12 | 13 | # Cypress configuration 14 | export CYPRESS_INSTALL_BINARY=0 15 | 16 | # Build cache directory 17 | export BUILD_CACHE_DIR="$PWD/.cache" 18 | 19 | # Enable colorized output 20 | export FORCE_COLOR=1 21 | 22 | # Rust toolchain 23 | export RUSTFLAGS="-C target-cpu=native" 24 | 25 | # Watch for changes to reload 26 | watch_file flake.nix 27 | watch_file flake.lock -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DBSyncService } from "./DBSyncService.js"; 2 | export { default as SyncService } from "./SyncService.js"; 3 | export * from "./Types.js"; 4 | export { default as DefaultConfig } from "./config/DefaultConfig.js"; 5 | export type { Config } from "./Types.js"; 6 | export { default as ServiceDB } from "./private/ServiceDB.js"; 7 | export { default as FSNotify } from "./private/FSNotify.js"; 8 | export { default as DBCache } from "./private/DBCache.js"; 9 | export type { SchemaRow } from "./private/ServiceDB.js"; 10 | export { JsonSerializer } from "@vlcn.io-community/direct-connect-common"; 11 | export { cryb64 } from "@vlcn.io-community/xplat-api"; 12 | -------------------------------------------------------------------------------- /packages/ws-server/src/fs/collapser.ts: -------------------------------------------------------------------------------- 1 | // Collapses repeated invocations to a method over some time, gathering all provided args into a single invocation. 2 | import { throttle } from "throttle-debounce"; 3 | 4 | export function collect(ms: number, fn: (a: T[]) => void): (a: T) => void { 5 | let args: T[] = []; 6 | const throttled = throttle( 7 | ms, 8 | () => { 9 | // re-assign args prior to invocation in case fn calls the throttled fn again. 10 | const x = args; 11 | args = []; 12 | fn(x); 13 | }, 14 | { 15 | noLeading: true, 16 | noTrailing: false, 17 | } 18 | ); 19 | return (a: T) => { 20 | args.push(a); 21 | throttled(); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /packages/sandbox/src/main.tsx: -------------------------------------------------------------------------------- 1 | import initWasm from "@vlcn.io-community/crsqlite-wasm"; 2 | import wasmUrl from "@vlcn.io-community/crsqlite-wasm/crsqlite.wasm?url"; 3 | 4 | const sqlite = await initWasm(() => wasmUrl); 5 | const db = await sqlite.open(":memory:"); 6 | 7 | await db.exec("CREATE TABLE foo (a primary key not null, b);"); 8 | 9 | db.onUpdate(() => { 10 | console.log("received update callback!"); 11 | }); 12 | 13 | try { 14 | await db.tx(async (tx) => { 15 | console.log("insert 1"); 16 | await tx.exec("INSERT INTO foo (1, 2);"); 17 | console.log("insert 2"); 18 | await tx.exec("INSERT INTO foo (2, 3);"); 19 | }); 20 | } catch (e) { 21 | console.log("wtf"); 22 | console.log(e); 23 | } 24 | -------------------------------------------------------------------------------- /packages/ws-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 5 | "module": "ESNext", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /packages/ws-litefs/src/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from "winston"; 2 | 3 | const logger = winston.createLogger({ 4 | level: "info", 5 | format: winston.format.json(), 6 | defaultMeta: { service: "ws-litefs" }, 7 | transports: [ 8 | // 9 | // - Write all logs with importance level of `error` or less to `error.log` 10 | // - Write all logs with importance level of `info` or less to `combined.log` 11 | // 12 | // new winston.transports.File({ filename: 'error.log', level: 'error' }), 13 | // new winston.transports.File({ filename: 'combined.log' }), 14 | new winston.transports.Console({ 15 | format: winston.format.simple(), 16 | }), 17 | ], 18 | }); 19 | 20 | export default logger; 21 | -------------------------------------------------------------------------------- /packages/ws-server/src/DBFactory.ts: -------------------------------------------------------------------------------- 1 | import DB, { IDB } from "./DB.js"; 2 | import { Config } from "./config.js"; 3 | import FSNotify from "./fs/FSNotify.js"; 4 | 5 | export interface IDBFactory { 6 | createDB( 7 | config: Config, 8 | fsnotify: FSNotify | null, 9 | room: string, 10 | schemaName: string, 11 | schemaVersion: bigint 12 | ): Promise; 13 | } 14 | 15 | export default class DBFactory implements IDBFactory { 16 | async createDB( 17 | config: Config, 18 | fsnotify: FSNotify | null, 19 | room: string, 20 | schemaName: string, 21 | schemaVersion: bigint 22 | ): Promise { 23 | return new DB(config, fsnotify, room, schemaName, schemaVersion); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/crsqlite-wasm 2 | 3 | WASM build of `sqlite` that can: 4 | 5 | - run without COEP headers 6 | - run in SharedWorkers 7 | - run concurrently in many tabs 8 | - run in the UI thread if desired 9 | - includes the `crsqlite` extension. 10 | 11 | Builds upon https://github.com/rhashimoto/wa-sqlite/. The only delta is that we add our extension at build time and expose a few extra sqlite methods. 12 | 13 | # Examples 14 | 15 | - [Observable Notebook](https://observablehq.com/@tantaman/cr-sqlite-basic-setup) 16 | - [Working TODO MVC](https://github.com/vlcn-io/js/tree/main/js/examples/p2p-todomvc) 17 | - [WIP Local-First Presentation Editor](https://github.com/tantaman/strut) 18 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/private/collapser.ts: -------------------------------------------------------------------------------- 1 | // Collapses repeated invocations to a method over some time, gathering all provided args into a single invocation. 2 | import { throttle } from "throttle-debounce"; 3 | 4 | export function collect(ms: number, fn: (a: T[]) => void): (a: T) => void { 5 | let args: T[] = []; 6 | const throttled = throttle( 7 | ms, 8 | () => { 9 | // re-assign args prior to invocation in case fn calls the throttled fn again. 10 | const x = args; 11 | args = []; 12 | fn(x); 13 | }, 14 | { 15 | noLeading: true, 16 | noTrailing: false, 17 | } 18 | ); 19 | return (a: T) => { 20 | args.push(a); 21 | throttled(); 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/logger.ts: -------------------------------------------------------------------------------- 1 | import winston from "winston"; 2 | 3 | const logger = winston.createLogger({ 4 | level: "info", 5 | format: winston.format.json(), 6 | defaultMeta: { service: "dc-nodejs" }, 7 | transports: [ 8 | // 9 | // - Write all logs with importance level of `error` or less to `error.log` 10 | // - Write all logs with importance level of `info` or less to `combined.log` 11 | // 12 | // new winston.transports.File({ filename: 'error.log', level: 'error' }), 13 | // new winston.transports.File({ filename: 'combined.log' }), 14 | new winston.transports.Console({ 15 | format: winston.format.simple(), 16 | }), 17 | ], 18 | }); 19 | 20 | export default logger; 21 | -------------------------------------------------------------------------------- /packages/sandbox/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", // path to output directory 4 | "sourceMap": true, // allow sourcemap support 5 | "strictNullChecks": true, // enable strict null checks as a best practice 6 | "module": "esnext", // specify module code generation 7 | "target": "esnext", // specify ECMAScript target version 8 | "moduleResolution": "node", 9 | "rootDir": "./", 10 | "allowJs": true, 11 | "jsx": "react", 12 | "strict": true, 13 | "skipLibCheck": true, 14 | "allowSyntheticDefaultImports": true, 15 | "importHelpers": true, 16 | "resolveJsonModule": true 17 | }, 18 | "files": ["./src/global.d.ts"], 19 | "include": ["./src/"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/ws-server/src/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import { parseSecHeader } from ".."; 3 | 4 | test("pullSecHeaders", () => { 5 | function encode(options: { authToken?: string; room: string }) { 6 | return btoa( 7 | `${options.authToken != null ? `auth=${options.authToken},` : ""}room=${ 8 | options.room 9 | }` 10 | ).replaceAll("=", ""); 11 | } 12 | 13 | let parsed = parseSecHeader(encode({ room: "test" })); 14 | expect(parsed.room).toBe("test"); 15 | expect(parsed.auth).toBeUndefined(); 16 | 17 | parsed = parseSecHeader(encode({ room: "test", authToken: "123" })); 18 | expect(parsed.room).toBe("test"); 19 | expect(parsed.auth).toBe("123"); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/@react-native/react-native/README.md: -------------------------------------------------------------------------------- 1 | # Fake react-native Package 2 | 3 | This is a fake package to block react-native from being hoisted to the root of the monorepo. 4 | 5 | ## Purpose 6 | 7 | In monorepo setups, package managers may hoist dependencies to the root `node_modules` for deduplication. This can cause issues with React Native and Expo projects that expect react-native to be in their local `node_modules`. 8 | 9 | By creating this fake package at the workspace root, we prevent the real react-native from being hoisted, ensuring it stays within the individual project directories where it's needed. 10 | 11 | ## Do Not Use 12 | 13 | This package should never be imported or used directly. It exists solely for dependency management purposes. -------------------------------------------------------------------------------- /packages/node-allinone/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/crsqlite-allinone 2 | 3 | A package for node & deno that provides a `sqlite3` library in addition to the `crsqlite` extension. It also exposes the same interface to node/deno that is exposed to the browser via the `@vlcn.io-community/crsqlite-wasm` package, allowing you to share abstractions between the client and server. 4 | 5 | If you are already using a `sqlite3` package (e.g., `better-sqlite3`) you can use `@vlcn.io/crsqlite` and load it as shared a module via `.loadModule`. 6 | 7 | Example: 8 | 9 | ``` 10 | import SQLiteDB from "better-sqlite3"; 11 | import { extensionPath } from "@vlcn.io/crsqlite"; 12 | 13 | const db = new SQLiteDB(dbPath); 14 | db.loadExtension(extensionPath); 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/direct-connect-common/src/msg/__tests__/sandbox.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import BinarySerializer from "../BinarySerializer"; 3 | import { StreamingChangesMsg, tags } from "../../types"; 4 | import JsonSerializer from "../JsonSerializer"; 5 | 6 | test("sandbox", () => { 7 | const msg = { 8 | _tag: tags.streamingChanges, 9 | seqStart: [0n, 0], 10 | seqEnd: [0n, 0], 11 | changes: [ 12 | ["", Uint8Array.from([0]), "", Uint8Array.from([1, 1, 1]), 0n, 0n, 1n, 0], 13 | ], 14 | } as const; 15 | const s = new JsonSerializer(); 16 | 17 | const encoded = JSON.stringify(s.encode(msg)); 18 | const decoded = s.decode(JSON.parse(encoded)); 19 | console.log(encoded); 20 | console.log(decoded); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/libsqlite3-darwin-x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/libsqlite3-darwin-x64", 3 | "version": "3.47.2", 4 | "description": "Prebuilt SQLite 3.47.2 with extension loading enabled for macOS x64", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/vlcn-io/cr-sqlite-js.git", 8 | "directory": "packages/libsqlite3-darwin-x64" 9 | }, 10 | "license": "MIT", 11 | "os": ["darwin"], 12 | "cpu": ["x64"], 13 | "files": [ 14 | "vendor/", 15 | "LICENSE.sqlite" 16 | ], 17 | "keywords": [ 18 | "sqlite", 19 | "sqlite3", 20 | "bun", 21 | "database", 22 | "extension", 23 | "dylib" 24 | ], 25 | "scripts": { 26 | "test": "echo 'Platform package - no tests needed'" 27 | } 28 | } -------------------------------------------------------------------------------- /packages/libsqlite3-darwin-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/libsqlite3-darwin-arm64", 3 | "version": "3.47.2", 4 | "description": "Prebuilt SQLite 3.47.2 with extension loading enabled for macOS ARM64", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/vlcn-io/cr-sqlite-js.git", 8 | "directory": "packages/libsqlite3-darwin-arm64" 9 | }, 10 | "license": "MIT", 11 | "os": ["darwin"], 12 | "cpu": ["arm64"], 13 | "files": [ 14 | "vendor/", 15 | "LICENSE.sqlite" 16 | ], 17 | "keywords": [ 18 | "sqlite", 19 | "sqlite3", 20 | "bun", 21 | "database", 22 | "extension", 23 | "dylib" 24 | ], 25 | "scripts": { 26 | "test": "echo 'Platform package - no tests needed'" 27 | } 28 | } -------------------------------------------------------------------------------- /demos/expo-crsql-demo/components/ui/IconSymbol.ios.tsx: -------------------------------------------------------------------------------- 1 | import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols'; 2 | import { StyleProp, ViewStyle } from 'react-native'; 3 | 4 | export function IconSymbol({ 5 | name, 6 | size = 24, 7 | color, 8 | style, 9 | weight = 'regular', 10 | }: { 11 | name: SymbolViewProps['name']; 12 | size?: number; 13 | color: string; 14 | style?: StyleProp; 15 | weight?: SymbolWeight; 16 | }) { 17 | return ( 18 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/src/cache.ts: -------------------------------------------------------------------------------- 1 | import log from "./log.js"; 2 | 3 | const re = /insert\s|update\s|delete\s/; 4 | const txRe = /begin\s|commit\s|rollback\s|savepoint\s/; 5 | 6 | export function computeCacheKey( 7 | sql: string, 8 | mode: "o" | "a", 9 | bind?: SQLiteCompatibleType[] 10 | ) { 11 | const lower = sql.toLowerCase(); 12 | 13 | if (txRe.exec(lower) != null) { 14 | return undefined; 15 | } 16 | 17 | // is it a write? 18 | if (re.exec(lower) != null) { 19 | log("received write"); 20 | return null; 21 | } 22 | 23 | if (bind != null) { 24 | const ret = 25 | lower + 26 | "|" + 27 | mode + 28 | "|" + 29 | bind.map((b) => (b != null ? b.toString() : "null")).join("|"); 30 | return ret; 31 | } 32 | return lower; 33 | } 34 | -------------------------------------------------------------------------------- /packages/logger-provider/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/logger-provider", 3 | "type": "module", 4 | "version": "0.2.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "scripts": { 12 | "build": "tsc --build", 13 | "watch": "tsc --build --watch", 14 | "test": "vitest run", 15 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git@github.com:vlcn-io/js.git", 20 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/logger-provider" 21 | }, 22 | "dependencies": { 23 | "winston": "^3.10.0" 24 | }, 25 | "devDependencies": { 26 | "vitest": "^3.2.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/id/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/id", 3 | "type": "module", 4 | "version": "0.1.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "main": "dist/index.js", 10 | "module": "dist/index.js", 11 | "sideEffects": false, 12 | "devDependencies": { 13 | "vitest": "^0.30.1" 14 | }, 15 | "scripts": { 16 | "build": "tsc --build", 17 | "watch": "tsc --build --watch", 18 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true", 19 | "test": "vitest run" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:vlcn-io/js.git", 24 | "directory": "https://github.com/vlcn-io/js/tree/main/js/sid" 25 | }, 26 | "dependencies": {}, 27 | "peerDependencies": { 28 | "react": "^18" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/rx-tbl/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/rx-tbl 2 | 3 | Simple "table based" reactivity. 4 | 5 | I.e., get notified whenever a table has rows inserted/deleted/updated. 6 | 7 | Future `rx` packages will improve this to bring ractivity to the query level. I.e., only react when the specific rows used by a query have changed. 8 | 9 | # Usage: 10 | 11 | ``` 12 | import tblrx from "@vlcn.io-community/rx-tbl"; 13 | 14 | // db is a handle to the database. Implements `@vlcn.io-community/xplat-api` 15 | // E.g., `@vlcn.io-community/crsqlite-wasm` or `@vlcn.io-community/crsqlite-allinone`. 16 | const rx = tblrx(db); 17 | 18 | const disposer = rx.on((modifiedTables: Set) => { 19 | // do whatever you need to do 20 | }); 21 | 22 | // when you need to release your subscription 23 | disposer(); 24 | ``` 25 | -------------------------------------------------------------------------------- /packages/rx-tbl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/rx-tbl", 3 | "type": "module", 4 | "version": "0.15.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "devDependencies": { 12 | "typescript": "^5.0.4", 13 | "vitest": "^0.30.1" 14 | }, 15 | "scripts": { 16 | "tsc": "tsc --build", 17 | "watch": "tsc --watch", 18 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true", 19 | "test": "vitest run" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:vlcn-io/js.git", 24 | "directory": "https://github.com/vlcn-io/js/tree/main/js/rx-tbl" 25 | }, 26 | "dependencies": { 27 | "@vlcn.io-community/xplat-api": "workspace:*" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/react/src/db/useSync.ts: -------------------------------------------------------------------------------- 1 | import { WorkerInterface } from "@vlcn.io-community/ws-client"; 2 | import { useEffect } from "react"; 3 | 4 | // hook to start syncing a DB 5 | export type Options = { 6 | dbname: string; 7 | endpoint: string; 8 | room: string; 9 | worker: Worker; 10 | authToken?: string; 11 | }; 12 | 13 | export default function useSync({ 14 | worker, 15 | dbname, 16 | room, 17 | endpoint, 18 | authToken, 19 | }: Options) { 20 | useEffect(() => { 21 | const workerInterface = new WorkerInterface(worker); 22 | 23 | workerInterface.startSync(dbname, { 24 | room: room, 25 | url: endpoint, 26 | authToken: authToken, 27 | }); 28 | return () => { 29 | workerInterface.stopSync(dbname); 30 | }; 31 | }, [dbname, room, endpoint, worker]); 32 | } 33 | -------------------------------------------------------------------------------- /packages/rx-query/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/rx-query", 3 | "type": "module", 4 | "version": "0.8.0", 5 | "files": [ 6 | "dist" 7 | ], 8 | "module": "dist/index.js", 9 | "main": "dist/index.js", 10 | "devDependencies": { 11 | "typescript": "^5.0.4", 12 | "vitest": "^0.30.1" 13 | }, 14 | "scripts": { 15 | "tsc": "tsc --build", 16 | "watch": "tsc --watch", 17 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true", 18 | "test": "echo todo" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:vlcn-io/crsqlite-js.git", 23 | "directory": "https://github.com/vlcn-io/crsqlite-js/tree/main/js/rx-query" 24 | }, 25 | "dependencies": { 26 | "@vlcn.io-community/xplat-api": "workspace:*" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/ws-client/src/config.ts: -------------------------------------------------------------------------------- 1 | import { Transport } from "./transport/Transport.js"; 2 | import { DB } from "./DB.js"; 3 | import { TransporOptions } from "./transport/Transport.js"; 4 | import WebSocketTransport from "./transport/WebSocketTransport.js"; 5 | 6 | export type Config = { 7 | dbProvider: (dbname: string) => PromiseLike; 8 | transportProvider: (transportOpts: TransporOptions) => Transport; 9 | }; 10 | 11 | export const defaultConfig: Config = Object.freeze({ 12 | dbProvider: (dbname: string): PromiseLike => { 13 | throw new Error( 14 | "You must configure a db provider. `config.dbProvider = yourProvider;`" 15 | ); 16 | }, 17 | 18 | transportProvider: (transportOpts: TransporOptions): Transport => { 19 | return new WebSocketTransport(transportOpts); 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/src/frontend.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is the entry point for the React app, it sets up the root 3 | * element and renders the App component to the DOM. 4 | * 5 | * It is included in `src/index.html`. 6 | */ 7 | 8 | import { StrictMode } from "react"; 9 | import { createRoot } from "react-dom/client"; 10 | import { App } from "./App"; 11 | 12 | const elem = document.getElementById("root")!; 13 | const app = ( 14 | 15 | 16 | 17 | ); 18 | 19 | if (import.meta.hot) { 20 | // With hot module reloading, `import.meta.hot.data` is persisted. 21 | const root = (import.meta.hot.data.root ??= createRoot(elem)); 22 | root.render(app); 23 | } else { 24 | // The hot module reloading API is not available in production. 25 | createRoot(elem).render(app); 26 | } 27 | -------------------------------------------------------------------------------- /packages/xplat-tests/src/automigrate.test.ts: -------------------------------------------------------------------------------- 1 | import { DBAsync } from "@vlcn.io-community/xplat-api"; 2 | type DB = DBAsync; 3 | 4 | // TODO: not xplat -- browser specific due to extra finalizae in automigrate 5 | export const tests = { 6 | "can automigrate an empty db": async ( 7 | dbProvider: () => Promise, 8 | assert: (p: boolean) => void 9 | ) => { 10 | const db = await dbProvider(); 11 | try { 12 | db.tx(async (tx) => { 13 | await tx.exec( 14 | `SELECT crsql_automigrate(?, 'SELECT crsql_finalize()')`, 15 | [ 16 | `CREATE TABLE IF NOT EXISTS test (id PRIMARY KEY not null, name TEXT); 17 | SELECT crsql_as_crr('test'); 18 | `, 19 | ] 20 | ); 21 | }); 22 | } catch (e) { 23 | assert(false); 24 | } 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/direct-connect-common/src/msg/JsonSerializer.ts: -------------------------------------------------------------------------------- 1 | import jsonEncode from "./jsonEncode.js"; 2 | import jsonDecode from "./jsonDecode.js"; 3 | import { ISerializer, Msg } from "../types.js"; 4 | 5 | export default class JsonSerializer implements ISerializer { 6 | constructor( 7 | private readonly stringify: boolean = false, 8 | private readonly parse: boolean = false 9 | ) {} 10 | 11 | get contentType(): "application/json" { 12 | return "application/json"; 13 | } 14 | 15 | encode(msg: Msg): any { 16 | const encoded = jsonEncode(msg); 17 | if (this.stringify) { 18 | return JSON.stringify(encoded); 19 | } 20 | return encoded; 21 | } 22 | 23 | decode(msg: any): Msg { 24 | if (this.parse) { 25 | msg = JSON.parse(msg); 26 | } 27 | return jsonDecode(msg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/xplat-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/xplat-api", 3 | "type": "module", 4 | "version": "0.15.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/xplat-api.js", 10 | "main": "dist/xplat-api.js", 11 | "sideEffects": false, 12 | "devDependencies": { 13 | "typescript": "^5.0.4", 14 | "vitest": "^0.30.1" 15 | }, 16 | "scripts": { 17 | "build": "tsc --build", 18 | "watch": "tsc --build --watch", 19 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true", 20 | "test": "vitest run" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git@github.com:vlcn-io/js.git", 25 | "directory": "https://github.com/vlcn-io/js/tree/main/js/xplat-api" 26 | }, 27 | "dependencies": { 28 | "comlink": "^4.4.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/ws-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/ws-common", 3 | "type": "module", 4 | "version": "0.2.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "scripts": { 12 | "build": "tsc --build", 13 | "watch": "tsc --build --watch", 14 | "test": "vitest run", 15 | "rmtdata": "rm dbs-test/*", 16 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git@github.com:vlcn-io/js.git", 21 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/ws-client" 22 | }, 23 | "dependencies": { 24 | "lib0": "^0.2.73" 25 | }, 26 | "devDependencies": { 27 | "fast-check": "^3.8.0", 28 | "vitest": "^0.30.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/components/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import { Href, Link } from 'expo-router'; 2 | import { openBrowserAsync } from 'expo-web-browser'; 3 | import { type ComponentProps } from 'react'; 4 | import { Platform } from 'react-native'; 5 | 6 | type Props = Omit, 'href'> & { href: Href & string }; 7 | 8 | export function ExternalLink({ href, ...rest }: Props) { 9 | return ( 10 | { 15 | if (Platform.OS !== 'web') { 16 | // Prevent the default behavior of linking to the default browser on native. 17 | event.preventDefault(); 18 | // Open the link in an in-app browser. 19 | await openBrowserAsync(href); 20 | } 21 | }} 22 | /> 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/browser-tests/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import { resolve } from "path"; 3 | import { defineConfig, searchForWorkspaceRoot } from "vite"; 4 | 5 | export default defineConfig({ 6 | build: { 7 | rollupOptions: { 8 | input: { 9 | index: resolve(__dirname, "index.html"), 10 | mainThread: resolve(__dirname, "main-thread.html"), 11 | onWorker: resolve(__dirname, "on-worker.html"), 12 | comlink: resolve(__dirname, "comlink.html"), 13 | }, 14 | }, 15 | }, 16 | server: { 17 | headers: { 18 | "Cross-Origin-Opener-Policy": "same-origin", 19 | "Cross-Origin-Embedder-Policy": "require-corp", 20 | }, 21 | fs: { 22 | allow: [ 23 | // search up for workspace root 24 | searchForWorkspaceRoot(process.cwd()), 25 | ], 26 | }, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /packages/ws-server/src/__tests__/DBFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import DB from "../DB.js"; 3 | import fs from "node:fs"; 4 | import { Config } from "../config.js"; 5 | import { cryb64 } from "@vlcn.io-community/ws-common"; 6 | import DBFactory from "../DBFactory.js"; 7 | 8 | test("db instantiation", async () => { 9 | const config: Config = { 10 | schemaFolder: "./testSchemas", 11 | dbFolder: null, 12 | pathPattern: /\/vlcn-ws/, 13 | }; 14 | 15 | const schemaContent = fs.readFileSync("./testSchemas/test.sql", "utf-8"); 16 | const schemaVersion = cryb64(schemaContent); 17 | const factory = new DBFactory(); 18 | const db = await factory.createDB( 19 | config, 20 | null, 21 | "some-db", 22 | "test.sql", 23 | schemaVersion 24 | ); 25 | expect(db).toBeDefined(); 26 | db.close(); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/ws-client/src/DB.ts: -------------------------------------------------------------------------------- 1 | import { Change } from "@vlcn.io-community/ws-common"; 2 | 3 | export interface DB { 4 | readonly siteid: Uint8Array; 5 | pullChangeset( 6 | since: readonly [bigint, number], 7 | excludeSites: readonly Uint8Array[], 8 | localOnly: boolean 9 | ): PromiseLike; 10 | applyChangesetAndSetLastSeen( 11 | changes: readonly Change[], 12 | siteId: Uint8Array, 13 | end: readonly [bigint, number] 14 | ): PromiseLike; 15 | 16 | getLastSeens(): PromiseLike<[Uint8Array, [bigint, number]][]>; 17 | getSchemaNameAndVersion(): PromiseLike<[string, bigint]>; 18 | 19 | /** 20 | * Allow the sync layer to observe when the database changes as a result 21 | * of non-sync events. 22 | */ 23 | onChange(cb: () => void): () => void; 24 | 25 | close(closeWrappedDB: boolean): void; 26 | } 27 | -------------------------------------------------------------------------------- /packages/browser-tests/src/main.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import typescriptLogo from './typescript.svg' 3 | import { setupCounter } from './counter' 4 | 5 | document.querySelector('#app')!.innerHTML = ` 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |

Vite + TypeScript

14 |
15 | 16 |
17 |

18 | Click on the Vite and TypeScript logos to learn more 19 |

20 |
21 | ` 22 | 23 | setupCounter(document.querySelector('#counter')!) 24 | -------------------------------------------------------------------------------- /packages/ws-server/src/fs/util.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import os from "os"; 3 | import fs from "fs"; 4 | 5 | const needsTouchHack = os.platform() === "darwin" || os.platform() === "win32"; 6 | const ex = { 7 | getTouchFilename(dbpath: string): string { 8 | return dbpath + ".touch"; 9 | }, 10 | 11 | fileEventNameToDbId(filename: string): string { 12 | return path.parse(filename).name.replace(/-[pos|shm|wal]+$/, ""); 13 | }, 14 | 15 | needsTouchHack() { 16 | return needsTouchHack; 17 | }, 18 | 19 | touchFile(dbpath: string): Promise { 20 | if (!needsTouchHack) { 21 | throw new Error("Touch hack is only required for darwin and windows"); 22 | } 23 | return fs.promises.open(ex.getTouchFilename(dbpath), "w").then((fd) => { 24 | return fd.close(); 25 | }); 26 | }, 27 | }; 28 | 29 | export default ex; 30 | -------------------------------------------------------------------------------- /packages/direct-connect-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/direct-connect-common", 3 | "type": "module", 4 | "version": "0.7.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "devDependencies": { 12 | "fast-check": "^3.8.0", 13 | "typescript": "^5.0.4", 14 | "vitest": "^0.30.1" 15 | }, 16 | "scripts": { 17 | "build": "tsc --build", 18 | "watch": "tsc --build --watch", 19 | "test": "vitest run", 20 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git@github.com:vlcn-io/js.git", 25 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/direct-connect-common" 26 | }, 27 | "dependencies": { 28 | "lib0": "^0.2.73" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/constants/Colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Below are the colors that are used in the app. The colors are defined in the light and dark mode. 3 | * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. 4 | */ 5 | 6 | const tintColorLight = '#0a7ea4'; 7 | const tintColorDark = '#fff'; 8 | 9 | export const Colors = { 10 | light: { 11 | text: '#11181C', 12 | background: '#fff', 13 | tint: tintColorLight, 14 | icon: '#687076', 15 | tabIconDefault: '#687076', 16 | tabIconSelected: tintColorLight, 17 | }, 18 | dark: { 19 | text: '#ECEDEE', 20 | background: '#151718', 21 | tint: tintColorDark, 22 | icon: '#9BA1A6', 23 | tabIconDefault: '#9BA1A6', 24 | tabIconSelected: tintColorDark, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/direct-connect-browser/src/shared.worker.ts: -------------------------------------------------------------------------------- 1 | import { ToWorkerMsg } from "./Types.js"; 2 | import SyncService from "./common/SyncService.js"; 3 | 4 | type Self = { 5 | onconnect: (event: MessageEvent) => void; 6 | }; 7 | const glob = self as unknown as Self; 8 | 9 | const svc = new SyncService(); 10 | 11 | /** 12 | * A shared worker that is responsible for syncing databases to other direct-connect 13 | * instances. 14 | */ 15 | glob.onconnect = (e: MessageEvent) => { 16 | const port = e.ports[0]; 17 | port.onmessage = (e) => { 18 | msgReceived(e, port); 19 | }; 20 | }; 21 | 22 | function msgReceived(e: MessageEvent, port: MessagePort) { 23 | const msg = e.data; 24 | 25 | switch (msg._tag) { 26 | case "StartSync": 27 | svc.startSync(msg, port); 28 | break; 29 | case "StopSync": 30 | svc.stopSync(msg, port); 31 | break; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/ws-server/src/__tests__/DB.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import DB from "../DB.js"; 3 | import fs from "node:fs"; 4 | import { Config } from "../config.js"; 5 | import { cryb64 } from "@vlcn.io-community/ws-common"; 6 | import DBFactory from "../DBFactory.js"; 7 | 8 | test("db instantiation", () => { 9 | const config: Config = { 10 | schemaFolder: "./testSchemas", 11 | dbFolder: null, 12 | pathPattern: /\/vlcn-ws/, 13 | }; 14 | 15 | const schemaContent = fs.readFileSync("./testSchemas/test.sql", "utf-8"); 16 | const schemaVersion = cryb64(schemaContent); 17 | const db = new DB(config, null, "some-db", "test.sql", schemaVersion); 18 | expect(db).toBeDefined(); 19 | db.close(); 20 | }); 21 | 22 | test("pull changes", () => {}); 23 | 24 | test("write changes", () => {}); 25 | 26 | test("get last seen", () => {}); 27 | 28 | // TODO: test schema migration 29 | -------------------------------------------------------------------------------- /packages/node-tests/src/__tests__/automigrate.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import crsqlite from "@vlcn.io-community/crsqlite-allinone"; 3 | 4 | test("automigrate", () => { 5 | const db = crsqlite.open(); 6 | const schema = /*sql*/ ` 7 | CREATE TABLE IF NOT EXISTS test (id PRIMARY KEY NOT NULL, name TEXT); 8 | SELECT crsql_as_crr('test'); 9 | `; 10 | db.exec(`SELECT crsql_automigrate(?);`, [schema]); 11 | const updatedSchema = /*sql*/ ` 12 | CREATE TABLE IF NOT EXISTS test (id PRIMARY KEY, name TEXT, time INTEGER); 13 | SELECT crsql_as_crr('test'); 14 | `; 15 | db.exec(`SELECT crsql_automigrate(?);`, [updatedSchema]); 16 | 17 | const db2 = crsqlite.open(); 18 | const schema2 = `CREATE TABLE IF NOT EXISTS test (id PRIMARY KEY NOT NULL, name TEXT); 19 | SELECT crsql_as_crr('test'); 20 | `; 21 | db2.exec(`SELECT crsql_automigrate(?);`, [schema]); 22 | }); 23 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/app/+not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Stack } from 'expo-router'; 2 | import { StyleSheet } from 'react-native'; 3 | 4 | import { ThemedText } from '@/components/ThemedText'; 5 | import { ThemedView } from '@/components/ThemedView'; 6 | 7 | export default function NotFoundScreen() { 8 | return ( 9 | <> 10 | 11 | 12 | This screen does not exist. 13 | 14 | Go to home screen! 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | const styles = StyleSheet.create({ 22 | container: { 23 | flex: 1, 24 | alignItems: 'center', 25 | justifyContent: 'center', 26 | padding: 20, 27 | }, 28 | link: { 29 | marginTop: 15, 30 | paddingVertical: 15, 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /packages/ws-client/src/worker/WorkerInterface.ts: -------------------------------------------------------------------------------- 1 | import { TransporOptions } from "../transport/Transport.js"; 2 | import { DBID } from "../types.js"; 3 | import { StartSyncMsg, StopSyncMsg } from "./workerMsgTypes.js"; 4 | 5 | export default class WorkerInterface { 6 | readonly #worker; 7 | readonly #syncs = new Set(); 8 | 9 | constructor(worker: Worker) { 10 | this.#worker = worker; 11 | } 12 | 13 | startSync(dbid: DBID, transportOpts: TransporOptions) { 14 | if (this.#syncs.has(dbid)) { 15 | throw new Error(`Already syncing ${dbid}`); 16 | } 17 | 18 | this.#syncs.add(dbid); 19 | this.#worker.postMessage({ 20 | _tag: "StartSync", 21 | dbid, 22 | transportOpts, 23 | } satisfies StartSyncMsg); 24 | } 25 | 26 | stopSync(dbid: DBID) { 27 | this.#worker.postMessage({ 28 | _tag: "StopSync", 29 | dbid, 30 | } satisfies StopSyncMsg); 31 | this.#syncs.delete(dbid); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/update-pkgjson.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pkgs="" 4 | if [[ $1 == "local" ]]; then 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" 6 | source "$SCRIPT_DIR/pkgs.inc.sh" 7 | pkgs=("${pkgslocal[@]}") 8 | else 9 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" 10 | pkgs=($("$SCRIPT_DIR/latest-versions.sh")) 11 | fi 12 | 13 | for pkg in "${pkgs[@]}"; do 14 | echo "processing $pkg" 15 | IFS=':' read -r NAME VERSION <<< "$pkg" 16 | PACKAGE_EXISTS=$(jq -r --arg name "$NAME" '.dependencies | has($name)' package.json) 17 | if [ "$PACKAGE_EXISTS" = "true" ]; then 18 | if [[ $1 == "local" ]]; then 19 | VERSION="link:$VERSION" 20 | fi 21 | # replace in package.json only if there is an entry for that package in the package.json 22 | jq --arg name "$NAME" --arg version "$VERSION" '.dependencies[$name] = $version' package.json > tmp.json && mv tmp.json package.json 23 | fi 24 | done 25 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [x] `@vlcn.io/*` -> `@vlcn.io-community/*` 2 | - [ ] update README with a reason why this fork exists 3 | - [ ] The CJS build of Vite's Node API is deprecated. See https://vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details. 4 | - [x] add an Expo React Native demo 5 | - [ ] add cr-sqlite stuff to the Expo React Native demo 6 | - [ ] add another server demo and publish it 7 | - [ ] Add error boundaries to React components for better error handling 8 | - [ ] Add input validation for API endpoints 9 | - [ ] Add test coverage for demo directory: 10 | - [ ] Unit tests for database operations, hooks, and components 11 | - [ ] Integration tests for Docker setup and API endpoints 12 | - [ ] CI/CD validation for new functionality 13 | - [ ] Enhance demo with advanced features: 14 | - [ ] Multi-device sync demonstration 15 | - [ ] CRDT conflict resolution examples 16 | - [ ] Performance metrics display (sync stats, database performance) 17 | -------------------------------------------------------------------------------- /packages/logger-provider/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io/logger-provider 2 | 3 | ## 0.2.0 4 | 5 | ### Minor Changes 6 | 7 | - e4a0a42: v0.16.0-next 8 | 9 | ### Patch Changes 10 | 11 | - d485812: prepare `tables_used` query, correctly unzip native library from pre-builds 12 | - 6f0ccac: fix error where separate connections would not report the correct db version 13 | 14 | ## 0.2.0-next.2 15 | 16 | ### Patch Changes 17 | 18 | - fix error where separate connections would not report the correct db version 19 | 20 | ## 0.2.0-next.1 21 | 22 | ### Patch Changes 23 | 24 | - prepare `tables_used` query, correctly unzip native library from pre-builds 25 | 26 | ## 0.2.0-next.0 27 | 28 | ### Minor Changes 29 | 30 | - v0.16.0-next 31 | 32 | ## 0.1.2 33 | 34 | ### Patch Changes 35 | 36 | - deploy correct build 37 | 38 | ## 0.1.1 39 | 40 | ### Patch Changes 41 | 42 | - b3f0b2d: add logging to debug litefs 43 | 44 | ## 0.1.1-next.0 45 | 46 | ### Patch Changes 47 | 48 | - add logging to debug litefs 49 | -------------------------------------------------------------------------------- /packages/ws-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/ws-client", 3 | "type": "module", 4 | "version": "0.2.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "exports": { 12 | ".": "./dist/index.js", 13 | "./worker.js": "./dist/worker/worker.js" 14 | }, 15 | "scripts": { 16 | "build": "tsc --build", 17 | "watch": "tsc --build --watch", 18 | "test": "vitest run", 19 | "rmtdata": "rm dbs-test/*", 20 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git@github.com:vlcn-io/js.git", 25 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/ws-client" 26 | }, 27 | "dependencies": { 28 | "@types/throttle-debounce": "^5.0.0", 29 | "@vlcn.io-community/ws-common": "workspace:*" 30 | }, 31 | "devDependencies": { 32 | "vitest": "^3.2.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/app/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; 2 | import { useFonts } from 'expo-font'; 3 | import { Stack } from 'expo-router'; 4 | import { StatusBar } from 'expo-status-bar'; 5 | import 'react-native-reanimated'; 6 | 7 | import { useColorScheme } from '@/hooks/useColorScheme'; 8 | 9 | export default function RootLayout() { 10 | const colorScheme = useColorScheme(); 11 | const [loaded] = useFonts({ 12 | SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), 13 | }); 14 | 15 | if (!loaded) { 16 | // Async font loading only occurs in development. 17 | return null; 18 | } 19 | 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Environment setup & latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "Preserve", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedIndexedAccess": true, 22 | "noImplicitOverride": true, 23 | 24 | "baseUrl": ".", 25 | "paths": { 26 | "@/*": ["./src/*"] 27 | }, 28 | 29 | // Some stricter flags (disabled by default) 30 | "noUnusedLocals": false, 31 | "noUnusedParameters": false, 32 | "noPropertyAccessFromIndexSignature": false 33 | }, 34 | 35 | "exclude": ["dist", "node_modules"] 36 | } 37 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/src/private/InboundStream.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AckChangesMsg, 3 | StreamingChangesMsg, 4 | } from "@vlcn.io-community/direct-connect-common"; 5 | 6 | /** 7 | * Takes the results of an outbound stream and 8 | * ingests them into a database. 9 | * 10 | * InboundStream differs from ApplyChanges in that it is stateful. 11 | * 12 | * ApplyChanges must look up seen_peers on every call to ensure the 13 | * changes can be applied (seqStart <= current retained seq). 14 | * 15 | * InboundStream would keep this state in-memory. 16 | * 17 | * For a server, 18 | * InboundStream would not work where the stream is not pinned to 19 | * a single server. 20 | * 21 | * This is because the underying DB state would change when other nodes 22 | * update it, causing the in-memory inbound stream to fall out of sync. 23 | */ 24 | export default class InboundStream { 25 | constructor() {} 26 | 27 | receiveChanges(changes: StreamingChangesMsg): AckChangesMsg { 28 | throw new Error(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/xplat-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/xplat-tests", 3 | "private": true, 4 | "type": "module", 5 | "version": "0.14.0", 6 | "files": [ 7 | "dist" 8 | ], 9 | "main": "dist/index.js", 10 | "sideEffects": false, 11 | "devDependencies": { 12 | "typescript": "^5.0.4", 13 | "vitest": "^0.30.1" 14 | }, 15 | "scripts": { 16 | "build": "tsc --build", 17 | "watch": "tsc --build --watch", 18 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true", 19 | "test": "echo 'These tests are run via node-tests and browser-tests'" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:vlcn-io/js.git", 24 | "directory": "https://github.com/vlcn-io/js/tree/main/js/test/xplat-tests" 25 | }, 26 | "dependencies": { 27 | "@vlcn.io-community/rx-tbl": "workspace:*", 28 | "@vlcn.io-community/sync-p2p": "workspace:*", 29 | "@vlcn.io-community/xplat-api": "workspace:*", 30 | "uuid": "^9.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/direct-connect-browser/src/Types.ts: -------------------------------------------------------------------------------- 1 | import { UpdateType, DBID } from "@vlcn.io-community/xplat-api"; 2 | 3 | export type Endpoints = { 4 | createOrMigrate: string; 5 | getChanges?: string; 6 | applyChanges: string; 7 | startOutboundStream: string; 8 | getLastSeen?: string; 9 | }; 10 | 11 | export type ToWorkerMsg = StartSyncMsg | StopSyncMsg; 12 | export type FromWorkerMsg = SyncedRemoteMsg; 13 | export type Port = { 14 | postMessage: (msg: any) => void; 15 | }; 16 | 17 | export type StartSyncMsg = { 18 | _tag: "StartSync"; 19 | wasmUri?: string; 20 | dbid: DBID; 21 | endpoints: Endpoints; 22 | transportContentType: "application/json" | "application/octet-stream"; 23 | }; 24 | 25 | export type StopSyncMsg = { 26 | _tag: "StopSync"; 27 | dbid: DBID; 28 | }; 29 | 30 | export type SyncedRemoteMsg = { 31 | _tag: "SyncedRemote"; 32 | dbid: DBID; 33 | collectedChanges: [UpdateType, string, bigint][]; 34 | }; 35 | 36 | export function newDbid() { 37 | return crypto.randomUUID().replaceAll("-", "") as DBID; 38 | } 39 | -------------------------------------------------------------------------------- /packages/node-tests/src/__tests__/xplat.test.ts: -------------------------------------------------------------------------------- 1 | import "../fill.js"; 2 | 3 | import { test, expect } from "vitest"; 4 | import { DBAsync, DB as DBSync } from "@vlcn.io-community/xplat-api"; 5 | type DB = DBAsync | DBSync; 6 | import crsqlite from "@vlcn.io-community/crsqlite-allinone"; 7 | 8 | function runTests(tests: { 9 | [key: string]: ( 10 | dbProvider: () => Promise, 11 | assert: (p: boolean) => void 12 | ) => any; 13 | }) { 14 | Object.entries(tests).forEach((x) => { 15 | test(x[0], () => { 16 | const tc = x[1]; 17 | tc( 18 | async () => crsqlite.open(), 19 | (p: boolean) => expect(p).toBe(true) 20 | ); 21 | }); 22 | }); 23 | } 24 | 25 | // import { wdbTests } from "@vlcn.io-community/xplat-tests"; 26 | // runTests(wdbTests); 27 | 28 | // TODO: better-sqlite3 currently does not expose an udpate hook 29 | // import { tblrxTests } from "@vlcn.io-community/xplat-tests"; 30 | // runTests(tblrxTests); 31 | 32 | import { intTests } from "@vlcn.io-community/xplat-tests"; 33 | runTests(intTests); 34 | -------------------------------------------------------------------------------- /packages/crsqlite-wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/crsqlite-wasm", 3 | "type": "module", 4 | "version": "0.16.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "exports": { 10 | ".": "./dist/index.js", 11 | "./crsqlite.wasm": "./dist/crsqlite.wasm" 12 | }, 13 | "module": "dist/index.js", 14 | "types": "dist/index.d.ts", 15 | "sideEffects": false, 16 | "devDependencies": { 17 | "typescript": "^5.9.2", 18 | "vitest": "^3.2.4" 19 | }, 20 | "scripts": { 21 | "build": "tsc --build", 22 | "watch": "tsc --build --watch", 23 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true", 24 | "test": "vitest run" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git@github.com:vlcn-io/js.git", 29 | "directory": "https://github.com/vlcn-io/js/tree/main/js/browser/crsqlite" 30 | }, 31 | "dependencies": { 32 | "@vlcn.io/wa-sqlite": "^0.22.0", 33 | "@vlcn.io-community/xplat-api": "workspace:*", 34 | "async-mutex": "^0.5.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/p2p/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/sync-p2p", 3 | "type": "module", 4 | "version": "0.14.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "devDependencies": { 12 | "@types/jest": "^29.5.0", 13 | "jest": "^29.5.0", 14 | "typescript": "^5.0.4" 15 | }, 16 | "scripts": { 17 | "build": "tsc --build", 18 | "watch": "tsc --build --watch", 19 | "test": "echo 'No tests yet'", 20 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git@github.com:vlcn-io/js.git", 25 | "directory": "https://github.com/vlcn-io/js/tree/main/js/sync-reference/p2p" 26 | }, 27 | "dependencies": { 28 | "@types/uuid": "^9.0.1", 29 | "@vlcn.io-community/xplat-api": "workspace:*", 30 | "peerjs": "^1.4.7", 31 | "uuid": "^9.0.0" 32 | }, 33 | "jest": { 34 | "testMatch": [ 35 | "**/__tests__/**/*.test.js" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tsbuild-all/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-template.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/", 5 | "rootDir": "./src" 6 | }, 7 | "include": ["./src/"], 8 | "references": [ 9 | { "path": "../packages/direct-connect-browser" }, 10 | { "path": "../packages/direct-connect-common" }, 11 | { "path": "../packages/direct-connect-nodejs" }, 12 | { "path": "../packages/node-allinone" }, 13 | { "path": "../packages/node-tests" }, 14 | { "path": "../packages/p2p" }, 15 | { "path": "../packages/react" }, 16 | { "path": "../packages/rx-tbl" }, 17 | { "path": "../packages/xplat-api" }, 18 | { "path": "../packages/xplat-tests" }, 19 | { "path": "../packages/crsqlite-wasm" }, 20 | { "path": "../packages/ws-server" }, 21 | { "path": "../packages/ws-common" }, 22 | { "path": "../packages/ws-client" }, 23 | { "path": "../packages/ws-browserdb" }, 24 | { "path": "../packages/ws-litefs" }, 25 | { "path": "../packages/logger-provider" }, 26 | { "path": "../packages/id" } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /packages/ws-browserdb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/ws-browserdb", 3 | "type": "module", 4 | "version": "0.2.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "scripts": { 12 | "build": "tsc --build", 13 | "watch": "tsc --build --watch", 14 | "test": "vitest run", 15 | "rmtdata": "rm dbs-test/*", 16 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git@github.com:vlcn-io/js.git", 21 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/ws-browserdb" 22 | }, 23 | "dependencies": { 24 | "@types/throttle-debounce": "^5.0.0", 25 | "@vlcn.io-community/ws-client": "workspace:*", 26 | "@vlcn.io-community/ws-common": "workspace:*", 27 | "@vlcn.io-community/crsqlite-wasm": "workspace:*", 28 | "@vlcn.io-community/xplat-api": "workspace:*", 29 | "@vlcn.io-community/rx-tbl": "workspace:*" 30 | }, 31 | "devDependencies": { 32 | "vitest": "^3.2.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/ws-server/src/Trasnport.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Changes, 3 | RejectChanges, 4 | StartStreaming, 5 | encode, 6 | } from "@vlcn.io-community/ws-common"; 7 | import { WebSocket } from "ws"; 8 | import { logger } from "@vlcn.io-community/logger-provider"; 9 | 10 | /** 11 | * Abstracts over the exact transport so we can swap out to any transport (http, websockets, tcp, etc) we want. 12 | */ 13 | export default class Transport { 14 | readonly #ws; 15 | constructor(ws: WebSocket) { 16 | this.#ws = ws; 17 | } 18 | 19 | sendChanges(msg: Changes): "buffer-full" | "sent" { 20 | if (this.#ws.bufferedAmount > 1024 * 1024 * 5) { 21 | logger.warn(`Buffer full. Telling DB to call us back later`); 22 | return "buffer-full"; 23 | } 24 | this.#ws.send(encode(msg)); 25 | return "sent"; 26 | // TODO: return back pressure if too much is buffered. 27 | // this.#ws.bufferedAmount 28 | } 29 | 30 | rejectChanges(msg: RejectChanges) { 31 | this.#ws.send(encode(msg)); 32 | } 33 | 34 | startStreaming(msg: StartStreaming) { 35 | this.#ws.send(encode(msg)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/sandbox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandbox", 3 | "private": true, 4 | "type": "module", 5 | "version": "0.7.1", 6 | "scripts": { 7 | "dev": "vite", 8 | "tsc": "tsc --noEmit", 9 | "preview": "vite preview", 10 | "bld": "vite build", 11 | "test:unit": "vitest", 12 | "test": "echo 'No tests yet'", 13 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 14 | }, 15 | "devDependencies": { 16 | "@types/express": "^4.17.17", 17 | "@types/react": "^18.0.37", 18 | "@types/react-dom": "^18.2.4", 19 | "vite": "^4.2.2" 20 | }, 21 | "dependencies": { 22 | "@vlcn.io-community/crsqlite-wasm": "workspace:*", 23 | "@vlcn.io-community/direct-connect-browser": "workspace:*", 24 | "@vlcn.io-community/direct-connect-common": "workspace:*", 25 | "@vlcn.io-community/direct-connect-nodejs": "workspace:*", 26 | "@vlcn.io-community/react": "workspace:*", 27 | "@vlcn.io-community/rx-tbl": "workspace:*", 28 | "express": "^4.18.2", 29 | "react": "^18.2.0", 30 | "react-dom": "^18.2.0", 31 | "vite-express": "^0.6.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/ws-litefs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/ws-litefs", 3 | "type": "module", 4 | "version": "0.2.2", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "scripts": { 12 | "build": "tsc --build", 13 | "watch": "tsc --build --watch", 14 | "test": "vitest run", 15 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git@github.com:vlcn-io/js.git", 20 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/ws-litefs" 21 | }, 22 | "dependencies": { 23 | "@vlcn.io/crsqlite": "^0.16.3", 24 | "@vlcn.io-community/ws-common": "workspace:*", 25 | "@vlcn.io-community/ws-server": "workspace:*", 26 | "better-sqlite3": "^12.2.0", 27 | "chokidar": "^3.5.3", 28 | "throttle-debounce": "^5.0.0", 29 | "winston": "^3.10.0" 30 | }, 31 | "devDependencies": { 32 | "@types/better-sqlite3": "^7.6.4", 33 | "@types/throttle-debounce": "^5.0.0", 34 | "vitest": "^0.30.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/components/HelloWave.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { StyleSheet } from 'react-native'; 3 | import Animated, { 4 | useAnimatedStyle, 5 | useSharedValue, 6 | withRepeat, 7 | withSequence, 8 | withTiming, 9 | } from 'react-native-reanimated'; 10 | 11 | import { ThemedText } from '@/components/ThemedText'; 12 | 13 | export function HelloWave() { 14 | const rotationAnimation = useSharedValue(0); 15 | 16 | useEffect(() => { 17 | rotationAnimation.value = withRepeat( 18 | withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })), 19 | 4 // Run the animation 4 times 20 | ); 21 | }, [rotationAnimation]); 22 | 23 | const animatedStyle = useAnimatedStyle(() => ({ 24 | transform: [{ rotate: `${rotationAnimation.value}deg` }], 25 | })); 26 | 27 | return ( 28 | 29 | 👋 30 | 31 | ); 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | text: { 36 | fontSize: 28, 37 | lineHeight: 32, 38 | marginTop: -6, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /packages/ws-client/src/transport/Transport.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AnnouncePresence, 3 | Changes, 4 | RejectChanges, 5 | StartStreaming, 6 | } from "@vlcn.io-community/ws-common"; 7 | 8 | export type TransporOptions = { 9 | url: string; 10 | room: string; 11 | authToken?: string; 12 | }; 13 | 14 | export interface Transport { 15 | // Announce ourselves to the server. 16 | // Give it our version vector so it can determine 17 | // min version to request from us and multiplex to peers. 18 | // Return to us @ what version we should start sending changes. 19 | start(onReady: () => void): void; 20 | 21 | announcePresence(msg: AnnouncePresence): void; 22 | sendChanges(msg: Changes): "reconnecting" | "buffer-full" | "sent"; 23 | rejectChanges(msg: RejectChanges): void; 24 | 25 | onChangesReceived: ((msg: Changes) => Promise) | null; 26 | 27 | // If we're set up in a p2p hub & spoke we'll want to rely on each peer 28 | // sending its own changes and excluding others. 29 | onStartStreaming: ((msg: StartStreaming) => Promise) | null; 30 | 31 | onResetStream: ((msg: StartStreaming) => Promise) | null; 32 | 33 | close(): void; 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 One Law LLC 4 | Copyright (c) 2025 Thomas Aylott 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /packages/node-allinone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/crsqlite-allinone", 3 | "version": "0.15.1", 4 | "description": "CR-SQLite loadable extension", 5 | "homepage": "https://vlcn.io", 6 | "type": "module", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/vlcn-io/cr-sqlite", 10 | "directory": "https://github.com/vlcn-io/js/tree/main/js/node-allinone" 11 | }, 12 | "module": "dist/index.js", 13 | "main": "dist/index.js", 14 | "files": [ 15 | "dist/**" 16 | ], 17 | "dependencies": { 18 | "@vlcn.io/crsqlite": "^0.16.3", 19 | "@vlcn.io-community/xplat-api": "workspace:*", 20 | "better-sqlite3": "^12.2.0" 21 | }, 22 | "scripts": { 23 | "build": "tsc --build", 24 | "watch": "tsc --build -w", 25 | "test": "vitest --run", 26 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 27 | }, 28 | "license": "MIT", 29 | "keywords": [ 30 | "sql", 31 | "sqlite", 32 | "sqlite3", 33 | "crdt" 34 | ], 35 | "devDependencies": { 36 | "@types/jest": "^29.5.0", 37 | "nanoid": "^4.0.2", 38 | "typescript": "^5.0.4", 39 | "vitest": "^0.30.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/react", 3 | "type": "module", 4 | "version": "3.1.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "main": "dist/index.js", 10 | "module": "dist/index.js", 11 | "sideEffects": false, 12 | "devDependencies": { 13 | "@types/react": "^18.0.37", 14 | "react": "^18.2.0", 15 | "typescript": "^5.0.4", 16 | "vitest": "^0.30.1" 17 | }, 18 | "scripts": { 19 | "build": "tsc --build", 20 | "watch": "tsc --build --watch", 21 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true", 22 | "test": "vitest run" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git@github.com:vlcn-io/js.git", 27 | "directory": "https://github.com/vlcn-io/js/tree/main/js/react" 28 | }, 29 | "dependencies": { 30 | "@vlcn.io-community/crsqlite-wasm": "workspace:*", 31 | "@vlcn.io-community/rx-tbl": "workspace:*", 32 | "@vlcn.io-community/ws-client": "workspace:*", 33 | "@vlcn.io-community/xplat-api": "workspace:*", 34 | "@vlcn.io/typed-sql": "^0.3.0", 35 | "async-mutex": "^0.4.0" 36 | }, 37 | "peerDependencies": { 38 | "react": "^18" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/node-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/nodeno-tests", 3 | "type": "module", 4 | "version": "0.14.1", 5 | "files": [ 6 | "dist" 7 | ], 8 | "module": "dist/index.js", 9 | "main": "dist/index.js", 10 | "sideEffects": false, 11 | "devDependencies": { 12 | "typescript": "^5.0.4", 13 | "vitest": "^0.30.1" 14 | }, 15 | "scripts": { 16 | "build": "tsc --build", 17 | "watch": "tsc --build --watch", 18 | "test": "vitest run", 19 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:vlcn-io/js.git", 24 | "directory": "https://github.com/vlcn-io/js/tree/main/js/tests/nodeno-tests" 25 | }, 26 | "dependencies": { 27 | "@vlcn.io/crsqlite": "^0.16.3", 28 | "@vlcn.io-community/crsqlite-allinone": "workspace:*", 29 | "@vlcn.io-community/rx-tbl": "workspace:*", 30 | "@vlcn.io-community/xplat-api": "workspace:*", 31 | "@vlcn.io-community/xplat-tests": "workspace:*", 32 | "better-sqlite3": "^12.2.0", 33 | "fast-check": "^3.8.0", 34 | "nanoid": "^4.0.2" 35 | }, 36 | "jest": { 37 | "transform": {} 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/logger-provider/src/index.ts: -------------------------------------------------------------------------------- 1 | import winston from "winston"; 2 | 3 | let loggerInternal: winston.Logger; 4 | 5 | const loggerProvider = { 6 | set logger(l: winston.Logger) { 7 | loggerInternal = l; 8 | }, 9 | 10 | get logger() { 11 | if (loggerInternal == null) { 12 | console.warn("No logger set! Using default logger."); 13 | loggerInternal = winston.createLogger({ 14 | level: "info", 15 | format: winston.format.json(), 16 | defaultMeta: { service: "ws-server" }, 17 | transports: [ 18 | // 19 | // - Write all logs with importance level of `error` or less to `error.log` 20 | // - Write all logs with importance level of `info` or less to `combined.log` 21 | // 22 | // new winston.transports.File({ filename: 'error.log', level: 'error' }), 23 | // new winston.transports.File({ filename: 'combined.log' }), 24 | new winston.transports.Console({ 25 | format: winston.format.simple(), 26 | }), 27 | ], 28 | }); 29 | } 30 | return loggerInternal; 31 | }, 32 | }; 33 | 34 | export const logger = loggerProvider.logger; 35 | 36 | export default loggerProvider; 37 | -------------------------------------------------------------------------------- /packages/ws-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/ws-server", 3 | "type": "module", 4 | "version": "0.2.2", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "scripts": { 12 | "build": "tsc --build", 13 | "watch": "tsc --build --watch", 14 | "test": "vitest run", 15 | "rmtdata": "rm testDbs/*", 16 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git@github.com:vlcn-io/js.git", 21 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/ws-server" 22 | }, 23 | "dependencies": { 24 | "@types/better-sqlite3": "^7.6.8", 25 | "@vlcn.io/crsqlite": "^0.16.3", 26 | "@vlcn.io-community/logger-provider": "workspace:*", 27 | "@vlcn.io-community/ws-common": "workspace:*", 28 | "better-sqlite3": "^12.2.0", 29 | "chokidar": "^3.5.3", 30 | "throttle-debounce": "^5.0.0", 31 | "winston": "^3.10.0", 32 | "ws": "^8.13.0" 33 | }, 34 | "devDependencies": { 35 | "@types/throttle-debounce": "^5.0.0", 36 | "@types/ws": "^8.5.5", 37 | "vitest": "^0.30.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/bun-sqlite-lib/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "bun:test"; 2 | import { getSQLitePath, isSQLiteAvailable, getSQLiteVersion } from "./index"; 3 | 4 | describe("bun-sqlite-lib", () => { 5 | it("should return correct SQLite path for current platform", () => { 6 | const path = getSQLitePath(); 7 | expect(path).toContain("libsqlite3"); 8 | expect(path).toContain(process.arch === "arm64" ? "darwin-arm64" : "darwin-x64"); 9 | }); 10 | 11 | it("should check if SQLite is available", () => { 12 | const available = isSQLiteAvailable(); 13 | // This will be true or false depending on whether the library is installed 14 | expect(typeof available).toBe("boolean"); 15 | }); 16 | 17 | it("should return SQLite version", () => { 18 | const version = getSQLiteVersion(); 19 | expect(version).toBe("3.47.2"); 20 | }); 21 | 22 | // Note: We can't test setBundledSQLite() in unit tests because: 23 | // 1. Database.setCustomSQLite() can only be called once per process 24 | // 2. It must be called before any Database instances are created 25 | // 3. Running tests may have already initialized SQLite 26 | // This functionality should be tested via integration tests or example usage 27 | }); -------------------------------------------------------------------------------- /demos/expo-crsql-demo/hooks/useDatabaseSuspense.ts: -------------------------------------------------------------------------------- 1 | import { use, useMemo } from "react"; 2 | import { initDatabase } from "@/services/database"; 3 | import type { DB } from "@/services/db"; 4 | import * as dbModule from "@/services/db"; 5 | 6 | // Singleton promise for database initialization 7 | let dbInitPromise: Promise | null = null; 8 | 9 | function getDbInitPromise(): Promise { 10 | if (!dbInitPromise) { 11 | dbInitPromise = initDatabase(); 12 | } 13 | return dbInitPromise; 14 | } 15 | 16 | /** 17 | * React 19 Suspense-based database hook 18 | * Use this inside a Suspense boundary 19 | */ 20 | export function useDatabaseSuspense() { 21 | // This will suspend until the database is ready 22 | const db = use(getDbInitPromise()); 23 | 24 | return { 25 | db, 26 | // Site ID and version can be fetched directly from the resolved db 27 | getSiteId: async () => { 28 | const result = await db.execute( 29 | "SELECT quote(crsql_site_id()) as site_id", 30 | ); 31 | return result.rows[0]?.site_id?.replace(/'/g, "") || "unknown"; 32 | }, 33 | getDbVersion: async () => { 34 | const result = await db.execute("SELECT crsql_db_version() as version"); 35 | return result.rows[0]?.version || 0; 36 | }, 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /packages/direct-connect-browser/src/dedicated.worker.ts: -------------------------------------------------------------------------------- 1 | import { Port, ToWorkerMsg } from "./Types.js"; 2 | import SyncService from "./common/SyncService.js"; 3 | 4 | const port: Port = { 5 | postMessage: (msg) => postMessage(msg), 6 | }; 7 | 8 | const svc = new SyncService(); 9 | 10 | const locks = new Map void>(); 11 | const doesHoldLock = new Map(); 12 | 13 | self.onmessage = (e: MessageEvent) => { 14 | const msg = e.data; 15 | 16 | switch (msg._tag) { 17 | case "StartSync": { 18 | let releaser: (() => void) | null = null; 19 | const hold = new Promise((resolve, _reject) => { 20 | releaser = resolve; 21 | }); 22 | locks.set(msg.dbid, releaser!); 23 | doesHoldLock.set(msg.dbid, false); 24 | navigator.locks.request(msg.dbid, () => { 25 | doesHoldLock.set(msg.dbid, true); 26 | svc.startSync(msg, port); 27 | return hold; 28 | }); 29 | break; 30 | } 31 | case "StopSync": { 32 | svc.stopSync(msg, port); 33 | const releaser = locks.get(msg.dbid); 34 | if (releaser) { 35 | releaser(); 36 | doesHoldLock.delete(msg.dbid); 37 | } 38 | break; 39 | } 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "crsql-demo", 4 | "slug": "crsql-demo", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "crsqldemo", 9 | "userInterfaceStyle": "automatic", 10 | "newArchEnabled": true, 11 | "ios": { 12 | "supportsTablet": true, 13 | "bundleIdentifier": "com.subtlegradient.crsql-demo" 14 | }, 15 | "android": { 16 | "adaptiveIcon": { 17 | "foregroundImage": "./assets/images/adaptive-icon.png", 18 | "backgroundColor": "#ffffff" 19 | }, 20 | "edgeToEdgeEnabled": true, 21 | "package": "com.subtlegradient.crsqldemo" 22 | }, 23 | "web": { 24 | "bundler": "metro", 25 | "output": "single", 26 | "favicon": "./assets/images/favicon.png" 27 | }, 28 | "plugins": [ 29 | "expo-router", 30 | [ 31 | "expo-splash-screen", 32 | { 33 | "image": "./assets/images/splash-icon.png", 34 | "imageWidth": 200, 35 | "resizeMode": "contain", 36 | "backgroundColor": "#ffffff" 37 | } 38 | ] 39 | ], 40 | "experiments": { 41 | "typedRoutes": true 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig } = require("expo/metro-config"); 2 | const path = require("path"); 3 | 4 | const config = getDefaultConfig(__dirname); 5 | 6 | // Watch the monorepo root but let Metro resolve symlinks naturally. 7 | const workspaceRoot = path.resolve(__dirname, "../.."); 8 | config.watchFolders = [...(config.watchFolders ?? []), workspaceRoot]; 9 | 10 | // Prefer symlinks over manual nodeModulesPaths/extraNodeModules. 11 | config.resolver = { 12 | ...config.resolver, 13 | unstable_enableSymlinks: true, 14 | }; 15 | 16 | // Keep WASM as an asset (ok if already present). 17 | if (!config.resolver.assetExts.includes("wasm")) { 18 | config.resolver.assetExts.push("wasm"); 19 | } 20 | 21 | // ✅ Treat ESM files correctly so import.meta is valid 22 | for (const ext of ["mjs", "cjs"]) { 23 | if (!config.resolver.sourceExts.includes(ext)) { 24 | config.resolver.sourceExts.push(ext); 25 | } 26 | } 27 | 28 | // Use Expo defaults for transforms; don't force alt transformer. 29 | config.transformer = { 30 | ...config.transformer, 31 | getTransformOptions: async () => ({ 32 | transform: { 33 | experimentalImportSupport: false, 34 | inlineRequires: true, 35 | }, 36 | }), 37 | }; 38 | 39 | module.exports = config; 40 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/component/automigrate.cy.ts: -------------------------------------------------------------------------------- 1 | import sqliteWasm from "@vlcn.io-community/crsqlite-wasm"; 2 | // @ts-ignore 3 | import wasmUrl from "@vlcn.io-community/crsqlite-wasm/crsqlite.wasm?url"; 4 | const crsqlite = await sqliteWasm((file) => wasmUrl); 5 | import { automigrateTests } from "@vlcn.io-community/xplat-tests"; 6 | 7 | describe("automigrate.cy.ts", () => { 8 | it("handles column addition", async () => { 9 | const db = await crsqlite.open(); 10 | const schema = /*sql*/ ` 11 | CREATE TABLE IF NOT EXISTS test (id PRIMARY KEY not null, name TEXT); 12 | SELECT crsql_as_crr('test'); 13 | `; 14 | await db.exec(schema); 15 | const updatedSchema = /*sql*/ ` 16 | CREATE TABLE IF NOT EXISTS test (id PRIMARY KEY not null, name TEXT, time INTEGER); 17 | SELECT crsql_as_crr('test'); 18 | `; 19 | await db.exec( 20 | /*sql*/ `SELECT crsql_automigrate(?, 'SELECT crsql_finalize();');`, 21 | [updatedSchema] 22 | ); 23 | }); 24 | 25 | Object.entries(automigrateTests).map((x) => { 26 | it(x[0], () => { 27 | const tc = x[1]; 28 | return tc( 29 | () => crsqlite.open(), 30 | (p: boolean) => expect(p).to.equal(true) 31 | ); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/hooks/pre-connect.sample: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # A sample pre-connect check 4 | # 5 | # Warms DNS before connecting to hosts in parallel 6 | # 7 | # These environment variables are available: 8 | # KAMAL_RECORDED_AT 9 | # KAMAL_PERFORMER 10 | # KAMAL_VERSION 11 | # KAMAL_HOSTS 12 | # KAMAL_ROLES (if set) 13 | # KAMAL_DESTINATION (if set) 14 | # KAMAL_RUNTIME 15 | 16 | hosts = ENV["KAMAL_HOSTS"].split(",") 17 | results = nil 18 | max = 3 19 | 20 | elapsed = Benchmark.realtime do 21 | results = hosts.map do |host| 22 | Thread.new do 23 | tries = 1 24 | 25 | begin 26 | Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) 27 | rescue SocketError 28 | if tries < max 29 | puts "Retrying DNS warmup: #{host}" 30 | tries += 1 31 | sleep rand 32 | retry 33 | else 34 | puts "DNS warmup failed: #{host}" 35 | host 36 | end 37 | end 38 | 39 | tries 40 | end 41 | end.map(&:value) 42 | end 43 | 44 | retries = results.sum - hosts.size 45 | nopes = results.count { |r| r == max } 46 | 47 | puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] 48 | -------------------------------------------------------------------------------- /packages/sandbox-node/src/script1.js: -------------------------------------------------------------------------------- 1 | import SQLiteDB from "better-sqlite3"; 2 | 3 | // the crsqlite package exports the path to the extension 4 | import { extensionPath } from "@vlcn.io/crsqlite"; 5 | 6 | const db = new SQLiteDB("f.db"); 7 | // suggested settings for best performance of sqlite 8 | // db.pragma("journal_mode = WAL"); 9 | // db.pragma("synchronous = NORMAL"); 10 | // load that extension with the `better-sqlite3` bindings 11 | db.loadExtension(extensionPath); 12 | 13 | db.exec(`DROP TABLE IF EXISTS items;`); 14 | db.exec(`DROP TABLE IF EXISTS items__crsql_clock;`); 15 | db.exec(`CREATE TABLE IF NOT EXISTS items ( 16 | "id" TEXT PRIMARY KEY not null, 17 | "data" TEXT 18 | );`); 19 | db.exec(`SELECT crsql_as_crr('items');`); 20 | 21 | const data = ["site", "data", "'some data'", "items", "'12345'", 1, 1]; 22 | const stmt = db.prepare( 23 | `INSERT INTO crsql_changes("site_id","cid","pk","table","val","db_version","col_version") VALUES (?,?,?,?,?,?,?)` 24 | ); 25 | stmt.bind(...data); 26 | 27 | stmt.run(); 28 | 29 | // await sleep(30000); 30 | 31 | function sleep(ms) { 32 | return new Promise((resolve) => { 33 | setTimeout(resolve, ms); 34 | }); 35 | } 36 | 37 | console.log(db.prepare("SELECT * FROM items").all()); 38 | console.log("THIS IS NEVER REACHED!"); 39 | -------------------------------------------------------------------------------- /packages/direct-connect-browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/direct-connect-browser", 3 | "type": "module", 4 | "version": "0.6.0", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "exports": { 12 | ".": "./dist/index.js", 13 | "./shared.worker.js": "./dist/shared.worker.js", 14 | "./dedicated.worker.js": "./dist/dedicated.worker.js" 15 | }, 16 | "devDependencies": { 17 | "@rollup/plugin-node-resolve": "^15.0.2", 18 | "rollup": "^3.21.8", 19 | "typescript": "^5.0.4" 20 | }, 21 | "scripts": { 22 | "build": "tsc --build", 23 | "watch": "tsc --build --watch", 24 | "test": "echo 'no tests yet'", 25 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git@github.com:vlcn-io/js.git", 30 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/direct-connect-browser" 31 | }, 32 | "dependencies": { 33 | "@vlcn.io-community/crsqlite-wasm": "workspace:*", 34 | "@vlcn.io-community/direct-connect-common": "workspace:*", 35 | "@vlcn.io-community/rx-tbl": "workspace:*", 36 | "@vlcn.io-community/xplat-api": "workspace:*" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/direct-connect-nodejs", 3 | "type": "module", 4 | "version": "0.7.1", 5 | "files": [ 6 | "dist", 7 | "src" 8 | ], 9 | "module": "dist/index.js", 10 | "main": "dist/index.js", 11 | "devDependencies": { 12 | "@types/better-sqlite3": "^7.6.4", 13 | "@types/node": "^18.16.1", 14 | "@types/throttle-debounce": "^5.0.0", 15 | "chokidar-cli": "^3.0.0", 16 | "typescript": "^5.0.4", 17 | "vitest": "^0.30.1" 18 | }, 19 | "scripts": { 20 | "build": "tsc --build", 21 | "watch": "tsc --build --watch", 22 | "test": "vitest run", 23 | "rmtdata": "rm dbs-test/*", 24 | "deep-clean": "rm -rf ./dist || true && rm tsconfig.tsbuildinfo || true" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git@github.com:vlcn-io/js.git", 29 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/direct-connect-nodejs" 30 | }, 31 | "dependencies": { 32 | "@vlcn.io/crsqlite": "^0.16.3", 33 | "@vlcn.io-community/direct-connect-common": "workspace:*", 34 | "@vlcn.io-community/xplat-api": "workspace:*", 35 | "better-sqlite3": "^12.2.0", 36 | "chokidar": "^3.5.3", 37 | "throttle-debounce": "^5.0.0", 38 | "winston": "^3.10.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/bun-sqlite-lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/bun-sqlite-lib", 3 | "version": "0.1.0", 4 | "description": "SQLite library loader for Bun with extension support", 5 | "type": "module", 6 | "main": "./dist/index.js", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "types": "./dist/index.d.ts", 11 | "import": "./dist/index.js" 12 | } 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/vlcn-io/cr-sqlite-js.git", 17 | "directory": "packages/bun-sqlite-lib" 18 | }, 19 | "license": "MIT", 20 | "files": [ 21 | "dist/", 22 | "README.md" 23 | ], 24 | "scripts": { 25 | "build": "tsc", 26 | "test": "bun test", 27 | "prepublishOnly": "bun run build" 28 | }, 29 | "optionalDependencies": { 30 | "@vlcn.io-community/libsqlite3-darwin-arm64": "3.47.2", 31 | "@vlcn.io-community/libsqlite3-darwin-x64": "3.47.2" 32 | }, 33 | "devDependencies": { 34 | "@types/bun": "^1.2.19", 35 | "@types/node": "^20.0.0", 36 | "typescript": "^5.9.2" 37 | }, 38 | "keywords": [ 39 | "sqlite", 40 | "sqlite3", 41 | "bun", 42 | "database", 43 | "extension", 44 | "loader" 45 | ], 46 | "bin": { 47 | "sqlite-path": "./dist/bin/sqlite-path.js" 48 | } 49 | } -------------------------------------------------------------------------------- /packages/direct-connect-nodejs/README.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io-community/direct-connect-nodejs 2 | 3 | Libraries for facilitating direct connect sync from nodejs. 4 | 5 | You can use these to easily create a REST server, WebSocket server, GRPC or anything else. 6 | 7 | ## Rest Example 8 | 9 | ```js 10 | import { 11 | DefaultConfig, 12 | ServiceDB, 13 | DBCache, 14 | SyncService, 15 | jsonDecode, 16 | jsonEncode, 17 | } from "@vlcn.io-community/direct-connect-nodejs"; 18 | const svcDb = new ServiceDB(DefaultConfig, true); 19 | const cache = new DBCache(DefaultConfig, svcDb.defaultSchemaProvider); 20 | let svc = new SyncService(DefaultConfig, cache, svcDb); 21 | 22 | app.get("/changes", (req, res) => { 23 | const query = url.parse(req.url).query; 24 | const msg = jsonDecode(JSON.parse(decodeURIComponent(query))); 25 | res.json(svc.getChanges(msg)); 26 | }); 27 | app.post("/changes", (req, res) => { 28 | const msg = jsonDecode(req.body); 29 | res.json(svc.applyChanges(msg)); 30 | }); 31 | app.post("/last-seen", (req, res) => { 32 | const msg = jsonDecode(req.body); 33 | res.json(svc.getLastSeen(msg)); 34 | }); 35 | app.post("/change-stream", (req, res) => { 36 | const msg = jsonDecode(req.body); 37 | const stream = svc.startOutboundStream(msg); 38 | // set up server sent event stream 39 | }); 40 | ``` 41 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/support/component.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | import { mount } from 'cypress/react18' 23 | 24 | // Augment the Cypress namespace to include type definitions for 25 | // your custom command. 26 | // Alternatively, can be defined in cypress/support/component.d.ts 27 | // with a at the top of your spec. 28 | declare global { 29 | namespace Cypress { 30 | interface Chainable { 31 | mount: typeof mount 32 | } 33 | } 34 | } 35 | 36 | Cypress.Commands.add('mount', mount) 37 | 38 | // Example use: 39 | // cy.mount() -------------------------------------------------------------------------------- /packages/crsqlite-wasm/src/DB2.ts: -------------------------------------------------------------------------------- 1 | import { DBAsync } from "@vlcn.io-community/xplat-api"; 2 | import { Mutex } from "async-mutex"; 3 | import { SQLITE_UTF8 } from "@vlcn.io/wa-sqlite"; 4 | 5 | // if we switch to SAHPool we'd just need DBInstanceMutexes? 6 | const wasmInstanceMutexes = new Map(); 7 | 8 | // export class DB implements DBAsync { 9 | // readonly #topLevelMutex; 10 | // // To do the improved query cache we'd need to hook into 11 | // // auth callbacks and understand used tables so we can do invalidations 12 | // // correctly. 13 | // readonly #queryCache = new Map>(); 14 | // #siteid: string | null = null; 15 | // // DB needs a top level mutex 16 | // // so we can serialize transactions 17 | // constructor(api: SQLiteAPI, db: number, filename: string) { 18 | // let mutex = wasmInstanceMutexes.get(api); 19 | // if (mutex == null) { 20 | // mutex = new Mutex(); 21 | // wasmInstanceMutexes.set(api, mutex); 22 | // } 23 | // this.#topLevelMutex = mutex; 24 | // } 25 | 26 | // get siteid(): string { 27 | // return this.#siteid!; 28 | // } 29 | 30 | // _setSiteid(siteid: string) { 31 | // if (this.#siteid) { 32 | // throw new Error("Site id already set"); 33 | // } 34 | // this.#siteid = siteid; 35 | // } 36 | // } 37 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Git 2 | .git 3 | .gitignore 4 | .github 5 | 6 | # Node modules (will be installed fresh in Docker) 7 | **/node_modules 8 | 9 | # Build outputs (but keep demo build for Docker) 10 | **/build 11 | !demos/expo-crsql-demo/build 12 | **/dist 13 | **/.next 14 | **/.expo 15 | **/out 16 | 17 | # iOS/Android native builds 18 | **/ios 19 | **/android 20 | 21 | # Development files 22 | **/.vscode 23 | **/.idea 24 | *.swp 25 | *.swo 26 | *~ 27 | .DS_Store 28 | 29 | # Test files 30 | **/coverage 31 | **/*.test.* 32 | **/*.spec.* 33 | **/tests 34 | **/test 35 | **/__tests__ 36 | **/cypress 37 | packages/browser-tests 38 | packages/node-tests 39 | packages/xplat-tests 40 | 41 | # Documentation 42 | *.md 43 | docs/ 44 | LICENSE 45 | 46 | # Environment files 47 | **/.env* 48 | !**/.env.example 49 | 50 | # Log files 51 | *.log 52 | npm-debug.log* 53 | yarn-debug.log* 54 | yarn-error.log* 55 | 56 | # TypeScript build info 57 | *.tsbuildinfo 58 | 59 | # Temporary files 60 | tmp/ 61 | temp/ 62 | 63 | # WASM build artifacts (will be built in Docker) 64 | wa-sqlite 65 | rust 66 | emsdk 67 | 68 | # Keep only essential files for Docker build 69 | !package.json 70 | !bun.lock 71 | !tsconfig.json 72 | !demos/expo-crsql-demo/package.json 73 | !demos/expo-crsql-demo/tsconfig.json 74 | !demos/expo-crsql-demo/build.ts 75 | !demos/expo-crsql-demo/server/ -------------------------------------------------------------------------------- /packages/ws-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vlcn.io-community/ws-demo", 3 | "type": "module", 4 | "private": true, 5 | "version": "1.1.2", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "module": "dist/index.js", 11 | "main": "dist/index.js", 12 | "scripts": { 13 | "dev": "vite", 14 | "ws": "node ./server.js", 15 | "build": "tsc && vite build", 16 | "preview": "vite preview", 17 | "test": "echo no tests here yet" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git@github.com:vlcn-io/js.git", 22 | "directory": "https://github.com/vlcn-io/js/tree/main/js/packages/ws-client" 23 | }, 24 | "dependencies": { 25 | "@vlcn.io-community/crsqlite-wasm": "workspace:*", 26 | "@vlcn.io-community/react": "workspace:*", 27 | "@vlcn.io-community/ws-browserdb": "workspace:*", 28 | "@vlcn.io-community/ws-client": "workspace:*", 29 | "@vlcn.io-community/ws-server": "workspace:*", 30 | "@vlcn.io-community/xplat-api": "workspace:*", 31 | "@vlcn.io-community/rx-tbl": "workspace:*", 32 | "express": "^4.18.2", 33 | "react": "^18.2.0", 34 | "react-dom": "^18.2.0", 35 | "vite": "^4.2.2" 36 | }, 37 | "devDependencies": { 38 | "@types/react": "^18.0.37", 39 | "@types/react-dom": "^18.2.4", 40 | "typescript": "^5.0.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/secrets: -------------------------------------------------------------------------------- 1 | # Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, 2 | # and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either 3 | # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. 4 | 5 | # security add-generic-password -a "KAMAL_REGISTRY_PASSWORD" -s "expo-crsql-demo" -w "$(osascript -e 'text returned of (display dialog "Password:" default answer "" with hidden answer)')" 6 | 7 | 8 | # Option 1: Read secrets from the environment 9 | KAMAL_REGISTRY_PASSWORD=$(security find-generic-password -a "KAMAL_REGISTRY_PASSWORD" -s "expo-crsql-demo" -w 2>/dev/null || echo "") 10 | 11 | # Option 2: Read secrets via a command 12 | # RAILS_MASTER_KEY=$(cat config/master.key) 13 | 14 | # Option 3: Read secrets via kamal secrets helpers 15 | # These will handle logging in and fetching the secrets in as few calls as possible 16 | # There are adapters for 1Password, LastPass + Bitwarden 17 | # 18 | # SECRETS=$(kamal secrets fetch --adapter 1password --account my-account --from MyVault/MyItem KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY) 19 | # KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS) 20 | # RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS) 21 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/.kamal/hooks/pre-build.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # A sample pre-build hook 4 | # 5 | # Checks: 6 | # 1. We have a clean checkout 7 | # 2. A remote is configured 8 | # 3. The branch has been pushed to the remote 9 | # 4. The version we are deploying matches the remote 10 | # 11 | # These environment variables are available: 12 | # KAMAL_RECORDED_AT 13 | # KAMAL_PERFORMER 14 | # KAMAL_VERSION 15 | # KAMAL_HOSTS 16 | # KAMAL_ROLES (if set) 17 | # KAMAL_DESTINATION (if set) 18 | 19 | if [ -n "$(git status --porcelain)" ]; then 20 | echo "Git checkout is not clean, aborting..." >&2 21 | git status --porcelain >&2 22 | exit 1 23 | fi 24 | 25 | first_remote=$(git remote) 26 | 27 | if [ -z "$first_remote" ]; then 28 | echo "No git remote set, aborting..." >&2 29 | exit 1 30 | fi 31 | 32 | current_branch=$(git branch --show-current) 33 | 34 | if [ -z "$current_branch" ]; then 35 | echo "Not on a git branch, aborting..." >&2 36 | exit 1 37 | fi 38 | 39 | remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) 40 | 41 | if [ -z "$remote_head" ]; then 42 | echo "Branch not pushed to remote, aborting..." >&2 43 | exit 1 44 | fi 45 | 46 | if [ "$KAMAL_VERSION" != "$remote_head" ]; then 47 | echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 48 | exit 1 49 | fi 50 | 51 | exit 0 52 | -------------------------------------------------------------------------------- /packages/sandbox/slurp.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * During the build step, this script will slurp all the schemas in the 3 | * schemas directory and insert them into the service database. 4 | * 5 | * This allows the service to dnymically load schemas and apply schemas 6 | * as new databases need to be created. 7 | */ 8 | 9 | import fs from "fs"; 10 | import { 11 | DefaultConfig, 12 | ServiceDB, 13 | cryb64, 14 | } from "@vlcn.io-community/direct-connect-nodejs"; 15 | import path from "path"; 16 | 17 | const dir = "./src/schemas"; 18 | 19 | async function slurp() { 20 | const schemas = await Promise.all( 21 | fs.readdirSync(dir).map((file) => { 22 | const filePath = path.join(dir, file); 23 | console.log(filePath); 24 | return import("./" + filePath); 25 | }) 26 | ); 27 | 28 | // INSERT OR IGNORE each schema 29 | const svcDb = new ServiceDB(DefaultConfig, true); 30 | const db = svcDb.__internal_getDb(); 31 | db.transaction(() => { 32 | for (const mod of schemas) { 33 | const s = mod.default; 34 | db.prepare( 35 | `INSERT OR IGNORE INTO schema (namespace, name, version, content, active) VALUES (?, ?, ?, ?, ?);` 36 | ).run( 37 | s.namespace, 38 | s.name, 39 | cryb64(s.content), 40 | s.content, 41 | s.active ? 1 : 0 42 | ); 43 | } 44 | })(); 45 | } 46 | 47 | slurp(); 48 | -------------------------------------------------------------------------------- /packages/browser-tests/src/typescript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/id/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest"; 2 | import { nanoid, customAlphabet, urlAlphabet } from "nanoid"; 3 | 4 | describe("id generation", () => { 5 | it("should generate unique IDs", () => { 6 | const id1 = nanoid(); 7 | const id2 = nanoid(); 8 | 9 | expect(id1).toBeDefined(); 10 | expect(id2).toBeDefined(); 11 | expect(id1).not.toBe(id2); 12 | }); 13 | 14 | it("should generate IDs of specified length", () => { 15 | const id = nanoid(10); 16 | expect(id.length).toBe(10); 17 | 18 | const longId = nanoid(32); 19 | expect(longId.length).toBe(32); 20 | }); 21 | 22 | it("should use custom alphabet", () => { 23 | const customNanoid = customAlphabet("0123456789", 10); 24 | const id = customNanoid(); 25 | 26 | expect(id.length).toBe(10); 27 | expect(/^[0-9]+$/.test(id)).toBe(true); 28 | }); 29 | 30 | it("should generate URL-safe IDs", () => { 31 | const id = nanoid(); 32 | 33 | // Check that ID only contains URL-safe characters 34 | for (const char of id) { 35 | expect(urlAlphabet).toContain(char); 36 | } 37 | }); 38 | 39 | it("should generate many unique IDs without collision", () => { 40 | const ids = new Set(); 41 | const count = 10000; 42 | 43 | for (let i = 0; i < count; i++) { 44 | ids.add(nanoid()); 45 | } 46 | 47 | expect(ids.size).toBe(count); 48 | }); 49 | }); -------------------------------------------------------------------------------- /packages/id/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @vlcn.io/id 2 | 3 | ## 0.1.0 4 | 5 | ### Minor Changes 6 | 7 | - e4a0a42: v0.16.0-next 8 | 9 | ### Patch Changes 10 | 11 | - d485812: prepare `tables_used` query, correctly unzip native library from pre-builds 12 | - 6f0ccac: fix error where separate connections would not report the correct db version 13 | 14 | ## 0.1.0-next.2 15 | 16 | ### Patch Changes 17 | 18 | - fix error where separate connections would not report the correct db version 19 | 20 | ## 0.1.0-next.1 21 | 22 | ### Patch Changes 23 | 24 | - prepare `tables_used` query, correctly unzip native library from pre-builds 25 | 26 | ## 0.1.0-next.0 27 | 28 | ### Minor Changes 29 | 30 | - v0.16.0-next 31 | 32 | ## 0.0.3 33 | 34 | ### Patch Changes 35 | 36 | - deploy correct build 37 | 38 | ## 0.0.2 39 | 40 | ### Patch Changes 41 | 42 | - bbb2c7f: fixes for react strict mode 43 | - 08f13fb: react strict mode fiex, migrator fixes, typed-sql basic support, ws replication, db provider hooks 44 | - 18b14c3: initial publish 45 | - f327068: rebuild 46 | 47 | ## 0.0.2-next.3 48 | 49 | ### Patch Changes 50 | 51 | - react strict mode fiex, migrator fixes, typed-sql basic support, ws replication, db provider hooks 52 | 53 | ## 0.0.2-next.2 54 | 55 | ### Patch Changes 56 | 57 | - rebuild 58 | 59 | ## 0.0.2-next.1 60 | 61 | ### Patch Changes 62 | 63 | - fixes for react strict mode 64 | 65 | ## 0.0.2-next.0 66 | 67 | ### Patch Changes 68 | 69 | - initial publish 70 | -------------------------------------------------------------------------------- /packages/browser-tests/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/browser-tests/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | // 12 | // 13 | // -- This is a parent command -- 14 | // Cypress.Commands.add('login', (email, password) => { ... }) 15 | // 16 | // 17 | // -- This is a child command -- 18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 19 | // 20 | // 21 | // -- This is a dual command -- 22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 23 | // 24 | // 25 | // -- This will overwrite an existing command -- 26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 27 | // 28 | // declare global { 29 | // namespace Cypress { 30 | // interface Chainable { 31 | // login(email: string, password: string): Chainable 32 | // drag(subject: string, options?: Partial): Chainable 33 | // dismiss(subject: string, options?: Partial): Chainable 34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable 35 | // } 36 | // } 37 | // } -------------------------------------------------------------------------------- /demos/expo-crsql-demo/ios/crsqldemo/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryFileTimestamp 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | 0A2A.1 21 | 3B52.1 22 | C617.1 23 | 24 | 25 | 26 | NSPrivacyAccessedAPIType 27 | NSPrivacyAccessedAPICategoryDiskSpace 28 | NSPrivacyAccessedAPITypeReasons 29 | 30 | E174.1 31 | 85F4.1 32 | 33 | 34 | 35 | NSPrivacyAccessedAPIType 36 | NSPrivacyAccessedAPICategorySystemBootTime 37 | NSPrivacyAccessedAPITypeReasons 38 | 39 | 35F9.1 40 | 41 | 42 | 43 | NSPrivacyCollectedDataTypes 44 | 45 | NSPrivacyTracking 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /demos/expo-crsql-demo/server/src/APITester.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, type FormEvent } from "react"; 2 | 3 | export function APITester() { 4 | const responseInputRef = useRef(null); 5 | 6 | const testEndpoint = async (e: FormEvent) => { 7 | e.preventDefault(); 8 | 9 | try { 10 | const form = e.currentTarget; 11 | const formData = new FormData(form); 12 | const endpoint = formData.get("endpoint") as string; 13 | const url = new URL(endpoint, location.href); 14 | const method = formData.get("method") as string; 15 | const res = await fetch(url, { method }); 16 | 17 | const data = await res.json(); 18 | responseInputRef.current!.value = JSON.stringify(data, null, 2); 19 | } catch (error) { 20 | responseInputRef.current!.value = String(error); 21 | } 22 | }; 23 | 24 | return ( 25 |
26 |
27 | 31 | 32 | 35 |
36 |