diff --git a/.gitignore b/.gitignore index f57862f..0c32205 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ /node_modules -/Packages -/DevPackages +/devpackages +/packages/**/node_modules +/packages/**/packages +/packages/**/wally.lock +/packages/**/README.md +/packages/**/LICENSE.md /sourcemap.json *.tsbuildinfo *.tgz diff --git a/bin/install.sh b/bin/install.sh deleted file mode 100644 index 4091d75..0000000 --- a/bin/install.sh +++ /dev/null @@ -1,4 +0,0 @@ -wally install -rojo sourcemap dev.project.json --output sourcemap.json -wally-package-types --sourcemap sourcemap.json Packages -wally-package-types --sourcemap sourcemap.json DevPackages diff --git a/bin/spec.server.luau b/bin/spec.server.luau deleted file mode 100644 index 81d6907..0000000 --- a/bin/spec.server.luau +++ /dev/null @@ -1,10 +0,0 @@ ---!nocheck - -local ReplicatedStorage = game:GetService("ReplicatedStorage") - -local Packages = ReplicatedStorage.Packages -local DevPackages = ReplicatedStorage.DevPackages - -local TestEZ = require(DevPackages.TestEZ) - -TestEZ.TestBootstrap:run({ Packages.charm.__tests__ }, TestEZ.Reporters.TextReporter) diff --git a/dev.project.json b/dev.project.json index 6b6d807..2114a90 100644 --- a/dev.project.json +++ b/dev.project.json @@ -6,14 +6,25 @@ "$className": "ReplicatedStorage", "DevPackages": { "$path": "DevPackages", - "Charm": { "$path": "src" } - }, - "Packages": { "$path": "Packages" } + "Charm": { + "$path": "packages/charm" + }, + "CharmSync": { + "$path": "packages/charm-sync" + }, + "ReactCharm": { + "$path": "packages/react" + } + } }, "TestService": { "$className": "TestService", - "$properties": { "ExecuteWithStudioRun": true }, - "spec": { "$path": "bin/spec.server.luau" } + "$properties": { + "ExecuteWithStudioRun": true + }, + "Tests": { + "$path": "tests" + } } } } diff --git a/package.json b/package.json index aeaa219..36d7fc4 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,11 @@ { - "name": "@rbxts/charm", - "version": "0.7.6", - "description": "Atomic state management for Roblox", + "private": true, "scripts": { "lint": "sh bin/check.sh", - "wally": "sh bin/install.sh" - }, - "main": "src/init.lua", - "types": "src/index.d.ts", - "keywords": [ - "roblox", - "typescript", - "roblox-ts", - "state" - ], - "author": "littensy", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/littensy/charm" - }, - "bugs": { - "url": "https://github.com/littensy/charm/issues" - }, - "homepage": "https://github.com/littensy/charm", - "files": [ - "src", - "!src/__tests__" - ], - "publishConfig": { - "access": "public" + "wally:install": "pnpm recursive run wally:install && rojo sourcemap dev.project.json --output sourcemap.json" }, "devDependencies": { "@rbxts/compiler-types": "3.0.0-types.0", - "@rbxts/react": "^0.4.0", "@rbxts/types": "^1.0.809", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", @@ -42,15 +14,7 @@ "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-roblox-ts": "^0.0.36", "prettier": "^3.3.3", - "roblox-ts": "3.0.0", + "roblox-ts": "^3.0.0", "typescript": "^5.6.2" - }, - "peerDependencies": { - "@rbxts/react": "*" - }, - "peerDependenciesMeta": { - "@rbxts/react": { - "optional": true - } } } diff --git a/.luaurc b/packages/charm-sync/.luaurc similarity index 100% rename from .luaurc rename to packages/charm-sync/.luaurc diff --git a/packages/charm-sync/default.project.json b/packages/charm-sync/default.project.json new file mode 100644 index 0000000..9c44c7c --- /dev/null +++ b/packages/charm-sync/default.project.json @@ -0,0 +1,6 @@ +{ + "name": "charm-sync", + "tree": { + "$path": "src" + } +} diff --git a/packages/charm-sync/package.json b/packages/charm-sync/package.json new file mode 100644 index 0000000..6ac444d --- /dev/null +++ b/packages/charm-sync/package.json @@ -0,0 +1,16 @@ +{ + "name": "@rbxts/charm-sync", + "version": "0.1.0", + "description": "Sync server state with clients using Charm", + "repository": "littensy/charm", + "license": "MIT", + "main": "src/init.luau", + "types": "src/index.d.ts", + "scripts": { + "prepare": "cp ../../LICENSE.md . && cp ../../README.md .", + "wally:install": "wally install" + }, + "dependencies": { + "@rbxts/charm": "workspace:*" + } +} diff --git a/src/sync/client.luau b/packages/charm-sync/src/client.luau similarity index 89% rename from src/sync/client.luau rename to packages/charm-sync/src/client.luau index a5901d3..a2c2de9 100644 --- a/src/sync/client.luau +++ b/packages/charm-sync/src/client.luau @@ -1,14 +1,13 @@ -local store = require(script.Parent.Parent.store) -local types = require(script.Parent.Parent.types) -type Atom = types.Atom -type SyncPayload = types.SyncPayload +local Charm = require(script.Parent.Parent.Charm) local patch = require(script.Parent.patch) +local types = require(script.Parent.types) +type SyncPayload = types.SyncPayload type ClientOptions = { --[=[ The atoms to synchronize with the server. ]=] - atoms: { [string]: Atom }, + atoms: { [string]: Charm.Atom }, --[=[ Whether to ignore patches sent before the client has been hydrated. Default is `true`. @@ -52,7 +51,7 @@ local function client(options: ClientOptions): ClientSyncer hydrated = true - store.batch(function() + Charm.batch(function() for key, state in next, payload.data do local atom = atoms[key] diff --git a/packages/charm-sync/src/index.d.ts b/packages/charm-sync/src/index.d.ts new file mode 100644 index 0000000..ef2b8d3 --- /dev/null +++ b/packages/charm-sync/src/index.d.ts @@ -0,0 +1,163 @@ +import { Atom, Molecule } from "@rbxts/charm"; + +export = CharmSync; +export as namespace CharmSync; + +type Cleanup = () => void; + +declare namespace CharmSync { + type AtomMap = Record>; + + type MoleculeMap = Record>; + + /** + * Recursively infers the type of the state produced by a map of molecules. + */ + type StateOfMap = { + [P in keyof T]: T[P] extends Molecule ? State : never; + }; + + /** + * A special value that denotes the absence of a value. Used to represent + * removed values in patches. + */ + interface None { + readonly __none: "__none"; + } + + type MaybeNone = undefined extends T ? None : never; + + /** + * Creates a `ClientSyncer` object that receives patches from the server and + * applies them to the local state. + * + * @client + * @param options The atoms to synchronize with the server. + * @returns A `ClientSyncer` object. + */ + function client(options: ClientOptions): ClientSyncer; + + /** + * Creates a `ServerSyncer` object that sends patches to the client and + * hydrates the client's state. + * + * @server + * @param options The atoms to synchronize with the client. + * @returns A `ServerSyncer` object. + */ + function server(options: ServerOptions): ServerSyncer; + + /** + * Checks whether a value is `None`. If `true`, the value is scheduled to be + * removed from the state when the patch is applied. + * + * @param value The value to check. + * @returns `true` if the value is `None`, otherwise `false`. + */ + function isNone(value: unknown): value is None; + + type DataTypes = { + [P in keyof CheckableTypes as P extends keyof CheckablePrimitives ? never : P]: CheckableTypes[P]; + }; + + /** + * A type that should not be made partial in patches. + */ + type DataType = DataTypes[keyof DataTypes]; + + /** + * A partial patch that can be applied to the state to update it. Represents + * the difference between the current state and the next state. + * + * If a value was removed, it is replaced with `None`. This can be checked + * using the `sync.isNone` function. + */ + type SyncPatch = + | MaybeNone + | (State extends ReadonlyMap | Map + ? ReadonlyMap | None> + : State extends Set | ReadonlySet + ? ReadonlyMap + : State extends readonly (infer T)[] + ? readonly (SyncPatch | None | undefined)[] + : State extends DataType + ? State + : State extends object + ? { readonly [P in keyof State]?: SyncPatch } + : State); + + /** + * A payload that can be sent from the server to the client to synchronize + * state between the two. + */ + type SyncPayload = + | { type: "init"; data: StateOfMap } + | { type: "patch"; data: SyncPatch> }; + + interface ClientOptions { + /** + * The atoms to synchronize with the server. + */ + atoms: Atoms; + /** + * Whether to ignore patches sent before the client has been hydrated. + * Defaults to `true`. + */ + ignoreUnhydrated?: boolean; + } + + interface ServerOptions { + /** + * The atoms to synchronize with the client. + */ + atoms: Molecules; + /** + * The interval at which to send patches to the client, in seconds. + * Defaults to `0` (patches are sent up to once per frame). Set to a + * negative value to disable automatic syncing. + */ + interval?: number; + /** + * Whether the history of state changes since the client's last update + * should be preserved. This is useful for values that change multiple times + * per frame, where each individual change is important. Defaults to `false`. + * + * If `true`, the broadcaster will send a list of payloads to the client + * instead of a single payload. The client will apply each payload in order + * to reconstruct the state's changes over time. + */ + preserveHistory?: boolean; + } + + interface ClientSyncer { + /** + * Applies a patch or initializes the state of the atoms with the given + * payload from the server. + * + * @param payloads The patches or hydration payloads to apply. + */ + sync(...payloads: SyncPayload[]): void; + } + + interface ServerSyncer { + /** + * Sets up a subscription to each atom that schedules a patch to be sent to + * the client whenever the state changes. When a change occurs, the `callback` + * is called with the player and the payloads to send. + * + * Note that a `payload` object should not be mutated. If you need to modify + * a payload, apply the changes to a copy of the object. + * + * @param callback The function to call when the state changes. + * @returns A cleanup function that unsubscribes all listeners. + */ + connect(callback: (player: Player, ...payloads: SyncPayload[]) => void): Cleanup; + /** + * Hydrates the client's state with the server's state. This should be + * called when a player joins the game and requires the server's state. + * + * @param player The player to hydrate. + */ + hydrate(player: Player): void; + } +} diff --git a/src/sync/init.luau b/packages/charm-sync/src/init.luau similarity index 80% rename from src/sync/init.luau rename to packages/charm-sync/src/init.luau index 7a236f4..b57c18f 100644 --- a/src/sync/init.luau +++ b/packages/charm-sync/src/init.luau @@ -1,6 +1,9 @@ local client = require(script.client) local patch = require(script.patch) local server = require(script.server) +local types = require(script.types) + +export type SyncPayload = types.SyncPayload --[=[ Synchronizes state between the client and server. The server sends patches diff --git a/src/utils/setInterval.luau b/packages/charm-sync/src/interval.luau similarity index 85% rename from src/utils/setInterval.luau rename to packages/charm-sync/src/interval.luau index e4650e7..31df067 100644 --- a/src/utils/setInterval.luau +++ b/packages/charm-sync/src/interval.luau @@ -7,7 +7,7 @@ local RunService = game:GetService("RunService") @param seconds The interval in seconds. @return A function that cancels the interval. ]=] -local function setInterval(callback: () -> (), seconds: number): () -> () +local function interval(callback: () -> (), seconds: number): () -> () if seconds < 0 then return function() end end @@ -27,4 +27,4 @@ local function setInterval(callback: () -> (), seconds: number): () -> () end end -return setInterval +return interval diff --git a/src/sync/patch.luau b/packages/charm-sync/src/patch.luau similarity index 97% rename from src/sync/patch.luau rename to packages/charm-sync/src/patch.luau index dd192f1..6780d1f 100644 --- a/src/sync/patch.luau +++ b/packages/charm-sync/src/patch.luau @@ -4,7 +4,7 @@ local validate = require(script.Parent.validate) A special symbol that denotes the absence of a value. Used to represent deleted values in patches. ]=] -local NONE = { __none = "__none" } +local None = { __none = "__none" } local function isNone(value: any): boolean return type(value) == "table" and value.__none == "__none" @@ -46,7 +46,7 @@ local function diff(prevState: { [any]: any }, nextState: { [any]: any }) if previous == current then patches[key] = nil elseif current == nil then - patches[key] = NONE + patches[key] = None elseif type(previous) == "table" and type(current) == "table" then patches[key] = diff(previous, current) end @@ -97,7 +97,7 @@ local function apply(state: any, patches: any): any end return { - NONE = NONE, + NONE = None, isNone = isNone, diff = diff, apply = apply, diff --git a/src/sync/server.luau b/packages/charm-sync/src/server.luau similarity index 93% rename from src/sync/server.luau rename to packages/charm-sync/src/server.luau index a395abc..53fe26e 100644 --- a/src/sync/server.luau +++ b/packages/charm-sync/src/server.luau @@ -1,17 +1,16 @@ local Players = game:GetService("Players") -local subscribe = require(script.Parent.Parent.subscribe) -local types = require(script.Parent.Parent.types) -type Atom = types.Atom -type SyncPayload = types.SyncPayload -local setInterval = require(script.Parent.Parent.utils.setInterval) +local Charm = require(script.Parent.Parent.Charm) +local interval = require(script.Parent.interval) local patch = require(script.Parent.patch) +local types = require(script.Parent.types) +type SyncPayload = types.SyncPayload type ServerOptions = { --[=[ The atoms to synchronize with the client. ]=] - atoms: { [string]: Atom }, + atoms: { [string]: Charm.Atom }, --[=[ The interval at which to send patches to the client, in seconds. Defaults to `0` (patches are sent up to once per frame). Set to a @@ -103,7 +102,7 @@ local function server(options: ServerOptions): ServerSyncer -- Populate the snapshot with the initial state of each atom. -- Subscribe to each atom and update the state when it changes. for key, atom in next, options.atoms do - subscriptions[key] = subscribe(atom, function(current, previous) + subscriptions[key] = Charm.subscribe(atom, function(current, previous) pushSnapshot(key, current, previous) changed = true end) @@ -111,7 +110,7 @@ local function server(options: ServerOptions): ServerSyncer snapshots[1] = createSnapshot() - local disconnect = setInterval(function() + local disconnect = interval(function() if not changed then return end @@ -187,13 +186,13 @@ local function server(options: ServerOptions): ServerSyncer -- Subscribe to each atom and update the state when one changes. for key, atom in next, options.atoms do - subscriptions[key] = subscribe(atom, function(state) + subscriptions[key] = Charm.subscribe(atom, function(state) currentSnapshot[key] = state changed = true end) end - local disconnect = setInterval(function() + local disconnect = interval(function() if not changed then return end diff --git a/packages/charm-sync/src/types.luau b/packages/charm-sync/src/types.luau new file mode 100644 index 0000000..de30101 --- /dev/null +++ b/packages/charm-sync/src/types.luau @@ -0,0 +1,10 @@ +--[=[ + A payload that can be sent from the server to the client to synchronize + state between the two. +]=] +export type SyncPayload = { + type: "patch" | "init", + data: { [any]: any }, +} + +return nil diff --git a/src/sync/validate.luau b/packages/charm-sync/src/validate.luau similarity index 96% rename from src/sync/validate.luau rename to packages/charm-sync/src/validate.luau index 26429ca..911816d 100644 --- a/src/sync/validate.luau +++ b/packages/charm-sync/src/validate.luau @@ -1,4 +1,4 @@ -local store = require(script.Parent.Parent.store) +local Charm = require(script.Parent.Parent.Charm) local SAFE_KEYS = { string = true, number = true } local SAFE_VALUES = { string = true, number = true, boolean = true, table = true, userdata = true } @@ -47,7 +47,7 @@ local function validate(value: any, key: any) elseif typeOfValue == "table" then if getmetatable(value) ~= nil then error(`Cannot sync tables with metatables! Got {value} at key '{key}'`) - elseif store.isAtom(value) then + elseif Charm.isAtom(value) then error(`Cannot sync nested atoms! Got atom at key '{key}'`) elseif isUnsafeTable(value) then error( diff --git a/packages/charm-sync/wally.toml b/packages/charm-sync/wally.toml new file mode 100644 index 0000000..346ac1a --- /dev/null +++ b/packages/charm-sync/wally.toml @@ -0,0 +1,7 @@ +[package] +name = "littensy/charm-sync" +version = "0.1.0" +registry = "https://github.com/UpliftGames/wally-index" +realm = "shared" + +[dependencies] diff --git a/packages/charm/.luaurc b/packages/charm/.luaurc new file mode 100644 index 0000000..e2b625c --- /dev/null +++ b/packages/charm/.luaurc @@ -0,0 +1,3 @@ +{ + "languageMode": "strict" +} diff --git a/default.project.json b/packages/charm/default.project.json similarity index 100% rename from default.project.json rename to packages/charm/default.project.json diff --git a/packages/charm/package.json b/packages/charm/package.json new file mode 100644 index 0000000..650ca3c --- /dev/null +++ b/packages/charm/package.json @@ -0,0 +1,13 @@ +{ + "name": "@rbxts/charm", + "version": "0.8.0", + "description": "An atomic state management library for Roblox", + "repository": "littensy/charm", + "license": "MIT", + "main": "src/init.luau", + "types": "src/index.d.ts", + "scripts": { + "prepare": "cp ../../LICENSE.md . && cp ../../README.md .", + "wally:install": "wally install" + } +} diff --git a/src/atom.luau b/packages/charm/src/atom.luau similarity index 100% rename from src/atom.luau rename to packages/charm/src/atom.luau diff --git a/src/computed.luau b/packages/charm/src/computed.luau similarity index 100% rename from src/computed.luau rename to packages/charm/src/computed.luau diff --git a/src/effect.luau b/packages/charm/src/effect.luau similarity index 100% rename from src/effect.luau rename to packages/charm/src/effect.luau diff --git a/src/index.d.ts b/packages/charm/src/index.d.ts similarity index 51% rename from src/index.d.ts rename to packages/charm/src/index.d.ts index 95f9e99..b36d393 100644 --- a/src/index.d.ts +++ b/packages/charm/src/index.d.ts @@ -42,16 +42,6 @@ declare namespace Charm { */ type StateOf = T extends Molecule ? State : never; - /** - * Recursively infers the type of the state produced by a map of molecules. - */ - type StateOfMap = { - [P in keyof T]: T[P] extends Molecule ? State : never; - }; - - type AtomMap = Record>; - type MoleculeMap = Record>; - interface AtomOptions { /** * A function that determines whether the state has changed. By default, @@ -189,166 +179,4 @@ declare namespace Charm { molecule: Molecule>, mapper: (value: V0, key: K0) => LuaTuple<[value: V1 | undefined, key: K1]> | V1, ): Molecule>; - - /** - * A hook that subscribes to changes in the given atom or molecule. The - * component is re-rendered whenever the state changes. - * - * If the `dependencies` array is provided, the subscription to the atom or - * molecule is re-created whenever the dependencies change. Otherwise, the - * subscription is created once when the component is mounted. - * - * @param molecule The atom or molecule to subscribe to. - * @param dependencies An array of values that the subscription depends on. - * @returns The current state. - */ - function useAtom(molecule: Molecule, dependencies?: unknown[]): State; - - /** - * Synchronizes state between the client and server. The server sends patches - * to the client, which applies them to its local state. - */ - const sync: { - /** - * Creates a `ClientSyncer` object that receives patches from the server and - * applies them to the local state. - * - * @client - * @param options The atoms to synchronize with the server. - * @returns A `ClientSyncer` object. - */ - client: (options: ClientOptions) => ClientSyncer; - /** - * Creates a `ServerSyncer` object that sends patches to the client and - * hydrates the client's state. - * - * @server - * @param options The atoms to synchronize with the client. - * @returns A `ServerSyncer` object. - */ - server: (options: ServerOptions) => ServerSyncer; - /** - * Checks whether a value is `None`. If `true`, the value is scheduled to be - * removed from the state when the patch is applied. - * - * @param value The value to check. - * @returns `true` if the value is `None`, otherwise `false`. - */ - isNone: (value: unknown) => value is None; - }; - - /** - * A special value that denotes the absence of a value. Used to represent - * removed values in patches. - */ - interface None { - readonly __none: "__none"; - } - - type MaybeNone = undefined extends T ? None : never; - - type DataTypes = { - [P in keyof CheckableTypes as P extends keyof CheckablePrimitives ? never : P]: CheckableTypes[P]; - }; - - /** - * A type that should not be made partial in patches. - */ - type DataType = DataTypes[keyof DataTypes]; - - /** - * A partial patch that can be applied to the state to update it. Represents - * the difference between the current state and the next state. - * - * If a value was removed, it is replaced with `None`. This can be checked - * using the `sync.isNone` function. - */ - type SyncPatch = - | MaybeNone - | (State extends ReadonlyMap | Map - ? ReadonlyMap | None> - : State extends Set | ReadonlySet - ? ReadonlyMap - : State extends readonly (infer T)[] - ? readonly (SyncPatch | None | undefined)[] - : State extends DataType - ? State - : State extends object - ? { readonly [P in keyof State]?: SyncPatch } - : State); - - /** - * A payload that can be sent from the server to the client to synchronize - * state between the two. - */ - type SyncPayload = - | { type: "init"; data: StateOfMap } - | { type: "patch"; data: SyncPatch> }; - - interface ClientOptions { - /** - * The atoms to synchronize with the server. - */ - atoms: Atoms; - /** - * Whether to ignore patches sent before the client has been hydrated. - * Defaults to `true`. - */ - ignoreUnhydrated?: boolean; - } - - interface ServerOptions { - /** - * The atoms to synchronize with the client. - */ - atoms: Molecules; - /** - * The interval at which to send patches to the client, in seconds. - * Defaults to `0` (patches are sent up to once per frame). Set to a - * negative value to disable automatic syncing. - */ - interval?: number; - /** - * Whether the history of state changes since the client's last update - * should be preserved. This is useful for values that change multiple times - * per frame, where each individual change is important. Defaults to `false`. - * - * If `true`, the broadcaster will send a list of payloads to the client - * instead of a single payload. The client will apply each payload in order - * to reconstruct the state's changes over time. - */ - preserveHistory?: boolean; - } - - interface ClientSyncer { - /** - * Applies a patch or initializes the state of the atoms with the given - * payload from the server. - * - * @param payloads The patches or hydration payloads to apply. - */ - sync(...payloads: SyncPayload[]): void; - } - - interface ServerSyncer { - /** - * Sets up a subscription to each atom that schedules a patch to be sent to - * the client whenever the state changes. When a change occurs, the `callback` - * is called with the player and the payloads to send. - * - * Note that a `payload` object should not be mutated. If you need to modify - * a payload, apply the changes to a copy of the object. - * - * @param callback The function to call when the state changes. - * @returns A cleanup function that unsubscribes all listeners. - */ - connect(callback: (player: Player, ...payloads: SyncPayload[]) => void): Cleanup; - /** - * Hydrates the client's state with the server's state. This should be - * called when a player joins the game and requires the server's state. - * - * @param player The player to hydrate. - */ - hydrate(player: Player): void; - } } diff --git a/src/init.luau b/packages/charm/src/init.luau similarity index 86% rename from src/init.luau rename to packages/charm/src/init.luau index 2e14c62..a739a13 100644 --- a/src/init.luau +++ b/packages/charm/src/init.luau @@ -5,10 +5,8 @@ local computed = require(script.computed) local effect = require(script.effect) local mapped = require(script.mapped) local observe = require(script.observe) -local useAtom = require(script.react.useAtom) local store = require(script.store) local subscribe = require(script.subscribe) -local sync = require(script.sync) local types = require(script.types) export type Atom = types.Atom @@ -21,12 +19,10 @@ return { effect = effect, mapped = mapped, observe = observe, - useAtom = useAtom, + subscribe = subscribe, batch = store.batch, capture = store.capture, isAtom = store.isAtom, notify = store.notify, peek = store.peek, - subscribe = subscribe, - sync = sync, } diff --git a/src/mapped.luau b/packages/charm/src/mapped.luau similarity index 100% rename from src/mapped.luau rename to packages/charm/src/mapped.luau diff --git a/src/observe.luau b/packages/charm/src/observe.luau similarity index 100% rename from src/observe.luau rename to packages/charm/src/observe.luau diff --git a/src/store.luau b/packages/charm/src/store.luau similarity index 100% rename from src/store.luau rename to packages/charm/src/store.luau diff --git a/src/subscribe.luau b/packages/charm/src/subscribe.luau similarity index 100% rename from src/subscribe.luau rename to packages/charm/src/subscribe.luau diff --git a/src/types.luau b/packages/charm/src/types.luau similarity index 100% rename from src/types.luau rename to packages/charm/src/types.luau diff --git a/packages/charm/wally.toml b/packages/charm/wally.toml new file mode 100644 index 0000000..d180c9e --- /dev/null +++ b/packages/charm/wally.toml @@ -0,0 +1,7 @@ +[package] +name = "littensy/charm" +version = "0.8.0" +registry = "https://github.com/UpliftGames/wally-index" +realm = "shared" + +[dependencies] diff --git a/packages/react/.luaurc b/packages/react/.luaurc new file mode 100644 index 0000000..e2b625c --- /dev/null +++ b/packages/react/.luaurc @@ -0,0 +1,3 @@ +{ + "languageMode": "strict" +} diff --git a/packages/react/default.project.json b/packages/react/default.project.json new file mode 100644 index 0000000..baf768c --- /dev/null +++ b/packages/react/default.project.json @@ -0,0 +1,6 @@ +{ + "name": "react-charm", + "tree": { + "$path": "src" + } +} diff --git a/packages/react/package.json b/packages/react/package.json new file mode 100644 index 0000000..c8bcefc --- /dev/null +++ b/packages/react/package.json @@ -0,0 +1,16 @@ +{ + "name": "@rbxts/react-charm", + "version": "0.1.0", + "description": "React bindings for Charm", + "repository": "littensy/charm", + "license": "MIT", + "main": "src/init.luau", + "types": "src/index.d.ts", + "scripts": { + "prepare": "cp ../../LICENSE.md . && cp ../../README.md .", + "wally:install": "wally install" + }, + "dependencies": { + "@rbxts/charm": "workspace:*" + } +} diff --git a/packages/react/src/index.d.ts b/packages/react/src/index.d.ts new file mode 100644 index 0000000..abf0fb3 --- /dev/null +++ b/packages/react/src/index.d.ts @@ -0,0 +1,15 @@ +import { Molecule } from "@rbxts/charm"; + +/** + * A hook that subscribes to changes in the given atom or molecule. The + * component is re-rendered whenever the state changes. + * + * If the `dependencies` array is provided, the subscription to the atom or + * molecule is re-created whenever the dependencies change. Otherwise, the + * subscription is created once when the component is mounted. + * + * @param molecule The atom or molecule to subscribe to. + * @param dependencies An array of values that the subscription depends on. + * @returns The current state. + */ +export function useAtom(molecule: Molecule, dependencies?: unknown[]): State; diff --git a/src/react/useAtom.luau b/packages/react/src/init.luau similarity index 62% rename from src/react/useAtom.luau rename to packages/react/src/init.luau index ef7ce3c..482b922 100644 --- a/src/react/useAtom.luau +++ b/packages/react/src/init.luau @@ -1,7 +1,5 @@ -local requireReact = require(script.Parent.Parent.modules.React) -local subscribe = require(script.Parent.Parent.subscribe) -local types = require(script.Parent.Parent.types) -type Molecule = types.Molecule +local Charm = require(script.Parent.Charm) +local React = require(script.Parent.React) --[=[ A hook that subscribes to changes in the given atom or molecule. The @@ -15,17 +13,17 @@ type Molecule = types.Molecule @param dependencies An array of values that the subscription depends on. @return The current state. ]=] -local function useAtom(molecule: Molecule, dependencies: { any }?): State - local React = requireReact() - +local function useAtom(molecule: Charm.Molecule, dependencies: { any }?): State local state, setState = React.useState(molecule) React.useEffect(function() setState(molecule()) - return subscribe(molecule, setState) + return Charm.subscribe(molecule, setState) end, dependencies or {}) return state end -return useAtom +return { + useAtom = useAtom, +} diff --git a/packages/react/wally.toml b/packages/react/wally.toml new file mode 100644 index 0000000..e265be0 --- /dev/null +++ b/packages/react/wally.toml @@ -0,0 +1,7 @@ +[package] +name = "littensy/react-charm" +version = "0.1.0" +registry = "https://github.com/UpliftGames/wally-index" +realm = "shared" + +[dependencies] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5ddd4d..42e369a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@rbxts/compiler-types': specifier: 3.0.0-types.0 version: 3.0.0-types.0 - '@rbxts/react': - specifier: ^0.4.0 - version: 0.4.0 '@rbxts/types': specifier: ^1.0.809 version: 1.0.809 @@ -39,12 +36,26 @@ importers: specifier: ^3.3.3 version: 3.3.3 roblox-ts: - specifier: 3.0.0 + specifier: ^3.0.0 version: 3.0.0 typescript: specifier: ^5.6.2 version: 5.6.2 + packages/charm: {} + + packages/charm-sync: + dependencies: + '@rbxts/charm': + specifier: workspace:* + version: link:../charm + + packages/react: + dependencies: + '@rbxts/charm': + specifier: workspace:* + version: link:../charm + packages: '@eslint-community/eslint-utils@4.4.0': @@ -97,12 +108,6 @@ packages: '@rbxts/compiler-types@3.0.0-types.0': resolution: {integrity: sha512-VGOHJPoL7+56NTatMGqQj3K7xWuzEV+aP4QD5vZiHu+bcff3kiTmtoadaF6NkJrmwfFAvbsd4Dg764ZjWNceag==} - '@rbxts/react-vendor@0.4.0': - resolution: {integrity: sha512-roWs9FmR7CQDK1N6gE/36tiCBQRl8v1S0pUJD2Ly3ZMVvfjAk2gixrkqGcKFGtXQQq6r4dTAtHduAeZJBd640g==} - - '@rbxts/react@0.4.0': - resolution: {integrity: sha512-yhqXzfH5YpwfZyiWipQMfQsI//2sC86Jqn2rMkP+l1y4CfhCvoYxpaI5qS7Xum9F+DsBHiwJNeUxm2V2LzXW0A==} - '@rbxts/types@1.0.809': resolution: {integrity: sha512-COmNdE5VKq44tU1Sqbj0b70qDcUpBY1Uu5BhNb25v1yJATwcoVjsmYTyaxmRO4EVdqOATjOUBU2jbxZa4K83uw==} @@ -879,12 +884,6 @@ snapshots: '@rbxts/compiler-types@3.0.0-types.0': {} - '@rbxts/react-vendor@0.4.0': {} - - '@rbxts/react@0.4.0': - dependencies: - '@rbxts/react-vendor': 0.4.0 - '@rbxts/types@1.0.809': {} '@roblox-ts/luau-ast@2.0.0': {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..924b55f --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - packages/* diff --git a/src/__tests__/react/init.spec.luau b/src/__tests__/react/init.spec.luau deleted file mode 100644 index 05ca21e..0000000 --- a/src/__tests__/react/init.spec.luau +++ /dev/null @@ -1,11 +0,0 @@ -return function() - local previous = _G.__DEV__ - - beforeAll(function() - _G.__DEV__ = false -- prevent React errors - end) - - afterAll(function() - _G.__DEV__ = previous - end) -end diff --git a/src/modules/Promise.luau b/src/modules/Promise.luau deleted file mode 100644 index c716f98..0000000 --- a/src/modules/Promise.luau +++ /dev/null @@ -1,9 +0,0 @@ -local Package = script.Parent.Parent - -if _G[script] then - return _G[script].Promise -end - -assert(Package.Parent:FindFirstChild("Promise"), `Could not find Promise in {Package.Parent:GetFullName()}`) - -return require(Package.Parent.Promise) diff --git a/src/modules/React.luau b/src/modules/React.luau deleted file mode 100644 index acb4d86..0000000 --- a/src/modules/React.luau +++ /dev/null @@ -1,17 +0,0 @@ -local Package = script.Parent.Parent -local Index = script:FindFirstAncestor("_Index") -local Packages = if Package.Parent:FindFirstChild("React") - then Package.Parent - elseif Index then Index.Parent - else Package.Parent.Parent:FindFirstChild("ReactLua") - -type React = typeof(require(Package.Parent.React)) - -local React: React - -return function() - if not React then - React = Packages and require(Packages.React) :: React - end - return React -end diff --git a/src/modules/ReactRoblox.luau b/src/modules/ReactRoblox.luau deleted file mode 100644 index 7e4560b..0000000 --- a/src/modules/ReactRoblox.luau +++ /dev/null @@ -1,9 +0,0 @@ -local Package = script.Parent.Parent -local Packages = if Package.Parent:FindFirstChild("ReactRoblox") - then Package.Parent - else Package.Parent.Parent:FindFirstChild("ReactLua") - -type ReactRoblox = typeof(require(Package.Parent.ReactRoblox)) - --- It's safe to require this module since it's only used in unit tests. -return (if Packages then require(Packages.ReactRoblox) else nil) :: ReactRoblox diff --git a/testez-companion.toml b/testez-companion.toml index cc9ae64..011658d 100644 --- a/testez-companion.toml +++ b/testez-companion.toml @@ -1 +1 @@ -roots = ["ReplicatedStorage/DevPackages/Charm/__tests__"] +roots = ["TestService/Tests"] diff --git a/src/__tests__/benchmarks/atom.bench.luau b/tests/benchmarks/atom.bench.luau similarity index 63% rename from src/__tests__/benchmarks/atom.bench.luau rename to tests/benchmarks/atom.bench.luau index f688841..af410ee 100644 --- a/src/__tests__/benchmarks/atom.bench.luau +++ b/tests/benchmarks/atom.bench.luau @@ -1,10 +1,8 @@ --- This file is for use by Benchmarker --- https://boatbomber.itch.io/benchmarker +local ReplicatedStorage = game:GetService("ReplicatedStorage") -local Package = game:GetService("ReplicatedStorage").DevPackages.Charm - -local atom = require(Package.atom) -local computed = require(Package.computed) +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local computed = Charm.computed +local atom = Charm.atom local function parameterGenerator() return atom(0) diff --git a/src/__tests__/sync/client.spec.luau b/tests/charm-sync/client.spec.luau similarity index 78% rename from src/__tests__/sync/client.spec.luau rename to tests/charm-sync/client.spec.luau index 90c128a..2d2de15 100644 --- a/src/__tests__/sync/client.spec.luau +++ b/tests/charm-sync/client.spec.luau @@ -1,11 +1,15 @@ -local atom = require(script.Parent.Parent.Parent.atom) -local subscribe = require(script.Parent.Parent.Parent.subscribe) -local sync = require(script.Parent.Parent.Parent.sync) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local subscribe = Charm.subscribe +local CharmSync = require(ReplicatedStorage.DevPackages.CharmSync) +local client = CharmSync.client return function() it("syncs state payload", function() local a = atom(1) - local client = sync.client({ atoms = { a = a } }) + local client = client({ atoms = { a = a } }) client:sync({ type = "init", @@ -18,7 +22,7 @@ return function() it("syncs state patch", function() local a = atom({ b = 1, c = 2 }) local previous = a() - local client = sync.client({ atoms = { a = a } }) + local client = client({ atoms = { a = a } }) client:sync({ type = "init", @@ -39,7 +43,7 @@ return function() it("fixes sparse array patches", function() local a = atom({ 1 }) - local client = sync.client({ atoms = { a = a } }) + local client = client({ atoms = { a = a } }) client:sync({ type = "init", @@ -60,7 +64,7 @@ return function() it("applies multiple patches", function() local a = atom(1) - local client = sync.client({ atoms = { a = a } }) + local client = client({ atoms = { a = a } }) local calls = {} subscribe(a, function(value) @@ -88,7 +92,7 @@ return function() it("batches patches", function() local a = atom(0) local b = atom(0) - local client = sync.client({ atoms = { a = a, b = b } }) + local client = client({ atoms = { a = a, b = b } }) local states = {} subscribe(a, function() diff --git a/src/__tests__/init.spec.luau b/tests/charm-sync/init.spec.luau similarity index 68% rename from src/__tests__/init.spec.luau rename to tests/charm-sync/init.spec.luau index 5a4c1fc..b8dbfd0 100644 --- a/src/__tests__/init.spec.luau +++ b/tests/charm-sync/init.spec.luau @@ -1,4 +1,6 @@ -local store = require(script.Parent.Parent.store) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local store = require(ReplicatedStorage.DevPackages.Charm.store) return function() local previous = _G.__DEV__ diff --git a/src/__tests__/sync/patch.spec.luau b/tests/charm-sync/patch.spec.luau similarity index 95% rename from src/__tests__/sync/patch.spec.luau rename to tests/charm-sync/patch.spec.luau index 813fdbc..176b694 100644 --- a/src/__tests__/sync/patch.spec.luau +++ b/tests/charm-sync/patch.spec.luau @@ -1,5 +1,7 @@ local HttpService = game:GetService("HttpService") -local patch = require(script.Parent.Parent.Parent.sync.patch) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local patch = require(ReplicatedStorage.DevPackages.CharmSync.patch) return function() local function serdes(value: { [any]: any }) diff --git a/src/__tests__/sync/server.spec.luau b/tests/charm-sync/server.spec.luau similarity index 73% rename from src/__tests__/sync/server.spec.luau rename to tests/charm-sync/server.spec.luau index da17b1e..7afd5dc 100644 --- a/src/__tests__/sync/server.spec.luau +++ b/tests/charm-sync/server.spec.luau @@ -1,15 +1,19 @@ -local atom = require(script.Parent.Parent.Parent.atom) -local sync = require(script.Parent.Parent.Parent.sync) -local patch = require(script.Parent.Parent.Parent.sync.patch) -local types = require(script.Parent.Parent.Parent.types) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local CharmSync = require(ReplicatedStorage.DevPackages.CharmSync) +type SyncPayload = CharmSync.SyncPayload +local server = CharmSync.server +local patch = require(ReplicatedStorage.DevPackages.CharmSync.patch) return function() local target: Player = {} :: any it("sends state to new players", function() local a = atom(1) - local server = sync.server({ atoms = { a = a }, interval = -1 }) - local player: Player, payload: types.SyncPayload + local server = server({ atoms = { a = a }, interval = -1 }) + local player: Player, payload: SyncPayload local disconnect = server:connect(function(...) player, payload = ... @@ -26,8 +30,8 @@ return function() it("sends state patches", function() local a = atom({ b = 1, c = 2 } :: { [string]: number? }) - local server = sync.server({ atoms = { a = a }, interval = -1 }) - local player: Player, payload: types.SyncPayload + local server = server({ atoms = { a = a }, interval = -1 }) + local player: Player, payload: SyncPayload local disconnect = server:connect(function(...) player, payload = ... @@ -54,9 +58,9 @@ return function() it("preserves history", function() local a = atom(1) local b = atom(1) - local payloads: { types.SyncPayload } + local payloads: { SyncPayload } - local server = sync.server({ + local server = server({ atoms = { a = a, b = b }, interval = -1, preserveHistory = true, diff --git a/src/__tests__/sync/validate.spec.luau b/tests/charm-sync/validate.spec.luau similarity index 82% rename from src/__tests__/sync/validate.spec.luau rename to tests/charm-sync/validate.spec.luau index 68d9bfd..8065b9f 100644 --- a/src/__tests__/sync/validate.spec.luau +++ b/tests/charm-sync/validate.spec.luau @@ -1,5 +1,8 @@ -local atom = require(script.Parent.Parent.Parent.atom) -local validate = require(script.Parent.Parent.Parent.sync.validate) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local validate = require(ReplicatedStorage.DevPackages.CharmSync.validate) return function() it("validates inputs", function() diff --git a/src/__tests__/atom.spec.luau b/tests/charm/atom.spec.luau similarity index 83% rename from src/__tests__/atom.spec.luau rename to tests/charm/atom.spec.luau index 09cfbb7..0872037 100644 --- a/src/__tests__/atom.spec.luau +++ b/tests/charm/atom.spec.luau @@ -1,5 +1,8 @@ -local atom = require(script.Parent.Parent.atom) -local store = require(script.Parent.Parent.store) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local store = require(ReplicatedStorage.DevPackages.Charm.store) return function() it("updates state", function() diff --git a/src/__tests__/batch.spec.luau b/tests/charm/batch.spec.luau similarity index 77% rename from src/__tests__/batch.spec.luau rename to tests/charm/batch.spec.luau index 319270b..375f515 100644 --- a/src/__tests__/batch.spec.luau +++ b/tests/charm/batch.spec.luau @@ -1,8 +1,11 @@ -local atom = require(script.Parent.Parent.atom) -local computed = require(script.Parent.Parent.computed) -local effect = require(script.Parent.Parent.effect) -local store = require(script.Parent.Parent.store) -local subscribe = require(script.Parent.Parent.subscribe) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local computed = Charm.computed +local subscribe = Charm.subscribe +local effect = Charm.effect +local store = require(ReplicatedStorage.DevPackages.Charm.store) return function() it("batches updates", function() diff --git a/src/__tests__/capture.spec.luau b/tests/charm/capture.spec.luau similarity index 84% rename from src/__tests__/capture.spec.luau rename to tests/charm/capture.spec.luau index bb19c5e..80669a5 100644 --- a/src/__tests__/capture.spec.luau +++ b/tests/charm/capture.spec.luau @@ -1,6 +1,9 @@ -local atom = require(script.Parent.Parent.atom) -local store = require(script.Parent.Parent.store) -local subscribe = require(script.Parent.Parent.subscribe) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local subscribe = Charm.subscribe +local store = require(ReplicatedStorage.DevPackages.Charm.store) return function() it("gracefully handles yielding", function() diff --git a/src/__tests__/computed.spec.luau b/tests/charm/computed.spec.luau similarity index 89% rename from src/__tests__/computed.spec.luau rename to tests/charm/computed.spec.luau index 87d0d6b..d71d46d 100644 --- a/src/__tests__/computed.spec.luau +++ b/tests/charm/computed.spec.luau @@ -1,6 +1,9 @@ -local atom = require(script.Parent.Parent.atom) -local computed = require(script.Parent.Parent.computed) -local store = require(script.Parent.Parent.store) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local computed = Charm.computed +local store = require(ReplicatedStorage.DevPackages.Charm.store) local collect = require(script.Parent.Parent.utils.collect) local count = require(script.Parent.Parent.utils.count) diff --git a/src/__tests__/effect.spec.luau b/tests/charm/effect.spec.luau similarity index 89% rename from src/__tests__/effect.spec.luau rename to tests/charm/effect.spec.luau index 9e50c0b..faa95b3 100644 --- a/src/__tests__/effect.spec.luau +++ b/tests/charm/effect.spec.luau @@ -1,5 +1,8 @@ -local atom = require(script.Parent.Parent.atom) -local effect = require(script.Parent.Parent.effect) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local effect = Charm.effect return function() it("runs on state change", function() diff --git a/tests/charm/init.spec.luau b/tests/charm/init.spec.luau new file mode 100644 index 0000000..f46b3cc --- /dev/null +++ b/tests/charm/init.spec.luau @@ -0,0 +1,22 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local store = Charm.store + +return function() + local previous = _G.__DEV__ + + beforeAll(function() + _G.__DEV__ = true + end) + + afterAll(function() + _G.__DEV__ = previous + end) + + afterEach(function() + table.clear(store.listeners :: any) + table.clear(store.capturing.stack) + store.capturing.index = 0 + end) +end diff --git a/src/__tests__/mapped.spec.luau b/tests/charm/mapped.spec.luau similarity index 88% rename from src/__tests__/mapped.spec.luau rename to tests/charm/mapped.spec.luau index ac33a85..ea3d406 100644 --- a/src/__tests__/mapped.spec.luau +++ b/tests/charm/mapped.spec.luau @@ -1,7 +1,10 @@ -local atom = require(script.Parent.Parent.atom) -local mapped = require(script.Parent.Parent.mapped) -local observe = require(script.Parent.Parent.observe) -local store = require(script.Parent.Parent.store) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local mapped = Charm.mapped +local observe = Charm.observe +local store = require(ReplicatedStorage.DevPackages.Charm.store) return function() it("returns mapped atoms", function() diff --git a/src/__tests__/observe.spec.luau b/tests/charm/observe.spec.luau similarity index 75% rename from src/__tests__/observe.spec.luau rename to tests/charm/observe.spec.luau index a4e6b63..314366c 100644 --- a/src/__tests__/observe.spec.luau +++ b/tests/charm/observe.spec.luau @@ -1,5 +1,8 @@ -local atom = require(script.Parent.Parent.atom) -local observe = require(script.Parent.Parent.observe) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local observe = Charm.observe return function() it("observes lifetime", function() diff --git a/src/__tests__/peek.spec.luau b/tests/charm/peek.spec.luau similarity index 50% rename from src/__tests__/peek.spec.luau rename to tests/charm/peek.spec.luau index a861517..7b72b86 100644 --- a/src/__tests__/peek.spec.luau +++ b/tests/charm/peek.spec.luau @@ -1,11 +1,15 @@ -local atom = require(script.Parent.Parent.atom) -local store = require(script.Parent.Parent.store) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local capture = Charm.capture +local peek = Charm.peek return function() it("calls an atom without capturing it", function() local a, b, c = atom(1), atom(1), atom(1) - local deps, result = store.capture(function() - return a() + store.peek(b) + c() + local deps, result = capture(function() + return a() + peek(b) + c() end) expect(deps[a]).to.be.ok() expect(deps[b]).to.never.be.ok() @@ -14,8 +18,8 @@ return function() end) it("passes through arguments", function() - expect(store.peek(1)).to.equal(1) - expect(store.peek(function() + expect(peek(1)).to.equal(1) + expect(peek(function() return 1 end)).to.equal(1) end) diff --git a/src/__tests__/subscribe.spec.luau b/tests/charm/subscribe.spec.luau similarity index 88% rename from src/__tests__/subscribe.spec.luau rename to tests/charm/subscribe.spec.luau index d5256e5..a732be9 100644 --- a/src/__tests__/subscribe.spec.luau +++ b/tests/charm/subscribe.spec.luau @@ -1,7 +1,10 @@ -local atom = require(script.Parent.Parent.atom) -local computed = require(script.Parent.Parent.computed) -local store = require(script.Parent.Parent.store) -local subscribe = require(script.Parent.Parent.subscribe) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local computed = Charm.computed +local subscribe = Charm.subscribe +local store = require(ReplicatedStorage.DevPackages.Charm.store) local collect = require(script.Parent.Parent.utils.collect) local count = require(script.Parent.Parent.utils.count) diff --git a/tests/init.server.luau b/tests/init.server.luau new file mode 100644 index 0000000..6c529d8 --- /dev/null +++ b/tests/init.server.luau @@ -0,0 +1,5 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local TestEZ = require(ReplicatedStorage.DevPackages.TestEZ) + +TestEZ.TestBootstrap:run({ script }, TestEZ.Reporters.TextReporter) diff --git a/src/__tests__/react/useAtom.spec.luau b/tests/react/useAtom.spec.luau similarity index 72% rename from src/__tests__/react/useAtom.spec.luau rename to tests/react/useAtom.spec.luau index e1cf6a2..7971f11 100644 --- a/src/__tests__/react/useAtom.spec.luau +++ b/tests/react/useAtom.spec.luau @@ -1,6 +1,10 @@ -local atom = require(script.Parent.Parent.Parent.atom) -local useAtom = require(script.Parent.Parent.Parent.react.useAtom) -local renderHook = require(script.Parent.helpers.renderHook) +local ReplicatedStorage = game:GetService("ReplicatedStorage") + +local Charm = require(ReplicatedStorage.DevPackages.Charm) +local atom = Charm.atom +local ReactCharm = require(ReplicatedStorage.DevPackages.ReactCharm) +local useAtom = ReactCharm.useAtom +local renderHook = require(script.Parent.Parent.utils.renderHook) return function() it("tracks atom state", function() diff --git a/src/utils/collect.luau b/tests/utils/collect.luau similarity index 100% rename from src/utils/collect.luau rename to tests/utils/collect.luau diff --git a/src/utils/count.luau b/tests/utils/count.luau similarity index 100% rename from src/utils/count.luau rename to tests/utils/count.luau diff --git a/src/__tests__/react/helpers/renderHook.luau b/tests/utils/renderHook.luau similarity index 79% rename from src/__tests__/react/helpers/renderHook.luau rename to tests/utils/renderHook.luau index 020b2ae..0d2d19f 100644 --- a/src/__tests__/react/helpers/renderHook.luau +++ b/tests/utils/renderHook.luau @@ -1,9 +1,9 @@ +local ReplicatedStorage = game:GetService("ReplicatedStorage") + _G.__ROACT_17_MOCK_SCHEDULER__ = true -local Package = script:FindFirstAncestor("__tests__").Parent - -local React = require(Package.modules.React)() -local ReactRoblox = require(Package.modules.ReactRoblox) +local React = require(ReplicatedStorage.DevPackages.React) +local ReactRoblox = require(ReplicatedStorage.DevPackages.ReactRoblox) type RenderedHook = { result: Result, diff --git a/tsconfig.json b/tsconfig.json index b255ab8..9da93c3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,7 @@ "typeRoots": ["node_modules/@rbxts"], // configurable - "rootDir": "src", + "rootDir": "packages", "outDir": "out", "incremental": true, "tsBuildInfoFile": "out/tsconfig.tsbuildinfo", diff --git a/wally.lock b/wally.lock index 09550f7..441e222 100644 --- a/wally.lock +++ b/wally.lock @@ -2,11 +2,6 @@ # It is not intended for manual editing. registry = "test" -[[package]] -name = "evaera/promise" -version = "4.0.0" -dependencies = [] - [[package]] name = "jsdotlua/boolean" version = "1.2.6" @@ -95,7 +90,7 @@ dependencies = [["collections", "jsdotlua/collections@1.2.6"]] [[package]] name = "littensy/charm" version = "0.1.0" -dependencies = [["Promise", "evaera/promise@4.0.0"], ["React", "jsdotlua/react@17.1.0"], ["ReactRoblox", "jsdotlua/react-roblox@17.1.0"], ["TestEZ", "roblox/testez@0.4.1"]] +dependencies = [["React", "jsdotlua/react@17.1.0"], ["ReactRoblox", "jsdotlua/react-roblox@17.1.0"], ["TestEZ", "roblox/testez@0.4.1"]] [[package]] name = "roblox/testez" diff --git a/wally.toml b/wally.toml index a9888e5..3db5b68 100644 --- a/wally.toml +++ b/wally.toml @@ -1,18 +1,8 @@ [package] name = "littensy/charm" -description = "Atomic state management for Roblox" -version = "0.7.6" -license = "MIT" +version = "0.1.0" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" -exclude = ["**"] -include = ["src", "src/**", "default.project.json", "wally.toml", "LICENSE.md"] - -[place] -shared-packages = "game.ReplicatedStorage.Packages" - -[dependencies] -Promise = "evaera/promise@4.0.0" [dev-dependencies] React = "jsdotlua/react@17.1.0"