valdres-angular 0.1.0-beta.0

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/dist/index.js ADDED
@@ -0,0 +1,231 @@
1
+ // src/injectAtom.ts
2
+ import { signal, DestroyRef, inject as inject2 } from "@angular/core";
3
+ import { isPromiseLike } from "valdres";
4
+
5
+ // src/injectStore.ts
6
+ import { inject } from "@angular/core";
7
+
8
+ // src/lib/VALDRES_STORE.ts
9
+ import { InjectionToken } from "@angular/core";
10
+ var VALDRES_STORE = new InjectionToken("VALDRES_STORE");
11
+
12
+ // src/injectStore.ts
13
+ var injectStore = (id) => {
14
+ const ctx = inject(VALDRES_STORE, { optional: true });
15
+ if (!ctx) {
16
+ throw new Error("No valdres store provided. Call provideValdres() in your application providers.");
17
+ }
18
+ if (id) {
19
+ const store = ctx.stores[id];
20
+ if (!store)
21
+ throw new Error(`No store with id "${id}" found in ancestor chain`);
22
+ return store;
23
+ }
24
+ return ctx.current;
25
+ };
26
+
27
+ // src/injectAtom.ts
28
+ var injectAtom = (atom, store) => {
29
+ const currentStore = store || injectStore();
30
+ const destroyRef = inject2(DestroyRef);
31
+ const initial = currentStore.get(atom);
32
+ if (isPromiseLike(initial)) {
33
+ throw new Error("injectAtom() received async state. Atoms should have synchronous default values.");
34
+ }
35
+ const inner = signal(initial);
36
+ const signalSet = inner.set.bind(inner);
37
+ const unsub = currentStore.sub(atom, () => {
38
+ const newValue = currentStore.get(atom);
39
+ if (!isPromiseLike(newValue)) {
40
+ signalSet(newValue);
41
+ }
42
+ }, false);
43
+ destroyRef.onDestroy(() => {
44
+ unsub();
45
+ });
46
+ const atomSignal = inner;
47
+ atomSignal.set = (value) => {
48
+ signalSet(value);
49
+ currentStore.set(atom, value);
50
+ };
51
+ atomSignal.update = (fn) => {
52
+ const next = fn(inner());
53
+ signalSet(next);
54
+ currentStore.set(atom, next);
55
+ };
56
+ atomSignal.reset = () => {
57
+ currentStore.reset(atom);
58
+ const resetValue = currentStore.get(atom);
59
+ if (!isPromiseLike(resetValue)) {
60
+ signalSet(resetValue);
61
+ }
62
+ };
63
+ return atomSignal;
64
+ };
65
+ // src/injectResetAtom.ts
66
+ var injectResetAtom = (atom, store) => {
67
+ const selectedStore = store || injectStore();
68
+ return () => selectedStore.reset(atom);
69
+ };
70
+ // src/injectSetAtom.ts
71
+ var injectSetAtom = (atom, store) => {
72
+ const selectedStore = store || injectStore();
73
+ return (value) => selectedStore.set(atom, value);
74
+ };
75
+ // src/injectTransaction.ts
76
+ var injectTransaction = (store) => {
77
+ const selectedStore = store || injectStore();
78
+ return (callback) => selectedStore.txn(callback);
79
+ };
80
+ // src/injectValue.ts
81
+ import {
82
+ signal as signal2,
83
+ computed,
84
+ DestroyRef as DestroyRef2,
85
+ inject as inject3
86
+ } from "@angular/core";
87
+ import { isPromiseLike as isPromiseLike2 } from "valdres";
88
+ var injectValue = (state, store) => {
89
+ const currentStore = store || injectStore();
90
+ const destroyRef = inject3(DestroyRef2);
91
+ const value = signal2(undefined);
92
+ const status = signal2("loading");
93
+ const error = signal2(undefined);
94
+ let destroyed = false;
95
+ let hasResolved = false;
96
+ let currentVersion = 0;
97
+ const handleValue = (val) => {
98
+ if (isPromiseLike2(val)) {
99
+ const version = ++currentVersion;
100
+ status.set(hasResolved ? "reloading" : "loading");
101
+ val.then((resolved) => {
102
+ if (destroyed || version !== currentVersion)
103
+ return;
104
+ hasResolved = true;
105
+ value.set(resolved);
106
+ status.set("resolved");
107
+ error.set(undefined);
108
+ }, (err) => {
109
+ if (destroyed || version !== currentVersion)
110
+ return;
111
+ error.set(err);
112
+ status.set("error");
113
+ });
114
+ } else {
115
+ hasResolved = true;
116
+ value.set(val);
117
+ status.set("resolved");
118
+ error.set(undefined);
119
+ }
120
+ };
121
+ handleValue(currentStore.get(state));
122
+ const unsub = currentStore.sub(state, () => {
123
+ handleValue(currentStore.get(state));
124
+ }, false);
125
+ destroyRef.onDestroy(() => {
126
+ destroyed = true;
127
+ unsub();
128
+ });
129
+ return {
130
+ value: value.asReadonly(),
131
+ status: status.asReadonly(),
132
+ error: error.asReadonly(),
133
+ isLoading: computed(() => status() === "loading" || status() === "reloading"),
134
+ hasValue: computed(() => status() === "resolved" || status() === "reloading")
135
+ };
136
+ };
137
+ // src/provideValdres.ts
138
+ import { makeEnvironmentProviders } from "@angular/core";
139
+ import { store as createStore } from "valdres";
140
+
141
+ // src/lib/hydrate.ts
142
+ var hydrate = (set, state) => {
143
+ for (const [atom, value] of state) {
144
+ set(atom, value);
145
+ }
146
+ };
147
+
148
+ // src/provideValdres.ts
149
+ var provideValdres = (options = {}) => {
150
+ return makeEnvironmentProviders([
151
+ {
152
+ provide: VALDRES_STORE,
153
+ useFactory: () => {
154
+ let store = options.store;
155
+ if (store) {
156
+ if (!store.data.batchUpdates) {
157
+ console.warn("valdres-angular: The store passed to provideValdres() was not created " + "with { batchUpdates: true }. Sequential store.set() calls " + "will trigger intermediate selector evaluations. Consider " + "using store({ batchUpdates: true }) for optimal performance.");
158
+ }
159
+ } else {
160
+ store = createStore({ batchUpdates: true });
161
+ }
162
+ if (options.initialize) {
163
+ store.txn((txn) => {
164
+ const pairs = options.initialize(txn);
165
+ if (pairs) {
166
+ hydrate(txn.set, pairs);
167
+ }
168
+ });
169
+ }
170
+ return {
171
+ current: store,
172
+ stores: { [store.data.id]: store }
173
+ };
174
+ }
175
+ }
176
+ ]);
177
+ };
178
+ // src/provideValdresScope.ts
179
+ import { inject as inject4, DestroyRef as DestroyRef3 } from "@angular/core";
180
+ var nextScopeId = 0;
181
+ var generateId = () => globalThis.crypto?.randomUUID?.() ?? `valdres-scope-${++nextScopeId}`;
182
+ var provideValdresScope = (options = {}) => {
183
+ return [
184
+ {
185
+ provide: VALDRES_STORE,
186
+ useFactory: () => {
187
+ const parentCtx = inject4(VALDRES_STORE, {
188
+ skipSelf: true,
189
+ optional: true
190
+ });
191
+ if (!parentCtx) {
192
+ throw new Error("No valdres store provided. provideValdresScope() must be nested under provideValdres().");
193
+ }
194
+ const destroyRef = inject4(DestroyRef3);
195
+ const scopeId = options.scopeId ?? generateId();
196
+ const scopeCreated = !parentCtx.current.data.scopes?.has(scopeId);
197
+ const scopedStore = parentCtx.current.scope(scopeId);
198
+ if (options.initialize) {
199
+ scopedStore.txn((txn) => {
200
+ const pairs = options.initialize(txn);
201
+ if (pairs) {
202
+ hydrate(txn.set, pairs);
203
+ }
204
+ });
205
+ }
206
+ destroyRef.onDestroy(() => {
207
+ scopedStore?.detach?.(scopeCreated);
208
+ });
209
+ return {
210
+ current: scopedStore,
211
+ stores: {
212
+ ...parentCtx.stores,
213
+ [parentCtx.current.data.id]: parentCtx.current,
214
+ [scopedStore.data.id]: scopedStore
215
+ }
216
+ };
217
+ }
218
+ }
219
+ ];
220
+ };
221
+ export {
222
+ provideValdresScope,
223
+ provideValdres,
224
+ injectValue,
225
+ injectTransaction,
226
+ injectStore,
227
+ injectSetAtom,
228
+ injectResetAtom,
229
+ injectAtom,
230
+ VALDRES_STORE
231
+ };
@@ -0,0 +1,14 @@
1
+ export { injectAtom } from "./injectAtom";
2
+ export type { AtomSignal } from "./injectAtom";
3
+ export { injectResetAtom } from "./injectResetAtom";
4
+ export { injectSetAtom } from "./injectSetAtom";
5
+ export { injectStore } from "./injectStore";
6
+ export { injectTransaction } from "./injectTransaction";
7
+ export { injectValue } from "./injectValue";
8
+ export type { ValueState, ValueStatus } from "./injectValue";
9
+ export { provideValdres } from "./provideValdres";
10
+ export type { ValdresOptions } from "./provideValdres";
11
+ export { provideValdresScope } from "./provideValdresScope";
12
+ export type { ValdresScopeOptions } from "./provideValdresScope";
13
+ export { VALDRES_STORE } from "./lib/VALDRES_STORE";
14
+ export type { ValdresContext } from "./lib/VALDRES_STORE";
@@ -0,0 +1,6 @@
1
+ import { type WritableSignal } from "@angular/core";
2
+ import { type Atom, type Store } from "valdres";
3
+ export type AtomSignal<V> = WritableSignal<V> & {
4
+ reset: () => void;
5
+ };
6
+ export declare const injectAtom: <V>(atom: Atom<V>, store?: Store) => AtomSignal<V>;
@@ -0,0 +1,2 @@
1
+ import type { Atom, Store } from "valdres";
2
+ export declare const injectResetAtom: <V>(atom: Atom<V>, store?: Store) => (() => void);
@@ -0,0 +1,2 @@
1
+ import type { Atom, Store } from "valdres";
2
+ export declare const injectSetAtom: <V>(atom: Atom<V>, store?: Store) => (value: V) => void;
@@ -0,0 +1,2 @@
1
+ import type { Store } from "valdres";
2
+ export declare const injectStore: (id?: string) => Store;
@@ -0,0 +1,2 @@
1
+ import type { Store, Transaction } from "valdres";
2
+ export declare const injectTransaction: (store?: Store) => (callback: (state: Transaction) => any) => void;
@@ -0,0 +1,13 @@
1
+ import { type Signal } from "@angular/core";
2
+ import { type Atom, type Selector, type Store } from "valdres";
3
+ /** Mirrors Angular's ResourceStatus naming where applicable. */
4
+ export type ValueStatus = "loading" | "reloading" | "resolved" | "error";
5
+ export interface ValueState<V> {
6
+ readonly value: Signal<V | undefined>;
7
+ readonly status: Signal<ValueStatus>;
8
+ readonly error: Signal<unknown | undefined>;
9
+ /** True when status is "loading" or "reloading". */
10
+ readonly isLoading: Signal<boolean>;
11
+ readonly hasValue: Signal<boolean>;
12
+ }
13
+ export declare const injectValue: <V>(state: Atom<V> | Selector<V>, store?: Store) => ValueState<V>;
@@ -0,0 +1,7 @@
1
+ import { InjectionToken } from "@angular/core";
2
+ import type { Store } from "valdres";
3
+ export interface ValdresContext {
4
+ current: Store;
5
+ stores: Record<string, Store>;
6
+ }
7
+ export declare const VALDRES_STORE: InjectionToken<ValdresContext>;
@@ -0,0 +1,2 @@
1
+ import type { Atom, SyncSetAtom } from "valdres";
2
+ export declare const hydrate: (set: SyncSetAtom, state: [Atom, any][]) => void;
@@ -0,0 +1,7 @@
1
+ import { type Store } from "valdres";
2
+ import type { InitializeCallback } from "./types/InitializeCallback";
3
+ export interface ValdresOptions {
4
+ store?: Store;
5
+ initialize?: InitializeCallback;
6
+ }
7
+ export declare const provideValdres: (options?: ValdresOptions) => import("@angular/core").EnvironmentProviders;
@@ -0,0 +1,7 @@
1
+ import { type Provider } from "@angular/core";
2
+ import type { InitializeCallback } from "./types/InitializeCallback";
3
+ export interface ValdresScopeOptions {
4
+ scopeId?: string;
5
+ initialize?: InitializeCallback;
6
+ }
7
+ export declare const provideValdresScope: (options?: ValdresScopeOptions) => Provider[];
@@ -0,0 +1,4 @@
1
+ import type { Atom, Transaction } from "valdres";
2
+ type AtomPair = [Atom<any>, any];
3
+ export type InitializeCallback = (txn: Transaction) => void | AtomPair[];
4
+ export {};
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "valdres-angular",
3
+ "version": "0.1.0-beta.0",
4
+ "license": "MIT",
5
+ "author": {
6
+ "name": "Eigil Sagafos"
7
+ },
8
+ "homepage": "https://valdres.dev",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/eigilsagafos/valdres.git"
12
+ },
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.js",
17
+ "types": "./dist/types/index.d.ts"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "peerDependencies": {
24
+ "@angular/core": ">=17",
25
+ "valdres": "workspace:^"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org/"
30
+ }
31
+ }