pleached 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/capabilities-ALpw-aBj.d.mts +28 -0
- package/dist/index.d.mts +40 -0
- package/dist/index.mjs +89 -0
- package/dist/internal-CnZZ5Mhp.mjs +18 -0
- package/dist/react.d.mts +56 -0
- package/dist/react.mjs +26 -0
- package/dist/spi-CzHSw4BF.d.mts +505 -0
- package/dist/spi.d.mts +2 -0
- package/dist/spi.mjs +1 -0
- package/dist/store-NWV8hqvw.d.mts +79 -0
- package/dist/yjs.d.mts +58 -0
- package/dist/yjs.mjs +8 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eral Almansouri
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# pleached
|
|
2
|
+
|
|
3
|
+
> **pleach** _(verb)_: to interlace living branches so that separate trees grow
|
|
4
|
+
> together into one.
|
|
5
|
+
|
|
6
|
+
Schema-first, CRDT-native state for TypeScript. Declare the shape of your
|
|
7
|
+
state once — including how every part of it merges under concurrent edits —
|
|
8
|
+
and get back a fully typed live tree you can mutate directly, subscribe to
|
|
9
|
+
surgically, validate into a refined value, and back with interchangeable
|
|
10
|
+
storage engines: an in-memory backend for single-user state, Yjs for
|
|
11
|
+
real-time collaboration, others via a small SPI.
|
|
12
|
+
|
|
13
|
+
> **Status: pre-implementation.** The full type surface and a red test suite
|
|
14
|
+
> covering every design promise are in place; the runtime is being
|
|
15
|
+
> implemented against them. See [docs/DESIGN.md](docs/DESIGN.md) for the
|
|
16
|
+
> complete design.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install pleached
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
React and Yjs are optional peer dependencies — install them only if you use
|
|
25
|
+
the corresponding subpath:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm install react # for pleached/react
|
|
29
|
+
npm install yjs # for pleached/yjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Modules
|
|
33
|
+
|
|
34
|
+
| Subpath | Contents |
|
|
35
|
+
| ---------------- | ---------------------------------------------------------------------------------- |
|
|
36
|
+
| `pleached` | `s` (schema builders), `createStore`, `local()`, `resolve`, type inference surface |
|
|
37
|
+
| `pleached/yjs` | `yjs()` backend factory (peer: `yjs`) |
|
|
38
|
+
| `pleached/react` | `useSnapshot`, `useStore`, capability hooks (peer: `react`) |
|
|
39
|
+
| `pleached/spi` | `Backend`, `Session`, driver interfaces — the backend contract |
|
|
40
|
+
|
|
41
|
+
## Quick look
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { s, createStore, local } from "pleached";
|
|
45
|
+
|
|
46
|
+
const Todo = s.object({
|
|
47
|
+
title: s.scalar(s.string),
|
|
48
|
+
done: s.scalar(s.boolean),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const store = createStore(s.map(Todo), local());
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
See the [design document](docs/DESIGN.md) for the full walkthrough, the
|
|
55
|
+
backend SPI, and decided tradeoffs. The design doc refers to the library by
|
|
56
|
+
its working name `pleach`; it is published as `pleached` (npm rejects the
|
|
57
|
+
bare name as too similar to `preact`).
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
pnpm install
|
|
63
|
+
pnpm check # format + lint + typecheck
|
|
64
|
+
pnpm test # run the test suite
|
|
65
|
+
pnpm build # bundle to dist/ with type declarations
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Releasing
|
|
69
|
+
|
|
70
|
+
Publishing to npm is automated via GitHub Actions: bump `version` in
|
|
71
|
+
`package.json`, push a tag `v<version>`, and the release workflow builds and
|
|
72
|
+
publishes.
|
|
73
|
+
|
|
74
|
+
Auth: the first-ever publish uses the `NPM_TOKEN` repository secret. After
|
|
75
|
+
that, configure a trusted publisher on npmjs.com (package Settings →
|
|
76
|
+
Trusted Publisher: this repo + `release.yml`) and delete the secret — the
|
|
77
|
+
workflow then authenticates via OIDC, no token needed.
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { M as Unsubscribe } from "./spi-CzHSw4BF.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/capabilities.d.ts
|
|
4
|
+
/** Undo/redo over this store's subtree, with transaction-boundary capture. */
|
|
5
|
+
interface UndoApi {
|
|
6
|
+
undo(): void;
|
|
7
|
+
redo(): void;
|
|
8
|
+
canUndo(): boolean;
|
|
9
|
+
canRedo(): boolean;
|
|
10
|
+
/** Closes the current capture group so the next edit starts a new undo step. */
|
|
11
|
+
stopCapturing(): void;
|
|
12
|
+
subscribe(listener: () => void): Unsubscribe;
|
|
13
|
+
}
|
|
14
|
+
/** Ephemeral per-peer state — cursors, names, focus. Never merged, never persisted. */
|
|
15
|
+
interface PresenceApi<TState> {
|
|
16
|
+
self(): TState | undefined;
|
|
17
|
+
setSelf(state: TState): void;
|
|
18
|
+
updateSelf(patch: Partial<TState>): void;
|
|
19
|
+
peers(): ReadonlyMap<number, TState>;
|
|
20
|
+
subscribe(listener: () => void): Unsubscribe;
|
|
21
|
+
}
|
|
22
|
+
/** Connection status, fed by the app's provider wiring. */
|
|
23
|
+
interface SyncApi {
|
|
24
|
+
status(): "synced" | "syncing" | "offline";
|
|
25
|
+
subscribe(listener: () => void): Unsubscribe;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { SyncApi as n, UndoApi as r, PresenceApi as t };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { $ as UnionValueCases, A as UnionNode, B as NodeOf, C as Node, D as PartialCases, E as Origin, F as Infer, G as ShapeInput, H as SchemaBuilders, I as InputOf, J as Simplify, K as ShapeKinds, L as Kind, M as Unsubscribe, N as resolve, O as ScalarNode, P as AnySchema, Q as UnionValue, R as KindRegistry, S as MapNode, T as ObjectNode, U as SchemaMembers, V as Schema, W as Shape, X as UnionKinds, Y as UnionInput, Z as UnionSchema, _ as CaseResult, b as ListNode, et as ValueOf, g as AnyNode, j as UnionNodeCases, k as TextNode, n as AnyDriver, nt as s, o as DriverSlot, q as ShapeValue, rt as $, tt as VariantShapes, v as Change, w as NodeOps, x as LoadOptions, y as CounterNode, z as KindsOf } from "./spi-CzHSw4BF.mjs";
|
|
2
|
+
import { n as SyncApi, r as UndoApi, t as PresenceApi } from "./capabilities-ALpw-aBj.mjs";
|
|
3
|
+
import { a as StoreOf, c as LocalOptions, i as Store, l as local, n as BackendSupports, o as createStore, r as CreateStoreOptions, s as LocalBackend, t as AnyStore } from "./store-NWV8hqvw.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/internal.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Raised by every runtime entry point while pleach is interfaces-only.
|
|
8
|
+
* The test suite is written against the designed behavior and is expected
|
|
9
|
+
* to fail with this error until the implementation lands.
|
|
10
|
+
*/
|
|
11
|
+
declare class NotImplementedError extends Error {
|
|
12
|
+
constructor(what: string);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/kind.d.ts
|
|
16
|
+
/** The composition context handed to a kind when it materializes. */
|
|
17
|
+
interface KindContext {
|
|
18
|
+
/** Stable per-client identity, for per-actor structures (counters). */
|
|
19
|
+
readonly actorId: string;
|
|
20
|
+
/** This node's position — materialize primitive drivers from it. */
|
|
21
|
+
readonly slot: DriverSlot;
|
|
22
|
+
/** A backend-native driver override for this kind, when the backend offers one. */
|
|
23
|
+
native(kind: Kind): AnyDriver | undefined;
|
|
24
|
+
/** Bubbles a change notification for this node's subtree. */
|
|
25
|
+
emit(change: Change): void;
|
|
26
|
+
}
|
|
27
|
+
/** The runtime half of a custom kind. */
|
|
28
|
+
interface KindSpec<TNode extends AnyNode> {
|
|
29
|
+
materialize(context: KindContext): TNode;
|
|
30
|
+
}
|
|
31
|
+
interface KindDefinition<TKind extends Kind, TNode extends AnyNode> extends KindSpec<TNode> {
|
|
32
|
+
readonly kind: TKind;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Defines a custom kind by composing primitive drivers into a node facade.
|
|
36
|
+
* Pair with a `KindRegistry` module augmentation for the type half.
|
|
37
|
+
*/
|
|
38
|
+
declare function defineKind<TKind extends Kind, TNode extends AnyNode>(kind: TKind, spec: KindSpec<TNode>): KindDefinition<TKind, TNode>;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { $, type AnyNode, type AnySchema, type AnyStore, type BackendSupports, type CaseResult, type Change, type CounterNode, type CreateStoreOptions, type Infer, type InputOf, type Kind, type KindContext, type KindDefinition, type KindRegistry, type KindSpec, type KindsOf, type ListNode, type LoadOptions, type LocalBackend, type LocalOptions, type MapNode, type Node, type NodeOf, type NodeOps, NotImplementedError, type ObjectNode, type Origin, type PartialCases, type PresenceApi, type ScalarNode, type Schema, type SchemaBuilders, type SchemaMembers, type Shape, type ShapeInput, type ShapeKinds, type ShapeValue, type Simplify, type Store, type StoreOf, type SyncApi, type TextNode, type UndoApi, type UnionInput, type UnionKinds, type UnionNode, type UnionNodeCases, type UnionSchema, type UnionValue, type UnionValueCases, type Unsubscribe, type ValueOf, type VariantShapes, createStore, defineKind, local, resolve, s };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { n as todo, t as NotImplementedError } from "./internal-CnZZ5Mhp.mjs";
|
|
2
|
+
//#region src/symbol.ts
|
|
3
|
+
/**
|
|
4
|
+
* Shared symbol namespace for hidden operations and metadata.
|
|
5
|
+
*
|
|
6
|
+
* Structural node operations (snapshot, load, subscribe) and schema internals
|
|
7
|
+
* live behind this symbol so they can never collide with author-chosen names —
|
|
8
|
+
* an object node is free to have a child named `snapshot`.
|
|
9
|
+
*/
|
|
10
|
+
const $ = Symbol("pleach");
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/node.ts
|
|
13
|
+
/**
|
|
14
|
+
* Resolves a dynamic path to a descendant node, or `undefined` when any
|
|
15
|
+
* segment is missing. The untyped escape hatch for callers that only know
|
|
16
|
+
* paths at runtime (visual builders); typed code traverses node properties
|
|
17
|
+
* instead.
|
|
18
|
+
*/
|
|
19
|
+
function resolve(node, segments) {
|
|
20
|
+
return todo("resolve");
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/schema.ts
|
|
24
|
+
const s = {
|
|
25
|
+
text: () => todo("s.text"),
|
|
26
|
+
scalar: () => todo("s.scalar"),
|
|
27
|
+
opaque: () => todo("s.opaque"),
|
|
28
|
+
list: () => todo("s.list"),
|
|
29
|
+
object: () => todo("s.object"),
|
|
30
|
+
map: () => todo("s.map"),
|
|
31
|
+
union: () => todo("s.union"),
|
|
32
|
+
counter: () => todo("s.counter")
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/store.ts
|
|
36
|
+
/**
|
|
37
|
+
* Materializes a schema into a live store on a backend (the in-memory local
|
|
38
|
+
* backend when none is given). The returned store carries the backend's
|
|
39
|
+
* capabilities as typed members — `yjs({ undo: true })` yields `store.undo`.
|
|
40
|
+
*/
|
|
41
|
+
function createStore(schema, options) {
|
|
42
|
+
return todo("createStore");
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/local.ts
|
|
46
|
+
/**
|
|
47
|
+
* The local backend — in-memory state for single-user stores, and the
|
|
48
|
+
* default when `createStore` receives no backend.
|
|
49
|
+
*
|
|
50
|
+
* Local is the *reference semantics* implementation: where behavior is
|
|
51
|
+
* ambiguous, the local backend's behavior is the spec. It is not a sync
|
|
52
|
+
* engine — no op queue, no persistence, no merging of two local sessions.
|
|
53
|
+
*
|
|
54
|
+
* Local also implements the undo capability (`local({ undo: true })`) as an
|
|
55
|
+
* inverse-operation log with transaction-boundary capture: two independent
|
|
56
|
+
* capability implementations keep the abstraction honest, and single-user
|
|
57
|
+
* stores want undo anyway.
|
|
58
|
+
*/
|
|
59
|
+
/** Creates an in-memory backend. `actorId` is random per session. */
|
|
60
|
+
function local(options) {
|
|
61
|
+
return todo("local");
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/kind.ts
|
|
65
|
+
/**
|
|
66
|
+
* Kind extensibility — composing new kinds from the primitive drivers.
|
|
67
|
+
*
|
|
68
|
+
* The backend SPI stays small and closed (five primitives); kinds above the
|
|
69
|
+
* primitives are composed in core and can be composed in userland exactly
|
|
70
|
+
* the same way. pleach's own `union` (scalar tag + object variants) and
|
|
71
|
+
* `counter` (map of per-actor sums) are proof cases: they need zero
|
|
72
|
+
* backend-specific code.
|
|
73
|
+
*
|
|
74
|
+
* A custom kind is two halves: a module augmentation registering the kind
|
|
75
|
+
* name on {@link KindRegistry}, and a runtime definition composing primitive
|
|
76
|
+
* drivers into a node facade.
|
|
77
|
+
*
|
|
78
|
+
* @experimental The least-settled corner of the design — expect this
|
|
79
|
+
* surface to move before implementation lands.
|
|
80
|
+
*/
|
|
81
|
+
/**
|
|
82
|
+
* Defines a custom kind by composing primitive drivers into a node facade.
|
|
83
|
+
* Pair with a `KindRegistry` module augmentation for the type half.
|
|
84
|
+
*/
|
|
85
|
+
function defineKind(kind, spec) {
|
|
86
|
+
return todo("defineKind");
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { $, NotImplementedError, createStore, defineKind, local, resolve, s };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/internal.ts
|
|
2
|
+
/**
|
|
3
|
+
* Raised by every runtime entry point while pleach is interfaces-only.
|
|
4
|
+
* The test suite is written against the designed behavior and is expected
|
|
5
|
+
* to fail with this error until the implementation lands.
|
|
6
|
+
*/
|
|
7
|
+
var NotImplementedError = class extends Error {
|
|
8
|
+
constructor(what) {
|
|
9
|
+
super(`pleach: ${what} is not implemented yet`);
|
|
10
|
+
this.name = "NotImplementedError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/** Stub body for designed-but-unimplemented runtime surface. */
|
|
14
|
+
function todo(what) {
|
|
15
|
+
throw new NotImplementedError(what);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { todo as n, NotImplementedError as t };
|
package/dist/react.d.mts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { C as Node, I as InputOf, P as AnySchema, g as AnyNode } from "./spi-CzHSw4BF.mjs";
|
|
2
|
+
import { n as SyncApi, r as UndoApi, t as PresenceApi } from "./capabilities-ALpw-aBj.mjs";
|
|
3
|
+
import { i as Store, t as AnyStore } from "./store-NWV8hqvw.mjs";
|
|
4
|
+
import { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
//#region src/react.d.ts
|
|
7
|
+
/** The snapshot (editable input) shape of a node. */
|
|
8
|
+
type SnapshotOf<TNode extends AnyNode> = TNode extends Node<infer TInput> ? TInput : never;
|
|
9
|
+
/** The snapshot shape of a store's whole tree. */
|
|
10
|
+
type StoreSnapshotOf<TStore extends AnyStore> = TStore extends Store<infer TSchema> ? InputOf<TSchema> : never;
|
|
11
|
+
/**
|
|
12
|
+
* Subscribes to a node's subtree and returns its snapshot — the workhorse.
|
|
13
|
+
* With a selector, re-renders only when the selected value changes.
|
|
14
|
+
*/
|
|
15
|
+
declare function useSnapshot<TNode extends AnyNode>(node: TNode): SnapshotOf<TNode> | undefined;
|
|
16
|
+
declare function useSnapshot<TNode extends AnyNode, TSelected>(node: TNode, selector: (snapshot: SnapshotOf<TNode> | undefined) => TSelected, equals?: (a: TSelected, b: TSelected) => boolean): TSelected;
|
|
17
|
+
/** Store-level selector subscription, zustand-shaped. */
|
|
18
|
+
declare function useStore<TStore extends AnyStore, TSelected>(store: TStore, selector: (snapshot: StoreSnapshotOf<TStore> | undefined) => TSelected, equals?: (a: TSelected, b: TSelected) => boolean): TSelected;
|
|
19
|
+
/** Undo state and actions; only accepts stores whose backend provides undo. */
|
|
20
|
+
declare function useUndo(store: AnyStore & {
|
|
21
|
+
undo: UndoApi;
|
|
22
|
+
}): {
|
|
23
|
+
undo(): void;
|
|
24
|
+
redo(): void;
|
|
25
|
+
canUndo: boolean;
|
|
26
|
+
canRedo: boolean;
|
|
27
|
+
};
|
|
28
|
+
/** Presence state; only accepts stores whose backend provides presence. */
|
|
29
|
+
declare function usePresence<TState>(store: AnyStore & {
|
|
30
|
+
presence: PresenceApi<TState>;
|
|
31
|
+
}): {
|
|
32
|
+
self: TState | undefined;
|
|
33
|
+
peers: ReadonlyMap<number, TState>;
|
|
34
|
+
setSelf(state: TState): void;
|
|
35
|
+
updateSelf(patch: Partial<TState>): void;
|
|
36
|
+
};
|
|
37
|
+
/** Live sync status; only accepts stores whose backend provides sync. */
|
|
38
|
+
declare function useSyncStatus(store: AnyStore & {
|
|
39
|
+
sync: SyncApi;
|
|
40
|
+
}): "synced" | "syncing" | "offline";
|
|
41
|
+
/**
|
|
42
|
+
* Optional context plumbing for component libraries. Members are standalone
|
|
43
|
+
* function values (component and hook), not `this`-bound methods — they are
|
|
44
|
+
* meant to be destructured.
|
|
45
|
+
*/
|
|
46
|
+
interface StoreContextBinding<TSchema extends AnySchema> {
|
|
47
|
+
readonly StoreProvider: (props: {
|
|
48
|
+
readonly store: Store<TSchema>;
|
|
49
|
+
readonly children?: ReactNode;
|
|
50
|
+
}) => ReactNode;
|
|
51
|
+
/** Throws outside a matching `StoreProvider`. */
|
|
52
|
+
readonly useStoreContext: () => Store<TSchema>;
|
|
53
|
+
}
|
|
54
|
+
declare function createStoreContext<TSchema extends AnySchema>(): StoreContextBinding<TSchema>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { SnapshotOf, StoreContextBinding, StoreSnapshotOf, createStoreContext, usePresence, useSnapshot, useStore, useSyncStatus, useUndo };
|
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { n as todo } from "./internal-CnZZ5Mhp.mjs";
|
|
2
|
+
//#region src/react.ts
|
|
3
|
+
function useSnapshot(..._args) {
|
|
4
|
+
return todo("useSnapshot");
|
|
5
|
+
}
|
|
6
|
+
/** Store-level selector subscription, zustand-shaped. */
|
|
7
|
+
function useStore(store, selector, equals) {
|
|
8
|
+
return todo("useStore");
|
|
9
|
+
}
|
|
10
|
+
/** Undo state and actions; only accepts stores whose backend provides undo. */
|
|
11
|
+
function useUndo(store) {
|
|
12
|
+
return todo("useUndo");
|
|
13
|
+
}
|
|
14
|
+
/** Presence state; only accepts stores whose backend provides presence. */
|
|
15
|
+
function usePresence(store) {
|
|
16
|
+
return todo("usePresence");
|
|
17
|
+
}
|
|
18
|
+
/** Live sync status; only accepts stores whose backend provides sync. */
|
|
19
|
+
function useSyncStatus(store) {
|
|
20
|
+
return todo("useSyncStatus");
|
|
21
|
+
}
|
|
22
|
+
function createStoreContext() {
|
|
23
|
+
return todo("createStoreContext");
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { createStoreContext, usePresence, useSnapshot, useStore, useSyncStatus, useUndo };
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { ArkErrors, Type, type } from "arktype";
|
|
2
|
+
|
|
3
|
+
//#region src/symbol.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Shared symbol namespace for hidden operations and metadata.
|
|
6
|
+
*
|
|
7
|
+
* Structural node operations (snapshot, load, subscribe) and schema internals
|
|
8
|
+
* live behind this symbol so they can never collide with author-chosen names —
|
|
9
|
+
* an object node is free to have a child named `snapshot`.
|
|
10
|
+
*/
|
|
11
|
+
declare const $: unique symbol;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/schema.d.ts
|
|
14
|
+
/**
|
|
15
|
+
* @private
|
|
16
|
+
* Flattens an intersection into a single object type for readable hovers.
|
|
17
|
+
*/
|
|
18
|
+
type Simplify<TObject> = { [TKey in keyof TObject]: TObject[TKey] } & {};
|
|
19
|
+
/**
|
|
20
|
+
* The open registry of kind names. Custom kinds register themselves via
|
|
21
|
+
* module augmentation:
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* declare module "pleach" {
|
|
25
|
+
* interface KindRegistry { richtext: true }
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
interface KindRegistry {
|
|
30
|
+
scalar: true;
|
|
31
|
+
text: true;
|
|
32
|
+
list: true;
|
|
33
|
+
object: true;
|
|
34
|
+
map: true;
|
|
35
|
+
union: true;
|
|
36
|
+
counter: true;
|
|
37
|
+
opaque: true;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The concurrent-editing semantics of one part of a state tree. An ArkType
|
|
41
|
+
* validator alone cannot distinguish a last-write-wins string register from
|
|
42
|
+
* collaboratively editable text — the kind carries that distinction.
|
|
43
|
+
*/
|
|
44
|
+
type Kind = Extract<keyof KindRegistry, string>;
|
|
45
|
+
/**
|
|
46
|
+
* The description of a piece of state: data shape, validation, and merge
|
|
47
|
+
* semantics.
|
|
48
|
+
*
|
|
49
|
+
* Declared as a function intersection rather than an interface call
|
|
50
|
+
* signature because the latter defeats generic inference at consumer call
|
|
51
|
+
* sites (proven in the formeddable prior art).
|
|
52
|
+
*
|
|
53
|
+
* - `TInput` — the editable (pre-validation) shape mutation works with.
|
|
54
|
+
* - `TValue` — the validated (post-morph) shape `validate` produces.
|
|
55
|
+
* - `TKind` — this schema's own kind.
|
|
56
|
+
* - `TNode` — the live node type this schema materializes.
|
|
57
|
+
* - `TKinds` — the union of kinds this subtree *requires of a backend*.
|
|
58
|
+
* Composed kinds decompose: a union contributes `"scalar" | "object"`, a
|
|
59
|
+
* counter contributes `"map"`, opaque contributes `"scalar"`. This is what
|
|
60
|
+
* `createStore` checks against a backend's `supportedKinds` at compile
|
|
61
|
+
* time.
|
|
62
|
+
*/
|
|
63
|
+
type Schema<TInput = unknown, TValue = TInput, TKind extends Kind = Kind, TNode extends Node<TInput> = Node<TInput>, TKinds extends Kind = Kind> = ((input: unknown) => TValue | ArkErrors) & SchemaMembers<TInput, TValue, TKind, TNode, TKinds>;
|
|
64
|
+
/** The members every {@link Schema} carries alongside its call signature. */
|
|
65
|
+
interface SchemaMembers<TInput, TValue, TKind extends Kind, TNode extends Node<TInput>, TKinds extends Kind> {
|
|
66
|
+
/** The concurrent-editing semantics of this part of the tree. */
|
|
67
|
+
readonly kind: TKind;
|
|
68
|
+
/**
|
|
69
|
+
* Pipes this schema's value into another ArkType schema, mirroring
|
|
70
|
+
* ArkType's `Type.to`. The schema must accept the current value as input;
|
|
71
|
+
* the result keeps this schema's editable input and node and takes its
|
|
72
|
+
* value from the piped schema's output. `s.text().to("string.numeric.parse")`
|
|
73
|
+
* is edited as collaborative text and validates to a number.
|
|
74
|
+
*/
|
|
75
|
+
to<const TSchema>(schema: type.validate<TSchema> & ([TValue] extends [type.infer.In<TSchema>] ? unknown : "schema must accept this schema's value as input")): Schema<TInput, type.infer.Out<TSchema>, TKind, TNode, TKinds>;
|
|
76
|
+
/**
|
|
77
|
+
* Pipes this schema's value through a transform, mirroring ArkType's
|
|
78
|
+
* `Type.pipe` — the escape hatch for conversions a schema string can't
|
|
79
|
+
* express.
|
|
80
|
+
*/
|
|
81
|
+
pipe<TNext>(morph: (value: TValue) => TNext): Schema<TInput, TNext, TKind, TNode, TKinds>;
|
|
82
|
+
/**
|
|
83
|
+
* Bakes in an initial input, applied on materialization when no loaded
|
|
84
|
+
* value exists.
|
|
85
|
+
*/
|
|
86
|
+
default(input: TInput): Schema<TInput, TValue, TKind, TNode, TKinds>;
|
|
87
|
+
/** Hidden internals: the compiled ArkType plus phantom inference slots. */
|
|
88
|
+
readonly [$]: {
|
|
89
|
+
/** The compiled ArkType backing this schema's validation. */readonly arkType: Type; /** Phantom; never present at runtime. */
|
|
90
|
+
readonly input: TInput; /** Phantom; never present at runtime. */
|
|
91
|
+
readonly value: TValue; /** Phantom; never present at runtime. */
|
|
92
|
+
readonly node: TNode; /** Phantom; never present at runtime. */
|
|
93
|
+
readonly kinds: TKinds;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A schema with all generics erased — the existential bucket used where
|
|
98
|
+
* schemas of unrelated shapes must mix.
|
|
99
|
+
*/
|
|
100
|
+
type AnySchema = Schema<unknown, unknown, Kind, AnyNode, Kind>;
|
|
101
|
+
/** The editable (pre-validation) value shape of a schema. */
|
|
102
|
+
type InputOf<TSchema extends AnySchema> = TSchema[typeof $]["input"];
|
|
103
|
+
/** The validated (post-morph) value shape of a schema. */
|
|
104
|
+
type ValueOf<TSchema extends AnySchema> = TSchema[typeof $]["value"];
|
|
105
|
+
/** The live node type a schema materializes. */
|
|
106
|
+
type NodeOf<TSchema extends AnySchema> = TSchema[typeof $]["node"];
|
|
107
|
+
/**
|
|
108
|
+
* The union of kinds a schema's subtree requires a backend to support.
|
|
109
|
+
* Composed kinds appear as the primitives they decompose into.
|
|
110
|
+
*/
|
|
111
|
+
type KindsOf<TSchema extends AnySchema> = TSchema[typeof $]["kinds"];
|
|
112
|
+
/** The `{ input; value }` bundle, in the style of alge's `Infer`. */
|
|
113
|
+
type Infer<TSchema extends AnySchema> = {
|
|
114
|
+
input: InputOf<TSchema>;
|
|
115
|
+
value: ValueOf<TSchema>;
|
|
116
|
+
};
|
|
117
|
+
/** A fixed record of named child schemas — the argument to `s.object`. */
|
|
118
|
+
type Shape = Record<string, AnySchema>;
|
|
119
|
+
/** The editable value shape of an object schema's children. */
|
|
120
|
+
type ShapeInput<TShape extends Shape> = Simplify<{ -readonly [TKey in keyof TShape]: InputOf<TShape[TKey]> }>;
|
|
121
|
+
/** The validated value shape of an object schema's children. */
|
|
122
|
+
type ShapeValue<TShape extends Shape> = Simplify<{ -readonly [TKey in keyof TShape]: ValueOf<TShape[TKey]> }>;
|
|
123
|
+
/** The kinds required by any child of a shape. */
|
|
124
|
+
type ShapeKinds<TShape extends Shape> = KindsOf<TShape[keyof TShape]>;
|
|
125
|
+
/** The variants record accepted by `s.union` — plain shapes, not schemas. */
|
|
126
|
+
type VariantShapes = Record<string, Shape>;
|
|
127
|
+
/** The flat, discriminated editable shape of a union — alge-style. */
|
|
128
|
+
type UnionInput<TTag extends string, TVariants extends VariantShapes> = { [TKey in keyof TVariants & string]: Simplify<Record<TTag, TKey> & ShapeInput<TVariants[TKey]>> }[keyof TVariants & string];
|
|
129
|
+
/** The flat, discriminated validated shape of a union. */
|
|
130
|
+
type UnionValue<TTag extends string, TVariants extends VariantShapes> = { [TKey in keyof TVariants & string]: Simplify<Record<TTag, TKey> & ShapeValue<TVariants[TKey]>> }[keyof TVariants & string];
|
|
131
|
+
/** The kinds a union requires: its own decomposition plus its fields'. */
|
|
132
|
+
type UnionKinds<TVariants extends VariantShapes> = "scalar" | "object" | { [TKey in keyof TVariants]: ShapeKinds<TVariants[TKey]> }[keyof TVariants];
|
|
133
|
+
/** Exhaustive per-tag handlers over flat variant values. */
|
|
134
|
+
type UnionValueCases<TTag extends string, TVariants extends VariantShapes> = { readonly [TKey in keyof TVariants & string]: (value: Simplify<Record<TTag, TKey> & ShapeInput<TVariants[TKey]>>) => unknown };
|
|
135
|
+
/**
|
|
136
|
+
* The variant names whose shape declares a field colliding with the tag key.
|
|
137
|
+
*/
|
|
138
|
+
type CollidingVariants<TTag extends string, TVariants extends VariantShapes> = { [TKey in keyof TVariants]: TTag extends keyof TVariants[TKey] ? TKey : never }[keyof TVariants];
|
|
139
|
+
type ValidateTag<TTag extends string, TVariants extends VariantShapes> = [CollidingVariants<TTag, TVariants>] extends [never] ? unknown : `variant '${CollidingVariants<TTag, TVariants> & string}' declares a field named '${TTag}'`;
|
|
140
|
+
/**
|
|
141
|
+
* A union schema: a {@link Schema} that additionally hosts exhaustive
|
|
142
|
+
* matching over flat (snapshot) values. Matching over live nodes lives on
|
|
143
|
+
* {@link UnionNode}.
|
|
144
|
+
*/
|
|
145
|
+
type UnionSchema<TTag extends string, TVariants extends VariantShapes> = Schema<UnionInput<TTag, TVariants>, UnionValue<TTag, TVariants>, "union", UnionNode<TTag, TVariants>, UnionKinds<TVariants>> & {
|
|
146
|
+
/**
|
|
147
|
+
* Exhaustive match over a flat union value. All tags are required unless
|
|
148
|
+
* `_` is present; the result type is the union of the cases' returns.
|
|
149
|
+
*/
|
|
150
|
+
match<TCases extends UnionValueCases<TTag, TVariants> | PartialCases<UnionValueCases<TTag, TVariants>>>(value: UnionInput<TTag, TVariants>, cases: TCases): CaseResult<TCases>;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Builders for state schemas — `import { s } from "pleach"`.
|
|
154
|
+
*
|
|
155
|
+
* Each builder pairs an ArkType validator with the concurrent-editing
|
|
156
|
+
* semantics of that part of the state tree; composite builders assemble
|
|
157
|
+
* their validator from their children so validation has a single source of
|
|
158
|
+
* truth. ArkType accepts any Standard Schema (`s.scalar(z.string())` works),
|
|
159
|
+
* so zod/valibot users enter for free.
|
|
160
|
+
*/
|
|
161
|
+
interface SchemaBuilders {
|
|
162
|
+
/**
|
|
163
|
+
* Collaboratively editable text. Prefer this for free-form text controls;
|
|
164
|
+
* use `s.scalar("string")` when last-write-wins is enough (selects, radio
|
|
165
|
+
* values, generated identifiers).
|
|
166
|
+
*/
|
|
167
|
+
text(): Schema<string, string, "text", TextNode, "text">;
|
|
168
|
+
/**
|
|
169
|
+
* A whole-value register validated by an ArkType schema. Morphs split the
|
|
170
|
+
* input and value types: `s.scalar("string.numeric.parse")` is edited as a
|
|
171
|
+
* string and validates to a number.
|
|
172
|
+
*/
|
|
173
|
+
scalar<const TSchema>(schema: type.validate<TSchema>): Schema<type.infer.In<TSchema>, type.infer.Out<TSchema>, "scalar", ScalarNode<type.infer.In<TSchema>>, "scalar">;
|
|
174
|
+
/**
|
|
175
|
+
* A register for structured values that are replaced atomically — file
|
|
176
|
+
* references, geo points, rich values picked whole from a dialog. The kind
|
|
177
|
+
* tells collaborative backends never to merge concurrent edits inside the
|
|
178
|
+
* value.
|
|
179
|
+
*/
|
|
180
|
+
opaque<const TSchema>(schema: type.validate<TSchema>): Schema<type.infer.In<TSchema>, type.infer.Out<TSchema>, "opaque", ScalarNode<type.infer.In<TSchema>>, "scalar">;
|
|
181
|
+
/** An ordered collection of nested state, edited per position. */
|
|
182
|
+
list<const TItem extends AnySchema>(item: TItem): Schema<Array<InputOf<TItem>>, Array<ValueOf<TItem>>, "list", ListNode<TItem>, "list" | KindsOf<TItem>>;
|
|
183
|
+
/**
|
|
184
|
+
* A fixed shape of named children, each edited independently and exposed
|
|
185
|
+
* as a property of the object node.
|
|
186
|
+
*/
|
|
187
|
+
object<const TShape extends Shape>(shape: TShape): Schema<ShapeInput<TShape>, ShapeValue<TShape>, "object", ObjectNode<TShape>, "object" | ShapeKinds<TShape>>;
|
|
188
|
+
/**
|
|
189
|
+
* A dynamic string-keyed collection — records keyed by id. Concurrent sets
|
|
190
|
+
* on the same key last-write-win; distinct keys merge.
|
|
191
|
+
*/
|
|
192
|
+
map<const TValue extends AnySchema>(value: TValue): Schema<Record<string, InputOf<TValue>>, Record<string, ValueOf<TValue>>, "map", MapNode<TValue>, "map" | KindsOf<TValue>>;
|
|
193
|
+
/**
|
|
194
|
+
* A discriminated ADT. The tag is an LWW register; each variant's fields
|
|
195
|
+
* live in an independent subtree retained across tag switches. Variants
|
|
196
|
+
* are plain shape records so the snapshot stays flat and the tag key can
|
|
197
|
+
* be checked against variant field names — declaring a field named like
|
|
198
|
+
* the tag is a compile error.
|
|
199
|
+
*/
|
|
200
|
+
union<const TTag extends string, const TVariants extends VariantShapes>(tag: TTag & ValidateTag<TTag, TVariants>, variants: TVariants): UnionSchema<TTag, TVariants>;
|
|
201
|
+
/**
|
|
202
|
+
* A number whose concurrent increments merge additively — a PN-counter,
|
|
203
|
+
* composed from a map of per-actor sums. Backends never see this kind.
|
|
204
|
+
*/
|
|
205
|
+
counter(): Schema<number, number, "counter", CounterNode, "map">;
|
|
206
|
+
}
|
|
207
|
+
declare const s: SchemaBuilders;
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/node.d.ts
|
|
210
|
+
/**
|
|
211
|
+
* The provenance of a change. Beyond the four built-in origins, any token
|
|
212
|
+
* passed to `store.transact(fn, { origin })` flows through unchanged.
|
|
213
|
+
*/
|
|
214
|
+
type Origin = "local" | "remote" | "undo" | "load" | (string & {}) | object;
|
|
215
|
+
/** Delivered to every subscriber when a subtree changes. */
|
|
216
|
+
interface Change {
|
|
217
|
+
readonly origin: Origin;
|
|
218
|
+
/** Convenience: whether this client caused the change. */
|
|
219
|
+
readonly local: boolean;
|
|
220
|
+
}
|
|
221
|
+
type Unsubscribe = () => void;
|
|
222
|
+
interface LoadOptions {
|
|
223
|
+
/** Suppresses change notification, for batch loads that notify once at the end. */
|
|
224
|
+
readonly silent?: boolean;
|
|
225
|
+
}
|
|
226
|
+
/** The structural operations behind `$`, shared by every node kind. */
|
|
227
|
+
interface NodeOps<TInput> {
|
|
228
|
+
/** Resolves one path segment to a child node, if this kind has children. */
|
|
229
|
+
child(segment: string | number): AnyNode | undefined;
|
|
230
|
+
/**
|
|
231
|
+
* Whether this subtree holds a value. Distinguishes "never touched"
|
|
232
|
+
* (absent from snapshots) from explicit empty values such as a cleared
|
|
233
|
+
* list.
|
|
234
|
+
*/
|
|
235
|
+
hasValue(): boolean;
|
|
236
|
+
/** Replaces this subtree's contents from a plain value. Origin: `"load"`. */
|
|
237
|
+
load(value: unknown, options?: LoadOptions): void;
|
|
238
|
+
/**
|
|
239
|
+
* An immutable, structurally shared plain-data view of this subtree, or
|
|
240
|
+
* `undefined` when no value is present. Unchanged subtrees keep reference
|
|
241
|
+
* identity between snapshots.
|
|
242
|
+
*/
|
|
243
|
+
snapshot(): TInput | undefined;
|
|
244
|
+
/** Subscribes to changes anywhere in this subtree. Returns an unsubscriber. */
|
|
245
|
+
subscribe(listener: (change: Change) => void): Unsubscribe;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* A live, mutable piece of state. Kind-specific API is public; structural
|
|
249
|
+
* operations live behind the `$` symbol.
|
|
250
|
+
*/
|
|
251
|
+
interface Node<TInput = unknown> {
|
|
252
|
+
readonly [$]: NodeOps<TInput>;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* A node with its input type erased — the existential bucket used where
|
|
256
|
+
* nodes of unrelated shapes must mix.
|
|
257
|
+
*/
|
|
258
|
+
type AnyNode = Node<unknown>;
|
|
259
|
+
/**
|
|
260
|
+
* A whole-value register. `set` replaces the value; concurrent writers
|
|
261
|
+
* last-write-win.
|
|
262
|
+
*/
|
|
263
|
+
interface ScalarNode<TInput> extends Node<TInput> {
|
|
264
|
+
set(value: TInput): void;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Collaboratively editable text, stored as a character sequence with
|
|
268
|
+
* per-position inserts and deletes rather than as a register.
|
|
269
|
+
*/
|
|
270
|
+
interface TextNode extends Node<string> {
|
|
271
|
+
/** Replaces the whole text — a register-style write. */
|
|
272
|
+
set(value: string): void;
|
|
273
|
+
/** Inserts `text` at character position `index`. */
|
|
274
|
+
insert(index: number, text: string): void;
|
|
275
|
+
/** Deletes `count` characters starting at character position `index`. */
|
|
276
|
+
delete(index: number, count: number): void;
|
|
277
|
+
/**
|
|
278
|
+
* Diffs `next` against the current value and emits the minimal
|
|
279
|
+
* insert/delete pair. The default choice for controlled inputs — it turns
|
|
280
|
+
* "the input now contains X" into collaboration-friendly per-position
|
|
281
|
+
* edits instead of a whole-value write.
|
|
282
|
+
*/
|
|
283
|
+
apply(next: string): void;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* An ordered collection of nested state. All edits are per position — there
|
|
287
|
+
* is deliberately no way to replace the whole array and no arbitrary move.
|
|
288
|
+
*/
|
|
289
|
+
interface ListNode<TItem extends AnySchema> extends Node<Array<InputOf<TItem>>> {
|
|
290
|
+
readonly length: number;
|
|
291
|
+
/** The item node at `index`, or `undefined` when out of bounds. */
|
|
292
|
+
at(index: number): NodeOf<TItem> | undefined;
|
|
293
|
+
/** The item node at `index`; throws when out of bounds. */
|
|
294
|
+
get(index: number): NodeOf<TItem>;
|
|
295
|
+
/** Appends a new item and returns its node. */
|
|
296
|
+
push(initialValue?: InputOf<TItem>): NodeOf<TItem>;
|
|
297
|
+
/** Inserts a new item at `index` and returns its node. */
|
|
298
|
+
insert(index: number, initialValue?: InputOf<TItem>): NodeOf<TItem>;
|
|
299
|
+
/** Deletes `count` items (default one) starting at `index`. */
|
|
300
|
+
delete(index: number, count?: number): void;
|
|
301
|
+
/** Empties the list. The list remains present, snapshotting as `[]`. */
|
|
302
|
+
clear(): void;
|
|
303
|
+
/**
|
|
304
|
+
* Iterates `[index, node, key]` tuples. The key is a backend-stable item
|
|
305
|
+
* identity for the lifetime of an item regardless of reordering — use it
|
|
306
|
+
* for React list keys.
|
|
307
|
+
*/
|
|
308
|
+
entries(): IterableIterator<[index: number, node: NodeOf<TItem>, key: string]>;
|
|
309
|
+
map<TResult>(fn: (node: NodeOf<TItem>, index: number) => TResult): TResult[];
|
|
310
|
+
[Symbol.iterator](): IterableIterator<NodeOf<TItem>>;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* A fixed shape of named children, each edited independently. Child nodes
|
|
314
|
+
* are exposed as properties — `node.address.city` — and are the node's only
|
|
315
|
+
* string keys, so author-chosen names can never collide with internals.
|
|
316
|
+
*/
|
|
317
|
+
type ObjectNode<TShape extends Shape> = { readonly [TKey in keyof TShape]: NodeOf<TShape[TKey]> } & Node<ShapeInput<TShape>>;
|
|
318
|
+
/**
|
|
319
|
+
* A dynamic string-keyed collection — the primitive for "records keyed by
|
|
320
|
+
* id". Concurrent sets on the same key last-write-win; distinct keys merge;
|
|
321
|
+
* add and delete are per key. Entries are reached through `get`, never as
|
|
322
|
+
* properties: the property-access sugar belongs to `ObjectNode` alone.
|
|
323
|
+
*/
|
|
324
|
+
interface MapNode<TValue extends AnySchema> extends Node<Record<string, InputOf<TValue>>> {
|
|
325
|
+
readonly size: number;
|
|
326
|
+
has(key: string): boolean;
|
|
327
|
+
get(key: string): NodeOf<TValue> | undefined;
|
|
328
|
+
/** Creates (or replaces) the entry at `key` and returns its node. */
|
|
329
|
+
set(key: string, initialValue?: InputOf<TValue>): NodeOf<TValue>;
|
|
330
|
+
delete(key: string): void;
|
|
331
|
+
clear(): void;
|
|
332
|
+
keys(): IterableIterator<string>;
|
|
333
|
+
entries(): IterableIterator<[key: string, node: NodeOf<TValue>]>;
|
|
334
|
+
[Symbol.iterator](): IterableIterator<[key: string, node: NodeOf<TValue>]>;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* A number whose concurrent increments merge additively instead of
|
|
338
|
+
* last-write-wins — two peers who each add 1 yield 2, not 1. Composed from a
|
|
339
|
+
* map of per-actor sums; backends never see this kind.
|
|
340
|
+
*/
|
|
341
|
+
interface CounterNode extends Node<number> {
|
|
342
|
+
readonly value: number;
|
|
343
|
+
increment(by?: number): void;
|
|
344
|
+
decrement(by?: number): void;
|
|
345
|
+
}
|
|
346
|
+
/** Exhaustive per-tag handlers over variant nodes. */
|
|
347
|
+
type UnionNodeCases<TVariants extends VariantShapes> = { readonly [TKey in keyof TVariants]: (variant: ObjectNode<TVariants[TKey]>) => unknown };
|
|
348
|
+
/** Partial handlers with a required fallback (which also covers the empty state). */
|
|
349
|
+
type PartialCases<TCases> = Partial<TCases> & {
|
|
350
|
+
readonly _: () => unknown;
|
|
351
|
+
};
|
|
352
|
+
/** The union of a case record's return types. */
|
|
353
|
+
type CaseResult<TCases> = { [TKey in keyof TCases]: TCases[TKey] extends ((...args: never[]) => infer TResult) ? TResult : never }[keyof TCases];
|
|
354
|
+
/**
|
|
355
|
+
* A discriminated ADT node. The tag is an LWW scalar register; each
|
|
356
|
+
* variant's fields live in an independent subtree that is retained across
|
|
357
|
+
* tag switches (snapshots and validation expose only the active variant).
|
|
358
|
+
*
|
|
359
|
+
* Variant fields are deliberately not flattened onto this node — the union
|
|
360
|
+
* node needs public methods, and method names must never mix with
|
|
361
|
+
* author-chosen field names. Access goes through `variant`, `as`, or `match`.
|
|
362
|
+
*/
|
|
363
|
+
interface UnionNode<TTag extends string, TVariants extends VariantShapes> extends Node<UnionInput<TTag, TVariants>> {
|
|
364
|
+
/** The active variant's tag; `undefined` when the union holds no value yet. */
|
|
365
|
+
readonly tag: (keyof TVariants & string) | undefined;
|
|
366
|
+
/** The active variant's object node; `undefined` when no value yet. */
|
|
367
|
+
readonly variant: { [TKey in keyof TVariants]: ObjectNode<TVariants[TKey]> }[keyof TVariants] | undefined;
|
|
368
|
+
/** Type guard: narrows both `tag` and `variant` in one check. */
|
|
369
|
+
is<TKey extends keyof TVariants & string>(tag: TKey): this is UnionNode<TTag, TVariants> & {
|
|
370
|
+
readonly tag: TKey;
|
|
371
|
+
readonly variant: ObjectNode<TVariants[TKey]>;
|
|
372
|
+
};
|
|
373
|
+
/** Narrowed access without a guard; `undefined` when another variant is active. */
|
|
374
|
+
as<TKey extends keyof TVariants & string>(tag: TKey): ObjectNode<TVariants[TKey]> | undefined;
|
|
375
|
+
/**
|
|
376
|
+
* Switches variants — an LWW tag write — and returns the (retained or
|
|
377
|
+
* fresh) variant node.
|
|
378
|
+
*/
|
|
379
|
+
set<TKey extends keyof TVariants & string>(tag: TKey, initialValue?: Simplify<ShapeInput<TVariants[TKey]>>): ObjectNode<TVariants[TKey]>;
|
|
380
|
+
/**
|
|
381
|
+
* Exhaustive match over variant nodes. All tags are required unless `_` is
|
|
382
|
+
* present; `_` also covers the empty (no-value) state, which otherwise
|
|
383
|
+
* throws. The result type is the union of the cases' return types.
|
|
384
|
+
*/
|
|
385
|
+
match<TCases extends UnionNodeCases<TVariants> | PartialCases<UnionNodeCases<TVariants>>>(cases: TCases): CaseResult<TCases>;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Resolves a dynamic path to a descendant node, or `undefined` when any
|
|
389
|
+
* segment is missing. The untyped escape hatch for callers that only know
|
|
390
|
+
* paths at runtime (visual builders); typed code traverses node properties
|
|
391
|
+
* instead.
|
|
392
|
+
*/
|
|
393
|
+
declare function resolve(node: AnyNode, segments: ReadonlyArray<string | number>): AnyNode | undefined;
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/spi.d.ts
|
|
396
|
+
/** The closed set of driver primitives every backend must provide. */
|
|
397
|
+
type PrimitiveKind = "scalar" | "text" | "list" | "object" | "map";
|
|
398
|
+
/** A capability record with no capabilities. */
|
|
399
|
+
type NoCapabilities = Record<never, never>;
|
|
400
|
+
/** Delivered by every driver's `observe`. */
|
|
401
|
+
interface DriverChange {
|
|
402
|
+
readonly origin: Origin;
|
|
403
|
+
}
|
|
404
|
+
/** A whole-value register cell. */
|
|
405
|
+
interface ScalarDriver {
|
|
406
|
+
get(): unknown;
|
|
407
|
+
has(): boolean;
|
|
408
|
+
set(value: unknown): void;
|
|
409
|
+
clear(): void;
|
|
410
|
+
observe(callback: (change: DriverChange) => void): Unsubscribe;
|
|
411
|
+
}
|
|
412
|
+
/** A character sequence with per-position edits. */
|
|
413
|
+
interface TextDriver {
|
|
414
|
+
get(): string | undefined;
|
|
415
|
+
has(): boolean;
|
|
416
|
+
insert(index: number, text: string): void;
|
|
417
|
+
delete(index: number, count: number): void;
|
|
418
|
+
set(value: string): void;
|
|
419
|
+
clear(): void;
|
|
420
|
+
observe(callback: (change: DriverChange) => void): Unsubscribe;
|
|
421
|
+
}
|
|
422
|
+
/** An ordered container of child slots with per-position edits. */
|
|
423
|
+
interface ListDriver {
|
|
424
|
+
readonly length: number;
|
|
425
|
+
/**
|
|
426
|
+
* A backend-stable identity for the item at `index` — stable for the
|
|
427
|
+
* lifetime of the item regardless of reordering (yjs: item IDs; local: a
|
|
428
|
+
* counter). Surfaces as the React list key.
|
|
429
|
+
*/
|
|
430
|
+
key(index: number): string;
|
|
431
|
+
/** Allocates a child slot at `index`. */
|
|
432
|
+
insert(index: number): DriverSlot;
|
|
433
|
+
delete(index: number, count: number): void;
|
|
434
|
+
child(index: number): DriverSlot | undefined;
|
|
435
|
+
clear(): void;
|
|
436
|
+
observe(callback: (change: DriverChange) => void): Unsubscribe;
|
|
437
|
+
}
|
|
438
|
+
/** A fixed-key container; children are allocated lazily per key. */
|
|
439
|
+
interface ObjectDriver {
|
|
440
|
+
child(key: string): DriverSlot;
|
|
441
|
+
observe(callback: (change: DriverChange) => void): Unsubscribe;
|
|
442
|
+
}
|
|
443
|
+
/** An open-key container with per-key adds and deletes. */
|
|
444
|
+
interface MapDriver {
|
|
445
|
+
keys(): IterableIterator<string>;
|
|
446
|
+
has(key: string): boolean;
|
|
447
|
+
/** Creates (or replaces) the entry at `key` and returns its slot. */
|
|
448
|
+
set(key: string): DriverSlot;
|
|
449
|
+
delete(key: string): void;
|
|
450
|
+
child(key: string): DriverSlot | undefined;
|
|
451
|
+
observe(callback: (change: DriverChange) => void): Unsubscribe;
|
|
452
|
+
}
|
|
453
|
+
type AnyDriver = ScalarDriver | TextDriver | ListDriver | ObjectDriver | MapDriver;
|
|
454
|
+
/**
|
|
455
|
+
* A place where a child driver of any primitive kind can be materialized —
|
|
456
|
+
* one position in a container, reified.
|
|
457
|
+
*
|
|
458
|
+
* The slot is the handshake between the two layers: the backend knows
|
|
459
|
+
* *where* children live (it owns the containers), core knows *what* lives
|
|
460
|
+
* there (it owns the schema). Calling one of these methods materializes the
|
|
461
|
+
* driver for this position — creating storage if the position is empty,
|
|
462
|
+
* adopting what's there if it already holds data (a loaded doc, a remote
|
|
463
|
+
* peer's insert). One slot, one call, one child.
|
|
464
|
+
*/
|
|
465
|
+
interface DriverSlot {
|
|
466
|
+
scalar(): ScalarDriver;
|
|
467
|
+
text(): TextDriver;
|
|
468
|
+
list(): ListDriver;
|
|
469
|
+
object(): ObjectDriver;
|
|
470
|
+
map(): MapDriver;
|
|
471
|
+
}
|
|
472
|
+
/** A document-level connection opened by a backend for one store. */
|
|
473
|
+
interface Session<TCaps extends object = NoCapabilities> {
|
|
474
|
+
/** Stable per-client identity (yjs: `doc.clientID`; local: random). */
|
|
475
|
+
readonly actorId: string;
|
|
476
|
+
/** The root container driver. */
|
|
477
|
+
readonly root: ObjectDriver;
|
|
478
|
+
/** Batches writes into one change, tagged with `origin`. */
|
|
479
|
+
transact<TResult>(fn: () => TResult, origin: Origin): TResult;
|
|
480
|
+
/**
|
|
481
|
+
* An optional backend-native driver for a registered non-primitive kind
|
|
482
|
+
* (e.g. a future yjs richtext). Preferred over composition when offered.
|
|
483
|
+
*/
|
|
484
|
+
native?(kind: Kind, slot: DriverSlot): AnyDriver | undefined;
|
|
485
|
+
/** Grafted onto the store, typed — see `CapabilitiesOf`. */
|
|
486
|
+
readonly capabilities: TCaps;
|
|
487
|
+
destroy(): void;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Where state actually lives. Instances come from factories — `local()`,
|
|
491
|
+
* `yjs({...})` — and are handed to `createStore`.
|
|
492
|
+
*/
|
|
493
|
+
interface Backend<TKinds extends Kind = PrimitiveKind, TCaps extends object = NoCapabilities> {
|
|
494
|
+
/** The primitives (plus any native kind overrides) this backend materializes. */
|
|
495
|
+
readonly supportedKinds: readonly TKinds[];
|
|
496
|
+
open(schema: AnySchema): Session<TCaps>;
|
|
497
|
+
}
|
|
498
|
+
/** A backend with its generics erased. */
|
|
499
|
+
type AnyBackend = Backend<Kind, object>;
|
|
500
|
+
/** The kinds a backend can materialize, recovered from its type. */
|
|
501
|
+
type SupportedKindsOf<TBackend extends AnyBackend> = TBackend extends Backend<infer TKinds, infer _TCaps> ? TKinds : never;
|
|
502
|
+
/** The capability record a backend grafts onto its stores. */
|
|
503
|
+
type CapabilitiesOf<TBackend extends AnyBackend> = TBackend extends Backend<infer _TKinds, infer TCaps> ? TCaps : NoCapabilities;
|
|
504
|
+
//#endregion
|
|
505
|
+
export { UnionValueCases as $, UnionNode as A, NodeOf as B, Node as C, PartialCases as D, Origin as E, Infer as F, ShapeInput as G, SchemaBuilders as H, InputOf as I, Simplify as J, ShapeKinds as K, Kind as L, Unsubscribe as M, resolve as N, ScalarNode as O, AnySchema as P, UnionValue as Q, KindRegistry as R, MapNode as S, ObjectNode as T, SchemaMembers as U, Schema as V, Shape as W, UnionKinds as X, UnionInput as Y, UnionSchema as Z, CaseResult as _, DriverChange as a, ListNode as b, MapDriver as c, PrimitiveKind as d, ValueOf as et, ScalarDriver as f, AnyNode as g, TextDriver as h, CapabilitiesOf as i, UnionNodeCases as j, TextNode as k, NoCapabilities as l, SupportedKindsOf as m, AnyDriver as n, s as nt, DriverSlot as o, Session as p, ShapeValue as q, Backend as r, $ as rt, ListDriver as s, AnyBackend as t, VariantShapes as tt, ObjectDriver as u, Change as v, NodeOps as w, LoadOptions as x, CounterNode as y, KindsOf as z };
|
package/dist/spi.d.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as DriverChange, c as MapDriver, d as PrimitiveKind, f as ScalarDriver, h as TextDriver, i as CapabilitiesOf, l as NoCapabilities, m as SupportedKindsOf, n as AnyDriver, o as DriverSlot, p as Session, r as Backend, s as ListDriver, t as AnyBackend, u as ObjectDriver } from "./spi-CzHSw4BF.mjs";
|
|
2
|
+
export { AnyBackend, AnyDriver, Backend, CapabilitiesOf, DriverChange, DriverSlot, ListDriver, MapDriver, NoCapabilities, ObjectDriver, PrimitiveKind, ScalarDriver, Session, SupportedKindsOf, TextDriver };
|
package/dist/spi.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { B as NodeOf, I as InputOf, M as Unsubscribe, P as AnySchema, d as PrimitiveKind, et as ValueOf, i as CapabilitiesOf, l as NoCapabilities, m as SupportedKindsOf, r as Backend, t as AnyBackend, v as Change, z as KindsOf } from "./spi-CzHSw4BF.mjs";
|
|
2
|
+
import { r as UndoApi } from "./capabilities-ALpw-aBj.mjs";
|
|
3
|
+
import { ArkErrors } from "arktype";
|
|
4
|
+
|
|
5
|
+
//#region src/local.d.ts
|
|
6
|
+
interface LocalOptions {
|
|
7
|
+
readonly undo?: boolean;
|
|
8
|
+
}
|
|
9
|
+
type LocalBackend<TOptions extends LocalOptions = LocalOptions> = Backend<PrimitiveKind, TOptions extends {
|
|
10
|
+
undo: true;
|
|
11
|
+
} ? {
|
|
12
|
+
undo: UndoApi;
|
|
13
|
+
} : NoCapabilities>;
|
|
14
|
+
/** Creates an in-memory backend. `actorId` is random per session. */
|
|
15
|
+
declare function local<const TOptions extends LocalOptions = LocalOptions>(options?: TOptions): LocalBackend<TOptions>;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/store.d.ts
|
|
18
|
+
interface Store<TSchema extends AnySchema> {
|
|
19
|
+
readonly schema: TSchema;
|
|
20
|
+
/** The typed live tree. */
|
|
21
|
+
readonly root: NodeOf<TSchema>;
|
|
22
|
+
/**
|
|
23
|
+
* A cached, immutable snapshot of the whole tree, structurally shared:
|
|
24
|
+
* unchanged subtrees keep reference identity between calls.
|
|
25
|
+
*/
|
|
26
|
+
get(): InputOf<TSchema> | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Validates the current snapshot — the same vocabulary as calling the
|
|
29
|
+
* schema: the (possibly morphed) value, or `ArkErrors`.
|
|
30
|
+
*/
|
|
31
|
+
validate(): ValueOf<TSchema> | ArkErrors;
|
|
32
|
+
/** Document-level change feed. */
|
|
33
|
+
subscribe(listener: (change: Change) => void): Unsubscribe;
|
|
34
|
+
/**
|
|
35
|
+
* Selector subscription: fires when the selected value changes
|
|
36
|
+
* (`Object.is` by default).
|
|
37
|
+
*/
|
|
38
|
+
subscribe<TSelected>(selector: (snapshot: InputOf<TSchema> | undefined) => TSelected, listener: (value: TSelected, previous: TSelected) => void, options?: {
|
|
39
|
+
readonly equals?: (a: TSelected, b: TSelected) => boolean;
|
|
40
|
+
readonly fireImmediately?: boolean;
|
|
41
|
+
}): Unsubscribe;
|
|
42
|
+
/**
|
|
43
|
+
* Batches writes into one notification, one undo step, and one tagged
|
|
44
|
+
* origin. Nested transactions flatten (inner adopts outer's origin).
|
|
45
|
+
* Every write outside an explicit transaction is an implicit single-op
|
|
46
|
+
* transaction with origin `"local"`.
|
|
47
|
+
*/
|
|
48
|
+
transact<TResult>(fn: () => TResult, options?: {
|
|
49
|
+
readonly origin?: unknown;
|
|
50
|
+
}): TResult;
|
|
51
|
+
/** Replaces the tree from a plain value — one notification, origin `"load"`. */
|
|
52
|
+
load(value: InputOf<TSchema> | undefined): void;
|
|
53
|
+
/** Loads `undefined`; schema defaults reapply. */
|
|
54
|
+
reset(): void;
|
|
55
|
+
destroy(): void;
|
|
56
|
+
[Symbol.dispose](): void;
|
|
57
|
+
}
|
|
58
|
+
/** The store type a schema produces — `StoreOf<typeof Board>`. */
|
|
59
|
+
type StoreOf<TSchema extends AnySchema> = Store<TSchema>;
|
|
60
|
+
/** A store with its schema erased. */
|
|
61
|
+
type AnyStore = Store<AnySchema>;
|
|
62
|
+
/**
|
|
63
|
+
* Compile-time backend compatibility: resolves to `unknown` (accepting
|
|
64
|
+
* anything) when the backend supports every kind the schema requires, and to
|
|
65
|
+
* a string-literal error message otherwise — arktype-style.
|
|
66
|
+
*/
|
|
67
|
+
type BackendSupports<TSchema extends AnySchema, TBackend extends AnyBackend> = [Exclude<KindsOf<TSchema>, SupportedKindsOf<TBackend>>] extends [never] ? unknown : `backend does not support kind '${Exclude<KindsOf<TSchema>, SupportedKindsOf<TBackend>> & string}'`;
|
|
68
|
+
interface CreateStoreOptions<TSchema extends AnySchema, TBackend extends AnyBackend> {
|
|
69
|
+
readonly backend?: TBackend & BackendSupports<TSchema, TBackend>;
|
|
70
|
+
readonly initial?: InputOf<TSchema>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Materializes a schema into a live store on a backend (the in-memory local
|
|
74
|
+
* backend when none is given). The returned store carries the backend's
|
|
75
|
+
* capabilities as typed members — `yjs({ undo: true })` yields `store.undo`.
|
|
76
|
+
*/
|
|
77
|
+
declare function createStore<TSchema extends AnySchema, TBackend extends AnyBackend = LocalBackend>(schema: TSchema, options?: CreateStoreOptions<TSchema, TBackend>): Store<TSchema> & CapabilitiesOf<TBackend>;
|
|
78
|
+
//#endregion
|
|
79
|
+
export { StoreOf as a, LocalOptions as c, Store as i, local as l, BackendSupports as n, createStore as o, CreateStoreOptions as r, LocalBackend as s, AnyStore as t };
|
package/dist/yjs.d.mts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { I as InputOf, J as Simplify, P as AnySchema, d as PrimitiveKind, l as NoCapabilities, r as Backend } from "./spi-CzHSw4BF.mjs";
|
|
2
|
+
import { n as SyncApi, r as UndoApi, t as PresenceApi } from "./capabilities-ALpw-aBj.mjs";
|
|
3
|
+
import { Doc } from "yjs";
|
|
4
|
+
|
|
5
|
+
//#region src/yjs.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* The slice of a y-protocols `Awareness` instance the presence capability
|
|
8
|
+
* needs — typed structurally so pleach never depends on y-protocols.
|
|
9
|
+
*/
|
|
10
|
+
interface AwarenessLike {
|
|
11
|
+
readonly clientID: number;
|
|
12
|
+
getLocalState(): Record<string, unknown> | null;
|
|
13
|
+
setLocalState(state: Record<string, unknown> | null): void;
|
|
14
|
+
getStates(): Map<number, Record<string, unknown>>;
|
|
15
|
+
on(event: string, handler: (...args: never[]) => void): void;
|
|
16
|
+
off(event: string, handler: (...args: never[]) => void): void;
|
|
17
|
+
}
|
|
18
|
+
interface YjsUndoOptions {
|
|
19
|
+
/** Edits closer together than this merge into one undo step. */
|
|
20
|
+
readonly captureTimeout?: number;
|
|
21
|
+
/** Transaction origins the undo manager tracks; defaults to this store's. */
|
|
22
|
+
readonly trackedOrigins?: ReadonlySet<unknown>;
|
|
23
|
+
}
|
|
24
|
+
interface YjsOptions {
|
|
25
|
+
/** A `Y.Doc` the app has already wired to its providers. */
|
|
26
|
+
readonly doc: Doc;
|
|
27
|
+
/**
|
|
28
|
+
* The top-level key this store lives under (`doc.getMap(root)`), enabling
|
|
29
|
+
* several stores per doc. Defaults to `"pleach"`.
|
|
30
|
+
*/
|
|
31
|
+
readonly root?: string;
|
|
32
|
+
readonly undo?: boolean | YjsUndoOptions;
|
|
33
|
+
readonly awareness?: AwarenessLike;
|
|
34
|
+
/**
|
|
35
|
+
* A pleach schema typing the presence state end to end. Presence is
|
|
36
|
+
* ephemeral — validated, never merged; kinds don't apply.
|
|
37
|
+
*/
|
|
38
|
+
readonly presence?: AnySchema;
|
|
39
|
+
}
|
|
40
|
+
/** The capability record a given options literal produces. */
|
|
41
|
+
type YjsCapabilities<TOptions extends YjsOptions> = Simplify<(TOptions extends {
|
|
42
|
+
undo: true | YjsUndoOptions;
|
|
43
|
+
} ? {
|
|
44
|
+
undo: UndoApi;
|
|
45
|
+
} : NoCapabilities) & (TOptions extends {
|
|
46
|
+
awareness: AwarenessLike;
|
|
47
|
+
} ? {
|
|
48
|
+
presence: PresenceApi<TOptions extends {
|
|
49
|
+
presence: infer TPresence extends AnySchema;
|
|
50
|
+
} ? InputOf<TPresence> : Record<string, unknown>>;
|
|
51
|
+
} : NoCapabilities) & {
|
|
52
|
+
sync: SyncApi;
|
|
53
|
+
}>;
|
|
54
|
+
type YjsBackend<TOptions extends YjsOptions> = Backend<PrimitiveKind, YjsCapabilities<TOptions>>;
|
|
55
|
+
/** Creates a collaborative backend over a user-wired `Y.Doc`. */
|
|
56
|
+
declare function yjs<const TOptions extends YjsOptions>(options: TOptions): YjsBackend<TOptions>;
|
|
57
|
+
//#endregion
|
|
58
|
+
export { AwarenessLike, YjsBackend, YjsCapabilities, YjsOptions, YjsUndoOptions, yjs };
|
package/dist/yjs.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pleached",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Schema-first, CRDT-native state: declare shape and merge semantics once, swap storage backends freely.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"collaboration",
|
|
7
|
+
"crdt",
|
|
8
|
+
"react",
|
|
9
|
+
"schema",
|
|
10
|
+
"state",
|
|
11
|
+
"store",
|
|
12
|
+
"yjs"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/eralmansouri/pleach#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/eralmansouri/pleach/issues"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Eral Almansouri",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/eralmansouri/pleach.git"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.mts",
|
|
32
|
+
"default": "./dist/index.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./react": {
|
|
35
|
+
"types": "./dist/react.d.mts",
|
|
36
|
+
"default": "./dist/react.mjs"
|
|
37
|
+
},
|
|
38
|
+
"./yjs": {
|
|
39
|
+
"types": "./dist/yjs.d.mts",
|
|
40
|
+
"default": "./dist/yjs.mjs"
|
|
41
|
+
},
|
|
42
|
+
"./spi": {
|
|
43
|
+
"types": "./dist/spi.d.mts",
|
|
44
|
+
"default": "./dist/spi.mjs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "vp pack src/index.ts src/react.ts src/yjs.ts src/spi.ts --dts --clean --publint",
|
|
49
|
+
"check": "vp check",
|
|
50
|
+
"test": "vp test",
|
|
51
|
+
"test:watch": "vp test --watch"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"arktype": "^2.2.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@testing-library/react": "^16.3.0",
|
|
58
|
+
"@types/node": "^25.1.0",
|
|
59
|
+
"@types/react": "^19.2.4",
|
|
60
|
+
"@types/react-dom": "^19.2.3",
|
|
61
|
+
"jsdom": "^26.1.0",
|
|
62
|
+
"publint": "^0.3.21",
|
|
63
|
+
"react": "^19.2.4",
|
|
64
|
+
"react-dom": "^19.2.4",
|
|
65
|
+
"typescript": "^6.0.2",
|
|
66
|
+
"vite-plus": "^0.2.2",
|
|
67
|
+
"yjs": "^13.6.20"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"react": ">=18",
|
|
71
|
+
"yjs": ">=13.6.0"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"react": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"yjs": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"engines": {
|
|
82
|
+
"node": ">=20"
|
|
83
|
+
},
|
|
84
|
+
"packageManager": "pnpm@10.33.0"
|
|
85
|
+
}
|