Skip to content

Commit 78f1e3a

Browse files
committed
[middleware] WillDelTablesCallback
1 parent 8701520 commit 78f1e3a

8 files changed

Lines changed: 224 additions & 6 deletions

File tree

src/@types/middleware/docs.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,21 @@
210210
* @since v8.0.0
211211
*/
212212
/// WillDelValuesCallback
213+
/**
214+
* The WillDelTablesCallback type describes a function that is called before all
215+
* Tables are deleted from the Store.
216+
*
217+
* The callback takes no parameters. It returns `true` to allow the deletion, or
218+
* `false` to prevent it.
219+
*
220+
* Multiple WillDelTablesCallback functions can be registered and they will be
221+
* called sequentially. If any callback returns `false`, the chain
222+
* short-circuits and the Tables will not be deleted.
223+
* @returns `true` to allow the deletion, `false` to prevent it.
224+
* @category Callback
225+
* @since v8.0.0
226+
*/
227+
/// WillDelTablesCallback
213228
/**
214229
* A Middleware object lets you intercept and validate writes to a Store.
215230
*
@@ -769,6 +784,39 @@
769784
* @since v8.0.0
770785
*/
771786
/// Middleware.addWillDelValuesCallback
787+
/**
788+
* The addWillDelTablesCallback method registers a WillDelTablesCallback that
789+
* will be called before all Tables are deleted from the Store.
790+
*
791+
* The callback returns `true` to allow the deletion or `false` to prevent
792+
* it. Multiple callbacks can be registered and they are called sequentially.
793+
* If any callback returns `false`, the deletion is prevented.
794+
* @param callback The WillDelTablesCallback to register.
795+
* @returns A reference to the Middleware object, for chaining.
796+
* @example
797+
* This example registers a callback that prevents deleting all Tables from
798+
* the pet store.
799+
*
800+
* ```js
801+
* import {createMiddleware, createStore} from 'tinybase';
802+
*
803+
* const store = createStore();
804+
* const middleware = createMiddleware(store);
805+
*
806+
* store.setTables({pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}});
807+
*
808+
* middleware.addWillDelTablesCallback(() => false);
809+
*
810+
* store.delTables();
811+
* console.log(store.getTables());
812+
* // -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
813+
*
814+
* middleware.destroy();
815+
* ```
816+
* @category Configuration
817+
* @since v8.0.0
818+
*/
819+
/// Middleware.addWillDelTablesCallback
772820
/**
773821
* The destroy method should be called when this Middleware object is no
774822
* longer used. It removes all hooks and listeners from the Store, and

src/@types/middleware/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ export type WillDelRowCallback = (tableId: Id, rowId: Id) => boolean;
6161
/// WillDelValueCallback
6262
export type WillDelValueCallback = (valueId: Id) => boolean;
6363

64+
/// WillDelTablesCallback
65+
export type WillDelTablesCallback = () => boolean;
66+
6467
/// WillDelValuesCallback
6568
export type WillDelValuesCallback = () => boolean;
6669

@@ -99,6 +102,9 @@ export interface Middleware {
99102
/// Middleware.addWillDelValueCallback
100103
addWillDelValueCallback(callback: WillDelValueCallback): Middleware;
101104

105+
/// Middleware.addWillDelTablesCallback
106+
addWillDelTablesCallback(callback: WillDelTablesCallback): Middleware;
107+
102108
/// Middleware.addWillDelValuesCallback
103109
addWillDelValuesCallback(callback: WillDelValuesCallback): Middleware;
104110

src/@types/middleware/with-schemas/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ export type WillDelValueCallback<
133133
: never,
134134
> = (...params: Params | [valueId: never]) => boolean;
135135

136+
/// WillDelTablesCallback
137+
export type WillDelTablesCallback = () => boolean;
138+
136139
/// WillDelValuesCallback
137140
export type WillDelValuesCallback = () => boolean;
138141

@@ -191,6 +194,11 @@ export interface Middleware<in out Schemas extends OptionalSchemas> {
191194
callback: WillDelValueCallback<Schemas[1]>,
192195
): Middleware<Schemas>;
193196

197+
/// Middleware.addWillDelTablesCallback
198+
addWillDelTablesCallback(
199+
callback: WillDelTablesCallback,
200+
): Middleware<Schemas>;
201+
194202
/// Middleware.addWillDelValuesCallback
195203
addWillDelValuesCallback(
196204
callback: WillDelValuesCallback,

src/middleware/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
WillDelRowCallback,
66
WillDelTableCallback,
77
WillDelValueCallback,
8+
WillDelTablesCallback,
89
WillDelValuesCallback,
910
WillSetCellCallback,
1011
WillSetRowCallback,
@@ -40,6 +41,7 @@ export const createMiddleware = getCreateFunction(
4041
const willSetValuesCallbacks: WillSetValuesCallback[] = [];
4142
const willDelCellCallbacks: WillDelCellCallback[] = [];
4243
const willDelTableCallbacks: WillDelTableCallback[] = [];
44+
const willDelTablesCallbacks: WillDelTablesCallback[] = [];
4345
const willDelRowCallbacks: WillDelRowCallback[] = [];
4446
const willDelValueCallbacks: WillDelValueCallback[] = [];
4547
const willDelValuesCallbacks: WillDelValuesCallback[] = [];
@@ -122,6 +124,9 @@ export const createMiddleware = getCreateFunction(
122124
const willDelValue = (valueId: Id): boolean =>
123125
arrayEvery(willDelValueCallbacks, (callback) => callback(valueId));
124126

127+
const willDelTables = (): boolean =>
128+
arrayEvery(willDelTablesCallbacks, (callback) => callback());
129+
125130
const willDelValues = (): boolean =>
126131
arrayEvery(willDelValuesCallbacks, (callback) => callback());
127132

@@ -137,6 +142,7 @@ export const createMiddleware = getCreateFunction(
137142
willSetTable,
138143
willDelTable,
139144
willSetTables,
145+
willDelTables,
140146
);
141147

142148
const getStore = (): Store => store;
@@ -183,6 +189,11 @@ export const createMiddleware = getCreateFunction(
183189
): Middleware =>
184190
fluent(() => arrayPush(willDelTableCallbacks, callback));
185191

192+
const addWillDelTablesCallback = (
193+
callback: WillDelTablesCallback,
194+
): Middleware =>
195+
fluent(() => arrayPush(willDelTablesCallbacks, callback));
196+
186197
const addWillDelValueCallback = (
187198
callback: WillDelValueCallback,
188199
): Middleware => fluent(() => arrayPush(willDelValueCallbacks, callback));
@@ -204,6 +215,7 @@ export const createMiddleware = getCreateFunction(
204215
addWillSetValuesCallback,
205216
addWillDelCellCallback,
206217
addWillDelTableCallback,
218+
addWillDelTablesCallback,
207219
addWillDelRowCallback,
208220
addWillDelValueCallback,
209221
addWillDelValuesCallback,

src/store/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export const createStore: typeof createStoreDecl = (): Store => {
179179
willSetTable?: (tableId: Id, table: Table) => Table | undefined,
180180
willDelTable?: (tableId: Id) => boolean,
181181
willSetTables?: (tables: Tables) => Tables | undefined,
182+
willDelTables?: () => boolean,
182183
] = [];
183184
let internalListeners: [
184185
preStartTransaction?: () => void,
@@ -1489,7 +1490,12 @@ export const createStore: typeof createStoreDecl = (): Store => {
14891490
});
14901491

14911492
const delTables = (): Store =>
1492-
fluentTransaction(() => setValidTables({}, true));
1493+
fluentTransaction(
1494+
() =>
1495+
(internalWillSets[11]?.() ?? true)
1496+
? setValidTables({}, true)
1497+
: 0,
1498+
);
14931499

14941500
const delTable = (tableId: Id): Store =>
14951501
fluentTransaction(
@@ -1822,6 +1828,7 @@ export const createStore: typeof createStoreDecl = (): Store => {
18221828
willSetTable: (tableId: Id, table: Table) => Table | undefined,
18231829
willDelTable: (tableId: Id) => boolean,
18241830
willSetTables: (tables: Tables) => Tables | undefined,
1831+
willDelTables: () => boolean,
18251832
) =>
18261833
(internalWillSets = [
18271834
willSetCell,
@@ -1835,6 +1842,7 @@ export const createStore: typeof createStoreDecl = (): Store => {
18351842
willSetTable,
18361843
willDelTable,
18371844
willSetTables,
1845+
willDelTables,
18381846
]);
18391847

18401848
const setInternalListeners = (

test/unit/core/other/middleware.test.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,130 @@ describe('willDelTable', () => {
18511851
});
18521852
});
18531853

1854+
describe('willDelTables', () => {
1855+
describe('allow', () => {
1856+
test('returning true allows delete', () => {
1857+
middleware.addWillDelTablesCallback(() => true);
1858+
store.setTables({t1: {r1: {c1: 'a'}}, t2: {r1: {c1: 'b'}}});
1859+
store.delTables();
1860+
expect(store.getTables()).toEqual({});
1861+
});
1862+
});
1863+
1864+
describe('block', () => {
1865+
test('returning false blocks delete', () => {
1866+
middleware.addWillDelTablesCallback(() => false);
1867+
store.setTables({t1: {r1: {c1: 'a'}}, t2: {r1: {c1: 'b'}}});
1868+
store.delTables();
1869+
expect(store.getTables()).toEqual({
1870+
t1: {r1: {c1: 'a'}},
1871+
t2: {r1: {c1: 'b'}},
1872+
});
1873+
});
1874+
});
1875+
1876+
describe('chaining', () => {
1877+
test('all must return true to allow', () => {
1878+
middleware
1879+
.addWillDelTablesCallback(() => true)
1880+
.addWillDelTablesCallback(() => true);
1881+
store.setTables({t1: {r1: {c1: 'a'}}});
1882+
store.delTables();
1883+
expect(store.getTables()).toEqual({});
1884+
});
1885+
1886+
test('any returning false blocks', () => {
1887+
middleware
1888+
.addWillDelTablesCallback(() => true)
1889+
.addWillDelTablesCallback(() => false);
1890+
store.setTables({t1: {r1: {c1: 'a'}}});
1891+
store.delTables();
1892+
expect(store.getTables()).toEqual({t1: {r1: {c1: 'a'}}});
1893+
});
1894+
1895+
test('first blocks, second never called', () => {
1896+
const secondCalled = vi.fn();
1897+
middleware
1898+
.addWillDelTablesCallback(() => false)
1899+
.addWillDelTablesCallback(() => {
1900+
secondCalled();
1901+
return true;
1902+
});
1903+
store.setTables({t1: {r1: {c1: 'a'}}});
1904+
store.delTables();
1905+
expect(secondCalled).not.toHaveBeenCalled();
1906+
expect(store.getTables()).toEqual({t1: {r1: {c1: 'a'}}});
1907+
});
1908+
});
1909+
1910+
describe('entry points', () => {
1911+
test('called from delTables', () => {
1912+
const calls: number[] = [];
1913+
middleware.addWillDelTablesCallback(() => {
1914+
calls.push(1);
1915+
return true;
1916+
});
1917+
store.setTables({t1: {r1: {c1: 'a'}}});
1918+
store.delTables();
1919+
expect(calls).toEqual([1]);
1920+
});
1921+
1922+
test('not called from delTable', () => {
1923+
const calls: number[] = [];
1924+
middleware.addWillDelTablesCallback(() => {
1925+
calls.push(1);
1926+
return true;
1927+
});
1928+
store.setTables({t1: {r1: {c1: 'a'}}, t2: {r1: {c1: 'b'}}});
1929+
store.delTable('t1');
1930+
expect(calls).toEqual([]);
1931+
expect(store.getTables()).toEqual({t2: {r1: {c1: 'b'}}});
1932+
});
1933+
1934+
test('not called from setTables', () => {
1935+
const calls: number[] = [];
1936+
middleware.addWillDelTablesCallback(() => {
1937+
calls.push(1);
1938+
return true;
1939+
});
1940+
store.setTables({t1: {r1: {c1: 'a'}}});
1941+
calls.length = 0;
1942+
store.setTables({t2: {r1: {c1: 'b'}}});
1943+
expect(calls).toEqual([]);
1944+
});
1945+
});
1946+
1947+
describe('interaction with willDelTable', () => {
1948+
test('willDelTables blocks before willDelTable is called', () => {
1949+
const tableCalls = vi.fn();
1950+
middleware.addWillDelTablesCallback(() => false);
1951+
middleware.addWillDelTableCallback((...args) => {
1952+
tableCalls(...args);
1953+
return true;
1954+
});
1955+
store.setTables({t1: {r1: {c1: 'a'}}, t2: {r1: {c1: 'b'}}});
1956+
store.delTables();
1957+
expect(tableCalls).not.toHaveBeenCalled();
1958+
expect(store.getTables()).toEqual({
1959+
t1: {r1: {c1: 'a'}},
1960+
t2: {r1: {c1: 'b'}},
1961+
});
1962+
});
1963+
1964+
test('willDelTables allows, willDelTable still checked', () => {
1965+
const tableCalls: string[] = [];
1966+
middleware.addWillDelTablesCallback(() => true);
1967+
middleware.addWillDelTableCallback((tableId) => {
1968+
tableCalls.push(tableId);
1969+
return true;
1970+
});
1971+
store.setTables({t1: {r1: {c1: 'a'}}, t2: {r1: {c1: 'b'}}});
1972+
store.delTables();
1973+
expect(tableCalls).toEqual(['t1', 't2']);
1974+
});
1975+
});
1976+
});
1977+
18541978
describe('willDelValue', () => {
18551979
describe('allow', () => {
18561980
test('returning true allows delete', () => {

test/unit/core/types/__snapshots__/types.test.tsx.snap

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,12 +1066,14 @@ exports[`Types in middleware.ts > 237:4 1`] = `"This comparison appears to be un
10661066
10671067
exports[`Types in middleware.ts > 249:17 1`] = `"Property 't2' does not exist on type 'Tables<{ readonly t0: { readonly c0: { readonly type: "number"; }; }; readonly t1: { readonly c1: { readonly type: "number"; }; readonly c1d: { readonly type: "string"; readonly default: ""; }; }; }, false>'."`;
10681068
1069-
exports[`Types in middleware.ts > 258:4 1`] = `"This comparison appears to be unintentional because the types '"v1" | "v1d"' and '"v2"' have no overlap."`;
1069+
exports[`Types in middleware.ts > 261:17 1`] = `"Property 't2' does not exist on type 'Tables<{ readonly t0: { readonly c0: { readonly type: "number"; }; }; readonly t1: { readonly c1: { readonly type: "number"; }; readonly c1d: { readonly type: "string"; readonly default: ""; }; }; }, false>'."`;
10701070
1071-
exports[`Types in middleware.ts > 270:17 1`] = `"Property 't2' does not exist on type 'Tables<{ readonly t0: { readonly c0: { readonly type: "number"; }; }; readonly t1: { readonly c1: { readonly type: "number"; }; readonly c1d: { readonly type: "string"; readonly default: ""; }; }; }, false>'."`;
1071+
exports[`Types in middleware.ts > 270:4 1`] = `"This comparison appears to be unintentional because the types '"v1" | "v1d"' and '"v2"' have no overlap."`;
10721072
10731073
exports[`Types in middleware.ts > 282:17 1`] = `"Property 't2' does not exist on type 'Tables<{ readonly t0: { readonly c0: { readonly type: "number"; }; }; readonly t1: { readonly c1: { readonly type: "number"; }; readonly c1d: { readonly type: "string"; readonly default: ""; }; }; }, false>'."`;
10741074
1075+
exports[`Types in middleware.ts > 294:17 1`] = `"Property 't2' does not exist on type 'Tables<{ readonly t0: { readonly c0: { readonly type: "number"; }; }; readonly t1: { readonly c1: { readonly type: "number"; }; readonly c1d: { readonly type: "string"; readonly default: ""; }; }; }, false>'."`;
1076+
10751077
exports[`Types in persisters.ts > 24:37 1`] = `"Argument of type '[{ t1: { r1: { c2: number; }; }; }, {}]' is not assignable to parameter of type 'Content<[{ readonly t0: { readonly c0: { readonly type: "number"; }; }; readonly t1: { readonly c1: { readonly type: "number"; }; readonly c1d: { readonly type: "string"; readonly default: ""; }; }; }, { ...; }], true> | (() => Content<...>) | undefined'."`;
10761078
10771079
exports[`Types in persisters.ts > 25:25 1`] = `"Argument of type '[{ t1: { r1: { c1: string; }; }; }, {}]' is not assignable to parameter of type 'Content<[{ readonly t0: { readonly c0: { readonly type: "number"; }; }; readonly t1: { readonly c1: { readonly type: "number"; }; readonly c1d: { readonly type: "string"; readonly default: ""; }; }; }, { ...; }], true> | (() => Content<...>) | undefined'."`;

test/unit/core/types/middleware.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,7 @@ middlewareWithSchema.getStore().getTables().t2; // !
9292
return _table;
9393
});
9494

95-
middlewareWithSchema.addWillSetTableCallback(
96-
(_tableId, table) => table,
97-
);
95+
middlewareWithSchema.addWillSetTableCallback((_tableId, table) => table);
9896
middlewareWithSchema.addWillSetTableCallback(() => undefined);
9997

10098
middlewareWithSchema
@@ -250,6 +248,18 @@ middlewareWithSchema.getStore().getTables().t2; // !
250248
.getTables().t2; // !
251249
};
252250

251+
// addWillDelTablesCallback
252+
() => {
253+
middlewareWithSchema
254+
.addWillDelTablesCallback(() => true)
255+
.getStore()
256+
.getTables().t1;
257+
middlewareWithSchema
258+
.addWillDelTablesCallback(() => true)
259+
.getStore()
260+
.getTables().t2; // !
261+
};
262+
253263
// addWillDelValueCallback
254264
() => {
255265
middlewareWithSchema.addWillDelValueCallback((valueId) => {

0 commit comments

Comments
 (0)