Skip to content

Commit

Permalink
feat(npm-vue): provide ctx in a plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
krulod committed Dec 21, 2023
1 parent c65d2de commit 4318f9c
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 86 deletions.
7 changes: 6 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 13 additions & 8 deletions packages/npm-vue/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,32 @@ npm i @reatom/npm-vue

## API

### `reatomRef`
### `createReatomVue`

A function that turns a Reatom atom into a Vue ref which is updated on target atom changes. A returned pseudo-ref is mutable if a target atom is mutable itself.
A function that creates a [Vue App plugin](https://vuejs.org/guide/reusability/plugins.html#plugins) which you can use. Accepts a `Ctx` object or creates one if it's not provided.

```ts
import { createReatomVue } from '@reatom/npm-vue'

Because all Reatom APIs require `ctx` to be available, you must either `provide` it with `reatomCtxKey` or pass it explicitly as a second argument to `reatomRef`.
app.use(createReatomVue())
```

### `reatomRef`

### `reatomCtxKey`
A function that turns a Reatom atom into a Vue ref which is updated on target atom changes. A returned pseudo-ref is mutable if a target atom is mutable itself.

A symbol to provide the integration with a `ctx` using Vue Dependency Injection API.
Because all Reatom APIs require `ctx` to be available, you must either provide it with `createReatomVue` plugin or pass it explicitly as a second argument to `reatomRef`.

## Example

Setup `ctx` somewhere in the app root:

```ts
import { createCtx } from '@reatom/core'
import { provide } from 'vue'
import { reatomCtxKey } from '@reatom/npm-vue'
import { createReatomVue } from '@reatom/npm-vue'

const ctx = createCtx()
provide(reatomCtxKey, ctx)
app.use(createReatomVue(/* optional */ ctx))
```

Then use Reatom state in your components:
Expand Down
4 changes: 4 additions & 0 deletions packages/npm-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
"/build",
"/package.json"
],
"devDependencies": {
"@reatom/hooks": "^3.5.2",
"@vue/composition-api": "^1.7.2"
},
"peerDependencies": {
"@vue/composition-api": "^1.7.2"
}
Expand Down
71 changes: 71 additions & 0 deletions packages/npm-vue/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions packages/npm-vue/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import { createTestCtx } from '@reatom/testing'
import { effectScope } from '@vue/reactivity'
import { reatomRef } from './'
import { atom } from '@reatom/core'
import {onConnect, onDisconnect} from '@reatom/hooks'

