antd-crud-table 0.4.0 → 0.7.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.
Files changed (41) hide show
  1. package/README.md +293 -783
  2. package/dist/CrudTable-Btf58zof.js +610 -0
  3. package/dist/CrudTable-DRN70sET.cjs +1 -0
  4. package/dist/CrudTable.cjs +1 -1
  5. package/dist/CrudTable.d.ts +85 -18
  6. package/dist/CrudTable.js +3 -306
  7. package/dist/CrudTableLazy.cjs +1 -1
  8. package/dist/CrudTableLazy.d.ts +4 -3
  9. package/dist/CrudTableLazy.js +8 -7
  10. package/dist/core/CustomDataSource.d.ts +35 -0
  11. package/dist/core/InMemoryDataSource.d.ts +30 -0
  12. package/dist/core/LocalStorageDataSource.d.ts +30 -0
  13. package/dist/core/RestDataSource.d.ts +120 -0
  14. package/dist/core/StaticDataSource.d.ts +20 -0
  15. package/dist/core/identity.d.ts +28 -0
  16. package/dist/core/inMemoryQuery.d.ts +26 -0
  17. package/dist/core/index.d.ts +11 -0
  18. package/dist/core/types.d.ts +86 -0
  19. package/dist/en_US-D3Kl6g3n.cjs +1 -0
  20. package/dist/en_US-DGCMDOdX.js +41 -0
  21. package/dist/exportData.cjs +13 -13
  22. package/dist/exportData.js +48 -39
  23. package/dist/fields/registry.d.ts +38 -0
  24. package/dist/fields/types.d.ts +29 -0
  25. package/dist/hooks/useCrudTable.d.ts +114 -57
  26. package/dist/hooks/useLocalStorageCrud.d.ts +14 -9
  27. package/dist/index.cjs +1 -0
  28. package/dist/index.d.ts +14 -6
  29. package/dist/index.js +7 -0
  30. package/dist/locale/en_US.d.ts +10 -0
  31. package/dist/locale/index.d.ts +4 -0
  32. package/dist/locale/types.d.ts +89 -0
  33. package/dist/locale/useResolvedLocale.d.ts +35 -0
  34. package/dist/useCrudTable-40jpmCkr.cjs +1 -0
  35. package/dist/useCrudTable-DxjnXVlQ.js +460 -0
  36. package/dist/useCrudTable.cjs +1 -1
  37. package/dist/useCrudTable.js +2 -235
  38. package/dist/useLocalStorageCrud.cjs +1 -1
  39. package/dist/useLocalStorageCrud.js +12 -198
  40. package/dist/utils/exportData.d.ts +37 -12
  41. package/package.json +52 -16
@@ -1,34 +1,101 @@
1
1
  import { ProColumns } from '@ant-design/pro-components';
