antd-crud-table 0.5.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 (50) hide show
  1. package/README.md +295 -744
  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 +84 -17
  6. package/dist/CrudTable.js +1 -1
  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 +3 -3
  22. package/dist/exportData.js +37 -24
  23. package/dist/fields/registry.d.ts +17 -17
  24. package/dist/fields/types.d.ts +29 -0
  25. package/dist/hooks/useCrudTable.d.ts +114 -58
  26. package/dist/hooks/useLocalStorageCrud.d.ts +12 -12
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.d.ts +13 -8
  29. package/dist/index.js +7 -6
  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 -2
  38. package/dist/useLocalStorageCrud.cjs +1 -1
  39. package/dist/useLocalStorageCrud.js +12 -51
  40. package/dist/utils/exportData.d.ts +35 -14
  41. package/package.json +30 -9
  42. package/dist/CrudTable-DUfRGJW2.js +0 -524
  43. package/dist/CrudTable-TTRqkEoZ.cjs +0 -1
  44. package/dist/hooks/useCrudTable.test.d.ts +0 -1
  45. package/dist/hooks/useLocalStorageCrud.test.d.ts +0 -1
  46. package/dist/useCrudTable-CEY_riQg.cjs +0 -1
  47. package/dist/useCrudTable-DpF-oZHz.js +0 -246
  48. package/dist/utils/exportData.test.d.ts +0 -1
  49. package/dist/utils/query.d.ts +0 -13
  50. package/dist/utils/query.test.d.ts +0 -1
@@ -1,34 +1,101 @@
1
1
  import { ProColumns } from '@ant-design/pro-components';
2
- import { UseCrudTableConfig, CrudTableActions } from './hooks/useCrudTable';
2
+ import { FormRule } from 'antd';
3
+ import { CrudTableActions, UseCrudTableOptions } from './hooks/useCrudTable';
3
4
  import { FieldType } from './fields/registry';
