@tanstack/db 0.0.20 → 0.0.21
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/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/local-only.cjs +96 -0
- package/dist/cjs/local-only.cjs.map +1 -0
- package/dist/cjs/local-only.d.cts +114 -0
- package/dist/cjs/local-storage.cjs +267 -0
- package/dist/cjs/local-storage.cjs.map +1 -0
- package/dist/cjs/local-storage.d.cts +141 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/local-only.d.ts +114 -0
- package/dist/esm/local-only.js +96 -0
- package/dist/esm/local-only.js.map +1 -0
- package/dist/esm/local-storage.d.ts +141 -0
- package/dist/esm/local-storage.js +267 -0
- package/dist/esm/local-storage.js.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/local-only.ts +302 -0
- package/src/local-storage.ts +644 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { CollectionConfig, DeleteMutationFnParams, InsertMutationFnParams, ResolveType, SyncConfig, UpdateMutationFnParams, UtilsRecord } from './types.cjs';
|
|
2
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
|
+
/**
|
|
4
|
+
* Storage API interface - subset of DOM Storage that we need
|
|
5
|
+
*/
|
|
6
|
+
export type StorageApi = Pick<Storage, `getItem` | `setItem` | `removeItem`>;
|
|
7
|
+
/**
|
|
8
|
+
* Storage event API - subset of Window for 'storage' events only
|
|
9
|
+
*/
|
|
10
|
+
export type StorageEventApi = {
|
|
11
|
+
addEventListener: (type: `storage`, listener: (event: StorageEvent) => void) => void;
|
|
12
|
+
removeEventListener: (type: `storage`, listener: (event: StorageEvent) => void) => void;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Configuration interface for localStorage collection options
|
|
16
|
+
* @template TExplicit - The explicit type of items in the collection (highest priority)
|
|
17
|
+
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
18
|
+
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Type resolution follows a priority order:
|
|
22
|
+
* 1. If you provide an explicit type via generic parameter, it will be used
|
|
23
|
+
* 2. If no explicit type is provided but a schema is, the schema's output type will be inferred
|
|
24
|
+
* 3. If neither explicit type nor schema is provided, the fallback type will be used
|
|
25
|
+
*
|
|
26
|
+
* You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.
|
|
27
|
+
*/
|
|
28
|
+
export interface LocalStorageCollectionConfig<TExplicit = unknown, TSchema extends StandardSchemaV1 = never, TFallback extends object = Record<string, unknown>> {
|
|
29
|
+
/**
|
|
30
|
+
* The key to use for storing the collection data in localStorage/sessionStorage
|
|
31
|
+
*/
|
|
32
|
+
storageKey: string;
|
|
33
|
+
/**
|
|
34
|
+
* Storage API to use (defaults to window.localStorage)
|
|
35
|
+
* Can be any object that implements the Storage interface (e.g., sessionStorage)
|
|
36
|
+
*/
|
|
37
|
+
storage?: StorageApi;
|
|
38
|
+
/**
|
|
39
|
+
* Storage event API to use for cross-tab synchronization (defaults to window)
|
|
40
|
+
* Can be any object that implements addEventListener/removeEventListener for storage events
|
|
41
|
+
*/
|
|
42
|
+
storageEventApi?: StorageEventApi;
|
|
43
|
+
/**
|
|
44
|
+
* Collection identifier (defaults to "local-collection:{storageKey}" if not provided)
|
|
45
|
+
*/
|
|
46
|
+
id?: string;
|
|
47
|
+
schema?: TSchema;
|
|
48
|
+
getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`getKey`];
|
|
49
|
+
sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`sync`];
|
|
50
|
+
/**
|
|
51
|
+
* Optional asynchronous handler function called before an insert operation
|
|
52
|
+
* @param params Object containing transaction and collection information
|
|
53
|
+
* @returns Promise resolving to any value
|
|
54
|
+
*/
|
|
55
|
+
onInsert?: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
56
|
+
/**
|
|
57
|
+
* Optional asynchronous handler function called before an update operation
|
|
58
|
+
* @param params Object containing transaction and collection information
|
|
59
|
+
* @returns Promise resolving to any value
|
|
60
|
+
*/
|
|
61
|
+
onUpdate?: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
62
|
+
/**
|
|
63
|
+
* Optional asynchronous handler function called before a delete operation
|
|
64
|
+
* @param params Object containing transaction and collection information
|
|
65
|
+
* @returns Promise resolving to any value
|
|
66
|
+
*/
|
|
67
|
+
onDelete?: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Type for the clear utility function
|
|
71
|
+
*/
|
|
72
|
+
export type ClearStorageFn = () => void;
|
|
73
|
+
/**
|
|
74
|
+
* Type for the getStorageSize utility function
|
|
75
|
+
*/
|
|
76
|
+
export type GetStorageSizeFn = () => number;
|
|
77
|
+
/**
|
|
78
|
+
* LocalStorage collection utilities type
|
|
79
|
+
*/
|
|
80
|
+
export interface LocalStorageCollectionUtils extends UtilsRecord {
|
|
81
|
+
clearStorage: ClearStorageFn;
|
|
82
|
+
getStorageSize: GetStorageSizeFn;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Creates localStorage collection options for use with a standard Collection
|
|
86
|
+
*
|
|
87
|
+
* This function creates a collection that persists data to localStorage/sessionStorage
|
|
88
|
+
* and synchronizes changes across browser tabs using storage events.
|
|
89
|
+
*
|
|
90
|
+
* @template TExplicit - The explicit type of items in the collection (highest priority)
|
|
91
|
+
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
92
|
+
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
93
|
+
* @param config - Configuration options for the localStorage collection
|
|
94
|
+
* @returns Collection options with utilities including clearStorage and getStorageSize
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Basic localStorage collection
|
|
98
|
+
* const collection = createCollection(
|
|
99
|
+
* localStorageCollectionOptions({
|
|
100
|
+
* storageKey: 'todos',
|
|
101
|
+
* getKey: (item) => item.id,
|
|
102
|
+
* })
|
|
103
|
+
* )
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* // localStorage collection with custom storage
|
|
107
|
+
* const collection = createCollection(
|
|
108
|
+
* localStorageCollectionOptions({
|
|
109
|
+
* storageKey: 'todos',
|
|
110
|
+
* storage: window.sessionStorage, // Use sessionStorage instead
|
|
111
|
+
* getKey: (item) => item.id,
|
|
112
|
+
* })
|
|
113
|
+
* )
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* // localStorage collection with mutation handlers
|
|
117
|
+
* const collection = createCollection(
|
|
118
|
+
* localStorageCollectionOptions({
|
|
119
|
+
* storageKey: 'todos',
|
|
120
|
+
* getKey: (item) => item.id,
|
|
121
|
+
* onInsert: async ({ transaction }) => {
|
|
122
|
+
* console.log('Item inserted:', transaction.mutations[0].modified)
|
|
123
|
+
* },
|
|
124
|
+
* })
|
|
125
|
+
* )
|
|
126
|
+
*/
|
|
127
|
+
export declare function localStorageCollectionOptions<TExplicit = unknown, TSchema extends StandardSchemaV1 = never, TFallback extends object = Record<string, unknown>>(config: LocalStorageCollectionConfig<TExplicit, TSchema, TFallback>): {
|
|
128
|
+
id: string;
|
|
129
|
+
sync: SyncConfig<ResolveType<TExplicit, TSchema, TFallback>, string | number> & {
|
|
130
|
+
manualTrigger?: () => void;
|
|
131
|
+
};
|
|
132
|
+
onInsert: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
133
|
+
onUpdate: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
134
|
+
onDelete: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
135
|
+
utils: {
|
|
136
|
+
clearStorage: ClearStorageFn;
|
|
137
|
+
getStorageSize: GetStorageSizeFn;
|
|
138
|
+
};
|
|
139
|
+
schema?: TSchema | undefined;
|
|
140
|
+
getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => string | number;
|
|
141
|
+
};
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import { createTransaction, getActiveTransaction } from "./transactions.js";
|
|
|
4
4
|
import { NonRetriableError } from "./errors.js";
|
|
5
5
|
import { createArrayChangeProxy, createChangeProxy, withArrayChangeTracking, withChangeTracking } from "./proxy.js";
|
|
6
6
|
import { createOptimisticAction } from "./optimistic-action.js";
|
|
7
|
+
import { localOnlyCollectionOptions } from "./local-only.js";
|
|
8
|
+
import { localStorageCollectionOptions } from "./local-storage.js";
|
|
7
9
|
import { BaseQueryBuilder, Query } from "./query/builder/index.js";
|
|
8
10
|
import { add, and, avg, coalesce, concat, count, eq, gt, gte, ilike, inArray, length, like, lower, lt, lte, max, min, not, or, sum, upper } from "./query/builder/functions.js";
|
|
9
11
|
import { compileQuery } from "./query/compiler/index.js";
|
|
@@ -38,6 +40,8 @@ export {
|
|
|
38
40
|
length,
|
|
39
41
|
like,
|
|
40
42
|
liveQueryCollectionOptions,
|
|
43
|
+
localOnlyCollectionOptions,
|
|
44
|
+
localStorageCollectionOptions,
|
|
41
45
|
lower,
|
|
42
46
|
lt,
|
|
43
47
|
lte,
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { DeleteMutationFnParams, InsertMutationFnParams, ResolveType, SyncConfig, UpdateMutationFnParams, UtilsRecord } from './types.js';
|
|
2
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration interface for Local-only collection options
|
|
5
|
+
* @template TExplicit - The explicit type of items in the collection (highest priority)
|
|
6
|
+
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
7
|
+
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
8
|
+
* @template TKey - The type of the key returned by getKey
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Type resolution follows a priority order:
|
|
12
|
+
* 1. If you provide an explicit type via generic parameter, it will be used
|
|
13
|
+
* 2. If no explicit type is provided but a schema is, the schema's output type will be inferred
|
|
14
|
+
* 3. If neither explicit type nor schema is provided, the fallback type will be used
|
|
15
|
+
*
|
|
16
|
+
* You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.
|
|
17
|
+
*/
|
|
18
|
+
export interface LocalOnlyCollectionConfig<TExplicit = unknown, TSchema extends StandardSchemaV1 = never, TFallback extends Record<string, unknown> = Record<string, unknown>, TKey extends string | number = string | number> {
|
|
19
|
+
/**
|
|
20
|
+
* Standard Collection configuration properties
|
|
21
|
+
*/
|
|
22
|
+
id?: string;
|
|
23
|
+
schema?: TSchema;
|
|
24
|
+
getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => TKey;
|
|
25
|
+
/**
|
|
26
|
+
* Optional initial data to populate the collection with on creation
|
|
27
|
+
* This data will be applied during the initial sync process
|
|
28
|
+
*/
|
|
29
|
+
initialData?: Array<ResolveType<TExplicit, TSchema, TFallback>>;
|
|
30
|
+
/**
|
|
31
|
+
* Optional asynchronous handler function called after an insert operation
|
|
32
|
+
* @param params Object containing transaction and collection information
|
|
33
|
+
* @returns Promise resolving to any value
|
|
34
|
+
*/
|
|
35
|
+
onInsert?: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
36
|
+
/**
|
|
37
|
+
* Optional asynchronous handler function called after an update operation
|
|
38
|
+
* @param params Object containing transaction and collection information
|
|
39
|
+
* @returns Promise resolving to any value
|
|
40
|
+
*/
|
|
41
|
+
onUpdate?: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
42
|
+
/**
|
|
43
|
+
* Optional asynchronous handler function called after a delete operation
|
|
44
|
+
* @param params Object containing transaction and collection information
|
|
45
|
+
* @returns Promise resolving to any value
|
|
46
|
+
*/
|
|
47
|
+
onDelete?: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Local-only collection utilities type (currently empty but matches the pattern)
|
|
51
|
+
*/
|
|
52
|
+
export interface LocalOnlyCollectionUtils extends UtilsRecord {
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Creates Local-only collection options for use with a standard Collection
|
|
56
|
+
*
|
|
57
|
+
* This is an in-memory collection that doesn't sync with external sources but uses a loopback sync config
|
|
58
|
+
* that immediately "syncs" all optimistic changes to the collection, making them permanent.
|
|
59
|
+
* Perfect for local-only data that doesn't need persistence or external synchronization.
|
|
60
|
+
*
|
|
61
|
+
* @template TExplicit - The explicit type of items in the collection (highest priority)
|
|
62
|
+
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
63
|
+
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
64
|
+
* @template TKey - The type of the key returned by getKey
|
|
65
|
+
* @param config - Configuration options for the Local-only collection
|
|
66
|
+
* @returns Collection options with utilities (currently empty but follows the pattern)
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* // Basic local-only collection
|
|
70
|
+
* const collection = createCollection(
|
|
71
|
+
* localOnlyCollectionOptions({
|
|
72
|
+
* getKey: (item) => item.id,
|
|
73
|
+
* })
|
|
74
|
+
* )
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Local-only collection with initial data
|
|
78
|
+
* const collection = createCollection(
|
|
79
|
+
* localOnlyCollectionOptions({
|
|
80
|
+
* getKey: (item) => item.id,
|
|
81
|
+
* initialData: [
|
|
82
|
+
* { id: 1, name: 'Item 1' },
|
|
83
|
+
* { id: 2, name: 'Item 2' },
|
|
84
|
+
* ],
|
|
85
|
+
* })
|
|
86
|
+
* )
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // Local-only collection with mutation handlers
|
|
90
|
+
* const collection = createCollection(
|
|
91
|
+
* localOnlyCollectionOptions({
|
|
92
|
+
* getKey: (item) => item.id,
|
|
93
|
+
* onInsert: async ({ transaction }) => {
|
|
94
|
+
* console.log('Item inserted:', transaction.mutations[0].modified)
|
|
95
|
+
* // Custom logic after insert
|
|
96
|
+
* },
|
|
97
|
+
* })
|
|
98
|
+
* )
|
|
99
|
+
*/
|
|
100
|
+
export declare function localOnlyCollectionOptions<TExplicit = unknown, TSchema extends StandardSchemaV1 = never, TFallback extends Record<string, unknown> = Record<string, unknown>, TKey extends string | number = string | number>(config: LocalOnlyCollectionConfig<TExplicit, TSchema, TFallback, TKey>): {
|
|
101
|
+
sync: SyncConfig<ResolveType<TExplicit, TSchema, TFallback>, TKey>;
|
|
102
|
+
onInsert: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
103
|
+
onUpdate: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
104
|
+
onDelete: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>, TKey, LocalOnlyCollectionUtils>) => Promise<any>;
|
|
105
|
+
utils: LocalOnlyCollectionUtils;
|
|
106
|
+
startSync: boolean;
|
|
107
|
+
gcTime: number;
|
|
108
|
+
/**
|
|
109
|
+
* Standard Collection configuration properties
|
|
110
|
+
*/
|
|
111
|
+
id?: string;
|
|
112
|
+
schema?: TSchema | undefined;
|
|
113
|
+
getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => TKey;
|
|
114
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function localOnlyCollectionOptions(config) {
|
|
2
|
+
const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config;
|
|
3
|
+
const syncResult = createLocalOnlySync(initialData);
|
|
4
|
+
const wrappedOnInsert = async (params) => {
|
|
5
|
+
let handlerResult;
|
|
6
|
+
if (onInsert) {
|
|
7
|
+
handlerResult = await onInsert(params) ?? {};
|
|
8
|
+
}
|
|
9
|
+
syncResult.confirmOperationsSync(params.transaction.mutations);
|
|
10
|
+
return handlerResult;
|
|
11
|
+
};
|
|
12
|
+
const wrappedOnUpdate = async (params) => {
|
|
13
|
+
let handlerResult;
|
|
14
|
+
if (onUpdate) {
|
|
15
|
+
handlerResult = await onUpdate(params) ?? {};
|
|
16
|
+
}
|
|
17
|
+
syncResult.confirmOperationsSync(params.transaction.mutations);
|
|
18
|
+
return handlerResult;
|
|
19
|
+
};
|
|
20
|
+
const wrappedOnDelete = async (params) => {
|
|
21
|
+
let handlerResult;
|
|
22
|
+
if (onDelete) {
|
|
23
|
+
handlerResult = await onDelete(params) ?? {};
|
|
24
|
+
}
|
|
25
|
+
syncResult.confirmOperationsSync(params.transaction.mutations);
|
|
26
|
+
return handlerResult;
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
...restConfig,
|
|
30
|
+
sync: syncResult.sync,
|
|
31
|
+
onInsert: wrappedOnInsert,
|
|
32
|
+
onUpdate: wrappedOnUpdate,
|
|
33
|
+
onDelete: wrappedOnDelete,
|
|
34
|
+
utils: {},
|
|
35
|
+
startSync: true,
|
|
36
|
+
gcTime: 0
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function createLocalOnlySync(initialData) {
|
|
40
|
+
let syncBegin = null;
|
|
41
|
+
let syncWrite = null;
|
|
42
|
+
let syncCommit = null;
|
|
43
|
+
const sync = {
|
|
44
|
+
/**
|
|
45
|
+
* Sync function that captures sync parameters and applies initial data
|
|
46
|
+
* @param params - Sync parameters containing begin, write, and commit functions
|
|
47
|
+
* @returns Unsubscribe function (empty since no ongoing sync is needed)
|
|
48
|
+
*/
|
|
49
|
+
sync: (params) => {
|
|
50
|
+
const { begin, write, commit } = params;
|
|
51
|
+
syncBegin = begin;
|
|
52
|
+
syncWrite = write;
|
|
53
|
+
syncCommit = commit;
|
|
54
|
+
if (initialData && initialData.length > 0) {
|
|
55
|
+
begin();
|
|
56
|
+
initialData.forEach((item) => {
|
|
57
|
+
write({
|
|
58
|
+
type: `insert`,
|
|
59
|
+
value: item
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
commit();
|
|
63
|
+
}
|
|
64
|
+
return () => {
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* Get sync metadata - returns empty object for local-only collections
|
|
69
|
+
* @returns Empty metadata object
|
|
70
|
+
*/
|
|
71
|
+
getSyncMetadata: () => ({})
|
|
72
|
+
};
|
|
73
|
+
const confirmOperationsSync = (mutations) => {
|
|
74
|
+
if (!syncBegin || !syncWrite || !syncCommit) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
syncBegin();
|
|
78
|
+
mutations.forEach((mutation) => {
|
|
79
|
+
if (syncWrite) {
|
|
80
|
+
syncWrite({
|
|
81
|
+
type: mutation.type,
|
|
82
|
+
value: mutation.modified
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
syncCommit();
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
sync,
|
|
90
|
+
confirmOperationsSync
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
localOnlyCollectionOptions
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=local-only.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-only.js","sources":["../../src/local-only.ts"],"sourcesContent":["import type {\n DeleteMutationFnParams,\n InsertMutationFnParams,\n OperationType,\n ResolveType,\n SyncConfig,\n UpdateMutationFnParams,\n UtilsRecord,\n} from \"./types\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Configuration interface for Local-only collection options\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @template TKey - The type of the key returned by getKey\n *\n * @remarks\n * Type resolution follows a priority order:\n * 1. If you provide an explicit type via generic parameter, it will be used\n * 2. If no explicit type is provided but a schema is, the schema's output type will be inferred\n * 3. If neither explicit type nor schema is provided, the fallback type will be used\n *\n * You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.\n */\nexport interface LocalOnlyCollectionConfig<\n TExplicit = unknown,\n TSchema extends StandardSchemaV1 = never,\n TFallback extends Record<string, unknown> = Record<string, unknown>,\n TKey extends string | number = string | number,\n> {\n /**\n * Standard Collection configuration properties\n */\n id?: string\n schema?: TSchema\n getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => TKey\n\n /**\n * Optional initial data to populate the collection with on creation\n * This data will be applied during the initial sync process\n */\n initialData?: Array<ResolveType<TExplicit, TSchema, TFallback>>\n\n /**\n * Optional asynchronous handler function called after an insert operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onInsert?: (\n params: InsertMutationFnParams<\n ResolveType<TExplicit, TSchema, TFallback>,\n TKey,\n LocalOnlyCollectionUtils\n >\n ) => Promise<any>\n\n /**\n * Optional asynchronous handler function called after an update operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onUpdate?: (\n params: UpdateMutationFnParams<\n ResolveType<TExplicit, TSchema, TFallback>,\n TKey,\n LocalOnlyCollectionUtils\n >\n ) => Promise<any>\n\n /**\n * Optional asynchronous handler function called after a delete operation\n * @param params Object containing transaction and collection information\n * @returns Promise resolving to any value\n */\n onDelete?: (\n params: DeleteMutationFnParams<\n ResolveType<TExplicit, TSchema, TFallback>,\n TKey,\n LocalOnlyCollectionUtils\n >\n ) => Promise<any>\n}\n\n/**\n * Local-only collection utilities type (currently empty but matches the pattern)\n */\nexport interface LocalOnlyCollectionUtils extends UtilsRecord {}\n\n/**\n * Creates Local-only collection options for use with a standard Collection\n *\n * This is an in-memory collection that doesn't sync with external sources but uses a loopback sync config\n * that immediately \"syncs\" all optimistic changes to the collection, making them permanent.\n * Perfect for local-only data that doesn't need persistence or external synchronization.\n *\n * @template TExplicit - The explicit type of items in the collection (highest priority)\n * @template TSchema - The schema type for validation and type inference (second priority)\n * @template TFallback - The fallback type if no explicit or schema type is provided\n * @template TKey - The type of the key returned by getKey\n * @param config - Configuration options for the Local-only collection\n * @returns Collection options with utilities (currently empty but follows the pattern)\n *\n * @example\n * // Basic local-only collection\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * })\n * )\n *\n * @example\n * // Local-only collection with initial data\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * initialData: [\n * { id: 1, name: 'Item 1' },\n * { id: 2, name: 'Item 2' },\n * ],\n * })\n * )\n *\n * @example\n * // Local-only collection with mutation handlers\n * const collection = createCollection(\n * localOnlyCollectionOptions({\n * getKey: (item) => item.id,\n * onInsert: async ({ transaction }) => {\n * console.log('Item inserted:', transaction.mutations[0].modified)\n * // Custom logic after insert\n * },\n * })\n * )\n */\nexport function localOnlyCollectionOptions<\n TExplicit = unknown,\n TSchema extends StandardSchemaV1 = never,\n TFallback extends Record<string, unknown> = Record<string, unknown>,\n TKey extends string | number = string | number,\n>(config: LocalOnlyCollectionConfig<TExplicit, TSchema, TFallback, TKey>) {\n type ResolvedType = ResolveType<TExplicit, TSchema, TFallback>\n\n const { initialData, onInsert, onUpdate, onDelete, ...restConfig } = config\n\n // Create the sync configuration with transaction confirmation capability\n const syncResult = createLocalOnlySync<ResolvedType, TKey>(initialData)\n\n /**\n * Create wrapper handlers that call user handlers first, then confirm transactions\n * Wraps the user's onInsert handler to also confirm the transaction immediately\n */\n const wrappedOnInsert = async (\n params: InsertMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onInsert) {\n handlerResult = (await onInsert(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onUpdate handler that also confirms the transaction immediately\n */\n const wrappedOnUpdate = async (\n params: UpdateMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onUpdate) {\n handlerResult = (await onUpdate(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n /**\n * Wrapper for onDelete handler that also confirms the transaction immediately\n */\n const wrappedOnDelete = async (\n params: DeleteMutationFnParams<ResolvedType, TKey, LocalOnlyCollectionUtils>\n ) => {\n // Call user handler first if provided\n let handlerResult\n if (onDelete) {\n handlerResult = (await onDelete(params)) ?? {}\n }\n\n // Then synchronously confirm the transaction by looping through mutations\n syncResult.confirmOperationsSync(params.transaction.mutations)\n\n return handlerResult\n }\n\n return {\n ...restConfig,\n sync: syncResult.sync,\n onInsert: wrappedOnInsert,\n onUpdate: wrappedOnUpdate,\n onDelete: wrappedOnDelete,\n utils: {} as LocalOnlyCollectionUtils,\n startSync: true,\n gcTime: 0,\n }\n}\n\n/**\n * Internal function to create Local-only sync configuration with transaction confirmation\n *\n * This captures the sync functions and provides synchronous confirmation of operations.\n * It creates a loopback sync that immediately confirms all optimistic operations,\n * making them permanent in the collection.\n *\n * @param initialData - Optional array of initial items to populate the collection\n * @returns Object with sync configuration and confirmOperationsSync function\n */\nfunction createLocalOnlySync<T extends object, TKey extends string | number>(\n initialData?: Array<T>\n) {\n // Capture sync functions for transaction confirmation\n let syncBegin: (() => void) | null = null\n let syncWrite: ((message: { type: OperationType; value: T }) => void) | null =\n null\n let syncCommit: (() => void) | null = null\n\n const sync: SyncConfig<T, TKey> = {\n /**\n * Sync function that captures sync parameters and applies initial data\n * @param params - Sync parameters containing begin, write, and commit functions\n * @returns Unsubscribe function (empty since no ongoing sync is needed)\n */\n sync: (params) => {\n const { begin, write, commit } = params\n\n // Capture sync functions for later use by confirmOperationsSync\n syncBegin = begin\n syncWrite = write\n syncCommit = commit\n\n // Apply initial data if provided\n if (initialData && initialData.length > 0) {\n begin()\n initialData.forEach((item) => {\n write({\n type: `insert`,\n value: item,\n })\n })\n commit()\n }\n\n // Return empty unsubscribe function - no ongoing sync needed\n return () => {}\n },\n /**\n * Get sync metadata - returns empty object for local-only collections\n * @returns Empty metadata object\n */\n getSyncMetadata: () => ({}),\n }\n\n /**\n * Synchronously confirms optimistic operations by immediately writing through sync\n *\n * This loops through transaction mutations and applies them to move from optimistic to synced state.\n * It's called after user handlers to make optimistic changes permanent.\n *\n * @param mutations - Array of mutation objects from the transaction\n */\n const confirmOperationsSync = (mutations: Array<any>) => {\n if (!syncBegin || !syncWrite || !syncCommit) {\n return // Sync not initialized yet, which is fine\n }\n\n // Immediately write back through sync interface\n syncBegin()\n mutations.forEach((mutation) => {\n if (syncWrite) {\n syncWrite({\n type: mutation.type,\n value: mutation.modified,\n })\n }\n })\n syncCommit()\n }\n\n return {\n sync,\n confirmOperationsSync,\n }\n}\n"],"names":[],"mappings":"AAwIO,SAAS,2BAKd,QAAwE;AAGxE,QAAM,EAAE,aAAa,UAAU,UAAU,UAAU,GAAG,eAAe;AAGrE,QAAM,aAAa,oBAAwC,WAAW;AAMtE,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,WAAW;AAAA,IACjB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO,CAAA;AAAA,IACP,WAAW;AAAA,IACX,QAAQ;AAAA,EAAA;AAEZ;AAYA,SAAS,oBACP,aACA;AAEA,MAAI,YAAiC;AACrC,MAAI,YACF;AACF,MAAI,aAAkC;AAEtC,QAAM,OAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMhC,MAAM,CAAC,WAAW;AAChB,YAAM,EAAE,OAAO,OAAO,OAAA,IAAW;AAGjC,kBAAY;AACZ,kBAAY;AACZ,mBAAa;AAGb,UAAI,eAAe,YAAY,SAAS,GAAG;AACzC,cAAA;AACA,oBAAY,QAAQ,CAAC,SAAS;AAC5B,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO;AAAA,UAAA,CACR;AAAA,QACH,CAAC;AACD,eAAA;AAAA,MACF;AAGA,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,iBAAiB,OAAO,CAAA;AAAA,EAAC;AAW3B,QAAM,wBAAwB,CAAC,cAA0B;AACvD,QAAI,CAAC,aAAa,CAAC,aAAa,CAAC,YAAY;AAC3C;AAAA,IACF;AAGA,cAAA;AACA,cAAU,QAAQ,CAAC,aAAa;AAC9B,UAAI,WAAW;AACb,kBAAU;AAAA,UACR,MAAM,SAAS;AAAA,UACf,OAAO,SAAS;AAAA,QAAA,CACjB;AAAA,MACH;AAAA,IACF,CAAC;AACD,eAAA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { CollectionConfig, DeleteMutationFnParams, InsertMutationFnParams, ResolveType, SyncConfig, UpdateMutationFnParams, UtilsRecord } from './types.js';
|
|
2
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
|
+
/**
|
|
4
|
+
* Storage API interface - subset of DOM Storage that we need
|
|
5
|
+
*/
|
|
6
|
+
export type StorageApi = Pick<Storage, `getItem` | `setItem` | `removeItem`>;
|
|
7
|
+
/**
|
|
8
|
+
* Storage event API - subset of Window for 'storage' events only
|
|
9
|
+
*/
|
|
10
|
+
export type StorageEventApi = {
|
|
11
|
+
addEventListener: (type: `storage`, listener: (event: StorageEvent) => void) => void;
|
|
12
|
+
removeEventListener: (type: `storage`, listener: (event: StorageEvent) => void) => void;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Configuration interface for localStorage collection options
|
|
16
|
+
* @template TExplicit - The explicit type of items in the collection (highest priority)
|
|
17
|
+
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
18
|
+
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Type resolution follows a priority order:
|
|
22
|
+
* 1. If you provide an explicit type via generic parameter, it will be used
|
|
23
|
+
* 2. If no explicit type is provided but a schema is, the schema's output type will be inferred
|
|
24
|
+
* 3. If neither explicit type nor schema is provided, the fallback type will be used
|
|
25
|
+
*
|
|
26
|
+
* You should provide EITHER an explicit type OR a schema, but not both, as they would conflict.
|
|
27
|
+
*/
|
|
28
|
+
export interface LocalStorageCollectionConfig<TExplicit = unknown, TSchema extends StandardSchemaV1 = never, TFallback extends object = Record<string, unknown>> {
|
|
29
|
+
/**
|
|
30
|
+
* The key to use for storing the collection data in localStorage/sessionStorage
|
|
31
|
+
*/
|
|
32
|
+
storageKey: string;
|
|
33
|
+
/**
|
|
34
|
+
* Storage API to use (defaults to window.localStorage)
|
|
35
|
+
* Can be any object that implements the Storage interface (e.g., sessionStorage)
|
|
36
|
+
*/
|
|
37
|
+
storage?: StorageApi;
|
|
38
|
+
/**
|
|
39
|
+
* Storage event API to use for cross-tab synchronization (defaults to window)
|
|
40
|
+
* Can be any object that implements addEventListener/removeEventListener for storage events
|
|
41
|
+
*/
|
|
42
|
+
storageEventApi?: StorageEventApi;
|
|
43
|
+
/**
|
|
44
|
+
* Collection identifier (defaults to "local-collection:{storageKey}" if not provided)
|
|
45
|
+
*/
|
|
46
|
+
id?: string;
|
|
47
|
+
schema?: TSchema;
|
|
48
|
+
getKey: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`getKey`];
|
|
49
|
+
sync?: CollectionConfig<ResolveType<TExplicit, TSchema, TFallback>>[`sync`];
|
|
50
|
+
/**
|
|
51
|
+
* Optional asynchronous handler function called before an insert operation
|
|
52
|
+
* @param params Object containing transaction and collection information
|
|
53
|
+
* @returns Promise resolving to any value
|
|
54
|
+
*/
|
|
55
|
+
onInsert?: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
56
|
+
/**
|
|
57
|
+
* Optional asynchronous handler function called before an update operation
|
|
58
|
+
* @param params Object containing transaction and collection information
|
|
59
|
+
* @returns Promise resolving to any value
|
|
60
|
+
*/
|
|
61
|
+
onUpdate?: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
62
|
+
/**
|
|
63
|
+
* Optional asynchronous handler function called before a delete operation
|
|
64
|
+
* @param params Object containing transaction and collection information
|
|
65
|
+
* @returns Promise resolving to any value
|
|
66
|
+
*/
|
|
67
|
+
onDelete?: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Type for the clear utility function
|
|
71
|
+
*/
|
|
72
|
+
export type ClearStorageFn = () => void;
|
|
73
|
+
/**
|
|
74
|
+
* Type for the getStorageSize utility function
|
|
75
|
+
*/
|
|
76
|
+
export type GetStorageSizeFn = () => number;
|
|
77
|
+
/**
|
|
78
|
+
* LocalStorage collection utilities type
|
|
79
|
+
*/
|
|
80
|
+
export interface LocalStorageCollectionUtils extends UtilsRecord {
|
|
81
|
+
clearStorage: ClearStorageFn;
|
|
82
|
+
getStorageSize: GetStorageSizeFn;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Creates localStorage collection options for use with a standard Collection
|
|
86
|
+
*
|
|
87
|
+
* This function creates a collection that persists data to localStorage/sessionStorage
|
|
88
|
+
* and synchronizes changes across browser tabs using storage events.
|
|
89
|
+
*
|
|
90
|
+
* @template TExplicit - The explicit type of items in the collection (highest priority)
|
|
91
|
+
* @template TSchema - The schema type for validation and type inference (second priority)
|
|
92
|
+
* @template TFallback - The fallback type if no explicit or schema type is provided
|
|
93
|
+
* @param config - Configuration options for the localStorage collection
|
|
94
|
+
* @returns Collection options with utilities including clearStorage and getStorageSize
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Basic localStorage collection
|
|
98
|
+
* const collection = createCollection(
|
|
99
|
+
* localStorageCollectionOptions({
|
|
100
|
+
* storageKey: 'todos',
|
|
101
|
+
* getKey: (item) => item.id,
|
|
102
|
+
* })
|
|
103
|
+
* )
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* // localStorage collection with custom storage
|
|
107
|
+
* const collection = createCollection(
|
|
108
|
+
* localStorageCollectionOptions({
|
|
109
|
+
* storageKey: 'todos',
|
|
110
|
+
* storage: window.sessionStorage, // Use sessionStorage instead
|
|
111
|
+
* getKey: (item) => item.id,
|
|
112
|
+
* })
|
|
113
|
+
* )
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* // localStorage collection with mutation handlers
|
|
117
|
+
* const collection = createCollection(
|
|
118
|
+
* localStorageCollectionOptions({
|
|
119
|
+
* storageKey: 'todos',
|
|
120
|
+
* getKey: (item) => item.id,
|
|
121
|
+
* onInsert: async ({ transaction }) => {
|
|
122
|
+
* console.log('Item inserted:', transaction.mutations[0].modified)
|
|
123
|
+
* },
|
|
124
|
+
* })
|
|
125
|
+
* )
|
|
126
|
+
*/
|
|
127
|
+
export declare function localStorageCollectionOptions<TExplicit = unknown, TSchema extends StandardSchemaV1 = never, TFallback extends object = Record<string, unknown>>(config: LocalStorageCollectionConfig<TExplicit, TSchema, TFallback>): {
|
|
128
|
+
id: string;
|
|
129
|
+
sync: SyncConfig<ResolveType<TExplicit, TSchema, TFallback>, string | number> & {
|
|
130
|
+
manualTrigger?: () => void;
|
|
131
|
+
};
|
|
132
|
+
onInsert: (params: InsertMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
133
|
+
onUpdate: (params: UpdateMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
134
|
+
onDelete: (params: DeleteMutationFnParams<ResolveType<TExplicit, TSchema, TFallback>>) => Promise<any>;
|
|
135
|
+
utils: {
|
|
136
|
+
clearStorage: ClearStorageFn;
|
|
137
|
+
getStorageSize: GetStorageSizeFn;
|
|
138
|
+
};
|
|
139
|
+
schema?: TSchema | undefined;
|
|
140
|
+
getKey: (item: ResolveType<TExplicit, TSchema, TFallback>) => string | number;
|
|
141
|
+
};
|