diff --git a/jest.config.js b/jest.config.js index 8b006e2..c3f3623 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,5 +6,8 @@ module.exports = { moduleNameMapper: { "@syncedstore/core": "/packages/core/src", "@syncedstore/yjs-reactive-bindings": "/packages/yjs-reactive-bindings/src", + "solid-js/web": "/node_modules/solid-js/web/dist/web.cjs", + "solid-js/store": "/node_modules/solid-js/store/dist/store.cjs", + "solid-js": "/node_modules/solid-js/dist/solid.cjs", }, }; diff --git a/package-lock.json b/package-lock.json index 889daf3..be98dba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33397,6 +33397,15 @@ "node": ">= 10" } }, + "node_modules/solid-js": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.6.10.tgz", + "integrity": "sha512-Sf0e6PQCEFkFtbPq0L+93Ua81YQOefBEbvDJ0YXT92b6Lzw0k7UvzSd2l1BbYM+yzE3UmepU1tyMDc/3nIByjA==", + "dev": true, + "dependencies": { + "csstype": "^3.1.0" + } + }, "node_modules/sorcery": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz", @@ -37964,7 +37973,8 @@ "devDependencies": { "@vue/reactivity": "^3.2.21", "microbundle": "^0.13.0", - "rimraf": "^3.0.2" + "rimraf": "^3.0.2", + "solid-js": "^1.6.10" }, "peerDependencies": { "yjs": "^13.5.13" @@ -42102,7 +42112,8 @@ "@types/eslint": "6.8.0", "@vue/reactivity": "^3.2.21", "microbundle": "^0.13.0", - "rimraf": "^3.0.2" + "rimraf": "^3.0.2", + "solid-js": "^1.6.10" } }, "@syncedstore/react": { @@ -60606,6 +60617,15 @@ "socks": "^2.6.2" } }, + "solid-js": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.6.10.tgz", + "integrity": "sha512-Sf0e6PQCEFkFtbPq0L+93Ua81YQOefBEbvDJ0YXT92b6Lzw0k7UvzSd2l1BbYM+yzE3UmepU1tyMDc/3nIByjA==", + "dev": true, + "requires": { + "csstype": "^3.1.0" + } + }, "sorcery": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz", diff --git a/packages/core/package.json b/packages/core/package.json index 982ae98..f890480 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,6 +38,7 @@ }, "devDependencies": { "@vue/reactivity": "^3.2.21", + "solid-js": "^1.6.10", "microbundle": "^0.13.0", "rimraf": "^3.0.2" }, diff --git a/packages/core/src/array.ts b/packages/core/src/array.ts index 14b820f..68dfb13 100644 --- a/packages/core/src/array.ts +++ b/packages/core/src/array.ts @@ -207,6 +207,17 @@ export function crdtArray(initializer: T[], arr = new Y.Array()) { if (p === "length") { return arr.length; } + + // proxy-trap to enable an idiomatic use of arrays and objects in Solid's Flow-component (without spreading) + // if (typeof p === "symbol" && p !== $reactiveproxy && p !== $reactive && p !== $skipreactive) { + // let ic = receiver[$reactiveproxy]?.implicitObserver; + // // (arr as any)._implicitObserver = ic; + // return arr.map((item) => { + // const ret = parseYjsReturnValue(item, ic); + // return ret; + // }); + // } + // forward to arrayimplementation const ret = Reflect.get(target, p, receiver); return ret; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 47482a5..7255ea2 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -11,7 +11,7 @@ import { } from "yjs"; import { crdtDoc, DocTypeDescription } from "./doc"; -export { enableMobxBindings, enableVueBindings } from "@syncedstore/yjs-reactive-bindings"; +export { enableMobxBindings, enableVueBindings, enableSolidBindings } from "@syncedstore/yjs-reactive-bindings"; export { Box, boxed } from "./boxed"; export * from "./util"; /** diff --git a/packages/core/test/solid.test.ts b/packages/core/test/solid.test.ts new file mode 100644 index 0000000..d7889b5 --- /dev/null +++ b/packages/core/test/solid.test.ts @@ -0,0 +1,62 @@ +import { createEffect, createRoot } from "solid-js"; +import * as solid from "solid-js/store"; +import { createMutable } from "solid-js/store"; + +import { enableSolidBindings, syncedStore, Y } from "../src"; + +describe("solid", () => { + type StoreType = { + arr: number[]; + object: { + nested?: number; + }; + todosNotBoxed: { text: string; completed: boolean }[]; + }; + + let fnSpy1: jest.Mock; + let fnSpy2: jest.Mock; + let implicitStore1: StoreType; + let doc1: Y.Doc; + let doc2: Y.Doc; + let store: StoreType; + + beforeEach(() => { + enableSolidBindings(solid); + fnSpy1 = jest.fn(() => {}); + fnSpy2 = jest.fn(() => {}); + + doc1 = new Y.Doc(); + + store = syncedStore( + { + arr: [], + object: {} as { nested?: number }, + todos: [], + todosNotBoxed: [], + xml: "xml" as "xml", + }, + doc1 + ); + }); + + it("indexOf works on store", async () => { + let renderCount = 0; + + createRoot(() => { + createEffect(() => { + renderCount++; + // should first log undefined then 'text' + console.log(store.todosNotBoxed[0]); + }); + queueMicrotask(() => { + store.todosNotBoxed.push({ + text: "text", + completed: false, + }); + }); + }); + + await new Promise((resolve) => setTimeout(() => resolve(), 500)); + expect(renderCount).toBe(2); + }); +}); diff --git a/packages/yjs-reactive-bindings/src/index.ts b/packages/yjs-reactive-bindings/src/index.ts index 138ade3..5ac454a 100644 --- a/packages/yjs-reactive-bindings/src/index.ts +++ b/packages/yjs-reactive-bindings/src/index.ts @@ -105,4 +105,9 @@ export function makeYDocObservable(doc: Y.Doc) { }); } -export { enableMobxBindings, enableReactiveBindings, enableVueBindings } from "./observableProvider"; +export { + enableMobxBindings, + enableReactiveBindings, + enableVueBindings, + enableSolidBindings, +} from "./observableProvider"; diff --git a/packages/yjs-reactive-bindings/src/observableProvider.ts b/packages/yjs-reactive-bindings/src/observableProvider.ts index 45f1eab..a3857c1 100644 --- a/packages/yjs-reactive-bindings/src/observableProvider.ts +++ b/packages/yjs-reactive-bindings/src/observableProvider.ts @@ -73,6 +73,31 @@ export function enableVueBindings(vue: any) { customReaction = undefined; } +/** + * Enable Solid integration + * + * @param solid An instance of Solid, e.g. import * as solid from "solid/store"; + */ +export function enableSolidBindings(solid: any) { + customCreateAtom = function (name: any, onBecomeObserved: any) { + let id = 0; + const data = solid.createMutable({ data: id }); + const atom = { + reportObserved() { + return data.data as any as boolean; + }, + reportChanged() { + data.data = ++id; + }, + }; + if (onBecomeObserved) { + onBecomeObserved(); + } + return atom; + }; + customReaction = undefined; +} + export function enableReactiveBindings(reactive: any) { customCreateAtom = function (name, onBecomeObserved, onBecomeUnobserved) { // TMP