2
- import { UseCrudTableConfig, CrudTableActions } from './hooks/useCrudTable';
3
- type DataType = Record<string, any>;
4
- type FieldType = 'string' | 'number' | 'date' | 'boolean' | 'enum' | 'custom';
5
- interface CrudColumn<T extends DataType> extends ProColumns<T> {
2
+ import { FormRule } from 'antd';
3
+ import { CrudTableActions, UseCrudTableOptions } from './hooks/useCrudTable';
4
+ import { FieldType } from './fields/registry';
5
+ import { EnumOption } from './fields/types';
6
+ import { PartialCrudTableLocale } from './locale/types';
7
+ /**
8
+ * A column bound to one property of `T`.
9
+ *
10
+ * `K` ties `dataIndex` to a real key, so `customRender` and `transform`
11
+ * receive that property's type rather than a widened one.
12
+ */
13
+ export interface CrudColumnFor<T, K extends keyof T> extends Omit<ProColumns<T>, 'dataIndex' | 'title' | 'render'> {
14
+ /** The record property this column reads. */
15
+ dataIndex: K;
16
+ /** Also used as the form label, so a plain string rather than a ReactNode. */
17
+ title: string;
18
+ /** Which registry entry renders and edits this column. Defaults to `string`. */
6
19
  fieldType?: FieldType;
7
- enumOptions?: Record<string, {
8
- text: string;
9
- [key: string]: any;
10
- }>;
11
- customRender?: (value: any, record: T) => React.ReactNode;
20
+ /** Selectable values, for `enum` columns. */
21
+ enumOptions?: Record<string, EnumOption>;
22
+ /** Cell renderer for `custom` columns, receiving this property's type. */
23
+ customRender?: (value: T[K], record: T) => React.ReactNode;
24
+ /** How this column behaves in the create/edit form. */
12
25
  formConfig?: {
26
+ /** Adds a required rule, unless `rules` is given instead. */
13
27
  required?: boolean;
28
+ /** Replaces the registry's control entirely. */
14
29
  component?: React.ReactNode;
15
- transform?: (value: any) => any;
16
- rules?: any[];
30
+ /** Applied to the submitted value before it reaches the data source. */
31
+ transform?: (value: T[K]) => T[K];
32
+ /** antd validation rules. Replaces `required` when both are present. */
33
+ rules?: FormRule[];
17
34
  };
35
+ /** Editable in the create/edit form. Defaults to true. */
18
36
  fieldEditable?: boolean;
37
+ /** Exposed in the search form. Defaults to true. */
19
38
  searchable?: boolean;
20
39
  }
21
- interface CrudTableConfig<T extends DataType> {
22
- columns: CrudColumn<T>[];
23
- rowKey: keyof T;
40
+ /**
41
+ * A column for any one key of `T` - the type an array of columns holds.
42
+ *
43
+ * Distributing over `keyof T` is what keeps each column's callbacks bound to
44
+ * its own property type. A single `CrudColumnFor<T, keyof T>` would widen
45
+ * `customRender` and `transform` to accept a union of every property type,
46
+ * losing exactly the precision this exists for.
47
+ */
48
+ export type CrudColumn<T> = {
49
+ [K in keyof T]-?: CrudColumnFor<T, K>;
50
+ }[keyof T];
51
+ /** Everything needed to render a {@link CrudTable}. */
52
+ export interface CrudTableConfig<T extends object, K extends keyof T> {
53
+ /** Column definitions, each bound to a property of `T`. */
54
+ columns: readonly CrudColumn<T>[];
55
+ /** The property holding each record's identity. */
56
+ rowKey: K;
57
+ /** Table header, also used to name exported files. */
24
58
  title: string;
59
+ /** Rows per page. Defaults to 10. */
25
60
  defaultPageSize?: number;
26
- hookConfig: UseCrudTableConfig<T>;
61
+ /** Selects and configures the data strategy. */
62
+ hookConfig: UseCrudTableOptions<T, K>;
63
+ /** Show row selection and the bulk delete control. Defaults to false. */
27
64
  enableBulkOperations?: boolean;
65
+ /** Show ProTable's column visibility and density controls. Defaults to true. */
28
66
  enableColumnSettings?: boolean;
67
+ /** Show the export entries in the toolbar menu. Defaults to true. */
29
68
  enableExport?: boolean;
30
- customActions?: (record: T, actions: CrudTableActions<T>) => React.ReactNode[];
69
+ /**
70
+ * Whether export covers the whole filtered result set or just the rows on
71
+ * screen. Defaults to `'all'`, falling back to `'page'` when the data source
72
+ * cannot list without pagination.
73
+ */
74
+ exportScope?: 'all' | 'page';
75
+ /** Extra per-row controls, appended after Edit and Delete. */
76
+ customActions?: (record: T, actions: CrudTableActions<T, K>) => React.ReactNode[];
77
+ /**
78
+ * Overrides for the library's own wording.
79
+ *
80
+ * antd and ProTable chrome follow the surrounding `ConfigProvider`; this is
81
+ * for the strings the library itself produces. Anything omitted falls back
82
+ * to English.
83
+ */
84
+ locale?: PartialCrudTableLocale;
31
85
  }
32
- declare const CrudTable: <T extends DataType>(config: CrudTableConfig<T>) => import("react/jsx-runtime").JSX.Element;
86
+ /**
87
+ * A paginated, searchable table with a create/edit form and CRUD wired through
88
+ * a {@link CrudDataSource}.
89
+ *
90
+ * @example
91
+ * ```tsx
92
+ * <CrudTable<User, 'id'>
93
+ * title="Users"
94
+ * rowKey="id"
95
+ * columns={[{ dataIndex: 'name', title: 'Name', fieldType: 'string' }]}
96
+ * hookConfig={{ staticData: users }}
97
+ * />
98
+ * ```
99
+ */
100
+ declare const CrudTable: <T extends object, K extends keyof T>(config: CrudTableConfig<T, K>) => import("react").JSX.Element;
33
101
  export default CrudTable;
34
- export type { CrudTableConfig, CrudColumn, DataType };
package/dist/CrudTable.js CHANGED
@@ -1,306 +1,3 @@
1
- import { useCrudTable as e } from "./useCrudTable.js";
2
- import { EllipsisOutlined as t, PlusOutlined as n } from "@ant-design/icons";
3
- import { ProConfigProvider as r, ProTable as i, enUSIntl as a } from "@ant-design/pro-components";
4
- import { Button as o, DatePicker as s, Dropdown as c, Form as l, Input as u, InputNumber as d, Modal as f, Select as p, Switch as m, Tag as h, message as g } from "antd";
5
- import { useEffect as _, useState as v } from "react";
6
- import { format as y, formatISO as b, parseISO as x } from "date-fns";
7
- import S from "dayjs";
8
- import { jsx as C, jsxs as w } from "react/jsx-runtime";
9
- //#region lib/CrudTable.tsx
10
- var T = (T) => {
11
- let { columns: E, rowKey: D, title: O, defaultPageSize: k = 10, hookConfig: A, enableBulkOperations: j = !1, customActions: M } = T, N = e(D, {
12
- defaultPageSize: k,
13
- ...A
14
- }), [P, F] = v(!1), [I, L] = v(null), [R, z] = v([]), [B] = l.useForm();
15
- _(() => {
16
- N.refresh();
17
- }, []);
18
- let V = E.map((e) => {
19
- let t = {
20
- ...e,
21
- dataIndex: e.dataIndex,
22
- title: e.title,
23
- search: e.searchable !== !1
24
- };
25
- switch (e.fieldType) {
26
- case "date": return {
27
- ...t,
28
- valueType: "dateTime",
29
- render: (t, n) => {
30
- let r = n[e.dataIndex];
31
- if (!r) return "-";
32
- try {
33
- return /* @__PURE__ */ C("span", { children: y(x(r), "yyyy-MM-dd HH:mm") });
34
- } catch {
35
- return /* @__PURE__ */ C("span", { children: r });
36
- }
37
- }
38
- };
39
- case "enum": return {
40
- ...t,
41
- valueType: "select",
42
- valueEnum: e.enumOptions,
43
- render: (t, n) => {
44
- let r = n[e.dataIndex], i = e.enumOptions?.[r];
45
- return i ? /* @__PURE__ */ C(h, {
46
- color: i.color,
47
- children: i.text
48
- }) : r;
49
- }
50
- };
51
- case "number": return {
52
- ...t,
53
- valueType: "digit",
54
- render: (t, n) => {
55
- let r = n[e.dataIndex];
56
- return typeof r == "number" ? r.toLocaleString() : r;
57
- }
58
- };
59
- case "boolean": return {
60
- ...t,
61
- valueType: "switch",
62
- render: (t, n) => /* @__PURE__ */ C(h, {
63
- color: n[e.dataIndex] ? "green" : "red",
64
- children: n[e.dataIndex] ? "Yes" : "No"
65
- })
66
- };
67
- case "custom": return {
68
- ...t,
69
- render: (t, n) => e.customRender?.(n[e.dataIndex], n)
70
- };
71
- default: return t;
72
- }
73
- });
74
- V.push({
75
- title: "Actions",
76
- valueType: "option",
77
- width: 200,
78
- render: (e, t) => {
79
- let n = [/* @__PURE__ */ C(o, {
80
- type: "link",
81
- size: "small",
82
- onClick: () => U(t),
83
- children: "Edit"
84
- }, "edit"), /* @__PURE__ */ C(o, {
85
- type: "link",
86
- size: "small",
87
- danger: !0,
88
- onClick: () => G(t[D]),
89
- children: "Delete"
90
- }, "delete")], r = M?.(t, N) || [];
91
- return [...n, ...r];
92
- }
93
- });
94
- let H = async (e, t, n) => {
95
- try {
96
- let r = {
97
- current: e.current,
98
- pageSize: e.pageSize,
99
- sortBy: Object.keys(t)[0],
100
- sortOrder: Object.values(t)[0],
101
- ...n,
102
- ...e
103
- }, i = N.operations;
104
- if (i?.getList) {
105
- let e = await i.getList(r);
106
- return {
107
- data: e.data,
108
- success: !0,
109
- total: e.total
110
- };
111
- }
112
- return {
113
- data: N.state.data,
114
- success: !0,
115
- total: N.state.total
116
- };
117
- } catch {
118
- return g.error("Failed to fetch data"), {
119
- data: [],
120
- success: !1,
121
- total: 0
122
- };
123
- }
124
- }, U = (e) => {
125
- if (L(e || null), e) {
126
- let t = { ...e };
127
- E.forEach((e) => {
128
- let n = e.dataIndex;
129
- if (e.fieldType === "date" && t[n]) try {
130
- t[n] = S(t[n]);
131
- } catch {}
132
- }), B.setFieldsValue(t);
133
- } else B.resetFields();
134
- F(!0);
135
- }, W = async () => {
136
- try {
137
- let e = await B.validateFields(), t = { ...e };
138
- E.forEach((n) => {
139
- let r = n.dataIndex;
140
- if (n.fieldType === "date" && e[r]) try {
141
- t[r] = b(e[r]);
142
- } catch {}
143
- n.formConfig?.transform && (t[r] = n.formConfig.transform(e[r]));
144
- }), I && I[D] ? await N.update(I[D], t) : await N.create(t), F(!1), N.actionRef.current?.reload();
145
- } catch (e) {
146
- console.error("Form validation failed:", e);
147
- }
148
- }, G = async (e) => {
149
- f.confirm({
150
- title: "Are you sure?",
151
- content: "This action cannot be undone.",
152
- okText: "Yes, Delete",
153
- okType: "danger",
154
- cancelText: "Cancel",
155
- onOk: async () => {
156
- await N.delete(e) && N.actionRef.current?.reload();
157
- }
158
- });
159
- }, K = async () => {
160
- if (R.length === 0) {
161
- g.warning("Please select items to delete");
162
- return;
163
- }
164
- f.confirm({
165
- title: `Delete ${R.length} items?`,
166
- content: "This action cannot be undone.",
167
- okText: "Yes, Delete All",
168
- okType: "danger",
169
- cancelText: "Cancel",
170
- onOk: async () => {
171
- let e = R.map((e) => N.delete(e));
172
- await Promise.allSettled(e), z([]), N.actionRef.current?.reload();
173
- }
174
- });
175
- }, q = j ? {
176
- selectedRowKeys: R,
177
- onChange: z
178
- } : void 0;
179
- return /* @__PURE__ */ w(r, {
180
- needDeps: !0,
181
- intl: a,
182
- children: [/* @__PURE__ */ C(i, {
183
- headerTitle: O,
184
- rowKey: D,
185
- rowClassName: (e, t) => t % 2 == 0 ? "row-differentiator" : "",
186
- actionRef: N.actionRef,
187
- columns: V,
188
- request: H,
189
- search: { labelWidth: "auto" },
190
- pagination: {
191
- pageSize: k,
192
- showSizeChanger: !0,
193
- showQuickJumper: !0
194
- },
195
- loading: N.state.loading,
196
- rowSelection: q,
197
- toolBarRender: () => [
198
- /* @__PURE__ */ C(o, {
199
- type: "primary",
200
- icon: /* @__PURE__ */ C(n, {}),
201
- onClick: () => U(),
202
- children: "New"
203
- }, "add"),
204
- ...j && R.length > 0 ? [/* @__PURE__ */ w(o, {
205
- danger: !0,
206
- onClick: K,
207
- children: [
208
- "Delete Selected (",
209
- R.length,
210
- ")"
211
- ]
212
- }, "bulk-delete")] : [],
213
- /* @__PURE__ */ C(c, {
214
- menu: { items: [{
215
- key: "export",
216
- label: "Export",
217
- disabled: !0
218
- }, {
219
- key: "refresh",
220
- label: "Refresh",
221
- onClick: () => N.refresh()
222
- }] },
223
- children: /* @__PURE__ */ C(o, { children: /* @__PURE__ */ C(t, {}) })
224
- }, "menu")
225
- ],
226
- options: {
227
- setting: { listsHeight: 400 },
228
- reload: () => N.refresh()
229
- },
230
- dateFormatter: "string"
231
- }), /* @__PURE__ */ C(f, {
232
- title: I ? "Edit Item" : "Create Item",
233
- open: P,
234
- onOk: W,
235
- onCancel: () => F(!1),
236
- destroyOnClose: !0,
237
- width: 600,
238
- children: /* @__PURE__ */ C(l, {
239
- form: B,
240
- layout: "vertical",
241
- children: E.map((e) => {
242
- if (!e.dataIndex) return null;
243
- let t = e.dataIndex, n = e.title, r = !(e.fieldEditable ?? !0), i = e.formConfig?.rules || (e.formConfig?.required ? [{
244
- required: !0,
245
- message: `${n} is required`
246
- }] : []);
247
- if (e.formConfig?.component) return /* @__PURE__ */ C(l.Item, {
248
- name: t,
249
- label: n,
250
- rules: i,
251
- children: e.formConfig.component
252
- }, t);
253
- switch (e.fieldType) {
254
- case "string": return /* @__PURE__ */ C(l.Item, {
255
- name: t,
256
- label: n,
257
- rules: i,
258
- children: /* @__PURE__ */ C(u, { disabled: r })
259
- }, t);
260
- case "number": return /* @__PURE__ */ C(l.Item, {
261
- name: t,
262
- label: n,
263
- rules: i,
264
- children: /* @__PURE__ */ C(d, {
265
- style: { width: "100%" },
266
- disabled: r
267
- })
268
- }, t);
269
- case "date": return /* @__PURE__ */ C(l.Item, {
270
- name: t,
271
- label: n,
272
- rules: i,
273
- children: /* @__PURE__ */ C(s, {
274
- style: { width: "100%" },
275
- showTime: !0,
276
- disabled: r
277
- })
278
- }, t);
279
- case "boolean": return /* @__PURE__ */ C(l.Item, {
280
- name: t,
281
- label: n,
282
- valuePropName: "checked",
283
- children: /* @__PURE__ */ C(m, { disabled: r })
284
- }, t);
285
- case "enum": return /* @__PURE__ */ C(l.Item, {
286
- name: t,
287
- label: n,
288
- rules: i,
289
- children: /* @__PURE__ */ C(p, {
290
- disabled: r,
291
- placeholder: `Select ${n.toLowerCase()}`,
292
- options: Object.entries(e.enumOptions || {}).map(([e, t]) => ({
293
- label: t.text,
294
- value: e
295
- }))
296
- })
297
- }, t);
298
- default: return null;
299
- }
300
- })
301
- })
302
- })]
303
- });
304
- };
305
- //#endregion
306
- export { T as default };
1
+ import { t as e } from "./CrudTable-Btf58zof.js";
2
+ import "./exportData.js";
3
+ export { e as default };
@@ -1 +1 @@
1
- require(`./CrudTable.cjs`);let e=require(`react`),t=require(`react/jsx-runtime`);var n=(0,e.lazy)(()=>Promise.resolve().then(()=>require(`./CrudTable.cjs`))),r=r=>(0,t.jsx)(e.Suspense,{fallback:(0,t.jsx)(`div`,{style:{textAlign:`center`,padding:`2rem`},children:`Loading curd table w antd`}),children:(0,t.jsx)(n,{...r})});module.exports=r;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./en_US-D3Kl6g3n.cjs");let t=require("react"),n=require("react/jsx-runtime");var r=(0,t.lazy)(()=>Promise.resolve().then(()=>require("./CrudTable.cjs"))),i=i=>(0,n.jsx)(t.Suspense,{fallback:(0,n.jsx)(`div`,{style:{textAlign:`center`,padding:`2rem`},children:i.locale?.loading??e.t.loading}),children:(0,n.jsx)(r,{...i})});exports.default=i;
@@ -1,4 +1,5 @@
1
- import { CrudTableConfig, DataType } from './CrudTable';
2
- declare const CrudTableLazy: <T extends DataType>(props: CrudTableConfig<T>) => import("react/jsx-runtime").JSX.Element;
1
+ import { CrudTableConfig } from './CrudTable';
2
+ /** Code-split `CrudTable`, keeping the same typed configuration. */
3
+ declare const CrudTableLazy: <T extends object, K extends keyof T>(props: CrudTableConfig<T, K>) => import("react").JSX.Element;
3
4
  export default CrudTableLazy;
4
- export type { CrudTableConfig, DataType };
5
+ export type { CrudTableConfig };
@@ -1,15 +1,16 @@
1
- import { Suspense as e, lazy as t } from "react";
2
- import { jsx as n } from "react/jsx-runtime";
1
+ import { t as e } from "./en_US-DGCMDOdX.js";
2
+ import { Suspense as t, lazy as n } from "react";
3
+ import { jsx as r } from "react/jsx-runtime";
3
4
  //#region lib/CrudTableLazy.tsx
4
- var r = t(() => import("./CrudTable.js")), i = (t) => /* @__PURE__ */ n(e, {
5
- fallback: /* @__PURE__ */ n("div", {
5
+ var i = n(() => import("./CrudTable.js")), a = (n) => /* @__PURE__ */ r(t, {
6
+ fallback: /* @__PURE__ */ r("div", {
6
7
  style: {
7
8
  textAlign: "center",
8
9
  padding: "2rem"
9
10
  },
10
- children: "Loading curd table w antd"
11
+ children: n.locale?.loading ?? e.loading
11
12
  }),
12
- children: /* @__PURE__ */ n(r, { ...t })
13
+ children: /* @__PURE__ */ r(i, { ...n })
13
14
  });
14
15
  //#endregion
15
- export { i as default };
16
+ export { a as default };
@@ -0,0 +1,35 @@
1
+ import { CrudDataSource, CrudDraft, CrudPage, CrudQuery } from './types';
2
+ /**
3
+ * The operations a consumer may supply. Every entry is optional so a
4
+ * read-only table need not invent write handlers it will never call.
5
+ */
6
+ export type CrudOperations<T, K extends keyof T> = Partial<CrudDataSource<T, K>>;
7
+ /**
8
+ * Raised when the table attempts an operation the consumer did not supply.
9
+ *
10
+ * Names the missing operation so the message points at the fix rather than
11
+ * surfacing as `undefined is not a function`.
12
+ */
13
+ export declare class UnsupportedOperationError extends Error {
14
+ /** The operation that was attempted but not supplied. */
15
+ readonly operation: string;
16
+ constructor(operation: string);
17
+ }
18
+ /**
19
+ * Adapts a bag of consumer-supplied functions to the full interface.
20
+ *
21
+ * Exists so the rest of the library can depend on `CrudDataSource` without
22
+ * null-checking each method at every call site; absent operations fail here
23
+ * with a message naming what is missing.
24
+ */
25
+ export declare class CustomDataSource<T extends object, K extends keyof T> implements CrudDataSource<T, K> {
26
+ private readonly operations;
27
+ constructor(operations: CrudOperations<T, K>);
28
+ /** Whether an operation was supplied, so callers can hide unavailable UI. */
29
+ supports(operation: keyof CrudDataSource<T, K>): boolean;
30
+ list(query: CrudQuery<T>): Promise<CrudPage<T>>;
31
+ listAll(query: Omit<CrudQuery<T>, 'page' | 'pageSize'>): Promise<readonly T[]>;
32
+ create(draft: CrudDraft<T>): Promise<T>;
33
+ update(id: T[K], draft: CrudDraft<T>): Promise<T>;
34
+ remove(id: T[K]): Promise<void>;
35
+ }
@@ -0,0 +1,30 @@
1
+ import { CrudDataSource, CrudDraft, CrudPage, CrudQuery, IdGenerator } from './types';
2
+ /**
3
+ * Shared behaviour for every source backed by a local collection.
4
+ *
5
+ * Subclasses supply only where the records live, via `read`/`write`. All
6
+ * querying, identity assignment and mutation semantics are defined once here
7
+ * so the static and localStorage strategies cannot drift apart.
8
+ */
9
+ export declare abstract class InMemoryDataSource<T extends object, K extends keyof T> implements CrudDataSource<T, K> {
10
+ /** The property holding each record's identity. */
11
+ protected readonly key: K;
12
+ /** Assigns the identity for newly created records. */
13
+ protected readonly generateId: IdGenerator<T, K>;
14
+ protected constructor(key: K, generateId?: IdGenerator<T, K>);
15
+ /** Current contents of the backing collection. */
16
+ protected abstract read(): readonly T[];
17
+ /** Replace the backing collection. */
18
+ protected abstract write(records: readonly T[]): void;
19
+ /**
20
+ * Hook for values a subclass stamps onto every write, such as timestamps.
21
+ * Returns nothing extra by default.
22
+ */
23
+ protected decorate(_record: CrudDraft<T>, _operation: 'create' | 'update'): CrudDraft<T>;
24
+ private indexOf;
25
+ list(query: CrudQuery<T>): Promise<CrudPage<T>>;
26
+ listAll(query: Omit<CrudQuery<T>, 'page' | 'pageSize'>): Promise<readonly T[]>;
27
+ create(draft: CrudDraft<T>): Promise<T>;
28
+ update(id: T[K], draft: CrudDraft<T>): Promise<T>;
29
+ remove(id: T[K]): Promise<void>;
30
+ }
@@ -0,0 +1,30 @@
1
+ import { InMemoryDataSource } from './InMemoryDataSource';
2
+ import { CrudDraft, IdGenerator } from './types';
3
+ /** Fields stamped onto records by the localStorage strategy. */
4
+ export interface Timestamped {
5
+ /** ISO timestamp set once, when the record is created. */
6
+ createdAt?: string;
7
+ /** ISO timestamp refreshed on every write. */
8
+ updatedAt?: string;
9
+ }
10
+ /**
11
+ * A source persisting to `localStorage` under a fixed key.
12
+ *
13
+ * Reads go through storage on every call rather than through a cache, so a
14
+ * second tab writing the same key is picked up on the next query instead of
15
+ * being shadowed by stale in-memory state.
16
+ *
17
+ * Storage is best-effort: a read that fails or returns malformed JSON falls
18
+ * back to the seed rather than throwing, since an unreadable cache should not
19
+ * take the table down. Writes that fail (quota, private mode) *do* throw -
20
+ * silently dropping a write the user believes succeeded is worse than an error.
21
+ */
22
+ export declare class LocalStorageDataSource<T extends object, K extends keyof T> extends InMemoryDataSource<T, K> {
23
+ private readonly storageKey;
24
+ private readonly seed;
25
+ constructor(storageKey: string, key: K, seed?: readonly T[], generateId?: IdGenerator<T, K>);
26
+ protected read(): readonly T[];
27
+ protected write(records: readonly T[]): void;
28
+ /** Stamps creation and modification times onto every write. */
29
+ protected decorate(_draft: CrudDraft<T>, operation: 'create' | 'update'): CrudDraft<T>;
30
+ }