4
- type DataType = Record<string, any>;
5
- interface CrudColumn<T extends DataType> extends ProColumns<T> {
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.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,3 +1,3 @@
1
- import { t as e } from "./CrudTable-DUfRGJW2.js";
1
+ import { t as e } from "./CrudTable-Btf58zof.js";
2
2
  import "./exportData.js";
3
3
  export { e as default };
@@ -1 +1 @@
1
- require(`./CrudTable-TTRqkEoZ.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 CRUD table…`}),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.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 CRUD table…"
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
+ }
@@ -0,0 +1,120 @@
1
+ import { CrudDataSource, CrudDraft, CrudPage, CrudQuery, CrudSort } from './types';
2
+ /** Paths appended to `baseUrl` for each operation. */
3
+ export interface RestEndpoints {
4
+ /** Path listing records. */
5
+ list: string;
6
+ /** Path accepting a new record. */
7
+ create: string;
8
+ /** Path for updating one record; the id is appended by `buildRecordPath`. */
9
+ update: string;
10
+ /** Path for deleting one record; the id is appended by `buildRecordPath`. */
11
+ remove: string;
12
+ }
13
+ /** Query-string parameter names, for APIs that do not speak ProTable's vocabulary. */
14
+ export interface RestParamNames {
15
+ /** Parameter carrying the 1-based page number. Defaults to `current`. */
16
+ page: string;
17
+ /** Parameter carrying the page size. Defaults to `pageSize`. */
18
+ pageSize: string;
19
+ /** Parameter carrying the field to sort by. Defaults to `sortBy`. */
20
+ sortBy: string;
21
+ /** Parameter carrying the sort direction. Defaults to `sortOrder`. */
22
+ sortOrder: string;
23
+ }
24
+ /** HTTP verbs used per mutating operation. */
25
+ export interface RestMethods {
26
+ /** Verb for creating a record. Defaults to `POST`. */
27
+ create: 'POST' | 'PUT';
28
+ /** Verb for updating a record. Defaults to `PUT`. */
29
+ update: 'PUT' | 'PATCH' | 'POST';
30
+ /** Verb for deleting a record. Defaults to `DELETE`. */
31
+ remove: 'DELETE' | 'POST';
32
+ }
33
+ /** Everything needed to point a {@link RestDataSource} at an API. */
34
+ export interface RestDataSourceOptions<T> {
35
+ /** Prefixed to every request path, exactly once. */
36
+ baseUrl?: string;
37
+ /** Overrides for the per-operation paths. */
38
+ endpoints?: Partial<RestEndpoints>;
39
+ /** Overrides for query-parameter names, for APIs with another vocabulary. */
40
+ paramNames?: Partial<RestParamNames>;
41
+ /** Overrides for the HTTP verb used by each mutating operation. */
42
+ methods?: Partial<RestMethods>;
43
+ /** Sent with every request, merged over the default content type. */
44
+ headers?: Readonly<Record<string, string>>;
45
+ /** Maps a draft onto the request body. Defaults to sending the draft as-is. */
46
+ serializeRequest?: (draft: CrudDraft<T>) => unknown;
47
+ /** Maps a list response onto a page. Required when the API wraps its payload. */
48
+ parseResponse?: (payload: unknown) => CrudPage<T>;
49
+ /** Injected for testing; defaults to the global `fetch`. */
50
+ fetchImpl?: typeof fetch;
51
+ }
52
+ /**
53
+ * Raised when the server answers with a non-2xx status.
54
+ *
55
+ * Carries the status and the raw body so consumers can branch on a 409 or
56
+ * surface a validation payload, rather than receiving a flattened string.
57
+ */
58
+ export declare class RestError extends Error {
59
+ /** The HTTP status the server responded with. */
60
+ readonly status: number;
61
+ /** The raw response body, for reading a validation payload. */
62
+ readonly body: string;
63
+ constructor(status: number, body: string, url: string);
64
+ }
65
+ /**
66
+ * A source over a REST API.
67
+ *
68
+ * URL and payload construction live in `protected` methods rather than inline,
69
+ * so an API that does not match the default dialect is a small subclass
70
+ * instead of a full reimplementation of the strategy.
71
+ */
72
+ export declare class RestDataSource<T extends object, K extends keyof T> implements CrudDataSource<T, K> {
73
+ /** Prefixed to every request path, exactly once. */
74
+ protected readonly baseUrl: string;
75
+ /** Resolved per-operation paths. */
76
+ protected readonly endpoints: RestEndpoints;
77
+ /** Resolved query-parameter names. */
78
+ protected readonly paramNames: RestParamNames;
79
+ /** Resolved HTTP verbs. */
80
+ protected readonly methods: RestMethods;
81
+ /** Headers sent with every request. */
82
+ protected readonly headers: Readonly<Record<string, string>>;
83
+ /** Maps a draft onto the request body. */
84
+ protected readonly serializeRequest: (draft: CrudDraft<T>) => unknown;
85
+ /** Maps a list payload onto a page, when the API wraps its results. */
86
+ protected readonly parseResponse?: (payload: unknown) => CrudPage<T>;
87
+ private readonly fetchImpl;
88
+ constructor(options?: RestDataSourceOptions<T>);
89
+ /**
90
+ * Encode sort instructions as query parameters.
91
+ *
92
+ * Only the first instruction is sent: `sortBy`/`sortOrder` is a single-sort
93
+ * shape, and inventing a multi-sort encoding would guess at a convention the
94
+ * server may not share. Override to support one.
95
+ */
96
+ protected serializeSort(sort: readonly CrudSort<T>[] | undefined, params: URLSearchParams): void;
97
+ /** Build the list URL, including pagination, sort and filters. */
98
+ protected buildListUrl(query: CrudQuery<T>): string;
99
+ /** Path addressing a single record. Override for `?id=` style APIs. */
100
+ protected buildRecordPath(endpoint: string, id: T[K]): string;
101
+ /**
102
+ * Interpret a list payload.
103
+ *
104
+ * Without a `parseResponse`, accepts either a bare array or the
105
+ * `{ data, total }` shape the previous implementation assumed.
106
+ */
107
+ protected toPage(payload: unknown): CrudPage<T>;
108
+ /**
109
+ * Single point where `baseUrl`, headers and error handling are applied.
110
+ *
111
+ * Every request goes through here so `baseUrl` is prefixed exactly once -
112
+ * building a full URL earlier and prefixing again is how it ended up
113
+ * duplicated for relative bases.
114
+ */
115
+ protected request(path: string, init?: RequestInit): Promise<unknown>;
116
+ list(query: CrudQuery<T>): Promise<CrudPage<T>>;
117
+ create(draft: CrudDraft<T>): Promise<T>;
118
+ update(id: T[K], draft: CrudDraft<T>): Promise<T>;
119
+ remove(id: T[K]): Promise<void>;
120
+ }
@@ -0,0 +1,20 @@
1
+ import { InMemoryDataSource } from './InMemoryDataSource';
2
+ import { IdGenerator } from './types';
3
+ /**
4
+ * A source over an in-process array, for fixtures, demos and tests.
5
+ *
6
+ * The collection is instance state, seeded once from the array passed to the
7
+ * constructor and never re-read from it. That is the fix for the old
8
+ * closure-factory behaviour: identity of the caller's array no longer
9
+ * controls the dataset's lifetime, so passing an inline literal from a
10
+ * component no longer discards every created and edited row when the parent
11
+ * happens to re-render.
12
+ *
13
+ * The seed array is copied, so mutations never write back to the caller's data.
14
+ */
15
+ export declare class StaticDataSource<T extends object, K extends keyof T> extends InMemoryDataSource<T, K> {
16
+ private records;
17
+ constructor(seed: readonly T[], key: K, generateId?: IdGenerator<T, K>);
18
+ protected read(): readonly T[];
19
+ protected write(records: readonly T[]): void;
20
+ }
@@ -0,0 +1,28 @@
1
+ import { IdGenerator } from './types';
2
+ /**
3
+ * Fresh RFC-4122 v4 identifier.
4
+ *
5
+ * `crypto.randomUUID` needs a secure context and is absent in some test
6
+ * environments, so this falls back to `getRandomValues` and, failing that,
7
+ * to `Math.random`. The fallback is for uniqueness within a client-side
8
+ * dataset, not for anything security-bearing.
9
+ */
10
+ declare const randomUuid: () => string;
11
+ /**
12
+ * Default identity policy for in-memory sources.
13
+ *
14
+ * Chooses from the *runtime type of the existing keys* rather than coercing
15
+ * them to numbers. Coercion was the old bug: `Number('a3f9-...')` is `NaN`,
16
+ * `|| 0` collapsed it to `0`, and every created row came back as `1` -
17
+ * duplicate keys, and `update`/`remove` then resolving to the wrong record.
18
+ *
19
+ * - numeric keys -> highest + 1
20
+ * - digit-like strings -> highest + 1, kept as a string
21
+ * - any other string -> a fresh UUID
22
+ * - empty dataset -> `1`, matching the previous behaviour for the common case
23
+ *
24
+ * Mixed or non-primitive key types cannot be continued safely, so those throw
25
+ * rather than silently colliding. Pass an explicit generator for those.
26
+ */
27
+ export declare const defaultIdGenerator: <T, K extends keyof T>(key: K) => IdGenerator<T, K>;
28
+ export { randomUuid };
@@ -0,0 +1,26 @@
1
+ import { CrudFilters, CrudPage, CrudQuery, CrudSort } from './types';
2
+ /**
3
+ * Apply filters to an in-memory collection.
4
+ *
5
+ * Filters whose value is undefined, null or the empty string are ignored, so
6
+ * a cleared search box does not exclude everything. A record missing the
7
+ * filtered field never matches.
8
+ */
9
+ export declare const filterRecords: <T>(records: readonly T[], filters?: CrudFilters<T>) => readonly T[];
10
+ /**
11
+ * Apply an ordered list of sort instructions.
12
+ *
13
+ * Earlier entries take precedence; later ones break ties. Missing values sort
14
+ * last in both directions - they carry no ordering information, so surfacing
15
+ * them ahead of real data in one direction would be arbitrary.
16
+ */
17
+ export declare const sortRecords: <T>(records: readonly T[], sort?: readonly CrudSort<T>[]) => readonly T[];
18
+ /** Take the requested page. Pages are 1-based; out-of-range pages yield an empty slice. */
19
+ export declare const paginateRecords: <T>(records: readonly T[], page: number, pageSize: number) => readonly T[];
20
+ /**
21
+ * The shared filter-then-sort-then-paginate pipeline.
22
+ *
23
+ * Every strategy backed by a local collection routes through this, so their
24
+ * querying behaviour cannot drift apart.
25
+ */
26
+ export declare const queryRecords: <T>(records: readonly T[], query: CrudQuery<T>) => CrudPage<T>;
@@ -0,0 +1,11 @@
1
+ export type { CrudDataSource, CrudDraft, CrudFilters, CrudFilterValue, CrudPage, CrudQuery, CrudSort, IdGenerator, SortDirection, } from './types';
2
+ export { defaultIdGenerator, randomUuid } from './identity';
3
+ export { filterRecords, paginateRecords, queryRecords, sortRecords } from './inMemoryQuery';
4
+ export { InMemoryDataSource } from './InMemoryDataSource';
5
+ export { StaticDataSource } from './StaticDataSource';
6
+ export { LocalStorageDataSource } from './LocalStorageDataSource';
7
+ export type { Timestamped } from './LocalStorageDataSource';
8
+ export { RestDataSource, RestError } from './RestDataSource';
9
+ export type { RestDataSourceOptions, RestEndpoints, RestMethods, RestParamNames, } from './RestDataSource';
10
+ export { CustomDataSource, UnsupportedOperationError } from './CustomDataSource';
11
+ export type { CrudOperations } from './CustomDataSource';
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The core data-access contract every CRUD strategy implements.
3
+ *
4
+ * `T` is the record shape and `K` the property holding its identity, so
5
+ * `T[K]` is the precise type of an id throughout - never `any`, never a
6
+ * widened `string | number`.
7
+ */
8
+ /** Sort direction, matching the vocabulary ProTable reports. */
9
+ export type SortDirection = 'ascend' | 'descend';
10
+ /** One sort instruction. Queries carry an ordered list to support multi-sort. */
11
+ export interface CrudSort<T> {
12
+ /** The property to order by. */
13
+ readonly field: keyof T;
14
+ /** Ascending or descending. */
15
+ readonly direction: SortDirection;
16
+ }
17
+ /**
18
+ * A value a column can be filtered by.
19
+ *
20
+ * Deliberately narrow: filters arrive from form inputs and query strings, so
21
+ * this is the complete set of things a user can actually express. Anything
22
+ * richer belongs in a custom data source.
23
+ */
24
+ export type CrudFilterValue = string | number | boolean;
25
+ /** Field-keyed filters. Absent keys are unfiltered. */
26
+ export type CrudFilters<T> = {
27
+ readonly [P in keyof T]?: CrudFilterValue;
28
+ };
29
+ /** A complete read request: which slice, in what order, matching what. */
30
+ export interface CrudQuery<T> {
31
+ /** 1-based, matching ProTable's `current`. */
32
+ readonly page: number;
33
+ /** Rows per page. */
34
+ readonly pageSize: number;
35
+ /** Ordered sort instructions; earlier entries take precedence. */
36
+ readonly sort?: readonly CrudSort<T>[];
37
+ /** Field-keyed filters. Absent keys are unfiltered. */
38
+ readonly filters?: CrudFilters<T>;
39
+ }
40
+ /** One page of results plus the total matching the filters (not the page size). */
41
+ export interface CrudPage<T> {
42
+ /** The records on this page. */
43
+ readonly items: readonly T[];
44
+ /** Records matching the filters, across all pages. */
45
+ readonly total: number;
46
+ }
47
+ /**
48
+ * A record being written.
49
+ *
50
+ * Partial because the identity is assigned by the source on create, and an
51
+ * update legitimately touches a subset of fields.
52
+ */
53
+ export type CrudDraft<T> = Partial<T>;
54
+ /**
55
+ * Produces the identity for a newly created record.
56
+ *
57
+ * Receives the existing records so a strategy can derive the next value
58
+ * (max + 1, a fresh UUID, a sequence). Supplying one replaces the built-in
59
+ * policy entirely.
60
+ */
61
+ export type IdGenerator<T, K extends keyof T> = (existing: readonly T[]) => T[K];
62
+ /**
63
+ * The interface every strategy implements: static fixtures, REST,
64
+ * localStorage, or a consumer's own.
65
+ *
66
+ * Implementations own their storage and lifetime. They are plain objects -
67
+ * no React involvement - so each is unit-testable without a renderer.
68
+ */
69
+ export interface CrudDataSource<T, K extends keyof T> {
70
+ /** Read one page. Implementations must apply filters before paginating. */
71
+ list(query: CrudQuery<T>): Promise<CrudPage<T>>;
72
+ /** Persist a new record and return it as stored, identity included. */
73
+ create(draft: CrudDraft<T>): Promise<T>;
74
+ /** Merge `draft` into the record at `id` and return the stored result. */
75
+ update(id: T[K], draft: CrudDraft<T>): Promise<T>;
76
+ /** Delete the record at `id`. */
77
+ remove(id: T[K]): Promise<void>;
78
+ /**
79
+ * Every record matching the query, ignoring pagination.
80
+ *
81
+ * Exists so export can cover the whole result set rather than the current
82
+ * page. Optional: a source over an unbounded remote collection may have no
83
+ * safe way to honour it, and callers must degrade to `list` when absent.
84
+ */
85
+ listAll?(query: Omit<CrudQuery<T>, 'page' | 'pageSize'>): Promise<readonly T[]>;
86
+ }
@@ -0,0 +1 @@
1
+ var e={actions:`Actions`,edit:`Edit`,delete:`Delete`,create:`New`,refresh:`Refresh`,createTitle:`Create Item`,editTitle:`Edit Item`,ok:`OK`,cancel:`Cancel`,requiredField:e=>`${e} is required`,confirmDeleteTitle:`Are you sure?`,confirmDeleteContent:`This action cannot be undone.`,confirmDeleteOk:`Yes, Delete`,confirmBulkDeleteTitle:e=>`Delete ${e} items?`,confirmBulkDeleteOk:`Yes, Delete All`,deleteSelected:e=>`Delete Selected (${e})`,selectItemsToDelete:`Please select items to delete`,bulkDeleteSuccess:e=>`Deleted ${e} items`,bulkDeletePartial:(e,t,n)=>`Deleted ${e} of ${t}. ${n} failed.`,exportAll:e=>`Export all as ${e}`,exportPage:e=>`Export page as ${e}`,nothingToExport:`Nothing to export`,exportFailed:e=>`Export failed: ${e}`,createSuccess:`Created successfully`,updateSuccess:`Updated successfully`,deleteSuccess:`Deleted successfully`,loading:`Loading CRUD table…`,yes:`Yes`,no:`No`,empty:`-`,selectPlaceholder:e=>`Select ${e.toLowerCase()}`,tagsPlaceholder:`Type and press enter`,invalidEmail:`Please enter a valid email`,invalidUrl:`Please enter a valid URL`,invalidImageUrl:`Please enter a valid image URL`,invalidJson:`Please enter valid JSON`};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return e}});
@@ -0,0 +1,41 @@
1
+ //#region lib/locale/en_US.ts
2
+ var e = {
3
+ actions: "Actions",
4
+ edit: "Edit",
5
+ delete: "Delete",
6
+ create: "New",
7
+ refresh: "Refresh",
8
+ createTitle: "Create Item",
9
+ editTitle: "Edit Item",
10
+ ok: "OK",
11
+ cancel: "Cancel",
12
+ requiredField: (e) => `${e} is required`,
13
+ confirmDeleteTitle: "Are you sure?",
14
+ confirmDeleteContent: "This action cannot be undone.",
15
+ confirmDeleteOk: "Yes, Delete",
16
+ confirmBulkDeleteTitle: (e) => `Delete ${e} items?`,
17
+ confirmBulkDeleteOk: "Yes, Delete All",
18
+ deleteSelected: (e) => `Delete Selected (${e})`,
19
+ selectItemsToDelete: "Please select items to delete",
20
+ bulkDeleteSuccess: (e) => `Deleted ${e} items`,
21
+ bulkDeletePartial: (e, t, n) => `Deleted ${e} of ${t}. ${n} failed.`,
22
+ exportAll: (e) => `Export all as ${e}`,
23
+ exportPage: (e) => `Export page as ${e}`,
24
+ nothingToExport: "Nothing to export",
25
+ exportFailed: (e) => `Export failed: ${e}`,
26
+ createSuccess: "Created successfully",
27
+ updateSuccess: "Updated successfully",
28
+ deleteSuccess: "Deleted successfully",
29
+ loading: "Loading CRUD table…",
30
+ yes: "Yes",
31
+ no: "No",
32
+ empty: "-",
33
+ selectPlaceholder: (e) => `Select ${e.toLowerCase()}`,
34
+ tagsPlaceholder: "Type and press enter",
35
+ invalidEmail: "Please enter a valid email",
36
+ invalidUrl: "Please enter a valid URL",
37
+ invalidImageUrl: "Please enter a valid image URL",
38
+ invalidJson: "Please enter valid JSON"
39
+ };
40
+ //#endregion
41
+ export { e as t };
@@ -1,5 +1,5 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});var e=(e,t)=>{let n=t.filter(e=>e.fieldType!==`option`&&e.dataIndex),r=n.map(e=>String(e.title||e.dataIndex)),i=e.map(e=>n.map(t=>{let n=e[t.dataIndex];return n==null?``:t.fieldType===`boolean`?n?`Yes`:`No`:t.fieldType===`date`?String(n):t.fieldType===`enum`&&t.enumOptions&&t.enumOptions[n]?.text||String(n)}));return[r.map(e=>`"${e}"`).join(`,`),...i.map(e=>e.map(e=>`"${String(e).replace(/"/g,`""`)}"`).join(`,`))].join(`
2
- `)},t=(e,t,n)=>{let r=new Blob([e],{type:n}),i=URL.createObjectURL(r),a=document.createElement(`a`);a.href=i,a.download=t,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)},n=n=>{let{data:r,columns:i,filename:a=`export`}=n;t(e(r,i),`${a}.csv`,`text/csv;charset=utf-8;`)},r=e=>{let{data:n,filename:r=`export`}=e;t(JSON.stringify(n,null,2),`${r}.json`,`application/json`)},i=e=>{let{data:n,columns:r,filename:i=`export`}=e,a=r.filter(e=>e.fieldType!==`option`&&e.dataIndex),o=a.map(e=>String(e.title||e.dataIndex)),s=n.map(e=>a.map(t=>{let n=e[t.dataIndex];return n==null?``:t.fieldType===`boolean`?n?`Yes`:`No`:t.fieldType===`enum`&&t.enumOptions?t.enumOptions[n]?.text||String(n):typeof n==`number`?n:String(n)})),c=e=>e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&apos;`),l=e=>`<Cell><Data ss:Type="${typeof e==`number`?`Number`:`String`}">${typeof e==`number`?String(e):c(e)}</Data></Cell>`;t(`<?xml version="1.0"?>
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=[`=`,`+`,`-`,`@`,` `,`\r`],t=/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/,n=n=>{if(n===``)return n;let r=n.trimStart();if(t.test(r))return n;let i=t=>t!==``&&e.includes(t[0]);return i(n)||i(r)?`'${n}`:n},r=(e,t)=>{let r=t.filter(e=>e.fieldType!==`option`&&e.dataIndex),i=r.map(e=>String(e.title||e.dataIndex)),a=e.map(e=>r.map(t=>{let n=e[t.dataIndex];return n==null?``:t.fieldType===`boolean`?n?`Yes`:`No`:t.fieldType===`date`?String(n):t.fieldType===`enum`&&t.enumOptions&&t.enumOptions[n]?.text||String(n)})),o=e=>`"${n(e).replace(/"/g,`""`)}"`;return[i.map(o).join(`,`),...a.map(e=>e.map(e=>o(String(e))).join(`,`))].join(`
2
+ `)},i=(e,t,n)=>{let r=new Blob([e],{type:n}),i=URL.createObjectURL(r),a=document.createElement(`a`);a.href=i,a.download=t,document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)},a=e=>{let{data:t,columns:n,filename:a=`export`}=e;i(r(t,n),`${a}.csv`,`text/csv;charset=utf-8;`)},o=e=>{let{data:t,filename:n=`export`}=e;i(JSON.stringify(t,null,2),`${n}.json`,`application/json`)},s=e=>{let{data:t,columns:n,filename:r=`export`}=e,a=n.filter(e=>e.fieldType!==`option`&&e.dataIndex),o=a.map(e=>String(e.title||e.dataIndex)),s=t.map(e=>a.map(t=>{let n=e[t.dataIndex];return n==null?``:t.fieldType===`boolean`?n?`Yes`:`No`:t.fieldType===`enum`&&t.enumOptions?t.enumOptions[n]?.text||String(n):typeof n==`number`?n:String(n)})),c=e=>e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/"/g,`&quot;`).replace(/'/g,`&apos;`),l=e=>`<Cell><Data ss:Type="${typeof e==`number`?`Number`:`String`}">${typeof e==`number`?String(e):c(e)}</Data></Cell>`;i(`<?xml version="1.0"?>
3
3
  <?mso-application progid="Excel.Sheet"?>
4
4
  <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
5
5
  xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
@@ -10,4 +10,4 @@ ${s.map(e=>`<Row>${e.map(e=>l(e)).join(``)}</Row>`).join(`
10
10
  `)}
11
11
  </Table>
12
12
  </Worksheet>
13
- </Workbook>`,`${i}.xls`,`application/vnd.ms-excel`)},a=e=>{let{format:t=`csv`}=e;switch(t){case`csv`:n(e);break;case`json`:r(e);break;case`xlsx`:i(e);break;default:n(e)}},o=async(e,t,n=`export`,r=`csv`)=>{try{a({data:await e(),columns:t,filename:n,format:r})}catch(e){throw console.error(`Export failed:`,e),e}};exports.default=a,exports.exportData=a,exports.exportAllData=o,exports.exportToCSV=n,exports.exportToJSON=r,exports.exportToXLSX=i;
13
+ </Workbook>`,`${r}.xls`,`application/vnd.ms-excel`)},c=e=>{let{format:t=`csv`}=e;switch(t){case`csv`:a(e);break;case`json`:o(e);break;case`excel`:s(e);break;default:a(e)}},l=async(e,t,n=`export`,r=`csv`)=>{try{c({data:await e(),columns:t,filename:n,format:r})}catch(e){throw console.error(`Export failed:`,e),e}};exports.exportAllData=l,exports.exportData=c,exports.exportToCSV=a,exports.exportToExcel=s,exports.exportToJSON=o;