@prabhask5/stellar-engine 1.1.9 → 1.1.10

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.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @fileoverview Store Factory Functions
3
+ *
4
+ * Generic factory functions that create Svelte-compatible reactive stores for
5
+ * common data-loading patterns. These eliminate the repetitive boilerplate that
6
+ * every collection or detail store requires: loading state management,
7
+ * sync-complete listener registration, and refresh logic.
8
+ *
9
+ * Both factories produce stores that follow the Svelte store contract
10
+ * (`subscribe`/`unsubscribe`) and expose a read-only `loading` sub-store.
11
+ *
12
+ * @see {@link ../engine} for `onSyncComplete` lifecycle hook
13
+ */
14
+ /**
15
+ * Configuration for creating a collection store.
16
+ *
17
+ * @typeParam T - The entity type stored in the collection.
18
+ */
19
+ export interface CollectionStoreConfig<T> {
20
+ /** Async function that fetches the full collection (e.g., from local DB). */
21
+ load: () => Promise<T[]>;
22
+ }
23
+ /**
24
+ * A reactive store managing a collection of entities with loading state,
25
+ * sync-complete auto-refresh, and optimistic mutation support.
26
+ *
27
+ * @typeParam T - The entity type stored in the collection.
28
+ */
29
+ export interface CollectionStore<T> {
30
+ /** Standard Svelte store subscribe method. */
31
+ subscribe: (run: (value: T[]) => void) => () => void;
32
+ /** Read-only loading sub-store. */
33
+ loading: {
34
+ subscribe: (run: (value: boolean) => void) => () => void;
35
+ };
36
+ /**
37
+ * Initial load: fetches data, sets loading state, and registers a
38
+ * sync-complete listener for automatic refresh on future syncs.
39
+ */
40
+ load(): Promise<void>;
41
+ /** Re-fetch data without toggling the loading flag. */
42
+ refresh(): Promise<void>;
43
+ /** Replace the store's data directly. */
44
+ set(data: T[]): void;
45
+ /** Apply an optimistic mutation to the current data. */
46
+ mutate(fn: (items: T[]) => T[]): void;
47
+ }
48
+ /**
49
+ * Configuration for creating a detail store.
50
+ *
51
+ * @typeParam T - The entity type for the detail view.
52
+ */
53
+ export interface DetailStoreConfig<T> {
54
+ /** Async function that fetches a single entity by ID. */
55
+ load: (id: string) => Promise<T | null>;
56
+ }
57
+ /**
58
+ * A reactive store managing a single entity with loading state,
59
+ * sync-complete auto-refresh, and ID tracking.
60
+ *
61
+ * @typeParam T - The entity type for the detail view.
62
+ */
63
+ export interface DetailStore<T> {
64
+ /** Standard Svelte store subscribe method. */
65
+ subscribe: (run: (value: T | null) => void) => () => void;
66
+ /** Read-only loading sub-store. */
67
+ loading: {
68
+ subscribe: (run: (value: boolean) => void) => () => void;
69
+ };
70
+ /**
71
+ * Load a single entity by ID. Registers a sync-complete listener that
72
+ * auto-refreshes the same entity on future syncs.
73
+ */
74
+ load(id: string): Promise<void>;
75
+ /** Reset the store to null and clear the tracked ID. */
76
+ clear(): void;
77
+ /** Replace the store's data directly. */
78
+ set(data: T | null): void;
79
+ /** Get the currently tracked entity ID, or null if none loaded. */
80
+ getCurrentId(): string | null;
81
+ }
82
+ /**
83
+ * Create a reactive collection store with built-in loading state and
84
+ * sync-complete auto-refresh.
85
+ *
86
+ * The returned store follows the Svelte store contract and can be used with
87
+ * the `$store` auto-subscription syntax. On the first `load()` call, a
88
+ * sync-complete listener is registered so the collection automatically
89
+ * refreshes whenever the sync engine completes a cycle.
90
+ *
91
+ * Uses `typeof window !== 'undefined'` for environment detection since
92
+ * stellar-engine is a library (not tied to SvelteKit's `browser` export).
93
+ *
94
+ * @typeParam T - The entity type stored in the collection.
95
+ * @param config - Configuration with a `load` function.
96
+ * @returns A `CollectionStore<T>` instance.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * import { createCollectionStore } from '@prabhask5/stellar-engine/stores';
101
+ *
102
+ * const store = createCollectionStore<Task>({
103
+ * load: () => queryAll<Task>('tasks'),
104
+ * });
105
+ *
106
+ * // In your component:
107
+ * await store.load();
108
+ * // $store is now Task[], $store.loading is boolean
109
+ * ```
110
+ */
111
+ export declare function createCollectionStore<T>(config: CollectionStoreConfig<T>): CollectionStore<T>;
112
+ /**
113
+ * Create a reactive detail store for a single entity, with built-in loading
114
+ * state, ID tracking, and sync-complete auto-refresh.
115
+ *
116
+ * The store tracks the currently loaded entity ID so that sync-complete
117
+ * listeners can refresh the correct entity. Calling `load(id)` with a
118
+ * different ID updates the tracked ID and fetches the new entity.
119
+ *
120
+ * @typeParam T - The entity type for the detail view.
121
+ * @param config - Configuration with a `load` function that takes an entity ID.
122
+ * @returns A `DetailStore<T>` instance.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * import { createDetailStore } from '@prabhask5/stellar-engine/stores';
127
+ *
128
+ * const store = createDetailStore<Task>({
129
+ * load: (id) => queryOne<Task>('tasks', id),
130
+ * });
131
+ *
132
+ * // In your component:
133
+ * await store.load(taskId);
134
+ * // $store is now Task | null, $store.loading is boolean
135
+ * ```
136
+ */
137
+ export declare function createDetailStore<T>(config: DetailStoreConfig<T>): DetailStore<T>;
138
+ //# sourceMappingURL=factories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factories.d.ts","sourceRoot":"","sources":["../../src/stores/factories.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH;;;;GAIG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,6EAA6E;IAC7E,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CAC1B;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,8CAA8C;IAC9C,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAErD,mCAAmC;IACnC,OAAO,EAAE;QAAE,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;KAAE,CAAC;IAEtE;;;OAGG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB,uDAAuD;IACvD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB,yCAAyC;IACzC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC;IAErB,wDAAwD;IACxD,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC;CACvC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,yDAAyD;IACzD,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CACzC;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,8CAA8C;IAC9C,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAE1D,mCAAmC;IACnC,OAAO,EAAE;QAAE,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;KAAE,CAAC;IAEtE;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC,wDAAwD;IACxD,KAAK,IAAI,IAAI,CAAC;IAEd,yCAAyC;IACzC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IAE1B,mEAAmE;IACnE,YAAY,IAAI,MAAM,GAAG,IAAI,CAAC;CAC/B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAyC7F;AAMD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CA6CjF"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @fileoverview Store Factory Functions
3
+ *
4
+ * Generic factory functions that create Svelte-compatible reactive stores for
5
+ * common data-loading patterns. These eliminate the repetitive boilerplate that
6
+ * every collection or detail store requires: loading state management,
7
+ * sync-complete listener registration, and refresh logic.
8
+ *
9
+ * Both factories produce stores that follow the Svelte store contract
10
+ * (`subscribe`/`unsubscribe`) and expose a read-only `loading` sub-store.
11
+ *
12
+ * @see {@link ../engine} for `onSyncComplete` lifecycle hook
13
+ */
14
+ import { writable } from 'svelte/store';
15
+ import { onSyncComplete } from '../engine';
16
+ // =============================================================================
17
+ // Collection Store Factory
18
+ // =============================================================================
19
+ /**
20
+ * Create a reactive collection store with built-in loading state and
21
+ * sync-complete auto-refresh.
22
+ *
23
+ * The returned store follows the Svelte store contract and can be used with
24
+ * the `$store` auto-subscription syntax. On the first `load()` call, a
25
+ * sync-complete listener is registered so the collection automatically
26
+ * refreshes whenever the sync engine completes a cycle.
27
+ *
28
+ * Uses `typeof window !== 'undefined'` for environment detection since
29
+ * stellar-engine is a library (not tied to SvelteKit's `browser` export).
30
+ *
31
+ * @typeParam T - The entity type stored in the collection.
32
+ * @param config - Configuration with a `load` function.
33
+ * @returns A `CollectionStore<T>` instance.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { createCollectionStore } from '@prabhask5/stellar-engine/stores';
38
+ *
39
+ * const store = createCollectionStore<Task>({
40
+ * load: () => queryAll<Task>('tasks'),
41
+ * });
42
+ *
43
+ * // In your component:
44
+ * await store.load();
45
+ * // $store is now Task[], $store.loading is boolean
46
+ * ```
47
+ */
48
+ export function createCollectionStore(config) {
49
+ const { subscribe, set, update } = writable([]);
50
+ const loading = writable(true);
51
+ let syncUnsubscribe = null;
52
+ return {
53
+ subscribe,
54
+ loading: { subscribe: loading.subscribe },
55
+ async load() {
56
+ loading.set(true);
57
+ try {
58
+ const data = await config.load();
59
+ set(data);
60
+ /* Register sync-complete listener once, only in the browser.
61
+ On each sync cycle, the collection auto-refreshes from local DB. */
62
+ if (typeof window !== 'undefined' && !syncUnsubscribe) {
63
+ syncUnsubscribe = onSyncComplete(async () => {
64
+ const refreshed = await config.load();
65
+ set(refreshed);
66
+ });
67
+ }
68
+ }
69
+ finally {
70
+ loading.set(false);
71
+ }
72
+ },
73
+ async refresh() {
74
+ const data = await config.load();
75
+ set(data);
76
+ },
77
+ set(data) {
78
+ set(data);
79
+ },
80
+ mutate(fn) {
81
+ update(fn);
82
+ }
83
+ };
84
+ }
85
+ // =============================================================================
86
+ // Detail Store Factory
87
+ // =============================================================================
88
+ /**
89
+ * Create a reactive detail store for a single entity, with built-in loading
90
+ * state, ID tracking, and sync-complete auto-refresh.
91
+ *
92
+ * The store tracks the currently loaded entity ID so that sync-complete
93
+ * listeners can refresh the correct entity. Calling `load(id)` with a
94
+ * different ID updates the tracked ID and fetches the new entity.
95
+ *
96
+ * @typeParam T - The entity type for the detail view.
97
+ * @param config - Configuration with a `load` function that takes an entity ID.
98
+ * @returns A `DetailStore<T>` instance.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { createDetailStore } from '@prabhask5/stellar-engine/stores';
103
+ *
104
+ * const store = createDetailStore<Task>({
105
+ * load: (id) => queryOne<Task>('tasks', id),
106
+ * });
107
+ *
108
+ * // In your component:
109
+ * await store.load(taskId);
110
+ * // $store is now Task | null, $store.loading is boolean
111
+ * ```
112
+ */
113
+ export function createDetailStore(config) {
114
+ const { subscribe, set } = writable(null);
115
+ const loading = writable(true);
116
+ let currentId = null;
117
+ let syncUnsubscribe = null;
118
+ return {
119
+ subscribe,
120
+ loading: { subscribe: loading.subscribe },
121
+ async load(id) {
122
+ currentId = id;
123
+ loading.set(true);
124
+ try {
125
+ const data = await config.load(id);
126
+ set(data);
127
+ /* Register sync-complete listener once, only in the browser.
128
+ Uses the tracked `currentId` so it always refreshes the active entity. */
129
+ if (typeof window !== 'undefined' && !syncUnsubscribe) {
130
+ syncUnsubscribe = onSyncComplete(async () => {
131
+ if (currentId) {
132
+ const refreshed = await config.load(currentId);
133
+ set(refreshed);
134
+ }
135
+ });
136
+ }
137
+ }
138
+ finally {
139
+ loading.set(false);
140
+ }
141
+ },
142
+ clear() {
143
+ currentId = null;
144
+ set(null);
145
+ },
146
+ set(data) {
147
+ set(data);
148
+ },
149
+ getCurrentId() {
150
+ return currentId;
151
+ }
152
+ };
153
+ }
154
+ //# sourceMappingURL=factories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factories.js","sourceRoot":"","sources":["../../src/stores/factories.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prabhask5/stellar-engine",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",