├── 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 |
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 |

12 |

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 |
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/packages/bun-sqlite-lib/src/bin/sqlite-path.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { getSQLitePath, isSQLiteAvailable, getSQLiteVersion } from '../index.js';
4 |
5 | const args = process.argv.slice(2);
6 | const command = args[0];
7 |
8 | switch (command) {
9 | case '--version':
10 | case '-v':
11 | console.log(`SQLite version: ${getSQLiteVersion()}`);
12 | break;
13 |
14 | case '--check':
15 | case '-c':
16 | if (isSQLiteAvailable()) {
17 | console.log('✅ SQLite library is available');
18 | console.log(`Path: ${getSQLitePath()}`);
19 | } else {
20 | console.error('❌ SQLite library not found');
21 | console.error('Install the platform-specific package:');
22 | console.error(` npm install @vlcn.io-community/libsqlite3-darwin-${process.arch === 'arm64' ? 'arm64' : 'x64'}`);
23 | process.exit(1);
24 | }
25 | break;
26 |
27 | case '--help':
28 | case '-h':
29 | console.log('Usage: sqlite-path [options]');
30 | console.log('');
31 | console.log('Options:');
32 | console.log(' (default) Print the path to the SQLite library');
33 | console.log(' --version Print the SQLite version');
34 | console.log(' --check Check if the library is available');
35 | console.log(' --help Show this help message');
36 | break;
37 |
38 | default:
39 | // Default: print the path
40 | try {
41 | console.log(getSQLitePath());
42 | } catch (error) {
43 | console.error('Error:', (error as Error).message);
44 | process.exit(1);
45 | }
46 | }
--------------------------------------------------------------------------------
/demos/expo-crsql-demo/components/Collapsible.tsx:
--------------------------------------------------------------------------------
1 | import { PropsWithChildren, useState } from 'react';
2 | import { StyleSheet, TouchableOpacity } from 'react-native';
3 |
4 | import { ThemedText } from '@/components/ThemedText';
5 | import { ThemedView } from '@/components/ThemedView';
6 | import { IconSymbol } from '@/components/ui/IconSymbol';
7 | import { Colors } from '@/constants/Colors';
8 | import { useColorScheme } from '@/hooks/useColorScheme';
9 |
10 | export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
11 | const [isOpen, setIsOpen] = useState(false);
12 | const theme = useColorScheme() ?? 'light';
13 |
14 | return (
15 |
16 | setIsOpen((value) => !value)}
19 | activeOpacity={0.8}>
20 |
27 |
28 | {title}
29 |
30 | {isOpen && {children}}
31 |
32 | );
33 | }
34 |
35 | const styles = StyleSheet.create({
36 | heading: {
37 | flexDirection: 'row',
38 | alignItems: 'center',
39 | gap: 6,
40 | },
41 | content: {
42 | marginTop: 6,
43 | marginLeft: 24,
44 | },
45 | });
46 |
--------------------------------------------------------------------------------
/demos/expo-crsql-demo/components/ThemedText.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Text, type TextProps } from 'react-native';
2 |
3 | import { useThemeColor } from '@/hooks/useThemeColor';
4 |
5 | export type ThemedTextProps = TextProps & {
6 | lightColor?: string;
7 | darkColor?: string;
8 | type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link';
9 | };
10 |
11 | export function ThemedText({
12 | style,
13 | lightColor,
14 | darkColor,
15 | type = 'default',
16 | ...rest
17 | }: ThemedTextProps) {
18 | const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text');
19 |
20 | return (
21 |
33 | );
34 | }
35 |
36 | const styles = StyleSheet.create({
37 | default: {
38 | fontSize: 16,
39 | lineHeight: 24,
40 | },
41 | defaultSemiBold: {
42 | fontSize: 16,
43 | lineHeight: 24,
44 | fontWeight: '600',
45 | },
46 | title: {
47 | fontSize: 32,
48 | fontWeight: 'bold',
49 | lineHeight: 32,
50 | },
51 | subtitle: {
52 | fontSize: 20,
53 | fontWeight: 'bold',
54 | },
55 | link: {
56 | lineHeight: 30,
57 | fontSize: 16,
58 | color: '#0a7ea4',
59 | },
60 | });
61 |
--------------------------------------------------------------------------------
/packages/ws-litefs/src/internal/util.ts:
--------------------------------------------------------------------------------
1 | import { Config, internal } from "@vlcn.io-community/ws-server";
2 | import fs from "fs";
3 | import logger from "../logger.js";
4 |
5 | export const util = {
6 | async readPrimaryFileIfExists(config: Config): Promise {
7 | return fs.promises
8 | .readFile(config.dbFolder + "/.primary", {
9 | encoding: "utf-8",
10 | })
11 | .then((content) => {
12 | return content.split("\n")[0];
13 | })
14 | .catch((e) => {
15 | return null;
16 | });
17 | },
18 |
19 | async getTxId(config: Config, room: string) {
20 | const content = await fs.promises.readFile(
21 | internal.getDbPath(room, config) + "-pos",
22 | { encoding: "utf-8" }
23 | );
24 | const [txidHex, _checksum] = content.split("/");
25 |
26 | if (txidHex.length != 16) {
27 | throw new Error("Unexpected txid length");
28 | }
29 |
30 | return BigInt("0x" + txidHex);
31 | },
32 | };
33 |
34 | export function waitUntil(
35 | config: Config,
36 | room: string,
37 | txid: bigint,
38 | notifier: InstanceType
39 | ) {
40 | return new Promise((resolve, reject) => {
41 | const removeListener = notifier.addListener(room, async () => {
42 | try {
43 | const currentTxid = await util.getTxId(config, room);
44 | if (currentTxid >= txid) {
45 | removeListener();
46 | resolve();
47 | }
48 | } catch (e) {
49 | logger.error(e);
50 | removeListener();
51 | reject(e);
52 | }
53 | });
54 | });
55 | }
56 |
--------------------------------------------------------------------------------
/demos/expo-crsql-demo/components/ui/IconSymbol.tsx:
--------------------------------------------------------------------------------
1 | // Fallback for using MaterialIcons on Android and web.
2 |
3 | import MaterialIcons from '@expo/vector-icons/MaterialIcons';
4 | import { SymbolWeight, SymbolViewProps } from 'expo-symbols';
5 | import { ComponentProps } from 'react';
6 | import { OpaqueColorValue, type StyleProp, type TextStyle } from 'react-native';
7 |
8 | type IconMapping = Record['name']>;
9 | type IconSymbolName = keyof typeof MAPPING;
10 |
11 | /**
12 | * Add your SF Symbols to Material Icons mappings here.
13 | * - see Material Icons in the [Icons Directory](https://icons.expo.fyi).
14 | * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.
15 | */
16 | const MAPPING = {
17 | 'house.fill': 'home',
18 | 'paperplane.fill': 'send',
19 | 'chevron.left.forwardslash.chevron.right': 'code',
20 | 'chevron.right': 'chevron-right',
21 | } as IconMapping;
22 |
23 | /**
24 | * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
25 | * This ensures a consistent look across platforms, and optimal resource usage.
26 | * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.
27 | */
28 | export function IconSymbol({
29 | name,
30 | size = 24,
31 | color,
32 | style,
33 | }: {
34 | name: IconSymbolName;
35 | size?: number;
36 | color: string | OpaqueColorValue;
37 | style?: StyleProp;
38 | weight?: SymbolWeight;
39 | }) {
40 | return ;
41 | }
42 |
--------------------------------------------------------------------------------
/packages/ws-browserdb/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect, vi } from "vitest";
2 |
3 | describe("ws-browserdb", () => {
4 | it("should implement DB interface for WebSocket sync", () => {
5 | // Mock browser database
6 | const mockDb = {
7 | execA: vi.fn().mockResolvedValue([]),
8 | execO: vi.fn().mockResolvedValue([]),
9 | transaction: vi.fn((fn) => fn()),
10 | };
11 |
12 | // Test sync operations
13 | expect(mockDb.execA).toBeDefined();
14 | expect(mockDb.execO).toBeDefined();
15 | expect(mockDb.transaction).toBeDefined();
16 | });
17 |
18 | it("should handle WebSocket messages", () => {
19 | const messages: any[] = [];
20 | const mockWs = {
21 | send: vi.fn((msg) => messages.push(msg)),
22 | readyState: 1, // OPEN
23 | };
24 |
25 | // Simulate sending a sync message
26 | const syncMsg = { type: "sync", data: "test" };
27 | mockWs.send(JSON.stringify(syncMsg));
28 |
29 | expect(messages.length).toBe(1);
30 | expect(JSON.parse(messages[0])).toEqual(syncMsg);
31 | });
32 |
33 | it("should handle connection state changes", () => {
34 | let state = "disconnected";
35 |
36 | const connect = () => {
37 | state = "connecting";
38 | setTimeout(() => {
39 | state = "connected";
40 | }, 0);
41 | };
42 |
43 | const disconnect = () => {
44 | state = "disconnected";
45 | };
46 |
47 | expect(state).toBe("disconnected");
48 | connect();
49 | expect(state).toBe("connecting");
50 |
51 | disconnect();
52 | expect(state).toBe("disconnected");
53 | });
54 | });
--------------------------------------------------------------------------------
/packages/ws-client/src/worker/SyncService.ts:
--------------------------------------------------------------------------------
1 | import { createAndStartSyncedDB_Exclusive } from "../SyncedDB.js";
2 | import { Config } from "../config.js";
3 | import { StartSyncMsg, StopSyncMsg } from "./workerMsgTypes.js";
4 |
5 | /**
6 | * There should be one instance of this class per application.
7 | * Create this instance outside of the React lifecylce (if you're using React).
8 | *
9 | * Do we need this class?
10 | */
11 | export default class SyncService {
12 | /**
13 | * Map from dbid to SyncedDB
14 | */
15 | private readonly dbs = new Map<
16 | string,
17 | ReturnType
18 | >();
19 |
20 | constructor(private config: Config) {}
21 |
22 | async startSync(msg: StartSyncMsg) {
23 | const entry = this.dbs.get(msg.dbid);
24 | if (!entry) {
25 | const creator = createAndStartSyncedDB_Exclusive(
26 | this.config,
27 | msg.dbid,
28 | msg.transportOpts
29 | );
30 | this.dbs.set(msg.dbid, creator);
31 | await creator;
32 | } else {
33 | console.warn(`Already syncing db: ${msg.dbid}`);
34 | return;
35 | }
36 | }
37 |
38 | async stopSync(msg: StopSyncMsg) {
39 | // decrement reference count for the given db
40 | // if reference count is 0, stop sync for that db
41 | // TODO: can we understand when message ports close due to browser tab closing?
42 | // If we send a msg on a closed channel do we just get an error?
43 | const handle = this.dbs.get(msg.dbid);
44 | if (handle) {
45 | this.dbs.delete(msg.dbid);
46 | const db = await handle;
47 | db.stop();
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/ws-demo/src/main.tsx:
--------------------------------------------------------------------------------
1 | import schemaContent from "./schemas/main.sql?raw";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.js";
4 | import "./index.css";
5 | import { DBProvider } from "@vlcn.io-community/react";
6 | import React from "react";
7 |
8 | const hash = parseHash();
9 | const room = getRoomId(hash);
10 | if (room != hash.room) {
11 | hash.room = room;
12 | window.location.hash = writeHash(hash);
13 | }
14 | localStorage.setItem("room", room);
15 |
16 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
17 |
18 |
25 |
26 |
27 |
28 | );
29 |
30 | type HashBag = { [key: string]: string };
31 | function parseHash(): HashBag {
32 | const hash = window.location.hash;
33 | const ret: { [key: string]: string } = {};
34 | if (hash.length > 1) {
35 | const substr = hash.substring(1);
36 | const parts = substr.split(",");
37 | for (const part of parts) {
38 | const [key, value] = part.split("=");
39 | ret[key] = value;
40 | }
41 | }
42 |
43 | return ret;
44 | }
45 |
46 | function writeHash(hash: HashBag) {
47 | const parts = [];
48 | for (const key in hash) {
49 | parts.push(`${key}=${hash[key]}`);
50 | }
51 | return parts.join(",");
52 | }
53 |
54 | function getRoomId(hash: HashBag): string {
55 | return (
56 | hash.room ||
57 | localStorage.getItem("room") ||
58 | crypto.randomUUID().replaceAll("-", "")
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/demos/expo-crsql-demo/app/(tabs)/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Tabs } from "expo-router";
2 | import React from "react";
3 | import { Platform } from "react-native";
4 |
5 | import { HapticTab } from "@/components/HapticTab";
6 | import { IconSymbol } from "@/components/ui/IconSymbol";
7 | import TabBarBackground from "@/components/ui/TabBarBackground";
8 | import { Colors } from "@/constants/Colors";
9 | import { useColorScheme } from "@/hooks/useColorScheme";
10 |
11 | export default function TabLayout() {
12 | const colorScheme = useColorScheme();
13 |
14 | return (
15 |
30 | (
35 |
36 | ),
37 | }}
38 | />
39 | (
44 |
45 | ),
46 | }}
47 | />
48 | (
53 |
54 | ),
55 | }}
56 | />
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/packages/rx-query/README.md:
--------------------------------------------------------------------------------
1 | # @vlcn.io-community/rx-query
2 |
3 | WIP - more efficient query based reactivity.
4 |
5 | `rx-tbl` would re-run all queries that hit a given table when a write hit that table. This is problematic for apps with hundreds of queries against the same table.
6 |
7 | Why would this happen? Well imagine a presentation editor. You have little slide thumbnails or previews. Each of these previews would query a `component` table to get the components belonging to the slide. Any time you add a component on a slide, each preview would re-query the component table. If you have hundreds of slides this could be hundreds of queries with the dumb `rx-tbl` approach.
8 |
9 | `rx-query` looks at the write being made and finds the exact queries that would be impacted by that write.
10 |
11 | # Where this is efficient
12 |
13 | - Single table queries (`SELECT * FROM foo WHERE ...`)
14 | - Queries that do a single hop join (`SELECT * FROM foo JOIN bar ON foo.id = bar.foo_id WHERE ...`)
15 | - Updates that include WHERE conditions on the same columns used by select queries
16 | - Deletes that include WHERE conditions on the same columns used by select queries
17 | - Inserts
18 |
19 | # Where this is inefficient
20 |
21 | - Queries that do multi-hop joins (`SELECT foo.* FROM foo JOIN bar ON foo.id = bar.foo_id JOIN baz on bar.baz_id = baz.id`)
22 | - Updates or deletes that do not include WHERE conditions on the same columns used by select queries
23 | - When thousands of queries against the same table are active. We currently scan the list of active queries for a table on insert. This is fine up to a few hundred active queries against a single table. In the future we'll introduce range trees to optimize this case.
24 |
--------------------------------------------------------------------------------
/packages/react/src/stateHooks.ts:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 |
3 | // export function useThrottledState() {
4 | // /**
5 | // * - a query that provides the state
6 | // * - a function to set the state
7 | // * - a mutator to persist to the db
8 | // *
9 | // * If we're called back while we still have an outstanding write, ignore the callback
10 | // */
11 | // }
12 |
13 | /**
14 | * Convenience function for mirroring a prop into state and ensuring that the state is updated
15 | * when the prop changes.
16 | *
17 | * Removes all the dragons from the pattern and gives us fine grained control over updates.
18 | *
19 | * @param propValue
20 | * @returns
21 | */
22 | export function useCachedState(propValue: T): [T, (value: T) => void] {
23 | const [lastValue, setLastValue] = useState(propValue);
24 | const [currValue, setCurrValue] = useState(propValue);
25 | if (propValue !== lastValue) {
26 | setLastValue(propValue);
27 | setCurrValue(propValue);
28 | }
29 |
30 | return [
31 | currValue,
32 | (value: T) => {
33 | setCurrValue(value);
34 | },
35 | ];
36 | }
37 |
38 | export function useCachedBinding(
39 | attr: keyof React.AllHTMLAttributes,
40 | value: any,
41 | event: keyof React.DOMAttributes,
42 | handler: React.ReactEventHandler
43 | ) {
44 | const [lastValue, setLastValue] = useState(value);
45 | const [currValue, setCurrValue] = useState(value);
46 | if (value !== lastValue) {
47 | setLastValue(value);
48 | setCurrValue(value);
49 | }
50 |
51 | return {
52 | [attr]: currValue,
53 | [event]: (e: React.SyntheticEvent) => {
54 | setCurrValue((e.target as any)[attr]);
55 | handler(e);
56 | },
57 | };
58 | }
59 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "inputs": {
5 | "systems": "systems"
6 | },
7 | "locked": {
8 | "lastModified": 1731533236,
9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
10 | "owner": "numtide",
11 | "repo": "flake-utils",
12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "numtide",
17 | "repo": "flake-utils",
18 | "type": "github"
19 | }
20 | },
21 | "nixpkgs": {
22 | "locked": {
23 | "lastModified": 1756125398,
24 | "narHash": "sha256-XexyKZpf46cMiO5Vbj+dWSAXOnr285GHsMch8FBoHbc=",
25 | "owner": "NixOS",
26 | "repo": "nixpkgs",
27 | "rev": "3b9f00d7a7bf68acd4c4abb9d43695afb04e03a5",
28 | "type": "github"
29 | },
30 | "original": {
31 | "owner": "NixOS",
32 | "ref": "nixos-unstable",
33 | "repo": "nixpkgs",
34 | "type": "github"
35 | }
36 | },
37 | "root": {
38 | "inputs": {
39 | "flake-utils": "flake-utils",
40 | "nixpkgs": "nixpkgs"
41 | }
42 | },
43 | "systems": {
44 | "locked": {
45 | "lastModified": 1681028828,
46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
47 | "owner": "nix-systems",
48 | "repo": "default",
49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
50 | "type": "github"
51 | },
52 | "original": {
53 | "owner": "nix-systems",
54 | "repo": "default",
55 | "type": "github"
56 | }
57 | }
58 | },
59 | "root": "root",
60 | "version": 7
61 | }
62 |
--------------------------------------------------------------------------------
/packages/bun-sqlite-lib/test.ts:
--------------------------------------------------------------------------------
1 | import { setBundledSQLite, getSQLitePath, isSQLiteAvailable } from './dist/index.js';
2 | import { Database } from 'bun:sqlite';
3 |
4 | console.log('🧪 Testing bun-sqlite-lib...\n');
5 |
6 | // Check if SQLite is available
7 | console.log('Checking SQLite availability:', isSQLiteAvailable() ? '✅' : '❌');
8 | console.log('SQLite path:', getSQLitePath());
9 |
10 | // Set the bundled SQLite
11 | console.log('\nSetting bundled SQLite...');
12 | const dylibPath = setBundledSQLite();
13 | console.log('Set to:', dylibPath);
14 |
15 | // Test with Bun's Database
16 | console.log('\n📊 Testing with bun:sqlite...');
17 | const db = new Database(':memory:');
18 |
19 | // Check version
20 | const versionResult = db.query('SELECT sqlite_version() as version').get() as { version: string };
21 | console.log('SQLite version:', versionResult.version);
22 |
23 | // Test extension loading capability
24 | console.log('\n🔌 Testing extension loading...');
25 | try {
26 | // Try to load the CR-SQLite extension
27 | const crsqlitePath = '/Users/tom/Developer/cr-sqlite-js/node_modules/@vlcn.io/crsqlite/dist/crsqlite.dylib';
28 | db.loadExtension(crsqlitePath);
29 | console.log('✅ CR-SQLite extension loaded successfully!');
30 |
31 | // Test CR-SQLite functionality
32 | db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name TEXT)');
33 | db.exec("SELECT crsql_as_crr('test')");
34 |
35 | const siteId = db.query('SELECT crsql_site_id()').get();
36 | console.log('✅ CR-SQLite is working! Site ID:', siteId);
37 | } catch (error) {
38 | console.log('⚠️ Extension loading test:', (error as Error).message);
39 | console.log(' (This is expected if the extension file doesn\'t exist)');
40 | }
41 |
42 | db.close();
43 | console.log('\n✅ All tests completed!');
--------------------------------------------------------------------------------
/packages/rx-query/src/QueryRewriter.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Queries need to be re-written to ensure every queried table
3 | * returns its rowid.
4 | *
5 | * This helps us to materialize joins into the relation cache.
6 | * We need joins materialized so when a row, or set of rows in a tx,
7 | * are written we can create the joined representation of those rows
8 | * in-memory.
9 | *
10 | * This joined representation is then used to find the queries (which were
11 | * doing the joins in the first place) that need updating.
12 | *
13 | * What shall we do about aggregate queries?
14 | * Just not support them for now.
15 | * And re-run any time a row for their table is written
16 | * for which we overlap on the constraint.
17 | *
18 | * So they have some support.
19 | *
20 | * How shall we handle `OR`?
21 | * If any of those constraints match, we go for it.
22 | *
23 | * We can skip constraint matching approach to start and just map
24 | * from relation -> queries and re-run all queries for that relation
25 | * against _this single reconstructed row_.
26 | *
27 | * Eventually optimize by indexing the query constraints.
28 | */
29 | export function rewriteQuery(query: string): QueryAST {
30 | return query;
31 | }
32 |
33 | /**
34 | * Query examples to re-write:
35 | *
36 | * SELECT * FROM slide WHERE deck_id = ?;
37 | * SELECT id FROM slide;
38 | * SELECT * FROM slide WHERE id = ?;
39 | * SELECT * FROM slide WHERE update_time > ? AND create_time < ?;
40 | */
41 |
42 | /**
43 | * Any tables in `from` we need rowids for (handle if they alias the from).
44 | * Any tables in `join` conditions we need rowids for.
45 | *
46 | * Can we re-write to Aphrodite style expressions?
47 | *
48 | * We would need to when it comes time to evaluate the query against the single row.
49 | */
50 |
--------------------------------------------------------------------------------
/.github/workflows/js-tests.yaml:
--------------------------------------------------------------------------------
1 | on: pull_request
2 | name: "js-tests"
3 | jobs:
4 | build:
5 | name: Testing on ${{ matrix.os }}
6 | runs-on: ${{ matrix.os }}
7 | strategy:
8 | matrix:
9 | include:
10 | - os: ubuntu-latest
11 | # - os: windows-2022 # windows hangs indefintely when installing build tools
12 | # can't switch to node19 for windows until https://github.com/WiseLibs/better-sqlite3/pull/870 is resolved
13 | # as some js tests use better-sqlite3
14 | - os: macos-latest
15 | steps:
16 | - name: Checkout repo
17 | uses: actions/checkout@v4
18 | with:
19 | submodules: recursive
20 |
21 | - name: Load .env file
22 | uses: xom9ikk/dotenv@v2
23 |
24 | - name: Install wabt (linux)
25 | if: runner.os == 'Linux'
26 | run: sudo apt-get update; sudo apt install -y wabt
27 |
28 | - name: Install wabt and gmake (osx)
29 | if: runner.os == 'macOS'
30 | run: brew install wabt; brew install make; printf '%s\n%s\n' "/usr/local/opt/make/libexec/gnubin" "$(cat $GITHUB_PATH)" > $GITHUB_PATH
31 |
32 | - name: Setup Rust
33 | uses: dtolnay/rust-toolchain@stable
34 | with:
35 | targets: wasm32-unknown-unknown
36 |
37 | - name: Setup Node
38 | uses: actions/setup-node@v3
39 | with:
40 | node-version: 20.x
41 |
42 | - name: Install bun
43 | uses: oven-sh/setup-bun@v1
44 | with:
45 | bun-version: latest
46 |
47 | - name: Windows build tools
48 | if: runner.os == 'Windows'
49 | run: npm install --global windows-build-tools
50 |
51 | - name: Build
52 | run: make
53 |
54 | - name: Test
55 | run: make test
56 |
--------------------------------------------------------------------------------
/packages/browser-tests/cypress/component/wa-sqlite-wrapper.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 |
6 | describe("wa-sqlite-wrapper.cy.ts", () => {
7 | it("rolls back transactions on failure", async () => {
8 | const db = await crsqlite.open();
9 | await db.exec("CREATE TABLE foo (a);");
10 |
11 | try {
12 | await db.tx(async (tx) => {
13 | await tx.exec("INSERT INTO foo VALUES (1);");
14 | throw new Error();
15 | });
16 | } catch (e) {}
17 |
18 | const fooCount = await db.execA("SELECT count(*) FROM foo");
19 | expect(fooCount[0][0]).to.equal(0);
20 | });
21 |
22 | it("commits transactions on success", async () => {
23 | const db = await crsqlite.open();
24 | await db.exec("CREATE TABLE foo (a);");
25 |
26 | await db.tx(async (tx) => {
27 | await tx.exec("INSERT INTO foo VALUES (1);");
28 | });
29 |
30 | const fooCount = await db.execA("SELECT count(*) FROM foo");
31 | expect(fooCount[0][0]).to.equal(1);
32 | });
33 |
34 | it("serializes access to wa-sqlite", async () => {
35 | const db = await crsqlite.open();
36 | await db.exec("CREATE TABLE foo (a);");
37 |
38 | // just need the following to not throw
39 | await Promise.all([
40 | db.exec("INSERT INTO foo VALUES (1)"),
41 | db.exec("INSERT INTO foo VALUES (2)"),
42 | db.exec("INSERT INTO foo VALUES (3)"),
43 | db.exec("INSERT INTO foo VALUES (4)"),
44 | db.exec("INSERT INTO foo VALUES (5)"),
45 | db.exec("INSERT INTO foo VALUES (6)"),
46 | ]);
47 | });
48 | });
49 |
50 | // TODO:
51 | // test transaction coordination
52 |
--------------------------------------------------------------------------------