@entifix/react-integration 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/url/entity-source.d.ts +9 -0
- package/dist/url/entity-source.d.ts.map +1 -0
- package/dist/url/entity-source.js +8 -0
- package/dist/url/entity-source.js.map +1 -0
- package/dist/url/url-filter.d.ts +40 -0
- package/dist/url/url-filter.d.ts.map +1 -0
- package/dist/url/url-filter.js +65 -0
- package/dist/url/url-filter.js.map +1 -0
- package/dist/url/url-query.d.ts +83 -0
- package/dist/url/url-query.d.ts.map +1 -0
- package/dist/url/url-query.js +122 -0
- package/dist/url/url-query.js.map +1 -0
- package/dist/url/url-state.d.ts +11 -0
- package/dist/url/url-state.d.ts.map +1 -0
- package/dist/url/url-state.js +40 -0
- package/dist/url/url-state.js.map +1 -0
- package/dist/url/use-entity-load.d.ts +29 -0
- package/dist/url/use-entity-load.d.ts.map +1 -0
- package/dist/url/use-entity-load.js +67 -0
- package/dist/url/use-entity-load.js.map +1 -0
- package/dist/url.d.ts +11 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +11 -0
- package/dist/url.js.map +1 -0
- package/package.json +11 -5
package/README.md
CHANGED
|
@@ -12,9 +12,10 @@ pnpm add @entifix/react-integration @tanstack/react-form @tanstack/react-query e
|
|
|
12
12
|
|
|
13
13
|
## Entry points
|
|
14
14
|
|
|
15
|
-
| Import
|
|
16
|
-
|
|
|
17
|
-
| `@entifix/react-integration`
|
|
15
|
+
| Import | What it holds |
|
|
16
|
+
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `@entifix/react-integration` | `EntifixQueryProvider`, `useEntityRecord`, `useEntityForm`, `useEntityMutation`, `useWizard`, `useReactiveInvalidation`, `useTransactionSettlement` |
|
|
18
|
+
| `@entifix/react-integration/url` | `defineUrlQuery`, `useUrlState`, `useEntityLoad`, `useUrlFilter`: a filter kept in the query string, answered by `loadAllUCFactory` over an `EntitySource`. No TanStack |
|
|
18
19
|
|
|
19
20
|
---
|
|
20
21
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EntityRepository } from '@entifix/business';
|
|
2
|
+
/**
|
|
3
|
+
* Where a repository comes from: asked for when a hook first needs it.
|
|
4
|
+
*
|
|
5
|
+
* A source may build its repository lazily — fetch a file, open a client —
|
|
6
|
+
* and should keep it once built, since a filter asks again on every change.
|
|
7
|
+
*/
|
|
8
|
+
export type EntitySource = () => Promise<EntityRepository>;
|
|
9
|
+
//# sourceMappingURL=entity-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-source.d.ts","sourceRoot":"","sources":["../../src/url/entity-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a repository comes from: asked for when a hook first needs it.
|
|
3
|
+
*
|
|
4
|
+
* A source may build its repository lazily — fetch a file, open a client —
|
|
5
|
+
* and should keep it once built, since a filter asks again on every change.
|
|
6
|
+
*/ export { };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=entity-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/url/entity-source.ts"],"sourcesContent":["import type { EntityRepository } from '@entifix/business';\n\n/**\n * Where a repository comes from: asked for when a hook first needs it.\n *\n * A source may build its repository lazily — fetch a file, open a client —\n * and should keep it once built, since a filter asks again on every change.\n */\nexport type EntitySource = () => Promise<EntityRepository>;\n"],"names":[],"mappings":"AAEA;;;;;CAKC,GACD,WAA2D"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Entity } from '@entifix/core';
|
|
2
|
+
import type { EntitySource } from './entity-source.js';
|
|
3
|
+
import type { UrlQuery, UrlState } from './url-query.js';
|
|
4
|
+
import { type EntityLoad } from './use-entity-load.js';
|
|
5
|
+
/** A filter kept in the URL, and the records its answer keeps. */
|
|
6
|
+
export interface UrlFilter<TKey extends string, TEntity extends Entity> {
|
|
7
|
+
/**
|
|
8
|
+
* The state the URL holds, or `null` in the server-rendered HTML and during
|
|
9
|
+
* hydration, which show every record and no controls.
|
|
10
|
+
*/
|
|
11
|
+
readonly filter: UrlState<TKey> | null;
|
|
12
|
+
/** Whether any parameter that filters is set; a chosen sort alone is not. */
|
|
13
|
+
readonly filtering: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The ids the latest answer keeps, or `undefined` while every record is
|
|
16
|
+
* shown. While a new answer loads, the last one stays.
|
|
17
|
+
*/
|
|
18
|
+
readonly kept: ReadonlySet<string> | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* The same ids in the answer's order, for a page whose sort the URL
|
|
21
|
+
* chooses, or `undefined` while every record is shown as built.
|
|
22
|
+
*/
|
|
23
|
+
readonly order: readonly string[] | undefined;
|
|
24
|
+
/** The load itself, for a page that shows its status. */
|
|
25
|
+
readonly load: EntityLoad<TEntity>;
|
|
26
|
+
/** Adds a value to a parameter, or takes it out when it is there. */
|
|
27
|
+
toggle(key: TKey, value: string): void;
|
|
28
|
+
/** Replaces a parameter's values: a search box's text. */
|
|
29
|
+
set(key: TKey, values: readonly string[]): void;
|
|
30
|
+
/** Every filter emptied: no filter at all. A chosen sort and selection stay. */
|
|
31
|
+
clear(): void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A page's filter, from the query string to the ids it keeps: `useUrlState`
|
|
35
|
+
* reads and writes the parameters, and `useEntityLoad` answers them through
|
|
36
|
+
* the `loadAll` use case over `source`. The page only lays
|
|
37
|
+
* out its controls, and shows or dims what `kept` names.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useUrlFilter<TKey extends string, TContext, TEntity extends Entity>(source: EntitySource, query: UrlQuery<TKey, TContext>, context: TContext): UrlFilter<TKey, TEntity>;
|
|
40
|
+
//# sourceMappingURL=url-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-filter.d.ts","sourceRoot":"","sources":["../../src/url/url-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,eAAe,CAAC;AAGxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,EAAE,KAAK,UAAU,EAAiB,MAAM,sBAAsB,CAAC;AAEtE,kEAAkE;AAClE,MAAM,WAAW,SAAS,CAAC,IAAI,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM;IACpE;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvC,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IAC/C;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9C,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC;IACnC,qEAAqE;IACrE,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,0DAA0D;IAC1D,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IAChD,gFAAgF;IAChF,KAAK,IAAI,IAAI,CAAC;CACf;AAkBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,SAAS,MAAM,EACnB,QAAQ,EACR,OAAO,SAAS,MAAM,EAEtB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,EAC/B,OAAO,EAAE,QAAQ,GAChB,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAqC1B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { useCallback, useMemo } from 'react';
|
|
3
|
+
import { useUrlState } from './url-state.js';
|
|
4
|
+
import { useEntityLoad } from './use-entity-load.js';
|
|
5
|
+
/** `values` with `value` added, or taken out when it is already there. */ function toggled(values, value) {
|
|
6
|
+
return values.includes(value) ? values.filter((each)=>each !== value) : [
|
|
7
|
+
...values,
|
|
8
|
+
value
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
/** The page an answer shows: the last one while the next is on its way. */ function shownPage(load) {
|
|
12
|
+
if (load.status === 'done') return load.page;
|
|
13
|
+
if (load.status === 'pending') return load.previous;
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A page's filter, from the query string to the ids it keeps: `useUrlState`
|
|
18
|
+
* reads and writes the parameters, and `useEntityLoad` answers them through
|
|
19
|
+
* the `loadAll` use case over `source`. The page only lays
|
|
20
|
+
* out its controls, and shows or dims what `kept` names.
|
|
21
|
+
*/ export function useUrlFilter(source, query, context) {
|
|
22
|
+
const [filter, write] = useUrlState(query);
|
|
23
|
+
const filtering = filter !== null && query.isFiltering(filter);
|
|
24
|
+
// A selection alone picks a record the page already has: nothing to ask.
|
|
25
|
+
const asking = filter !== null && query.isAsking(filter);
|
|
26
|
+
const load = useEntityLoad(source, asking ? query.request(filter, context) : null);
|
|
27
|
+
const page = shownPage(load);
|
|
28
|
+
const order = useMemo(()=>page == null ? void 0 : page.items.map((record)=>String(record.id)), [
|
|
29
|
+
page
|
|
30
|
+
]);
|
|
31
|
+
const kept = useMemo(()=>order === undefined ? undefined : new Set(order), [
|
|
32
|
+
order
|
|
33
|
+
]);
|
|
34
|
+
const set = useCallback((key, values)=>{
|
|
35
|
+
if (filter !== null) write(_extends({}, filter, {
|
|
36
|
+
[key]: values
|
|
37
|
+
}));
|
|
38
|
+
}, [
|
|
39
|
+
filter,
|
|
40
|
+
write
|
|
41
|
+
]);
|
|
42
|
+
const toggle = useCallback((key, value)=>{
|
|
43
|
+
if (filter !== null) set(key, toggled(filter[key], value));
|
|
44
|
+
}, [
|
|
45
|
+
filter,
|
|
46
|
+
set
|
|
47
|
+
]);
|
|
48
|
+
const clear = useCallback(()=>write(filter === null ? query.empty : query.withoutFilters(filter)), [
|
|
49
|
+
filter,
|
|
50
|
+
query,
|
|
51
|
+
write
|
|
52
|
+
]);
|
|
53
|
+
return {
|
|
54
|
+
filter,
|
|
55
|
+
filtering,
|
|
56
|
+
kept,
|
|
57
|
+
order,
|
|
58
|
+
load,
|
|
59
|
+
toggle,
|
|
60
|
+
set,
|
|
61
|
+
clear
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=url-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/url/url-filter.ts"],"sourcesContent":["import type { Entity, EntityPage } from '@entifix/core';\nimport { useCallback, useMemo } from 'react';\n\nimport type { EntitySource } from './entity-source.js';\nimport type { UrlQuery, UrlState } from './url-query.js';\nimport { useUrlState } from './url-state.js';\nimport { type EntityLoad, useEntityLoad } from './use-entity-load.js';\n\n/** A filter kept in the URL, and the records its answer keeps. */\nexport interface UrlFilter<TKey extends string, TEntity extends Entity> {\n /**\n * The state the URL holds, or `null` in the server-rendered HTML and during\n * hydration, which show every record and no controls.\n */\n readonly filter: UrlState<TKey> | null;\n /** Whether any parameter that filters is set; a chosen sort alone is not. */\n readonly filtering: boolean;\n /**\n * The ids the latest answer keeps, or `undefined` while every record is\n * shown. While a new answer loads, the last one stays.\n */\n readonly kept: ReadonlySet<string> | undefined;\n /**\n * The same ids in the answer's order, for a page whose sort the URL\n * chooses, or `undefined` while every record is shown as built.\n */\n readonly order: readonly string[] | undefined;\n /** The load itself, for a page that shows its status. */\n readonly load: EntityLoad<TEntity>;\n /** Adds a value to a parameter, or takes it out when it is there. */\n toggle(key: TKey, value: string): void;\n /** Replaces a parameter's values: a search box's text. */\n set(key: TKey, values: readonly string[]): void;\n /** Every filter emptied: no filter at all. A chosen sort and selection stay. */\n clear(): void;\n}\n\n/** `values` with `value` added, or taken out when it is already there. */\nfunction toggled(values: readonly string[], value: string): string[] {\n return values.includes(value)\n ? values.filter(each => each !== value)\n : [...values, value];\n}\n\n/** The page an answer shows: the last one while the next is on its way. */\nfunction shownPage<TEntity extends Entity>(\n load: EntityLoad<TEntity>,\n): EntityPage<TEntity> | undefined {\n if (load.status === 'done') return load.page;\n if (load.status === 'pending') return load.previous;\n return undefined;\n}\n\n/**\n * A page's filter, from the query string to the ids it keeps: `useUrlState`\n * reads and writes the parameters, and `useEntityLoad` answers them through\n * the `loadAll` use case over `source`. The page only lays\n * out its controls, and shows or dims what `kept` names.\n */\nexport function useUrlFilter<\n TKey extends string,\n TContext,\n TEntity extends Entity,\n>(\n source: EntitySource,\n query: UrlQuery<TKey, TContext>,\n context: TContext,\n): UrlFilter<TKey, TEntity> {\n const [filter, write] = useUrlState(query);\n const filtering = filter !== null && query.isFiltering(filter);\n // A selection alone picks a record the page already has: nothing to ask.\n const asking = filter !== null && query.isAsking(filter);\n const load = useEntityLoad<TEntity>(\n source,\n asking ? query.request<TEntity>(filter, context) : null,\n );\n const page = shownPage(load);\n const order = useMemo(\n () => page?.items.map(record => String(record.id)),\n [page],\n );\n const kept = useMemo(\n () => (order === undefined ? undefined : new Set(order)),\n [order],\n );\n\n const set = useCallback(\n (key: TKey, values: readonly string[]) => {\n if (filter !== null) write({ ...filter, [key]: values });\n },\n [filter, write],\n );\n const toggle = useCallback(\n (key: TKey, value: string) => {\n if (filter !== null) set(key, toggled(filter[key], value));\n },\n [filter, set],\n );\n const clear = useCallback(\n () => write(filter === null ? query.empty : query.withoutFilters(filter)),\n [filter, query, write],\n );\n\n return { filter, filtering, kept, order, load, toggle, set, clear };\n}\n"],"names":["useCallback","useMemo","useUrlState","useEntityLoad","toggled","values","value","includes","filter","each","shownPage","load","status","page","previous","undefined","useUrlFilter","source","query","context","write","filtering","isFiltering","asking","isAsking","request","order","items","map","record","String","id","kept","Set","set","key","toggle","clear","empty","withoutFilters"],"mappings":";AACA,SAASA,WAAW,EAAEC,OAAO,QAAQ,QAAQ;AAI7C,SAASC,WAAW,QAAQ,iBAAiB;AAC7C,SAA0BC,aAAa,QAAQ,uBAAuB;AA+BtE,wEAAwE,GACxE,SAASC,QAAQC,MAAyB,EAAEC,KAAa;IACvD,OAAOD,OAAOE,QAAQ,CAACD,SACnBD,OAAOG,MAAM,CAACC,CAAAA,OAAQA,SAASH,SAC/B;WAAID;QAAQC;KAAM;AACxB;AAEA,yEAAyE,GACzE,SAASI,UACPC,IAAyB;IAEzB,IAAIA,KAAKC,MAAM,KAAK,QAAQ,OAAOD,KAAKE,IAAI;IAC5C,IAAIF,KAAKC,MAAM,KAAK,WAAW,OAAOD,KAAKG,QAAQ;IACnD,OAAOC;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASC,aAKdC,MAAoB,EACpBC,KAA+B,EAC/BC,OAAiB;IAEjB,MAAM,CAACX,QAAQY,MAAM,GAAGlB,YAAYgB;IACpC,MAAMG,YAAYb,WAAW,QAAQU,MAAMI,WAAW,CAACd;IACvD,yEAAyE;IACzE,MAAMe,SAASf,WAAW,QAAQU,MAAMM,QAAQ,CAAChB;IACjD,MAAMG,OAAOR,cACXc,QACAM,SAASL,MAAMO,OAAO,CAAUjB,QAAQW,WAAW;IAErD,MAAMN,OAAOH,UAAUC;IACvB,MAAMe,QAAQzB,QACZ,IAAMY,wBAAAA,KAAMc,KAAK,CAACC,GAAG,CAACC,CAAAA,SAAUC,OAAOD,OAAOE,EAAE,IAChD;QAAClB;KAAK;IAER,MAAMmB,OAAO/B,QACX,IAAOyB,UAAUX,YAAYA,YAAY,IAAIkB,IAAIP,QACjD;QAACA;KAAM;IAGT,MAAMQ,MAAMlC,YACV,CAACmC,KAAW9B;QACV,IAAIG,WAAW,MAAMY,MAAM,aAAKZ;YAAQ,CAAC2B,IAAI,EAAE9B;;IACjD,GACA;QAACG;QAAQY;KAAM;IAEjB,MAAMgB,SAASpC,YACb,CAACmC,KAAW7B;QACV,IAAIE,WAAW,MAAM0B,IAAIC,KAAK/B,QAAQI,MAAM,CAAC2B,IAAI,EAAE7B;IACrD,GACA;QAACE;QAAQ0B;KAAI;IAEf,MAAMG,QAAQrC,YACZ,IAAMoB,MAAMZ,WAAW,OAAOU,MAAMoB,KAAK,GAAGpB,MAAMqB,cAAc,CAAC/B,UACjE;QAACA;QAAQU;QAAOE;KAAM;IAGxB,OAAO;QAAEZ;QAAQa;QAAWW;QAAMN;QAAOf;QAAMyB;QAAQF;QAAKG;IAAM;AACpE"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Entity, EntityFilter, EntityLoadRequest, EntitySorting, FilterGroup } from '@entifix/core';
|
|
2
|
+
/**
|
|
3
|
+
* Any entity, as far as a parameter can tell: it names its property by text,
|
|
4
|
+
* since a dotted path (`title.en`) is no key of the entity's type.
|
|
5
|
+
*/
|
|
6
|
+
type AnyEntity = Entity & Record<string, unknown>;
|
|
7
|
+
/** What one parameter of the query string turns into. */
|
|
8
|
+
export type Condition = EntityFilter<AnyEntity> | FilterGroup<AnyEntity>;
|
|
9
|
+
/**
|
|
10
|
+
* One parameter a query string may carry: the values it accepts and the
|
|
11
|
+
* condition they become. `allowed` drops anything else, so a hand-edited or
|
|
12
|
+
* stale link filters on what still exists; `single` keeps the first value
|
|
13
|
+
* only, as a search box has one.
|
|
14
|
+
*/
|
|
15
|
+
export interface FilterParam<TContext> {
|
|
16
|
+
readonly allowed?: readonly string[];
|
|
17
|
+
readonly single?: boolean;
|
|
18
|
+
readonly condition: (values: readonly string[], context: TContext) => Condition;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A parameter that orders rather than filters: each value it accepts names a
|
|
22
|
+
* sorting (`?sort=date-desc`). It holds one value, and any other is dropped.
|
|
23
|
+
*/
|
|
24
|
+
export interface SortParam {
|
|
25
|
+
readonly sorting: Readonly<Record<string, EntitySorting<AnyEntity>[]>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A parameter that picks one record to show rather than filters or orders
|
|
29
|
+
* the answer: `?adr=0016`. It holds one value, `allowed` when given, and never
|
|
30
|
+
* reaches the request.
|
|
31
|
+
*/
|
|
32
|
+
export interface SelectParam {
|
|
33
|
+
readonly select: true;
|
|
34
|
+
readonly allowed?: readonly string[];
|
|
35
|
+
}
|
|
36
|
+
export type UrlParam<TContext> = FilterParam<TContext> | SortParam | SelectParam;
|
|
37
|
+
/** The values of every parameter, in the order the URL gave them. */
|
|
38
|
+
export type UrlState<TKey extends string> = Readonly<Record<TKey, readonly string[]>>;
|
|
39
|
+
export interface UrlQuery<TKey extends string, TContext> {
|
|
40
|
+
/** Every parameter with no value: no filter at all. */
|
|
41
|
+
readonly empty: UrlState<TKey>;
|
|
42
|
+
parse(search: string): UrlState<TKey>;
|
|
43
|
+
/** `''` for an empty state, else `?` and the parameters in declared order. */
|
|
44
|
+
serialize(state: UrlState<TKey>): string;
|
|
45
|
+
isEmpty(state: UrlState<TKey>): boolean;
|
|
46
|
+
/** Whether any parameter that filters has a value; a sort alone does not. */
|
|
47
|
+
isFiltering(state: UrlState<TKey>): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the state asks the load anything: a filter or a sort. A
|
|
50
|
+
* selection alone does not.
|
|
51
|
+
*/
|
|
52
|
+
isAsking(state: UrlState<TKey>): boolean;
|
|
53
|
+
/** The state with every filter emptied, and its sort and selection kept. */
|
|
54
|
+
withoutFilters(state: UrlState<TKey>): UrlState<TKey>;
|
|
55
|
+
/** The load request the state stands for: one condition per parameter set. */
|
|
56
|
+
request<TEntity extends Entity>(state: UrlState<TKey>, context: TContext): EntityLoadRequest<TEntity>;
|
|
57
|
+
}
|
|
58
|
+
export interface UrlQueryOptions<TKey extends string, TContext> {
|
|
59
|
+
readonly params: Readonly<Record<TKey, UrlParam<TContext>>>;
|
|
60
|
+
/** Applied to every request, filtered or not, unless a sort is chosen. */
|
|
61
|
+
readonly sorting?: EntitySorting<AnyEntity>[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A filter kept in the query string, read as an entifix `EntityLoadRequest`.
|
|
65
|
+
*
|
|
66
|
+
* entifix's own `parseLoadRequestParams` writes RSQL and accepts only members
|
|
67
|
+
* marked `filterable`, which no collection or localized member may be. This
|
|
68
|
+
* declares its parameters instead: readable URLs (`?tag=a&tag=b&q=text`) whose
|
|
69
|
+
* every value is checked, each becoming one condition of the request.
|
|
70
|
+
*/
|
|
71
|
+
export declare function defineUrlQuery<TKey extends string, TContext = undefined>({ params, sorting: defaultSorting, }: UrlQueryOptions<TKey, TContext>): UrlQuery<TKey, TContext>;
|
|
72
|
+
/** Any of the values: `in`, which matches any element of a collection too. */
|
|
73
|
+
export declare function anyOf(property: string): (values: readonly string[]) => Condition;
|
|
74
|
+
/**
|
|
75
|
+
* The text as a substring of a member, as the repository's `like` matches
|
|
76
|
+
* it. `property` names the member for a context, as a locale picks the
|
|
77
|
+
* member holding its text.
|
|
78
|
+
*/
|
|
79
|
+
export declare function containing<TContext>(property: (context: TContext) => string): FilterParam<TContext>['condition'];
|
|
80
|
+
/** A date inside any of the given years (UTC), as `between` per year. */
|
|
81
|
+
export declare function inAnyYear(property: string): (values: readonly string[]) => Condition;
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=url-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-query.d.ts","sourceRoot":"","sources":["../../src/url/url-query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/** Whether a parameter orders the answer rather than filters it. */ function isSortParam(param) {
|
|
3
|
+
return 'sorting' in param;
|
|
4
|
+
}
|
|
5
|
+
/** Whether a parameter picks a record rather than asks the load anything. */ function isSelectParam(param) {
|
|
6
|
+
return 'select' in param;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A filter kept in the query string, read as an entifix `EntityLoadRequest`.
|
|
10
|
+
*
|
|
11
|
+
* entifix's own `parseLoadRequestParams` writes RSQL and accepts only members
|
|
12
|
+
* marked `filterable`, which no collection or localized member may be. This
|
|
13
|
+
* declares its parameters instead: readable URLs (`?tag=a&tag=b&q=text`) whose
|
|
14
|
+
* every value is checked, each becoming one condition of the request.
|
|
15
|
+
*/ export function defineUrlQuery({ params, sorting: defaultSorting }) {
|
|
16
|
+
const keys = Object.keys(params);
|
|
17
|
+
const filterKeys = keys.filter((key)=>!isSortParam(params[key]) && !isSelectParam(params[key]));
|
|
18
|
+
const askingKeys = keys.filter((key)=>!isSelectParam(params[key]));
|
|
19
|
+
const stateOf = (values)=>Object.fromEntries(keys.map((key)=>[
|
|
20
|
+
key,
|
|
21
|
+
values(key)
|
|
22
|
+
]));
|
|
23
|
+
const empty = stateOf(()=>[]);
|
|
24
|
+
const accepted = (key, values)=>{
|
|
25
|
+
const param = params[key];
|
|
26
|
+
const { allowed, single } = isSortParam(param) ? {
|
|
27
|
+
allowed: Object.keys(param.sorting),
|
|
28
|
+
single: true
|
|
29
|
+
} : isSelectParam(param) ? {
|
|
30
|
+
allowed: param.allowed,
|
|
31
|
+
single: true
|
|
32
|
+
} : param;
|
|
33
|
+
// Kept as written: a search box being typed into holds its spaces.
|
|
34
|
+
const kept = values.filter((value)=>value.trim() !== '').filter((value)=>allowed === undefined || allowed.includes(value));
|
|
35
|
+
const unique = [
|
|
36
|
+
...new Set(kept)
|
|
37
|
+
];
|
|
38
|
+
return single ? unique.slice(0, 1) : unique;
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
empty,
|
|
42
|
+
parse (search) {
|
|
43
|
+
const query = new URLSearchParams(search);
|
|
44
|
+
return stateOf((key)=>accepted(key, query.getAll(key)));
|
|
45
|
+
},
|
|
46
|
+
serialize (state) {
|
|
47
|
+
const query = new URLSearchParams();
|
|
48
|
+
for (const key of keys){
|
|
49
|
+
for (const value of state[key])query.append(key, value);
|
|
50
|
+
}
|
|
51
|
+
const text = query.toString();
|
|
52
|
+
return text === '' ? '' : `?${text}`;
|
|
53
|
+
},
|
|
54
|
+
isEmpty (state) {
|
|
55
|
+
return keys.every((key)=>state[key].length === 0);
|
|
56
|
+
},
|
|
57
|
+
isFiltering (state) {
|
|
58
|
+
return filterKeys.some((key)=>state[key].length > 0);
|
|
59
|
+
},
|
|
60
|
+
isAsking (state) {
|
|
61
|
+
return askingKeys.some((key)=>state[key].length > 0);
|
|
62
|
+
},
|
|
63
|
+
withoutFilters (state) {
|
|
64
|
+
return stateOf((key)=>filterKeys.includes(key) ? [] : state[key]);
|
|
65
|
+
},
|
|
66
|
+
request (state, context) {
|
|
67
|
+
var _chosen_;
|
|
68
|
+
const filtering = filterKeys.flatMap((key)=>{
|
|
69
|
+
const param = params[key];
|
|
70
|
+
return state[key].length === 0 ? [] : [
|
|
71
|
+
param.condition(state[key], context)
|
|
72
|
+
];
|
|
73
|
+
});
|
|
74
|
+
const chosen = keys.flatMap((key)=>{
|
|
75
|
+
const param = params[key];
|
|
76
|
+
const [value] = state[key];
|
|
77
|
+
return isSortParam(param) && value !== undefined ? [
|
|
78
|
+
param.sorting[value]
|
|
79
|
+
] : [];
|
|
80
|
+
});
|
|
81
|
+
const sorting = (_chosen_ = chosen[0]) != null ? _chosen_ : defaultSorting;
|
|
82
|
+
return _extends({}, filtering.length > 0 && {
|
|
83
|
+
filtering: filtering
|
|
84
|
+
}, sorting !== undefined && {
|
|
85
|
+
sorting: sorting
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Any of the values: `in`, which matches any element of a collection too. */ export function anyOf(property) {
|
|
91
|
+
return (values)=>({
|
|
92
|
+
property,
|
|
93
|
+
operator: 'in',
|
|
94
|
+
values: [
|
|
95
|
+
...values
|
|
96
|
+
]
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The text as a substring of a member, as the repository's `like` matches
|
|
101
|
+
* it. `property` names the member for a context, as a locale picks the
|
|
102
|
+
* member holding its text.
|
|
103
|
+
*/ export function containing(property) {
|
|
104
|
+
return ([value], context)=>({
|
|
105
|
+
property: property(context),
|
|
106
|
+
operator: 'like',
|
|
107
|
+
value: value.trim()
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** A date inside any of the given years (UTC), as `between` per year. */ export function inAnyYear(property) {
|
|
111
|
+
return (values)=>({
|
|
112
|
+
operator: 'or',
|
|
113
|
+
values: values.map((year)=>({
|
|
114
|
+
property,
|
|
115
|
+
operator: 'between',
|
|
116
|
+
start: new Date(`${year}-01-01T00:00:00.000Z`),
|
|
117
|
+
end: new Date(`${year}-12-31T23:59:59.999Z`)
|
|
118
|
+
}))
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=url-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/url/url-query.ts"],"sourcesContent":["import type {\n Entity,\n EntityFilter,\n EntityFiltering,\n EntityLoadRequest,\n EntitySorting,\n FilterGroup,\n} from '@entifix/core';\n\n/**\n * Any entity, as far as a parameter can tell: it names its property by text,\n * since a dotted path (`title.en`) is no key of the entity's type.\n */\ntype AnyEntity = Entity & Record<string, unknown>;\n\n/** What one parameter of the query string turns into. */\nexport type Condition = EntityFilter<AnyEntity> | FilterGroup<AnyEntity>;\n\n/**\n * One parameter a query string may carry: the values it accepts and the\n * condition they become. `allowed` drops anything else, so a hand-edited or\n * stale link filters on what still exists; `single` keeps the first value\n * only, as a search box has one.\n */\nexport interface FilterParam<TContext> {\n readonly allowed?: readonly string[];\n readonly single?: boolean;\n readonly condition: (\n values: readonly string[],\n context: TContext,\n ) => Condition;\n}\n\n/**\n * A parameter that orders rather than filters: each value it accepts names a\n * sorting (`?sort=date-desc`). It holds one value, and any other is dropped.\n */\nexport interface SortParam {\n readonly sorting: Readonly<Record<string, EntitySorting<AnyEntity>[]>>;\n}\n\n/**\n * A parameter that picks one record to show rather than filters or orders\n * the answer: `?adr=0016`. It holds one value, `allowed` when given, and never\n * reaches the request.\n */\nexport interface SelectParam {\n readonly select: true;\n readonly allowed?: readonly string[];\n}\n\nexport type UrlParam<TContext> =\n FilterParam<TContext> | SortParam | SelectParam;\n\n/** Whether a parameter orders the answer rather than filters it. */\nfunction isSortParam<TContext>(param: UrlParam<TContext>): param is SortParam {\n return 'sorting' in param;\n}\n\n/** Whether a parameter picks a record rather than asks the load anything. */\nfunction isSelectParam<TContext>(\n param: UrlParam<TContext>,\n): param is SelectParam {\n return 'select' in param;\n}\n\n/** The values of every parameter, in the order the URL gave them. */\nexport type UrlState<TKey extends string> = Readonly<\n Record<TKey, readonly string[]>\n>;\n\nexport interface UrlQuery<TKey extends string, TContext> {\n /** Every parameter with no value: no filter at all. */\n readonly empty: UrlState<TKey>;\n parse(search: string): UrlState<TKey>;\n /** `''` for an empty state, else `?` and the parameters in declared order. */\n serialize(state: UrlState<TKey>): string;\n isEmpty(state: UrlState<TKey>): boolean;\n /** Whether any parameter that filters has a value; a sort alone does not. */\n isFiltering(state: UrlState<TKey>): boolean;\n /**\n * Whether the state asks the load anything: a filter or a sort. A\n * selection alone does not.\n */\n isAsking(state: UrlState<TKey>): boolean;\n /** The state with every filter emptied, and its sort and selection kept. */\n withoutFilters(state: UrlState<TKey>): UrlState<TKey>;\n /** The load request the state stands for: one condition per parameter set. */\n request<TEntity extends Entity>(\n state: UrlState<TKey>,\n context: TContext,\n ): EntityLoadRequest<TEntity>;\n}\n\nexport interface UrlQueryOptions<TKey extends string, TContext> {\n readonly params: Readonly<Record<TKey, UrlParam<TContext>>>;\n /** Applied to every request, filtered or not, unless a sort is chosen. */\n readonly sorting?: EntitySorting<AnyEntity>[];\n}\n\n/**\n * A filter kept in the query string, read as an entifix `EntityLoadRequest`.\n *\n * entifix's own `parseLoadRequestParams` writes RSQL and accepts only members\n * marked `filterable`, which no collection or localized member may be. This\n * declares its parameters instead: readable URLs (`?tag=a&tag=b&q=text`) whose\n * every value is checked, each becoming one condition of the request.\n */\nexport function defineUrlQuery<TKey extends string, TContext = undefined>({\n params,\n sorting: defaultSorting,\n}: UrlQueryOptions<TKey, TContext>): UrlQuery<TKey, TContext> {\n const keys = Object.keys(params) as TKey[];\n const filterKeys = keys.filter(\n key => !isSortParam(params[key]) && !isSelectParam(params[key]),\n );\n const askingKeys = keys.filter(key => !isSelectParam(params[key]));\n const stateOf = (values: (key: TKey) => readonly string[]) =>\n Object.fromEntries(\n keys.map(key => [key, values(key)]),\n ) as unknown as UrlState<TKey>;\n const empty = stateOf(() => []);\n\n const accepted = (key: TKey, values: string[]) => {\n const param = params[key];\n const { allowed, single } = isSortParam(param)\n ? { allowed: Object.keys(param.sorting), single: true }\n : isSelectParam(param)\n ? { allowed: param.allowed, single: true }\n : param;\n // Kept as written: a search box being typed into holds its spaces.\n const kept = values\n .filter(value => value.trim() !== '')\n .filter(value => allowed === undefined || allowed.includes(value));\n const unique = [...new Set(kept)];\n return single ? unique.slice(0, 1) : unique;\n };\n\n return {\n empty,\n parse(search) {\n const query = new URLSearchParams(search);\n return stateOf(key => accepted(key, query.getAll(key)));\n },\n serialize(state) {\n const query = new URLSearchParams();\n for (const key of keys) {\n for (const value of state[key]) query.append(key, value);\n }\n const text = query.toString();\n return text === '' ? '' : `?${text}`;\n },\n isEmpty(state) {\n return keys.every(key => state[key].length === 0);\n },\n isFiltering(state) {\n return filterKeys.some(key => state[key].length > 0);\n },\n isAsking(state) {\n return askingKeys.some(key => state[key].length > 0);\n },\n withoutFilters(state) {\n return stateOf(key => (filterKeys.includes(key) ? [] : state[key]));\n },\n request<TEntity extends Entity>(state: UrlState<TKey>, context: TContext) {\n const filtering = filterKeys.flatMap(key => {\n const param = params[key] as FilterParam<TContext>;\n return state[key].length === 0\n ? []\n : [param.condition(state[key], context)];\n });\n const chosen = keys.flatMap(key => {\n const param = params[key];\n const [value] = state[key];\n return isSortParam(param) && value !== undefined\n ? [param.sorting[value] as EntitySorting<AnyEntity>[]]\n : [];\n });\n const sorting = chosen[0] ?? defaultSorting;\n return {\n ...(filtering.length > 0 && {\n filtering: filtering as unknown as EntityFiltering<TEntity>[],\n }),\n ...(sorting !== undefined && {\n sorting: sorting as unknown as EntitySorting<TEntity>[],\n }),\n };\n },\n };\n}\n\n/** Any of the values: `in`, which matches any element of a collection too. */\nexport function anyOf(property: string) {\n return (values: readonly string[]): Condition => ({\n property,\n operator: 'in',\n values: [...values],\n });\n}\n\n/**\n * The text as a substring of a member, as the repository's `like` matches\n * it. `property` names the member for a context, as a locale picks the\n * member holding its text.\n */\nexport function containing<TContext>(\n property: (context: TContext) => string,\n): FilterParam<TContext>['condition'] {\n return ([value], context) => ({\n property: property(context),\n operator: 'like',\n value: (value as string).trim(),\n });\n}\n\n/** A date inside any of the given years (UTC), as `between` per year. */\nexport function inAnyYear(property: string) {\n return (values: readonly string[]): Condition => ({\n operator: 'or',\n values: values.map(year => ({\n property,\n operator: 'between' as const,\n start: new Date(`${year}-01-01T00:00:00.000Z`),\n end: new Date(`${year}-12-31T23:59:59.999Z`),\n })),\n });\n}\n"],"names":["isSortParam","param","isSelectParam","defineUrlQuery","params","sorting","defaultSorting","keys","Object","filterKeys","filter","key","askingKeys","stateOf","values","fromEntries","map","empty","accepted","allowed","single","kept","value","trim","undefined","includes","unique","Set","slice","parse","search","query","URLSearchParams","getAll","serialize","state","append","text","toString","isEmpty","every","length","isFiltering","some","isAsking","withoutFilters","request","context","chosen","filtering","flatMap","condition","anyOf","property","operator","containing","inAnyYear","year","start","Date","end"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { UrlQuery, UrlState } from './url-query.js';
|
|
2
|
+
/**
|
|
3
|
+
* A query's state, read from the URL, and a function writing a new one.
|
|
4
|
+
*
|
|
5
|
+
* The state is `null` while rendering on the server and during hydration: the
|
|
6
|
+
* server-rendered HTML is the page with no filter, which is also what a visitor without
|
|
7
|
+
* scripting keeps. Writing replaces the history entry rather than adding one,
|
|
8
|
+
* so back leaves the page instead of undoing a filter.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useUrlState<TKey extends string>(query: Pick<UrlQuery<TKey, never>, 'parse' | 'serialize'>): readonly [UrlState<TKey> | null, (state: UrlState<TKey>) => void];
|
|
11
|
+
//# sourceMappingURL=url-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-state.d.ts","sourceRoot":"","sources":["../../src/url/url-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAiBzD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,SAAS,MAAM,EAC7C,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC,GACxD,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAgBnE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useCallback, useMemo, useSyncExternalStore } from 'react';
|
|
2
|
+
/** Dispatched after a state is written, as `replaceState` fires nothing. */ const SEARCH_CHANGED = 'entifix-url-search-changed';
|
|
3
|
+
function subscribe(onChange) {
|
|
4
|
+
window.addEventListener('popstate', onChange);
|
|
5
|
+
window.addEventListener(SEARCH_CHANGED, onChange);
|
|
6
|
+
return ()=>{
|
|
7
|
+
window.removeEventListener('popstate', onChange);
|
|
8
|
+
window.removeEventListener(SEARCH_CHANGED, onChange);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const currentSearch = ()=>window.location.search;
|
|
12
|
+
const noSearch = ()=>null;
|
|
13
|
+
/**
|
|
14
|
+
* A query's state, read from the URL, and a function writing a new one.
|
|
15
|
+
*
|
|
16
|
+
* The state is `null` while rendering on the server and during hydration: the
|
|
17
|
+
* server-rendered HTML is the page with no filter, which is also what a visitor without
|
|
18
|
+
* scripting keeps. Writing replaces the history entry rather than adding one,
|
|
19
|
+
* so back leaves the page instead of undoing a filter.
|
|
20
|
+
*/ export function useUrlState(query) {
|
|
21
|
+
const search = useSyncExternalStore(subscribe, currentSearch, noSearch);
|
|
22
|
+
const state = useMemo(()=>search === null ? null : query.parse(search), [
|
|
23
|
+
query,
|
|
24
|
+
search
|
|
25
|
+
]);
|
|
26
|
+
const write = useCallback((next)=>{
|
|
27
|
+
const url = new URL(window.location.href);
|
|
28
|
+
url.search = query.serialize(next);
|
|
29
|
+
window.history.replaceState(window.history.state, '', url);
|
|
30
|
+
window.dispatchEvent(new Event(SEARCH_CHANGED));
|
|
31
|
+
}, [
|
|
32
|
+
query
|
|
33
|
+
]);
|
|
34
|
+
return [
|
|
35
|
+
state,
|
|
36
|
+
write
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=url-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/url/url-state.ts"],"sourcesContent":["import { useCallback, useMemo, useSyncExternalStore } from 'react';\n\nimport type { UrlQuery, UrlState } from './url-query.js';\n\n/** Dispatched after a state is written, as `replaceState` fires nothing. */\nconst SEARCH_CHANGED = 'entifix-url-search-changed';\n\nfunction subscribe(onChange: () => void) {\n window.addEventListener('popstate', onChange);\n window.addEventListener(SEARCH_CHANGED, onChange);\n return () => {\n window.removeEventListener('popstate', onChange);\n window.removeEventListener(SEARCH_CHANGED, onChange);\n };\n}\n\nconst currentSearch = () => window.location.search;\nconst noSearch = () => null;\n\n/**\n * A query's state, read from the URL, and a function writing a new one.\n *\n * The state is `null` while rendering on the server and during hydration: the\n * server-rendered HTML is the page with no filter, which is also what a visitor without\n * scripting keeps. Writing replaces the history entry rather than adding one,\n * so back leaves the page instead of undoing a filter.\n */\nexport function useUrlState<TKey extends string>(\n query: Pick<UrlQuery<TKey, never>, 'parse' | 'serialize'>,\n): readonly [UrlState<TKey> | null, (state: UrlState<TKey>) => void] {\n const search = useSyncExternalStore(subscribe, currentSearch, noSearch);\n const state = useMemo(\n () => (search === null ? null : query.parse(search)),\n [query, search],\n );\n const write = useCallback(\n (next: UrlState<TKey>) => {\n const url = new URL(window.location.href);\n url.search = query.serialize(next);\n window.history.replaceState(window.history.state, '', url);\n window.dispatchEvent(new Event(SEARCH_CHANGED));\n },\n [query],\n );\n return [state, write] as const;\n}\n"],"names":["useCallback","useMemo","useSyncExternalStore","SEARCH_CHANGED","subscribe","onChange","window","addEventListener","removeEventListener","currentSearch","location","search","noSearch","useUrlState","query","state","parse","write","next","url","URL","href","serialize","history","replaceState","dispatchEvent","Event"],"mappings":"AAAA,SAASA,WAAW,EAAEC,OAAO,EAAEC,oBAAoB,QAAQ,QAAQ;AAInE,0EAA0E,GAC1E,MAAMC,iBAAiB;AAEvB,SAASC,UAAUC,QAAoB;IACrCC,OAAOC,gBAAgB,CAAC,YAAYF;IACpCC,OAAOC,gBAAgB,CAACJ,gBAAgBE;IACxC,OAAO;QACLC,OAAOE,mBAAmB,CAAC,YAAYH;QACvCC,OAAOE,mBAAmB,CAACL,gBAAgBE;IAC7C;AACF;AAEA,MAAMI,gBAAgB,IAAMH,OAAOI,QAAQ,CAACC,MAAM;AAClD,MAAMC,WAAW,IAAM;AAEvB;;;;;;;CAOC,GACD,OAAO,SAASC,YACdC,KAAyD;IAEzD,MAAMH,SAAST,qBAAqBE,WAAWK,eAAeG;IAC9D,MAAMG,QAAQd,QACZ,IAAOU,WAAW,OAAO,OAAOG,MAAME,KAAK,CAACL,SAC5C;QAACG;QAAOH;KAAO;IAEjB,MAAMM,QAAQjB,YACZ,CAACkB;QACC,MAAMC,MAAM,IAAIC,IAAId,OAAOI,QAAQ,CAACW,IAAI;QACxCF,IAAIR,MAAM,GAAGG,MAAMQ,SAAS,CAACJ;QAC7BZ,OAAOiB,OAAO,CAACC,YAAY,CAAClB,OAAOiB,OAAO,CAACR,KAAK,EAAE,IAAII;QACtDb,OAAOmB,aAAa,CAAC,IAAIC,MAAMvB;IACjC,GACA;QAACW;KAAM;IAET,OAAO;QAACC;QAAOE;KAAM;AACvB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Entity, type EntityLoadRequest, type EntityPage } from '@entifix/core';
|
|
2
|
+
import type { EntitySource } from './entity-source.js';
|
|
3
|
+
export type EntityLoad<TEntity extends Entity> = {
|
|
4
|
+
readonly status: 'idle';
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Waiting for an answer. `previous` is the last answer from the same
|
|
8
|
+
* source, so a filter being changed keeps showing what it showed.
|
|
9
|
+
*/
|
|
10
|
+
| {
|
|
11
|
+
readonly status: 'pending';
|
|
12
|
+
readonly previous?: EntityPage<TEntity>;
|
|
13
|
+
} | {
|
|
14
|
+
readonly status: 'done';
|
|
15
|
+
readonly page: EntityPage<TEntity>;
|
|
16
|
+
} | {
|
|
17
|
+
readonly status: 'failed';
|
|
18
|
+
readonly error: unknown;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Every record a request selects, through the `loadAll` use case over a source.
|
|
22
|
+
*
|
|
23
|
+
* `null` asks for nothing and answers `idle`: before hydration, and while no
|
|
24
|
+
* filter is set, the page shows what the build rendered. An answer counts only
|
|
25
|
+
* for the request it was asked for, so a late answer to an older filter is
|
|
26
|
+
* never shown as the current one.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useEntityLoad<TEntity extends Entity>(source: EntitySource, request: EntityLoadRequest<TEntity> | null): EntityLoad<TEntity>;
|
|
29
|
+
//# sourceMappingURL=use-entity-load.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-entity-load.d.ts","sourceRoot":"","sources":["../../src/url/use-entity-load.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,UAAU,EAChB,MAAM,eAAe,CAAC;AAIvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,MAAM,UAAU,CAAC,OAAO,SAAS,MAAM,IACzC;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC7B;;;GAGG;GACD;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;CAAE,GACvE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,CAAA;CAAE,GAC/D;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAiC3D;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,SAAS,MAAM,EAClD,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC,GAAG,IAAI,GACzC,UAAU,CAAC,OAAO,CAAC,CA0BrB"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ConfigurationRepositoryTag, EntityLoadRequestTag, EntityRepositoryTag, loadAllUCFactory } from '@entifix/business';
|
|
2
|
+
import { ConfigurationClientInMemory } from '@entifix/core';
|
|
3
|
+
import { Context, Effect } from 'effect';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
const IDLE = {
|
|
6
|
+
status: 'idle'
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Every record a request keeps, through the `loadAll` use case: a filter
|
|
10
|
+
* never pages. A source's repository reads no configuration here, but the
|
|
11
|
+
* port leaves `ConfigurationRepositoryTag` on every method's requirement
|
|
12
|
+
* channel, so an empty one discharges it.
|
|
13
|
+
*/ async function loadEvery(repository, request) {
|
|
14
|
+
const context = Context.make(EntityRepositoryTag, repository).pipe(Context.add(ConfigurationRepositoryTag, new ConfigurationClientInMemory({})), Context.add(EntityLoadRequestTag, request));
|
|
15
|
+
const items = await Effect.runPromise(Effect.provide(loadAllUCFactory(), context));
|
|
16
|
+
return {
|
|
17
|
+
items,
|
|
18
|
+
total: items.length,
|
|
19
|
+
request
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Every record a request selects, through the `loadAll` use case over a source.
|
|
24
|
+
*
|
|
25
|
+
* `null` asks for nothing and answers `idle`: before hydration, and while no
|
|
26
|
+
* filter is set, the page shows what the build rendered. An answer counts only
|
|
27
|
+
* for the request it was asked for, so a late answer to an older filter is
|
|
28
|
+
* never shown as the current one.
|
|
29
|
+
*/ export function useEntityLoad(source, request) {
|
|
30
|
+
const [answer, setAnswer] = useState();
|
|
31
|
+
// Requests are rebuilt on every render; their content is what changes.
|
|
32
|
+
const key = request === null ? null : JSON.stringify(request);
|
|
33
|
+
useEffect(()=>{
|
|
34
|
+
if (request === null || key === null) return;
|
|
35
|
+
const settle = (load)=>setAnswer({
|
|
36
|
+
source,
|
|
37
|
+
key,
|
|
38
|
+
load
|
|
39
|
+
});
|
|
40
|
+
source().then((repository)=>loadEvery(repository, request)).then((page)=>settle({
|
|
41
|
+
status: 'done',
|
|
42
|
+
page
|
|
43
|
+
}), (error)=>settle({
|
|
44
|
+
status: 'failed',
|
|
45
|
+
error
|
|
46
|
+
}));
|
|
47
|
+
// `request` is read through `key`, its content: a new object with the
|
|
48
|
+
// same content is the same request.
|
|
49
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
50
|
+
}, [
|
|
51
|
+
source,
|
|
52
|
+
key
|
|
53
|
+
]);
|
|
54
|
+
if (key === null) return IDLE;
|
|
55
|
+
if ((answer == null ? void 0 : answer.source) !== source) return {
|
|
56
|
+
status: 'pending'
|
|
57
|
+
};
|
|
58
|
+
if (answer.key === key) return answer.load;
|
|
59
|
+
return answer.load.status === 'done' ? {
|
|
60
|
+
status: 'pending',
|
|
61
|
+
previous: answer.load.page
|
|
62
|
+
} : {
|
|
63
|
+
status: 'pending'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=use-entity-load.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/url/use-entity-load.ts"],"sourcesContent":["import {\n ConfigurationRepositoryTag,\n EntityLoadRequestTag,\n type EntityRepository,\n EntityRepositoryTag,\n loadAllUCFactory,\n} from '@entifix/business';\nimport {\n ConfigurationClientInMemory,\n type Entity,\n type EntityLoadRequest,\n type EntityPage,\n} from '@entifix/core';\nimport { Context, Effect } from 'effect';\nimport { useEffect, useState } from 'react';\n\nimport type { EntitySource } from './entity-source.js';\n\nexport type EntityLoad<TEntity extends Entity> =\n | { readonly status: 'idle' }\n /**\n * Waiting for an answer. `previous` is the last answer from the same\n * source, so a filter being changed keeps showing what it showed.\n */\n | { readonly status: 'pending'; readonly previous?: EntityPage<TEntity> }\n | { readonly status: 'done'; readonly page: EntityPage<TEntity> }\n | { readonly status: 'failed'; readonly error: unknown };\n\nconst IDLE = { status: 'idle' } as const;\n\n/**\n * Every record a request keeps, through the `loadAll` use case: a filter\n * never pages. A source's repository reads no configuration here, but the\n * port leaves `ConfigurationRepositoryTag` on every method's requirement\n * channel, so an empty one discharges it.\n */\nasync function loadEvery<TEntity extends Entity>(\n repository: EntityRepository,\n request: EntityLoadRequest<TEntity>,\n): Promise<EntityPage<TEntity>> {\n const context = Context.make(EntityRepositoryTag, repository).pipe(\n Context.add(\n ConfigurationRepositoryTag,\n new ConfigurationClientInMemory({}),\n ),\n Context.add(EntityLoadRequestTag, request as unknown as EntityLoadRequest),\n );\n const items = await Effect.runPromise(\n Effect.provide(loadAllUCFactory<TEntity>(), context),\n );\n return { items, total: items.length, request };\n}\n\ninterface Answer<TEntity extends Entity> {\n readonly source: EntitySource;\n readonly key: string;\n readonly load: EntityLoad<TEntity>;\n}\n\n/**\n * Every record a request selects, through the `loadAll` use case over a source.\n *\n * `null` asks for nothing and answers `idle`: before hydration, and while no\n * filter is set, the page shows what the build rendered. An answer counts only\n * for the request it was asked for, so a late answer to an older filter is\n * never shown as the current one.\n */\nexport function useEntityLoad<TEntity extends Entity>(\n source: EntitySource,\n request: EntityLoadRequest<TEntity> | null,\n): EntityLoad<TEntity> {\n const [answer, setAnswer] = useState<Answer<TEntity>>();\n // Requests are rebuilt on every render; their content is what changes.\n const key = request === null ? null : JSON.stringify(request);\n\n useEffect(() => {\n if (request === null || key === null) return;\n const settle = (load: EntityLoad<TEntity>) =>\n setAnswer({ source, key, load });\n source()\n .then(repository => loadEvery<TEntity>(repository, request))\n .then(\n page => settle({ status: 'done', page }),\n (error: unknown) => settle({ status: 'failed', error }),\n );\n // `request` is read through `key`, its content: a new object with the\n // same content is the same request.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [source, key]);\n\n if (key === null) return IDLE;\n if (answer?.source !== source) return { status: 'pending' };\n if (answer.key === key) return answer.load;\n return answer.load.status === 'done'\n ? { status: 'pending', previous: answer.load.page }\n : { status: 'pending' };\n}\n"],"names":["ConfigurationRepositoryTag","EntityLoadRequestTag","EntityRepositoryTag","loadAllUCFactory","ConfigurationClientInMemory","Context","Effect","useEffect","useState","IDLE","status","loadEvery","repository","request","context","make","pipe","add","items","runPromise","provide","total","length","useEntityLoad","source","answer","setAnswer","key","JSON","stringify","settle","load","then","page","error","previous"],"mappings":"AAAA,SACEA,0BAA0B,EAC1BC,oBAAoB,EAEpBC,mBAAmB,EACnBC,gBAAgB,QACX,oBAAoB;AAC3B,SACEC,2BAA2B,QAItB,gBAAgB;AACvB,SAASC,OAAO,EAAEC,MAAM,QAAQ,SAAS;AACzC,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAQ;AAc5C,MAAMC,OAAO;IAAEC,QAAQ;AAAO;AAE9B;;;;;CAKC,GACD,eAAeC,UACbC,UAA4B,EAC5BC,OAAmC;IAEnC,MAAMC,UAAUT,QAAQU,IAAI,CAACb,qBAAqBU,YAAYI,IAAI,CAChEX,QAAQY,GAAG,CACTjB,4BACA,IAAII,4BAA4B,CAAC,KAEnCC,QAAQY,GAAG,CAAChB,sBAAsBY;IAEpC,MAAMK,QAAQ,MAAMZ,OAAOa,UAAU,CACnCb,OAAOc,OAAO,CAACjB,oBAA6BW;IAE9C,OAAO;QAAEI;QAAOG,OAAOH,MAAMI,MAAM;QAAET;IAAQ;AAC/C;AAQA;;;;;;;CAOC,GACD,OAAO,SAASU,cACdC,MAAoB,EACpBX,OAA0C;IAE1C,MAAM,CAACY,QAAQC,UAAU,GAAGlB;IAC5B,uEAAuE;IACvE,MAAMmB,MAAMd,YAAY,OAAO,OAAOe,KAAKC,SAAS,CAAChB;IAErDN,UAAU;QACR,IAAIM,YAAY,QAAQc,QAAQ,MAAM;QACtC,MAAMG,SAAS,CAACC,OACdL,UAAU;gBAAEF;gBAAQG;gBAAKI;YAAK;QAChCP,SACGQ,IAAI,CAACpB,CAAAA,aAAcD,UAAmBC,YAAYC,UAClDmB,IAAI,CACHC,CAAAA,OAAQH,OAAO;gBAAEpB,QAAQ;gBAAQuB;YAAK,IACtC,CAACC,QAAmBJ,OAAO;gBAAEpB,QAAQ;gBAAUwB;YAAM;IAEzD,sEAAsE;IACtE,oCAAoC;IACpC,uDAAuD;IACzD,GAAG;QAACV;QAAQG;KAAI;IAEhB,IAAIA,QAAQ,MAAM,OAAOlB;IACzB,IAAIgB,CAAAA,0BAAAA,OAAQD,MAAM,MAAKA,QAAQ,OAAO;QAAEd,QAAQ;IAAU;IAC1D,IAAIe,OAAOE,GAAG,KAAKA,KAAK,OAAOF,OAAOM,IAAI;IAC1C,OAAON,OAAOM,IAAI,CAACrB,MAAM,KAAK,SAC1B;QAAEA,QAAQ;QAAWyB,UAAUV,OAAOM,IAAI,CAACE,IAAI;IAAC,IAChD;QAAEvB,QAAQ;IAAU;AAC1B"}
|
package/dist/url.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A filter kept in the URL and answered by the `loadAll` use case, apart from
|
|
3
|
+
* the main entry: a page that only filters a list must not pull TanStack
|
|
4
|
+
* Query and Form, which every hook behind `.` builds on.
|
|
5
|
+
*/
|
|
6
|
+
export * from './url/entity-source.js';
|
|
7
|
+
export * from './url/url-filter.js';
|
|
8
|
+
export * from './url/url-query.js';
|
|
9
|
+
export * from './url/url-state.js';
|
|
10
|
+
export * from './url/use-entity-load.js';
|
|
11
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC"}
|
package/dist/url.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A filter kept in the URL and answered by the `loadAll` use case, apart from
|
|
3
|
+
* the main entry: a page that only filters a list must not pull TanStack
|
|
4
|
+
* Query and Form, which every hook behind `.` builds on.
|
|
5
|
+
*/ export * from './url/entity-source.js';
|
|
6
|
+
export * from './url/url-filter.js';
|
|
7
|
+
export * from './url/url-query.js';
|
|
8
|
+
export * from './url/url-state.js';
|
|
9
|
+
export * from './url/use-entity-load.js';
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=url.js.map
|
package/dist/url.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/url.ts"],"sourcesContent":["/**\n * A filter kept in the URL and answered by the `loadAll` use case, apart from\n * the main entry: a page that only filters a list must not pull TanStack\n * Query and Form, which every hook behind `.` builds on.\n */\nexport * from './url/entity-source.js';\nexport * from './url/url-filter.js';\nexport * from './url/url-query.js';\nexport * from './url/url-state.js';\nexport * from './url/use-entity-load.js';\n"],"names":[],"mappings":"AAAA;;;;CAIC,GACD,cAAc,yBAAyB;AACvC,cAAc,sBAAsB;AACpC,cAAc,qBAAqB;AACnC,cAAc,qBAAqB;AACnC,cAAc,2BAA2B"}
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
|
-
"version": "0.1.
|
|
24
|
+
"version": "0.1.3",
|
|
25
25
|
"description": "React hooks that bind entities to data: TanStack Query and Form over the entity adapters, drafts, mutations and live invalidation.",
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"repository": {
|
|
@@ -43,6 +43,12 @@
|
|
|
43
43
|
"types": "./dist/index.d.ts",
|
|
44
44
|
"import": "./dist/index.js",
|
|
45
45
|
"default": "./dist/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./url": {
|
|
48
|
+
"@entifix/source": "./src/url.ts",
|
|
49
|
+
"types": "./dist/url.d.ts",
|
|
50
|
+
"import": "./dist/url.js",
|
|
51
|
+
"default": "./dist/url.js"
|
|
46
52
|
}
|
|
47
53
|
},
|
|
48
54
|
"files": [
|
|
@@ -51,16 +57,16 @@
|
|
|
51
57
|
"LICENSE"
|
|
52
58
|
],
|
|
53
59
|
"dependencies": {
|
|
54
|
-
"@entifix/transactions": "0.1.
|
|
55
|
-
"@entifix/business": "0.1.
|
|
56
|
-
"@entifix/core": "0.1.
|
|
60
|
+
"@entifix/transactions": "0.1.3",
|
|
61
|
+
"@entifix/business": "0.1.3",
|
|
62
|
+
"@entifix/core": "0.1.3"
|
|
57
63
|
},
|
|
58
64
|
"devDependencies": {
|
|
59
65
|
"@tanstack/react-form": "1.33.5",
|
|
60
66
|
"@tanstack/react-query": "^5.101.4",
|
|
61
67
|
"effect": "3.22.1",
|
|
62
68
|
"react": "19.2.8",
|
|
63
|
-
"@entifix/testing-unit": "0.1.
|
|
69
|
+
"@entifix/testing-unit": "0.1.3"
|
|
64
70
|
},
|
|
65
71
|
"peerDependencies": {
|
|
66
72
|
"@tanstack/react-form": ">=1.33 <2",
|