test('reatomRef', async () => {
const ctx = createTestCtx()
const state = atom(0)

let connected = false
state.__reatom.connectHooks = new Set([() => (connected = true)])
state.__reatom.disconnectHooks = new Set([() => (connected = false)])
onConnect(state, () => (connected = true))
onDisconnect(state, () => (connected = false))

const scope = effectScope()
scope.run(() => {
const stateRef = reatomRef(ctx, state)
const stateRef = reatomRef(state, ctx)

assert.is(connected, false)
assert.is(stateRef.value, 0)
Expand All @@ -27,6 +28,8 @@ test('reatomRef', async () => {
assert.is(stateRef.value, 2)
})

assert.is(connected, true)

scope.stop()

assert.is(connected, false)
Expand Down
25 changes: 12 additions & 13 deletions packages/npm-vue/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,31 @@ import {
CtxSpy,
Unsubscribe,
atom,
createCtx,
isAtom,
throwReatomError,
} from '@reatom/core'
// import { parseAtoms, type ParseAtoms } from '@reatom/lens'
// import { ReadonlyDeep } from '@reatom/utils'
import {
App,
ref,
Ref,
onScopeDispose,
inject,
InjectionKey,
} from '@vue/composition-api'

export const reatomCtxKey = Symbol('reatomCtxKey') as InjectionKey<Ctx>
const reatomCtxKey = Symbol('reatomCtxKey') as InjectionKey<Ctx>

export const createReatomVue = (ctx = createCtx()) => ({
install(app: App) {
app.provide(reatomCtxKey, ctx)
},
})

export const reatomRef = ((target: any, ctx = inject(reatomCtxKey)!) => {
throwReatomError(
!ctx,
'"ctx" is not passed explicitly nor provided with "vue:provide"',
'"ctx" is not passed explicitly nor provided with "createReatomVue"',
)

if (typeof target === 'function' && !isAtom(target)) target = atom(target)
Expand All @@ -37,6 +43,8 @@ export const reatomRef = ((target: any, ctx = inject(reatomCtxKey)!) => {
let unsub: Unsubscribe
onScopeDispose(() => unsub?.())



return {
get value() {
unsub ??= ctx.subscribe(target, (atomState) => (state.value = atomState))
Expand All @@ -51,12 +59,3 @@ export const reatomRef = ((target: any, ctx = inject(reatomCtxKey)!) => {
<T>(atom: AtomMut<T>, ctx?: Ctx): Ref<T>
<T>(atom: Atom<T> | ((ctx: CtxSpy) => T), ctx?: Ctx): Readonly<Ref<T>>
}

// TODO
// export const reatomReactive = ((ctx, target: any) => {
// if (typeof target === 'function' && !isAtom(target)) target = atom(target)
// return reatomRef(ctx, (ctx) => parseAtoms(ctx, target))
// }) as {
// <T>(ctx: Ctx, state: (ctx: CtxSpy) => T): ReadonlyDeep<Ref<ParseAtoms<T>>>
// <T>(ctx: Ctx, state: T): ReadonlyDeep<Ref<ParseAtoms<T>>>
// }
61 changes: 0 additions & 61 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,67 +5,6 @@ export type UndefinedToOptional<T extends object> = Partial<T> &
// https://stackoverflow.com/a/51390763
export type Falsy = false | 0 | '' | null | undefined

export type ReadonlyDeep<T> = T extends BuiltIns
? T
: T extends new (...args: any[]) => unknown
? T // Skip class constructors
: T extends (...arguments_: any[]) => unknown
? {} extends ReadonlyObjectDeep<T>
? T
: HasMultipleCallSignatures<T> extends true
? T
: ((...arguments_: Parameters<T>) => ReturnType<T>) & ReadonlyObjectDeep<T>
: T extends Readonly<ReadonlyMap<infer KeyType, infer ValueType>>
? ReadonlyMapDeep<KeyType, ValueType>
: T extends Readonly<ReadonlySet<infer ItemType>>
? ReadonlySetDeep<ItemType>
: // Identify tuples to avoid converting them to arrays inadvertently; special case `readonly [...never[]]`, as it emerges undesirably from recursive invocations of ReadonlyDeep below.
T extends readonly [] | readonly [...never[]]
? readonly []
: T extends readonly [infer U, ...infer V]
? readonly [ReadonlyDeep<U>, ...ReadonlyDeep<V>]
: T extends readonly [...infer U, infer V]
? readonly [...ReadonlyDeep<U>, ReadonlyDeep<V>]
: T extends ReadonlyArray<infer ItemType>
? ReadonlyArray<ReadonlyDeep<ItemType>>
: T extends object
? ReadonlyObjectDeep<T>
: unknown

type BuiltIns =
| null
| undefined
| string
| number
| boolean
| symbol
| bigint
| void
| Date
| RegExp

type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknown> =
T extends {
(...arguments_: infer A): unknown
(...arguments_: any[]): unknown
}
? unknown[] extends A
? false
: true
: false

type ReadonlyMapDeep<KeyType, ValueType> = {} & Readonly<
ReadonlyMap<ReadonlyDeep<KeyType>, ReadonlyDeep<ValueType>>
>

type ReadonlySetDeep<ItemType> = {} & Readonly<
ReadonlySet<ReadonlyDeep<ItemType>>
>

type ReadonlyObjectDeep<ObjectType extends object> = {
readonly [KeyType in keyof ObjectType]: ReadonlyDeep<ObjectType[KeyType]>
}

// TODO infer `Atom` and `AtomMut` signature
/** Remove named generics, show plain type. */
export type Plain<Intersection> = Intersection extends (
Expand Down

0 comments on commit 4318f9c

Please sign in to comment.