@ngstato/core 0.1.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.mjs ADDED
@@ -0,0 +1,455 @@
1
+ // src/store.ts
2
+ var StatoStore = class {
3
+ // Le state interne — jamais accessible directement
4
+ _state;
5
+ // Les abonnés — notifiés à chaque changement
6
+ _subscribers = /* @__PURE__ */ new Set();
7
+ // Les actions enregistrées
8
+ _actions = {};
9
+ // Les computed enregistrés
10
+ _computed = {};
11
+ // Les cleanups à appeler à la destruction
12
+ _cleanups = [];
13
+ // Les hooks lifecycle
14
+ _hooks;
15
+ constructor(config) {
16
+ const { actions, computed, hooks, ...initialState } = config;
17
+ this._state = initialState;
18
+ this._hooks = hooks ?? {};
19
+ if (actions) {
20
+ for (const [name, fn] of Object.entries(actions)) {
21
+ this._actions[name] = fn;
22
+ }
23
+ }
24
+ if (computed) {
25
+ for (const [name, fn] of Object.entries(computed)) {
26
+ if (typeof fn === "function") {
27
+ this._computed[name] = () => fn(this._state);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ // ── Lire le state ──────────────────────────────────
33
+ getState() {
34
+ return { ...this._state };
35
+ }
36
+ // ── Modifier le state — usage interne uniquement ───
37
+ _setState(partial) {
38
+ this._state = { ...this._state, ...partial };
39
+ this._notify();
40
+ }
41
+ // ── Notifier tous les abonnés ──────────────────────
42
+ _notify() {
43
+ for (const subscriber of this._subscribers) {
44
+ subscriber({ ...this._state });
45
+ }
46
+ }
47
+ // ── S'abonner aux changements ──────────────────────
48
+ subscribe(fn) {
49
+ this._subscribers.add(fn);
50
+ return () => this._subscribers.delete(fn);
51
+ }
52
+ // ── Exécuter une action ────────────────────────────
53
+ async dispatch(actionName, ...args) {
54
+ const action = this._actions[actionName];
55
+ if (!action) {
56
+ throw new Error(`[Stato] Action "${actionName}" introuvable`);
57
+ }
58
+ this._hooks.onAction?.(actionName, args);
59
+ const start = Date.now();
60
+ const prevState = { ...this._state };
61
+ const stateProxy = new Proxy({ ...this._state }, {
62
+ set: (target, key, value) => {
63
+ target[key] = value;
64
+ this._setState({ [key]: value });
65
+ return true;
66
+ }
67
+ });
68
+ try {
69
+ await action(stateProxy, ...args);
70
+ this._hooks.onActionDone?.(actionName, Date.now() - start);
71
+ this._hooks.onStateChange?.(prevState, { ...this._state });
72
+ } catch (error) {
73
+ this._hooks.onError?.(error, actionName);
74
+ throw error;
75
+ }
76
+ }
77
+ // ── Lire une valeur computed ───────────────────────
78
+ getComputed(name) {
79
+ const fn = this._computed[name];
80
+ if (!fn) throw new Error(`[Stato] Computed "${name}" introuvable`);
81
+ return fn();
82
+ }
83
+ // ── Enregistrer un cleanup (pour fromStream) ───────
84
+ registerCleanup(fn) {
85
+ this._cleanups.push(fn);
86
+ }
87
+ // ── Lifecycle — appelé par l'adaptateur Angular ────
88
+ init(publicStore) {
89
+ this._hooks.onInit?.(publicStore);
90
+ }
91
+ destroy(publicStore) {
92
+ this._hooks.onDestroy?.(publicStore);
93
+ for (const cleanup of this._cleanups) {
94
+ cleanup();
95
+ }
96
+ this._cleanups = [];
97
+ this._subscribers.clear();
98
+ }
99
+ };
100
+ function createStore(config) {
101
+ const store = new StatoStore(config);
102
+ const publicStore = {
103
+ // Accès au store interne — pour les adaptateurs Angular/React/Vue
104
+ __store__: store,
105
+ // S'abonner aux changements
106
+ subscribe: store.subscribe.bind(store),
107
+ // Lire le state complet
108
+ getState: store.getState.bind(store),
109
+ // Enregistrer un cleanup
110
+ registerCleanup: store.registerCleanup.bind(store)
111
+ };
112
+ const initialState = store.getState();
113
+ for (const key of Object.keys(initialState)) {
114
+ Object.defineProperty(publicStore, key, {
115
+ get: () => store.getState()[key],
116
+ enumerable: true,
117
+ configurable: true
118
+ });
119
+ }
120
+ const { actions, computed } = config;
121
+ if (actions) {
122
+ for (const name of Object.keys(actions)) {
123
+ publicStore[name] = (...args) => store.dispatch(name, ...args);
124
+ }
125
+ }
126
+ if (computed) {
127
+ for (const name of Object.keys(computed)) {
128
+ Object.defineProperty(publicStore, name, {
129
+ get: () => store.getComputed(name),
130
+ enumerable: true,
131
+ configurable: true
132
+ });
133
+ }
134
+ }
135
+ return publicStore;
136
+ }
137
+
138
+ // src/types.ts
139
+ var StatoHttpError = class extends Error {
140
+ constructor(status, body) {
141
+ super(`HTTP ${status}: ${body}`);
142
+ this.status = status;
143
+ this.body = body;
144
+ this.name = "StatoHttpError";
145
+ }
146
+ };
147
+
148
+ // src/http.ts
149
+ var StatoHttp = class {
150
+ _config;
151
+ constructor(config = {}) {
152
+ this._config = config;
153
+ }
154
+ // ── GET ───────────────────────────────────────────
155
+ get(url, options) {
156
+ return this._request("GET", url, void 0, options);
157
+ }
158
+ // ── POST ──────────────────────────────────────────
159
+ post(url, body, options) {
160
+ return this._request("POST", url, body, options);
161
+ }
162
+ // ── PUT ───────────────────────────────────────────
163
+ put(url, body, options) {
164
+ return this._request("PUT", url, body, options);
165
+ }
166
+ // ── PATCH ─────────────────────────────────────────
167
+ patch(url, body, options) {
168
+ return this._request("PATCH", url, body, options);
169
+ }
170
+ // ── DELETE ────────────────────────────────────────
171
+ delete(url, options) {
172
+ return this._request("DELETE", url, void 0, options);
173
+ }
174
+ // ── MOTEUR INTERNE ────────────────────────────────
175
+ async _request(method, url, body, options) {
176
+ const fullUrl = this._buildUrl(url, options?.params);
177
+ const token = this._config.auth?.();
178
+ const headers = {
179
+ "Content-Type": "application/json",
180
+ ...this._config.headers,
181
+ ...options?.headers,
182
+ ...token ? { Authorization: `Bearer ${token}` } : {}
183
+ };
184
+ let signal = options?.signal;
185
+ let timeoutId;
186
+ if (this._config.timeout && !signal) {
187
+ const controller = new AbortController();
188
+ signal = controller.signal;
189
+ timeoutId = setTimeout(
190
+ () => controller.abort(),
191
+ this._config.timeout
192
+ );
193
+ }
194
+ try {
195
+ const response = await fetch(fullUrl, {
196
+ method,
197
+ headers,
198
+ signal,
199
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
200
+ });
201
+ if (!response.ok) {
202
+ const errorBody = await response.text();
203
+ throw new StatoHttpError(response.status, errorBody);
204
+ }
205
+ const contentType = response.headers.get("content-type");
206
+ if (response.status === 204 || !contentType?.includes("application/json")) {
207
+ return void 0;
208
+ }
209
+ return response.json();
210
+ } finally {
211
+ if (timeoutId) clearTimeout(timeoutId);
212
+ }
213
+ }
214
+ // ── CONSTRUIRE L'URL AVEC PARAMS ──────────────────
215
+ _buildUrl(path, params) {
216
+ const url = path.startsWith("http") ? path : `${this._config.baseUrl ?? ""}${path}`;
217
+ if (!params || Object.keys(params).length === 0) return url;
218
+ const qs = new URLSearchParams(
219
+ Object.entries(params).map(([k, v]) => [k, String(v)])
220
+ ).toString();
221
+ return `${url}?${qs}`;
222
+ }
223
+ };
224
+ function createHttp(config = {}) {
225
+ return new StatoHttp(config);
226
+ }
227
+ var _globalHttp = new StatoHttp();
228
+ function configureHttp(config) {
229
+ _globalHttp = new StatoHttp(config);
230
+ }
231
+ var http = {
232
+ get: (url, options) => _globalHttp.get(url, options),
233
+ post: (url, body, options) => _globalHttp.post(url, body, options),
234
+ put: (url, body, options) => _globalHttp.put(url, body, options),
235
+ patch: (url, body, options) => _globalHttp.patch(url, body, options),
236
+ delete: (url, options) => _globalHttp.delete(url, options)
237
+ };
238
+
239
+ // src/helpers/abortable.ts
240
+ function abortable(fn) {
241
+ let controller = null;
242
+ return async (state, ...args) => {
243
+ if (controller) {
244
+ controller.abort();
245
+ }
246
+ controller = new AbortController();
247
+ const signal = controller.signal;
248
+ try {
249
+ await fn(state, ...args, { signal });
250
+ } catch (error) {
251
+ if (error?.name === "AbortError") return;
252
+ throw error;
253
+ } finally {
254
+ controller = null;
255
+ }
256
+ };
257
+ }
258
+
259
+ // src/helpers/debounced.ts
260
+ function debounced(fn, ms = 300) {
261
+ let timer = null;
262
+ return (state, ...args) => {
263
+ if (timer) clearTimeout(timer);
264
+ return new Promise((resolve, reject) => {
265
+ timer = setTimeout(async () => {
266
+ try {
267
+ await fn(state, ...args);
268
+ resolve();
269
+ } catch (error) {
270
+ reject(error);
271
+ } finally {
272
+ timer = null;
273
+ }
274
+ }, ms);
275
+ });
276
+ };
277
+ }
278
+
279
+ // src/helpers/throttled.ts
280
+ function throttled(fn, ms = 300) {
281
+ let lastCall = 0;
282
+ let timer = null;
283
+ return async (state, ...args) => {
284
+ const now = Date.now();
285
+ const remaining = ms - (now - lastCall);
286
+ if (remaining <= 0) {
287
+ lastCall = now;
288
+ if (timer) {
289
+ clearTimeout(timer);
290
+ timer = null;
291
+ }
292
+ await fn(state, ...args);
293
+ } else {
294
+ if (timer) clearTimeout(timer);
295
+ return new Promise((resolve, reject) => {
296
+ timer = setTimeout(async () => {
297
+ lastCall = Date.now();
298
+ timer = null;
299
+ try {
300
+ await fn(state, ...args);
301
+ resolve();
302
+ } catch (error) {
303
+ reject(error);
304
+ }
305
+ }, remaining);
306
+ });
307
+ }
308
+ };
309
+ }
310
+
311
+ // src/helpers/retryable.ts
312
+ function retryable(fn, options = {
313
+ attempts: 3,
314
+ backoff: "exponential",
315
+ delay: 1e3
316
+ }) {
317
+ return async (state, ...args) => {
318
+ const { attempts, backoff, delay = 1e3, onRetry } = options;
319
+ for (let i = 0; i < attempts; i++) {
320
+ try {
321
+ await fn(state, ...args);
322
+ return;
323
+ } catch (error) {
324
+ const isLastAttempt = i === attempts - 1;
325
+ if (isLastAttempt) throw error;
326
+ const waitMs = backoff === "exponential" ? delay * Math.pow(2, i) : delay;
327
+ onRetry?.(i + 1, error);
328
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
329
+ }
330
+ }
331
+ };
332
+ }
333
+
334
+ // src/helpers/from-stream.ts
335
+ function fromStream(setupFn, updateFn, options) {
336
+ return (state) => {
337
+ const stream$ = setupFn(state);
338
+ const subscription = stream$.subscribe({
339
+ next: (value) => {
340
+ updateFn(state, value);
341
+ },
342
+ error: (error) => {
343
+ options?.onError?.(error);
344
+ },
345
+ complete: () => {
346
+ options?.onComplete?.();
347
+ }
348
+ });
349
+ return () => subscription.unsubscribe();
350
+ };
351
+ }
352
+
353
+ // src/helpers/optimistic.ts
354
+ function optimistic(immediate, confirm) {
355
+ return async (state, ...args) => {
356
+ const snapshot = { ...state };
357
+ immediate(state, ...args);
358
+ try {
359
+ await confirm(state, ...args);
360
+ } catch (error) {
361
+ Object.assign(state, snapshot);
362
+ throw error;
363
+ }
364
+ };
365
+ }
366
+
367
+ // src/devtools.ts
368
+ function createDevTools(maxLogs = 50) {
369
+ let counter = 0;
370
+ const state = {
371
+ logs: [],
372
+ isOpen: false,
373
+ maxLogs
374
+ };
375
+ const listeners = /* @__PURE__ */ new Set();
376
+ function notify() {
377
+ listeners.forEach((cb) => cb({ ...state, logs: [...state.logs] }));
378
+ }
379
+ return {
380
+ state,
381
+ logAction(log) {
382
+ const entry = {
383
+ ...log,
384
+ id: ++counter,
385
+ at: (/* @__PURE__ */ new Date()).toISOString()
386
+ };
387
+ state.logs = [entry, ...state.logs].slice(0, maxLogs);
388
+ notify();
389
+ },
390
+ clear() {
391
+ state.logs = [];
392
+ notify();
393
+ },
394
+ open() {
395
+ state.isOpen = true;
396
+ notify();
397
+ },
398
+ close() {
399
+ state.isOpen = false;
400
+ notify();
401
+ },
402
+ toggle() {
403
+ state.isOpen = !state.isOpen;
404
+ notify();
405
+ },
406
+ subscribe(cb) {
407
+ listeners.add(cb);
408
+ return () => listeners.delete(cb);
409
+ }
410
+ };
411
+ }
412
+ var devTools = createDevTools();
413
+ function connectDevTools(store, storeName) {
414
+ if (!devTools) return;
415
+ let prevState = {};
416
+ const internalStore = store.__store__;
417
+ if (!internalStore) return;
418
+ const existingHooks = { ...internalStore["_hooks"] };
419
+ internalStore["_hooks"] = {
420
+ ...existingHooks,
421
+ onAction(name, args) {
422
+ prevState = store.getState();
423
+ existingHooks.onAction?.(name, args);
424
+ },
425
+ onActionDone(name, duration) {
426
+ const nextState = store.getState();
427
+ devTools.logAction({
428
+ name: `[${storeName}] ${name}`,
429
+ args: [],
430
+ duration,
431
+ status: "success",
432
+ prevState: { ...prevState },
433
+ nextState: { ...nextState }
434
+ });
435
+ existingHooks.onActionDone?.(name, duration);
436
+ },
437
+ onError(error, actionName) {
438
+ devTools.logAction({
439
+ name: `[${storeName}] ${actionName}`,
440
+ args: [],
441
+ duration: 0,
442
+ status: "error",
443
+ error: error.message,
444
+ prevState: { ...prevState },
445
+ nextState: { ...prevState }
446
+ });
447
+ existingHooks.onError?.(error, actionName);
448
+ },
449
+ onStateChange: existingHooks.onStateChange
450
+ };
451
+ }
452
+
453
+ export { StatoHttp, StatoHttpError, abortable, configureHttp, connectDevTools, createDevTools, createHttp, createStore, debounced, devTools, fromStream, http, optimistic, retryable, throttled };
454
+ //# sourceMappingURL=index.mjs.map
455
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store.ts","../src/types.ts","../src/http.ts","../src/helpers/abortable.ts","../src/helpers/debounced.ts","../src/helpers/throttled.ts","../src/helpers/retryable.ts","../src/helpers/from-stream.ts","../src/helpers/optimistic.ts","../src/devtools.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// ─────────────────────────────────────────────────────\r\n// @ngstato/core — createStore()\r\n// Le moteur principal de Stato\r\n// ─────────────────────────────────────────────────────\r\n\r\nimport type {\r\n StatoStoreConfig,\r\n StatoHooks,\r\n StateSlice\r\n} from './types'\r\n\r\n// ─────────────────────────────────────────────────────\r\n// CLASSE INTERNE — jamais exposée directement\r\n// ─────────────────────────────────────────────────────\r\n\r\nclass StatoStore<S extends object> {\r\n\r\n // Le state interne — jamais accessible directement\r\n private _state: StateSlice<S>\r\n\r\n // Les abonnés — notifiés à chaque changement\r\n private _subscribers: Set<(state: StateSlice<S>) => void> = new Set()\r\n\r\n // Les actions enregistrées\r\n private _actions: Record<string, Function> = {}\r\n\r\n // Les computed enregistrés\r\n private _computed: Record<string, () => unknown> = {}\r\n\r\n // Les cleanups à appeler à la destruction\r\n private _cleanups: Array<() => void> = []\r\n\r\n // Les hooks lifecycle\r\n private _hooks: StatoHooks<any>\r\n\r\n constructor(config: StatoStoreConfig<S>) {\r\n // 1. Extraire le state initial — tout sauf actions/computed/hooks\r\n const { actions, computed, hooks, ...initialState } = config\r\n this._state = initialState as StateSlice<S>\r\n this._hooks = hooks ?? {}\r\n\r\n // 2. Enregistrer les actions\r\n if (actions) {\r\n for (const [name, fn] of Object.entries(actions)) {\r\n this._actions[name] = fn\r\n }\r\n }\r\n\r\n // 3. Enregistrer les computed\r\n if (computed) {\r\n for (const [name, fn] of Object.entries(computed)) {\r\n if (typeof fn === 'function') {\r\n this._computed[name] = () => (fn as Function)(this._state)\r\n }\r\n }\r\n }\r\n }\r\n\r\n // ── Lire le state ──────────────────────────────────\r\n getState(): Readonly<StateSlice<S>> {\r\n return { ...this._state }\r\n }\r\n\r\n // ── Modifier le state — usage interne uniquement ───\r\n private _setState(partial: Partial<StateSlice<S>>) {\r\n // Copie immutable — on ne modifie jamais l'objet original\r\n this._state = { ...this._state, ...partial }\r\n this._notify()\r\n }\r\n\r\n // ── Notifier tous les abonnés ──────────────────────\r\n private _notify() {\r\n for (const subscriber of this._subscribers) {\r\n subscriber({ ...this._state })\r\n }\r\n }\r\n\r\n // ── S'abonner aux changements ──────────────────────\r\n subscribe(fn: (state: StateSlice<S>) => void): () => void {\r\n this._subscribers.add(fn)\r\n // Retourne une fonction de désabonnement\r\n return () => this._subscribers.delete(fn)\r\n }\r\n\r\n // ── Exécuter une action ────────────────────────────\r\nasync dispatch(actionName: string, ...args: unknown[]) {\r\n const action = this._actions[actionName]\r\n if (!action) {\r\n throw new Error(`[Stato] Action \"${actionName}\" introuvable`)\r\n }\r\n\r\n // Hook onAction — avant l'exécution\r\n this._hooks.onAction?.(actionName, args)\r\n\r\n const start = Date.now()\r\n const prevState = { ...this._state }\r\n\r\n const stateProxy = new Proxy({ ...this._state } as any, {\r\n set: (target, key, value) => {\r\n target[key] = value\r\n this._setState({ [key]: value } as any)\r\n return true\r\n }\r\n })\r\n\r\n try {\r\n await action(stateProxy, ...args)\r\n\r\n // Hook onActionDone — après l'exécution\r\n this._hooks.onActionDone?.(actionName, Date.now() - start)\r\n\r\n // Hook onStateChange — si le state a changé\r\n this._hooks.onStateChange?.(prevState as any, { ...this._state } as any)\r\n\r\n } catch (error) {\r\n // Hook onError — si une erreur est lancée\r\n this._hooks.onError?.(error as Error, actionName)\r\n throw error // on remonte l'erreur quand même\r\n }\r\n}\r\n\r\n // ── Lire une valeur computed ───────────────────────\r\n getComputed(name: string): unknown {\r\n const fn = this._computed[name]\r\n if (!fn) throw new Error(`[Stato] Computed \"${name}\" introuvable`)\r\n return fn()\r\n }\r\n\r\n // ── Enregistrer un cleanup (pour fromStream) ───────\r\n registerCleanup(fn: () => void) {\r\n this._cleanups.push(fn)\r\n }\r\n\r\n // ── Lifecycle — appelé par l'adaptateur Angular ────\r\n init(publicStore: any) {\r\n this._hooks.onInit?.(publicStore)\r\n }\r\n\r\n destroy(publicStore: any) {\r\n this._hooks.onDestroy?.(publicStore)\r\n // Nettoyer tous les streams ouverts\r\n for (const cleanup of this._cleanups) {\r\n cleanup()\r\n }\r\n this._cleanups = []\r\n this._subscribers.clear()\r\n }\r\n}\r\n\r\n// ─────────────────────────────────────────────────────\r\n// FONCTION PUBLIQUE — createStore()\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport function createStore<S extends object>(config: S & StatoStoreConfig<S>) {\r\n\r\n // Créer l'instance interne\r\n const store = new StatoStore<S>(config as StatoStoreConfig<S>)\r\n\r\n // Construire l'objet public\r\n // Les propriétés du state sont accessibles directement\r\n // Les actions sont exposées sans le paramètre state\r\n const publicStore: any = {\r\n // Accès au store interne — pour les adaptateurs Angular/React/Vue\r\n __store__: store,\r\n\r\n // S'abonner aux changements\r\n subscribe: store.subscribe.bind(store),\r\n\r\n // Lire le state complet\r\n getState: store.getState.bind(store),\r\n\r\n // Enregistrer un cleanup\r\n registerCleanup: store.registerCleanup.bind(store),\r\n }\r\n\r\n // Exposer chaque propriété du state\r\n const initialState = store.getState()\r\n for (const key of Object.keys(initialState as object)) {\r\n Object.defineProperty(publicStore, key, {\r\n get: () => store.getState()[key as keyof typeof initialState],\r\n enumerable: true,\r\n configurable: true\r\n })\r\n }\r\n\r\n // Exposer chaque action\r\n const { actions, computed } = config as StatoStoreConfig<S>\r\n\r\n if (actions) {\r\n for (const name of Object.keys(actions)) {\r\n publicStore[name] = (...args: unknown[]) => store.dispatch(name, ...args)\r\n }\r\n }\r\n\r\n // Exposer chaque computed\r\n if (computed) {\r\n for (const name of Object.keys(computed)) {\r\n Object.defineProperty(publicStore, name, {\r\n get: () => store.getComputed(name),\r\n enumerable: true,\r\n configurable: true\r\n })\r\n }\r\n }\r\n\r\n return publicStore\r\n}\r\n\r\n// ─────────────────────────────────────────────────────\r\n// store.on() — réactions inter-stores\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport function on<S extends object>(\r\n sourceAction: Function,\r\n handler: (state: S) => void | Promise<void>\r\n) {\r\n // Sera implémenté dans v0.2\r\n // après que le core soit stable\r\n console.warn('[Stato] store.on() disponible en v0.2')\r\n}","// ─────────────────────────────────────────────────────\r\n// TYPES PUBLICS DE @ngstato/core\r\n// ─────────────────────────────────────────────────────\r\n\r\n// Le state de base — tout sauf actions, computed, hooks\r\nexport type StateSlice<T> = Omit<T, 'actions' | 'computed' | 'hooks'>\r\n\r\n// Une action — sync ou async\r\nexport type Action<S> = (state: S, ...args: any[]) => void | Promise<void> | (() => void)\r\n\r\n// Map d'actions\r\nexport type ActionsMap<S> = Record<string, Action<S>>\r\n\r\n// Computed — dérivé du state local\r\nexport type ComputedFn<S> = (state: S) => unknown\r\n\r\n// Hooks lifecycle\r\nexport interface StatoHooks<S> {\r\n // Lifecycle du store\r\n onInit?: (store: S) => void | Promise<void>\r\n onDestroy?: (store: S) => void | Promise<void>\r\n\r\n // Lifecycle des actions\r\n onAction?: (name: string, args: unknown[]) => void\r\n onActionDone?: (name: string, duration: number) => void\r\n onError?: (error: Error, actionName: string) => void\r\n\r\n // Lifecycle du state\r\n onStateChange?: (prev: S, next: S) => void\r\n}\r\n\r\n// Configuration du store\r\nexport interface StatoStoreConfig<S extends object> {\r\n actions?: ActionsMap<StateSlice<S>>\r\n computed?: Record<string, ComputedFn<StateSlice<S>> | unknown[]>\r\n hooks?: StatoHooks<any>\r\n [key: string]: unknown\r\n}\r\n\r\n// Instance publique du store\r\nexport type StatoStoreInstance<S extends object> = {\r\n // readonly — on ne peut lire, jamais écrire directement\r\n readonly [K in keyof StateSlice<S>]: StateSlice<S>[K]\r\n} & {\r\n // Les actions sont exposées sans le paramètre state\r\n [K in keyof S as S[K] extends Function ? K : never]:\r\n S[K] extends (state: any, ...args: infer A) => infer R\r\n ? (...args: A) => R\r\n : never\r\n}\r\n\r\n// Configuration du client HTTP\r\nexport interface StatoConfig {\r\n baseUrl?: string\r\n timeout?: number\r\n headers?: Record<string, string>\r\n auth?: () => string | null | undefined\r\n}\r\n\r\n// Erreur HTTP\r\nexport class StatoHttpError extends Error {\r\n constructor(\r\n public status: number,\r\n public body: string\r\n ) {\r\n super(`HTTP ${status}: ${body}`)\r\n this.name = 'StatoHttpError'\r\n }\r\n}","// ─────────────────────────────────────────────────────\r\n// @ngstato/core — StatoHttp\r\n// Client HTTP natif — fetch + baseUrl + auth + timeout\r\n// Zero dépendance — pas de HttpClient Angular\r\n// ─────────────────────────────────────────────────────\r\n\r\nimport type { StatoConfig } from './types'\r\nimport { StatoHttpError } from './types'\r\n\r\n// ─────────────────────────────────────────────────────\r\n// OPTIONS PAR REQUÊTE\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport interface RequestOptions {\r\n params?: Record<string, string | number | boolean>\r\n headers?: Record<string, string>\r\n signal?: AbortSignal // pour abortable()\r\n}\r\n\r\n// ─────────────────────────────────────────────────────\r\n// CLASSE PRINCIPALE\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport class StatoHttp {\r\n\r\n private _config: StatoConfig\r\n\r\n constructor(config: StatoConfig = {}) {\r\n this._config = config\r\n }\r\n\r\n // ── GET ───────────────────────────────────────────\r\n get<T = unknown>(url: string, options?: RequestOptions): Promise<T> {\r\n return this._request<T>('GET', url, undefined, options)\r\n }\r\n\r\n // ── POST ──────────────────────────────────────────\r\n post<T = unknown>(url: string, body?: unknown, options?: RequestOptions): Promise<T> {\r\n return this._request<T>('POST', url, body, options)\r\n }\r\n\r\n // ── PUT ───────────────────────────────────────────\r\n put<T = unknown>(url: string, body?: unknown, options?: RequestOptions): Promise<T> {\r\n return this._request<T>('PUT', url, body, options)\r\n }\r\n\r\n // ── PATCH ─────────────────────────────────────────\r\n patch<T = unknown>(url: string, body?: unknown, options?: RequestOptions): Promise<T> {\r\n return this._request<T>('PATCH', url, body, options)\r\n }\r\n\r\n // ── DELETE ────────────────────────────────────────\r\n delete<T = unknown>(url: string, options?: RequestOptions): Promise<T> {\r\n return this._request<T>('DELETE', url, undefined, options)\r\n }\r\n\r\n // ── MOTEUR INTERNE ────────────────────────────────\r\n private async _request<T>(\r\n method: string,\r\n url: string,\r\n body?: unknown,\r\n options?: RequestOptions\r\n ): Promise<T> {\r\n\r\n // 1. Construire l'URL complète avec baseUrl + params\r\n const fullUrl = this._buildUrl(url, options?.params)\r\n\r\n // 2. Récupérer le token auth si configuré\r\n const token = this._config.auth?.()\r\n\r\n // 3. Construire les headers\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n ...this._config.headers,\r\n ...options?.headers,\r\n ...(token ? { Authorization: `Bearer ${token}` } : {})\r\n }\r\n\r\n // 4. Configurer le timeout si défini\r\n let signal = options?.signal\r\n let timeoutId: ReturnType<typeof setTimeout> | undefined\r\n\r\n if (this._config.timeout && !signal) {\r\n const controller = new AbortController()\r\n signal = controller.signal\r\n timeoutId = setTimeout(\r\n () => controller.abort(),\r\n this._config.timeout\r\n )\r\n }\r\n\r\n // 5. Exécuter la requête\r\n try {\r\n const response = await fetch(fullUrl, {\r\n method,\r\n headers,\r\n signal,\r\n ...(body !== undefined\r\n ? { body: JSON.stringify(body) }\r\n : {}\r\n )\r\n })\r\n\r\n // 6. Gérer les erreurs HTTP automatiquement\r\n if (!response.ok) {\r\n const errorBody = await response.text()\r\n throw new StatoHttpError(response.status, errorBody)\r\n }\r\n\r\n // 7. Réponse vide — ex: DELETE 204\r\n const contentType = response.headers.get('content-type')\r\n if (\r\n response.status === 204 ||\r\n !contentType?.includes('application/json')\r\n ) {\r\n return undefined as T\r\n }\r\n\r\n // 8. Parser le JSON\r\n return response.json() as Promise<T>\r\n\r\n } finally {\r\n // Toujours nettoyer le timeout\r\n if (timeoutId) clearTimeout(timeoutId)\r\n }\r\n }\r\n\r\n // ── CONSTRUIRE L'URL AVEC PARAMS ──────────────────\r\n private _buildUrl(\r\n path: string,\r\n params?: Record<string, string | number | boolean>\r\n ): string {\r\n\r\n // Si l'URL est absolue — on ne préfixe pas baseUrl\r\n const url = path.startsWith('http')\r\n ? path\r\n : `${this._config.baseUrl ?? ''}${path}`\r\n\r\n // Ajouter les query params si présents\r\n if (!params || Object.keys(params).length === 0) return url\r\n\r\n const qs = new URLSearchParams(\r\n Object.entries(params).map(([k, v]) => [k, String(v)])\r\n ).toString()\r\n\r\n return `${url}?${qs}`\r\n }\r\n}\r\n\r\n// ─────────────────────────────────────────────────────\r\n// FACTORY FUNCTION — createHttp()\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport function createHttp(config: StatoConfig = {}): StatoHttp {\r\n return new StatoHttp(config)\r\n}\r\n\r\n// ─────────────────────────────────────────────────────\r\n// INSTANCE GLOBALE — http\r\n// Utilisée dans les actions des stores\r\n// Configurée via provideStato()\r\n// ─────────────────────────────────────────────────────\r\n\r\nlet _globalHttp: StatoHttp = new StatoHttp()\r\n\r\nexport function configureHttp(config: StatoConfig): void {\r\n _globalHttp = new StatoHttp(config)\r\n}\r\n\r\n// L'objet http — utilisé dans les actions\r\n// await http.get('/api/users')\r\nexport const http = {\r\n get: <T>(url: string, options?: RequestOptions) =>\r\n _globalHttp.get<T>(url, options),\r\n\r\n post: <T>(url: string, body?: unknown, options?: RequestOptions) =>\r\n _globalHttp.post<T>(url, body, options),\r\n\r\n put: <T>(url: string, body?: unknown, options?: RequestOptions) =>\r\n _globalHttp.put<T>(url, body, options),\r\n\r\n patch: <T>(url: string, body?: unknown, options?: RequestOptions) =>\r\n _globalHttp.patch<T>(url, body, options),\r\n\r\n delete: <T>(url: string, options?: RequestOptions) =>\r\n _globalHttp.delete<T>(url, options),\r\n}","// ─────────────────────────────────────────────────────\r\n// @ngstato/core — abortable()\r\n// Annule automatiquement la requête précédente\r\n// Remplace switchMap de RxJS — sans RxJS\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport interface AbortableOptions {\r\n signal: AbortSignal\r\n}\r\n\r\nexport function abortable<S, A extends unknown[]>(\r\n fn: (state: S, ...args: [...A, AbortableOptions]) => Promise<void>\r\n) {\r\n let controller: AbortController | null = null\r\n\r\n return async (state: S, ...args: A): Promise<void> => {\r\n // Annuler la requête précédente si elle tourne encore\r\n if (controller) {\r\n controller.abort()\r\n }\r\n\r\n // Créer un nouveau controller pour cette requête\r\n controller = new AbortController()\r\n const signal = controller.signal\r\n\r\n try {\r\n await fn(state, ...args, { signal } as any)\r\n } catch (error: any) {\r\n // Ignorer silencieusement les erreurs d'annulation\r\n if (error?.name === 'AbortError') return\r\n throw error\r\n } finally {\r\n controller = null\r\n }\r\n }\r\n}","// ─────────────────────────────────────────────────────\r\n// @ngstato/core — debounced()\r\n// Attend que l'utilisateur arrête de taper\r\n// Remplace debounceTime de RxJS — sans RxJS\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport function debounced<S, A extends unknown[]>(\r\n fn: (state: S, ...args: A) => void | Promise<void>,\r\n ms: number = 300\r\n) {\r\n let timer: ReturnType<typeof setTimeout> | null = null\r\n\r\n return (state: S, ...args: A): Promise<void> => {\r\n // Annuler le timer précédent\r\n if (timer) clearTimeout(timer)\r\n\r\n // Retourner une Promise qui se résout après le délai\r\n return new Promise((resolve, reject) => {\r\n timer = setTimeout(async () => {\r\n try {\r\n await fn(state, ...args)\r\n resolve()\r\n } catch (error) {\r\n reject(error)\r\n } finally {\r\n timer = null\r\n }\r\n }, ms)\r\n })\r\n }\r\n}","// ─────────────────────────────────────────────────────\r\n// @ngstato/core — throttled()\r\n// Limite la fréquence d'exécution d'une action\r\n// Remplace throttleTime de RxJS — sans RxJS\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport function throttled<S, A extends unknown[]>(\r\n fn: (state: S, ...args: A) => void | Promise<void>,\r\n ms: number = 300\r\n) {\r\n let lastCall = 0\r\n let timer: ReturnType<typeof setTimeout> | null = null\r\n\r\n return async (state: S, ...args: A): Promise<void> => {\r\n const now = Date.now()\r\n const remaining = ms - (now - lastCall)\r\n\r\n if (remaining <= 0) {\r\n // Assez de temps passé — on exécute immédiatement\r\n lastCall = now\r\n if (timer) {\r\n clearTimeout(timer)\r\n timer = null\r\n }\r\n await fn(state, ...args)\r\n } else {\r\n // Trop tôt — on planifie pour la fin du délai\r\n if (timer) clearTimeout(timer)\r\n return new Promise((resolve, reject) => {\r\n timer = setTimeout(async () => {\r\n lastCall = Date.now()\r\n timer = null\r\n try {\r\n await fn(state, ...args)\r\n resolve()\r\n } catch (error) {\r\n reject(error)\r\n }\r\n }, remaining)\r\n })\r\n }\r\n }\r\n}","// ─────────────────────────────────────────────────────\r\n// @ngstato/core — retryable()\r\n// Réessaie automatiquement en cas d'échec\r\n// Remplace retryWhen de RxJS — sans RxJS\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport interface RetryOptions {\r\n attempts: number // nombre de tentatives\r\n backoff: 'fixed' | 'exponential' // stratégie de délai\r\n delay?: number // délai de base en ms (défaut 1000)\r\n onRetry?: (attempt: number, error: Error) => void // callback optionnel\r\n}\r\n\r\nexport function retryable<S, A extends unknown[]>(\r\n fn: (state: S, ...args: A) => Promise<void>,\r\n options: RetryOptions = {\r\n attempts: 3,\r\n backoff: 'exponential',\r\n delay: 1000\r\n }\r\n) {\r\n return async (state: S, ...args: A): Promise<void> => {\r\n const { attempts, backoff, delay = 1000, onRetry } = options\r\n\r\n for (let i = 0; i < attempts; i++) {\r\n try {\r\n await fn(state, ...args)\r\n return // succès — on sort immédiatement\r\n\r\n } catch (error) {\r\n const isLastAttempt = i === attempts - 1\r\n\r\n // Dernière tentative — on remonte l'erreur\r\n if (isLastAttempt) throw error\r\n\r\n // Calculer le délai avant la prochaine tentative\r\n const waitMs = backoff === 'exponential'\r\n ? delay * Math.pow(2, i) // 1s, 2s, 4s, 8s...\r\n : delay // fixe : toujours 1s\r\n\r\n // Callback optionnel — pour logger ou afficher un message\r\n onRetry?.(i + 1, error as Error)\r\n\r\n // Attendre avant de réessayer\r\n await new Promise(resolve => setTimeout(resolve, waitMs))\r\n }\r\n }\r\n }\r\n}","// ─────────────────────────────────────────────────────\r\n// @ngstato/core — fromStream()\r\n// Pont entre un flux Observable/callback et le state\r\n// Pour : Firebase, Supabase, WebSocket, SSE\r\n// RxJS optionnel — pas obligatoire\r\n// ─────────────────────────────────────────────────────\r\n\r\n// Interface minimale d'un Observable\r\n// Compatible RxJS sans l'importer\r\nexport interface StatoObservable<T> {\r\n subscribe(observer: {\r\n next?: (value: T) => void\r\n error?: (error: unknown) => void\r\n complete?: () => void\r\n }): { unsubscribe(): void }\r\n}\r\n\r\nexport interface StreamOptions {\r\n // Appelé quand le stream se termine normalement\r\n onComplete?: () => void\r\n // Appelé quand le stream rencontre une erreur\r\n onError?: (error: unknown) => void\r\n}\r\n\r\nexport function fromStream<S, T>(\r\n // setupFn — retourne l'Observable ou le flux\r\n setupFn: (state: S) => StatoObservable<T>,\r\n // updateFn — appelé à chaque valeur émise\r\n updateFn: (state: S, value: T) => void,\r\n options?: StreamOptions\r\n) {\r\n return (state: S): (() => void) => {\r\n // Démarrer le flux\r\n const stream$ = setupFn(state)\r\n const subscription = stream$.subscribe({\r\n\r\n next: (value: T) => {\r\n // Mettre à jour le state à chaque émission\r\n updateFn(state, value)\r\n },\r\n\r\n error: (error: unknown) => {\r\n options?.onError?.(error)\r\n },\r\n\r\n complete: () => {\r\n options?.onComplete?.()\r\n }\r\n })\r\n\r\n // Retourner la fonction de cleanup\r\n // appelée automatiquement à la destruction du store\r\n return () => subscription.unsubscribe()\r\n }\r\n}","// ─────────────────────────────────────────────────────\r\n// @ngstato/core — optimistic()\r\n// Mise à jour optimiste avec rollback automatique\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport function optimistic<S, A extends unknown[]>(\r\n // Action immédiate — modifie le state sans attendre\r\n immediate: (state: S, ...args: A) => void,\r\n // Confirmation API — si elle échoue → rollback\r\n confirm: (state: S, ...args: A) => Promise<void>\r\n) {\r\n return async (state: S, ...args: A): Promise<void> => {\r\n // 1. Snapshot du state AVANT la modification\r\n const snapshot = { ...(state as object) } as S\r\n\r\n // 2. Appliquer la modification immédiatement\r\n immediate(state, ...args)\r\n\r\n try {\r\n // 3. Confirmer avec l'API\r\n await confirm(state, ...args)\r\n // Succès — le state optimiste est correct, rien à faire\r\n\r\n } catch (error) {\r\n // 4. Échec — restaurer le state d'avant\r\n Object.assign(state as object, snapshot)\r\n throw error\r\n }\r\n }\r\n}","// ─────────────────────────────────────────────────────\r\n// @ngstato/core — DevTools\r\n// Logique pure — pas de dépendance Angular\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport interface ActionLog {\r\n id: number\r\n name: string\r\n args: unknown[]\r\n duration: number\r\n status: 'success' | 'error'\r\n error?: string\r\n prevState: unknown\r\n nextState: unknown\r\n at: string\r\n}\r\n\r\nexport interface DevToolsState {\r\n logs: ActionLog[]\r\n isOpen: boolean\r\n maxLogs: number\r\n}\r\n\r\nexport interface DevToolsInstance {\r\n state: DevToolsState\r\n logAction: (log: Omit<ActionLog, 'id' | 'at'>) => void\r\n clear: () => void\r\n open: () => void\r\n close: () => void\r\n toggle: () => void\r\n subscribe: (cb: (state: DevToolsState) => void) => () => void\r\n}\r\n\r\n// ─────────────────────────────────────────────────────\r\n// FACTORY\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport function createDevTools(maxLogs = 50): DevToolsInstance {\r\n let counter = 0\r\n\r\n const state: DevToolsState = {\r\n logs: [],\r\n isOpen: false,\r\n maxLogs\r\n }\r\n\r\n const listeners = new Set<(state: DevToolsState) => void>()\r\n\r\n function notify() {\r\n listeners.forEach(cb => cb({ ...state, logs: [...state.logs] }))\r\n }\r\n\r\n return {\r\n state,\r\n\r\n logAction(log) {\r\n const entry: ActionLog = {\r\n ...log,\r\n id: ++counter,\r\n at: new Date().toISOString()\r\n }\r\n\r\n state.logs = [entry, ...state.logs].slice(0, maxLogs)\r\n notify()\r\n },\r\n\r\n clear() {\r\n state.logs = []\r\n notify()\r\n },\r\n\r\n open() {\r\n state.isOpen = true\r\n notify()\r\n },\r\n\r\n close() {\r\n state.isOpen = false\r\n notify()\r\n },\r\n\r\n toggle() {\r\n state.isOpen = !state.isOpen\r\n notify()\r\n },\r\n\r\n subscribe(cb) {\r\n listeners.add(cb)\r\n return () => listeners.delete(cb)\r\n }\r\n }\r\n}\r\n\r\n// ─────────────────────────────────────────────────────\r\n// INSTANCE GLOBALE — singleton partagé entre tous les stores\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport const devTools = createDevTools()\r\n\r\n// ─────────────────────────────────────────────────────\r\n// PLUGIN — connecte un store aux DevTools\r\n// ─────────────────────────────────────────────────────\r\n\r\nexport function connectDevTools(store: any, storeName: string) {\r\n if (!devTools) return\r\n\r\n let prevState: any = {}\r\n\r\n // Accès aux hooks via __store__\r\n const internalStore = store.__store__\r\n\r\n if (!internalStore) return\r\n\r\n // Sauvegarder les hooks existants\r\n const existingHooks = { ...internalStore['_hooks'] }\r\n\r\n // Remplacer les hooks\r\n internalStore['_hooks'] = {\r\n ...existingHooks,\r\n\r\n onAction(name: string, args: unknown[]) {\r\n prevState = store.getState()\r\n existingHooks.onAction?.(name, args)\r\n },\r\n\r\n onActionDone(name: string, duration: number) {\r\n const nextState = store.getState()\r\n devTools.logAction({\r\n name: `[${storeName}] ${name}`,\r\n args: [],\r\n duration,\r\n status: 'success',\r\n prevState: { ...prevState },\r\n nextState: { ...nextState }\r\n })\r\n existingHooks.onActionDone?.(name, duration)\r\n },\r\n\r\n onError(error: Error, actionName: string) {\r\n devTools.logAction({\r\n name: `[${storeName}] ${actionName}`,\r\n args: [],\r\n duration: 0,\r\n status: 'error',\r\n error: error.message,\r\n prevState: { ...prevState },\r\n nextState: { ...prevState }\r\n })\r\n existingHooks.onError?.(error, actionName)\r\n },\r\n\r\n onStateChange: existingHooks.onStateChange\r\n }\r\n}"]}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@ngstato/core",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Stato — Zero-dependency state management engine",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "test": "vitest run",
19
+ "dev": "tsup --watch"
20
+ },
21
+ "devDependencies": {
22
+ "tsup": "^8.0.0",
23
+ "vitest": "^1.6.0",
24
+ "typescript": "^5.4.0"
25
+ }
26
+ }