@chelone/core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -39,6 +39,8 @@ export interface ListParamsPatch {
|
|
|
39
39
|
page?: number;
|
|
40
40
|
}
|
|
41
41
|
export interface ListController<TRecord> extends MicroStore<ListControllerState<TRecord>> {
|
|
42
|
+
/** The `perPage` the list starts at when nothing is persisted or in the URL. Optional so other list controllers and fakes need not set it. */
|
|
43
|
+
readonly defaultPerPage?: number;
|
|
42
44
|
setPage(page: number): void;
|
|
43
45
|
setPerPage(perPage: number): void;
|
|
44
46
|
/** Single payload on the SAME single-sorted field toggles order; anything else replaces. */
|
|
@@ -136,4 +138,24 @@ export declare function resetPageTarget(s: Pick<ListControllerState<unknown>, "p
|
|
|
136
138
|
* action path and are guarded to run only on SETTLED results, so they
|
|
137
139
|
* cannot ping-pong.
|
|
138
140
|
*/
|
|
141
|
+
/**
|
|
142
|
+
* The actions of a list controller, for an adapter to spread next to its own
|
|
143
|
+
* reactive state. They are the same functions across state changes.
|
|
144
|
+
*/
|
|
145
|
+
export declare function listControllerActions(controller: Omit<ListController<unknown>, keyof MicroStore<ListControllerState<unknown>> | "defaultPerPage">): {
|
|
146
|
+
setPage: (page: number) => void;
|
|
147
|
+
setPerPage: (perPage: number) => void;
|
|
148
|
+
setSort: (sort: SortPayload | SortPayload[]) => void;
|
|
149
|
+
setFilters: (filter: FilterPayload, displayedFilters?: Record<string, boolean>, debounced?: boolean) => void;
|
|
150
|
+
setParams: (patch: ListParamsPatch) => void;
|
|
151
|
+
showFilter: (name: string, defaultValue?: unknown) => void;
|
|
152
|
+
hideFilter: (name: string) => void;
|
|
153
|
+
select: (ids: Identifier[]) => void;
|
|
154
|
+
toggleItem: (id: Identifier) => void;
|
|
155
|
+
unselectAll: () => void;
|
|
156
|
+
selectAll: (options?: {
|
|
157
|
+
limit?: number;
|
|
158
|
+
}) => Promise<void>;
|
|
159
|
+
refetch: () => void;
|
|
160
|
+
};
|
|
139
161
|
export declare function createListController<TResources extends ResourceMap, TName extends keyof TResources & string>(ctx: ListControllerContext<TResources>, resource: TName, options?: ListControllerOptions): ListController<RecordOf<TResources, TName>>;
|
|
@@ -121,6 +121,22 @@ function resetPageTarget(s) {
|
|
|
121
121
|
if (s.page > 1 && s.data !== void 0 && s.data.length === 0) return 1;
|
|
122
122
|
return void 0;
|
|
123
123
|
}
|
|
124
|
+
function listControllerActions(controller) {
|
|
125
|
+
return {
|
|
126
|
+
setPage: controller.setPage,
|
|
127
|
+
setPerPage: controller.setPerPage,
|
|
128
|
+
setSort: controller.setSort,
|
|
129
|
+
setFilters: controller.setFilters,
|
|
130
|
+
setParams: controller.setParams,
|
|
131
|
+
showFilter: controller.showFilter,
|
|
132
|
+
hideFilter: controller.hideFilter,
|
|
133
|
+
select: controller.select,
|
|
134
|
+
toggleItem: controller.toggleItem,
|
|
135
|
+
unselectAll: controller.unselectAll,
|
|
136
|
+
selectAll: controller.selectAll,
|
|
137
|
+
refetch: controller.refetch
|
|
138
|
+
};
|
|
139
|
+
}
|
|
124
140
|
function createListController(ctx, resource, options = {}) {
|
|
125
141
|
const storeKey = options.storeKey === false ? null : options.storeKey ?? resource;
|
|
126
142
|
const paramsKey = storeKey ? `${storeKey}.listParams` : null;
|
|
@@ -377,6 +393,7 @@ function createListController(ctx, resource, options = {}) {
|
|
|
377
393
|
}
|
|
378
394
|
return {
|
|
379
395
|
...store,
|
|
396
|
+
defaultPerPage: defaults.perPage,
|
|
380
397
|
setPage,
|
|
381
398
|
setPerPage,
|
|
382
399
|
setSort,
|
|
@@ -397,6 +414,7 @@ export {
|
|
|
397
414
|
createErrorAnnouncer,
|
|
398
415
|
createListController,
|
|
399
416
|
errorMessageKey,
|
|
417
|
+
listControllerActions,
|
|
400
418
|
listParamsFromPatch,
|
|
401
419
|
notifyFetchError,
|
|
402
420
|
notifySelectAllLimit,
|
package/dist/data/undoable.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type QueryClient, type QueryKey } from "@tanstack/query-core";
|
|
2
|
+
import type { Identifier } from "../resource/identifier.js";
|
|
3
|
+
import type { ResourceRecord } from "../resource/record.js";
|
|
4
|
+
import type { DeleteParams, UpdateManyParams, UpdateParams } from "./types.js";
|
|
2
5
|
/**
|
|
3
6
|
* A snapshot of the query cache taken BEFORE an optimistic apply: an array of
|
|
4
7
|
* `[query key, data-at-that-key]` tuples. Rolling back writes each value back.
|
|
@@ -57,6 +60,53 @@ export interface UndoableHandle<TResult = unknown> {
|
|
|
57
60
|
* `commit()` settle it. React/Vue-free — pure query-core.
|
|
58
61
|
*/
|
|
59
62
|
export declare function startUndoableMutation<TResult = unknown>(config: UndoableMutationConfig<TResult>): UndoableHandle<TResult>;
|
|
63
|
+
/**
|
|
64
|
+
* The callbacks an undoable `mutate` takes as its second argument. They are
|
|
65
|
+
* not TanStack Query's: the server call is deferred, so `onSuccess` fires with
|
|
66
|
+
* the optimistic result and `onError`/`onSettled` follow the deferred commit.
|
|
67
|
+
*/
|
|
68
|
+
export interface UndoableCallbacks<TData, TParams> {
|
|
69
|
+
/** Runs right after the optimistic cache change, not after the undo window. */
|
|
70
|
+
onSuccess?: (data: TData, params: TParams) => void;
|
|
71
|
+
/** Runs when the deferred commit fails, after the rollback and error notification. */
|
|
72
|
+
onError?: (error: unknown, params: TParams) => void;
|
|
73
|
+
/** Runs once the commit ends, whether it succeeded or failed. Never after an Undo. */
|
|
74
|
+
onSettled?: (params: TParams) => void;
|
|
75
|
+
}
|
|
76
|
+
/** What an undoable `useUpdate` hands `onSuccess`: the patched record. */
|
|
77
|
+
export type UndoableUpdateCallbacks<TRecord extends ResourceRecord> = UndoableCallbacks<TRecord, UpdateParams<TRecord>>;
|
|
78
|
+
/** What an undoable `useDelete` hands `onSuccess`: the previous record, when given. */
|
|
79
|
+
export type UndoableDeleteCallbacks<TRecord extends ResourceRecord> = UndoableCallbacks<TRecord | undefined, DeleteParams<TRecord>>;
|
|
80
|
+
/** What an undoable `useUpdateMany` hands `onSuccess`: the ids. */
|
|
81
|
+
export type UndoableUpdateManyCallbacks<TRecord extends ResourceRecord> = UndoableCallbacks<Identifier[], UpdateManyParams<TRecord>>;
|
|
82
|
+
/** The calls an undoable `mutate` makes for its callbacks; see {@link undoableCallbacks}. */
|
|
83
|
+
export interface UndoableSettler<TData> {
|
|
84
|
+
/** Wraps the deferred `commit` so `onError` and `onSettled` run when it ends. */
|
|
85
|
+
commit<TResult>(commit: () => Promise<TResult>): () => Promise<TResult>;
|
|
86
|
+
/** Runs `onSuccess`: call it once the optimistic change is applied. */
|
|
87
|
+
success(data: TData): void;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Bind an undoable `mutate`'s callbacks to its `params`. The wrapped commit
|
|
91
|
+
* rethrows a failure, so the handle still rolls back and the adapter still
|
|
92
|
+
* notifies; the callbacks wait a macrotask to run after both.
|
|
93
|
+
*/
|
|
94
|
+
export declare function undoableCallbacks<TData, TParams>(params: TParams, callbacks?: UndoableCallbacks<TData, TParams>): UndoableSettler<TData>;
|
|
95
|
+
/** What an adapter's `runUndoable` needs besides the deferred `commit`. */
|
|
96
|
+
export interface UndoableCacheOptions {
|
|
97
|
+
queryKeys: QueryKey[];
|
|
98
|
+
applyOptimistic: () => void;
|
|
99
|
+
message: string;
|
|
100
|
+
messageArgs: {
|
|
101
|
+
smart_count: number;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Patch `ids` in the `resource` caches now; the notification reads "updated". */
|
|
105
|
+
export declare function undoableUpdateOptions(queryClient: QueryClient, resource: string, ids: Identifier[], data: object): UndoableCacheOptions;
|
|
106
|
+
/** Remove `ids` from the `resource` caches now; the notification reads "deleted". */
|
|
107
|
+
export declare function undoableDeleteOptions(queryClient: QueryClient, resource: string, ids: Identifier[]): UndoableCacheOptions;
|
|
108
|
+
/** The record an undoable update shows at once: the previous one with the patch on top. */
|
|
109
|
+
export declare function patchedRecord<TRecord extends ResourceRecord>(params: UpdateParams<TRecord>): TRecord;
|
|
60
110
|
/** True while at least one undoable mutation is still awaiting commit/undo. */
|
|
61
111
|
export declare function hasPendingUndoables(): boolean;
|
|
62
112
|
/** True while an undoable is pending or its committed server call is still in flight. */
|
package/dist/data/undoable.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { partialMatchKey } from "@tanstack/query-core";
|
|
2
|
+
import { removeRecordsFromCache, updateRecordsInCache } from "./cache-ops.js";
|
|
2
3
|
const pendingUndoables = /* @__PURE__ */ new Set();
|
|
3
4
|
const settledListeners = /* @__PURE__ */ new Set();
|
|
4
5
|
let inFlightCommits = 0;
|
|
@@ -23,10 +24,10 @@ function keysOverlap(a, b) {
|
|
|
23
24
|
(x) => b.some((y) => partialMatchKey(x, y) || partialMatchKey(y, x))
|
|
24
25
|
);
|
|
25
26
|
}
|
|
26
|
-
function entriesToReplay(
|
|
27
|
+
function entriesToReplay(later2, entry) {
|
|
27
28
|
const affectedKeys = [...entry.queryKeys];
|
|
28
29
|
const affected = [];
|
|
29
|
-
for (const other of
|
|
30
|
+
for (const other of later2) {
|
|
30
31
|
if (!keysOverlap(affectedKeys, other.queryKeys)) continue;
|
|
31
32
|
affected.push(other);
|
|
32
33
|
affectedKeys.push(...other.queryKeys);
|
|
@@ -59,12 +60,12 @@ function startUndoableMutation(config) {
|
|
|
59
60
|
const rollback = () => {
|
|
60
61
|
const open = order;
|
|
61
62
|
const index = open.indexOf(entry);
|
|
62
|
-
const
|
|
63
|
+
const later2 = entriesToReplay(open.slice(index + 1), entry);
|
|
63
64
|
open.splice(index, 1);
|
|
64
|
-
for (const other of [...
|
|
65
|
+
for (const other of [...later2].reverse())
|
|
65
66
|
restore(queryClient, other.snapshot);
|
|
66
67
|
restore(queryClient, entry.snapshot);
|
|
67
|
-
for (const other of
|
|
68
|
+
for (const other of later2) {
|
|
68
69
|
other.snapshot = takeSnapshot(queryClient, other.queryKeys);
|
|
69
70
|
other.applyOptimistic();
|
|
70
71
|
}
|
|
@@ -111,6 +112,61 @@ function startUndoableMutation(config) {
|
|
|
111
112
|
pendingUndoables.add(handle);
|
|
112
113
|
return handle;
|
|
113
114
|
}
|
|
115
|
+
function later(run) {
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
try {
|
|
118
|
+
run();
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error(error);
|
|
121
|
+
}
|
|
122
|
+
}, 0);
|
|
123
|
+
}
|
|
124
|
+
function undoableCallbacks(params, callbacks = {}) {
|
|
125
|
+
const { onSuccess, onError, onSettled } = callbacks;
|
|
126
|
+
return {
|
|
127
|
+
success: (data) => onSuccess?.(data, params),
|
|
128
|
+
// The callbacks run in a timer, never inside the commit's promise: one that
|
|
129
|
+
// throws must not read as a failed commit and roll the cache back.
|
|
130
|
+
commit: (commit) => async () => {
|
|
131
|
+
let result;
|
|
132
|
+
try {
|
|
133
|
+
result = await commit();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
later(() => {
|
|
136
|
+
onError?.(error, params);
|
|
137
|
+
onSettled?.(params);
|
|
138
|
+
});
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
later(() => onSettled?.(params));
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function undoableUpdateOptions(queryClient, resource, ids, data) {
|
|
147
|
+
return {
|
|
148
|
+
queryKeys: [[resource]],
|
|
149
|
+
applyOptimistic: () => updateRecordsInCache(
|
|
150
|
+
queryClient,
|
|
151
|
+
resource,
|
|
152
|
+
ids,
|
|
153
|
+
data
|
|
154
|
+
),
|
|
155
|
+
message: "chelone.notification.updated",
|
|
156
|
+
messageArgs: { smart_count: ids.length }
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function undoableDeleteOptions(queryClient, resource, ids) {
|
|
160
|
+
return {
|
|
161
|
+
queryKeys: [[resource]],
|
|
162
|
+
applyOptimistic: () => removeRecordsFromCache(queryClient, resource, ids),
|
|
163
|
+
message: "chelone.notification.deleted",
|
|
164
|
+
messageArgs: { smart_count: ids.length }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function patchedRecord(params) {
|
|
168
|
+
return { ...params.previousData, ...params.data, id: params.id };
|
|
169
|
+
}
|
|
114
170
|
function hasPendingUndoables() {
|
|
115
171
|
return pendingUndoables.size > 0;
|
|
116
172
|
}
|
|
@@ -132,5 +188,9 @@ export {
|
|
|
132
188
|
hasPendingUndoables,
|
|
133
189
|
hasUnsettledUndoables,
|
|
134
190
|
onUndoablesSettled,
|
|
135
|
-
|
|
191
|
+
patchedRecord,
|
|
192
|
+
startUndoableMutation,
|
|
193
|
+
undoableCallbacks,
|
|
194
|
+
undoableDeleteOptions,
|
|
195
|
+
undoableUpdateOptions
|
|
136
196
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export { isFilterSelected, toggleFilterValue, } from "./controller/filter-toggle
|
|
|
22
22
|
export type { InfiniteListController, InfiniteListControllerState, } from "./controller/infinite-list-controller.js";
|
|
23
23
|
export { createInfiniteListController } from "./controller/infinite-list-controller.js";
|
|
24
24
|
export type { ListController, ListControllerContext, ListControllerOptions, ListControllerState, ListParamsPatch, } from "./controller/list-controller.js";
|
|
25
|
-
export { createListController } from "./controller/list-controller.js";
|
|
25
|
+
export { createListController, listControllerActions, } from "./controller/list-controller.js";
|
|
26
26
|
export type { ListParamsQuery, ListUrlState, } from "./controller/list-params.js";
|
|
27
27
|
export { hasCustomParams, listPatchFromSearch, parseListParams, serializeListParams, serializeListState, } from "./controller/list-params.js";
|
|
28
28
|
export type { PrevNextOptions, PrevNextPage, PrevNextParams, PrevNextResolution, PrevNextStoredParams, } from "./controller/prev-next.js";
|
|
@@ -61,8 +61,8 @@ export { createMutationOptions, deleteMutationOptions, updateMutationOptions, }
|
|
|
61
61
|
export type { DataContext } from "./data/query-options.js";
|
|
62
62
|
export { getInfiniteListOptions, getListOptions, getManyOptions, getManyReferenceOptions, getOneOptions, } from "./data/query-options.js";
|
|
63
63
|
export type { CreateParams, CreateResult, CursorPagination, DataProvider, DeleteManyParams, DeleteManyResult, DeleteParams, DeleteResult, FilterPayload, GetListParams, GetListResult, GetManyParams, GetManyReferenceParams, GetManyReferenceResult, GetManyResult, GetOneParams, GetOneResult, InfiniteListParams, PageInfo, PagePagination, Pagination, SortPayload, UpdateManyParams, UpdateManyResult, UpdateParams, UpdateResult, } from "./data/types.js";
|
|
64
|
-
export type { UndoableHandle, UndoableMutationConfig, UndoableSnapshot, } from "./data/undoable.js";
|
|
65
|
-
export { commitAllUndoables, hasPendingUndoables, startUndoableMutation, } from "./data/undoable.js";
|
|
64
|
+
export type { UndoableCacheOptions, UndoableCallbacks, UndoableDeleteCallbacks, UndoableHandle, UndoableMutationConfig, UndoableSettler, UndoableSnapshot, UndoableUpdateCallbacks, UndoableUpdateManyCallbacks, } from "./data/undoable.js";
|
|
65
|
+
export { commitAllUndoables, hasPendingUndoables, patchedRecord, startUndoableMutation, undoableCallbacks, undoableDeleteOptions, undoableUpdateOptions, } from "./data/undoable.js";
|
|
66
66
|
export type { LifecycleCallbacks, LifecycleHandler, } from "./data/with-lifecycle-callbacks.js";
|
|
67
67
|
export { withLifecycleCallbacks } from "./data/with-lifecycle-callbacks.js";
|
|
68
68
|
export type { ApplicationUpdateSnapshot, ApplicationUpdateStoreOptions, } from "./environment/application-update-store.js";
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { createClientListController } from "./controller/client-list-controller.
|
|
|
10
10
|
import { arrangeColumns, columnLabelMatches, dataTableColumnKeys, defaultDataTableStoreKey, getDataTableColumnId, moveColumn, toggleHiddenColumn } from "./controller/data-table-columns.js";
|
|
11
11
|
import { isFilterSelected, toggleFilterValue } from "./controller/filter-toggle.js";
|
|
12
12
|
import { createInfiniteListController } from "./controller/infinite-list-controller.js";
|
|
13
|
-
import { createListController } from "./controller/list-controller.js";
|
|
13
|
+
import { createListController, listControllerActions } from "./controller/list-controller.js";
|
|
14
14
|
import { hasCustomParams, listPatchFromSearch, parseListParams, serializeListParams, serializeListState } from "./controller/list-params.js";
|
|
15
15
|
import { canUseCachedPage, getPrevNextParams, resolvePrevNext } from "./controller/prev-next.js";
|
|
16
16
|
import { createCreateController, createDeleteController, createEditController, createShowController } from "./controller/record-controllers.js";
|
|
@@ -36,7 +36,7 @@ import { HttpError } from "./data/http-error.js";
|
|
|
36
36
|
import { infiniteListKey, listKey, lockKey, locksKey, manyKey, manyReferenceKey, oneKey, resourceKey } from "./data/keys.js";
|
|
37
37
|
import { createMutationOptions, deleteMutationOptions, updateMutationOptions } from "./data/mutation-options.js";
|
|
38
38
|
import { getInfiniteListOptions, getListOptions, getManyOptions, getManyReferenceOptions, getOneOptions } from "./data/query-options.js";
|
|
39
|
-
import { commitAllUndoables, hasPendingUndoables, startUndoableMutation } from "./data/undoable.js";
|
|
39
|
+
import { commitAllUndoables, hasPendingUndoables, patchedRecord, startUndoableMutation, undoableCallbacks, undoableDeleteOptions, undoableUpdateOptions } from "./data/undoable.js";
|
|
40
40
|
import { withLifecycleCallbacks } from "./data/with-lifecycle-callbacks.js";
|
|
41
41
|
import { createApplicationUpdateStore } from "./environment/application-update-store.js";
|
|
42
42
|
import { createOfflineStore } from "./environment/offline-store.js";
|
|
@@ -246,6 +246,7 @@ export {
|
|
|
246
246
|
joinBasename,
|
|
247
247
|
latLngToLngLat,
|
|
248
248
|
latitude,
|
|
249
|
+
listControllerActions,
|
|
249
250
|
listKey,
|
|
250
251
|
listPatchFromSearch,
|
|
251
252
|
lngLatToLatLng,
|
|
@@ -283,6 +284,7 @@ export {
|
|
|
283
284
|
parseCsvWith,
|
|
284
285
|
parseListParams,
|
|
285
286
|
parseLocation,
|
|
287
|
+
patchedRecord,
|
|
286
288
|
permissionsQueryOptions,
|
|
287
289
|
polygonToBBox,
|
|
288
290
|
presenceTopic,
|
|
@@ -330,6 +332,9 @@ export {
|
|
|
330
332
|
toPresenceUser,
|
|
331
333
|
toggleFilterValue,
|
|
332
334
|
toggleHiddenColumn,
|
|
335
|
+
undoableCallbacks,
|
|
336
|
+
undoableDeleteOptions,
|
|
337
|
+
undoableUpdateOptions,
|
|
333
338
|
unreadKey,
|
|
334
339
|
updateMutationOptions,
|
|
335
340
|
updateRecordsInCache,
|